mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-25 16:11:45 +00:00
hwmon updates for v4.8
New drivers for FTS BMC "Teutates", TI INA3221, and Sensirion SHT3x. Added support for Microchip MCP9808 and TI TMP461. Cleanup and minor fixes in various drivers. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJXlXNKAAoJEMsfJm/On5mB7QwP/06g1PTYkZDtsvPD0kYwjqs4 4je/OpmGFoSaytTJiOd1tQxfgmHe4m8eOZbTw0MyvvvJaDGSHPEUsEpH8DautTUD O4ngVfFV9R4fV6lBunLaXZc92+00pDqKYky5jzsMy/t0TJ43ycRvEt3Q+k9D85Go 0hjP72CE6eEHzLKlrbDxbyZOEohbJyqO5bQL8FUy0k7+4LxN8FAKNyNgRW0fNwf4 FSHMUNil0hGNvhApadvEk6uvQoPYpkTxQSXzFldT3zouJVKhgILBmQGNA+Be0bwP PS7ALhcVRcTtcZprY6BNf86cujo+5yWLI1Ifqeu+sNQUkwaZk8df/fxF4XpjWrez L1HN4p9nCUXhDGpeTxDedTeWZHDBLr2CuPPmm2vWbRM+gl+LJ5CFLq3oqOEcSR43 bq8+oRgXtJHK0tlWJG3neabbArMV57bhrEsft4OthMFFaNRquZnqZylX7dBlQO+/ rEGqALwmutHY2BmVM/jP/WQ6SBZTxWmsq/XVhheDqu842oukzH5CijddCL/JUQbr aC+u3gmXb2/gquEOgYosRAAAqLL0IH7AjSxXvhLM2lKSgJlJGmBXqezx9E4bYn6o RAAG4qlCIt5YJUkQ61r8j9WmlXd/BIzACyVSCk5tau61kGscepPk7Vk0dgdYUXMC wcbDIYHdC2p0voaZdXLf =O4g9 -----END PGP SIGNATURE----- Merge tag 'hwmon-for-linus-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - New drivers for FTS BMC "Teutates", TI INA3221, and Sensirion SHT3x. - Added support for Microchip MCP9808 and TI TMP461. - Cleanup and minor fixes in various drivers. * tag 'hwmon-for-linus-v4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (37 commits) Documentation: dtb: xgene: Add hwmon dts binding documentation hwmon: (ftsteutates) Remove unused including <linux/version.h> hwmon: (adt7411) set bit 3 in CFG1 register hwmon: Add driver for FTS BMC chip "Teutates" hwmon: (sht3x) add humidity heater element control hwmon: (jc42) Add support for generic JC-42.4 devicetree binding dt/bindings: Add bindings for JC-42.4 compatible temperature sensors hwmon: (tmp102) Convert to use regmap, and drop local cache hwmon: (tmp102) Rework chip configuration hwmon: (tmp102) Improve handling of initial read delay hwmon: (lm90) Drop unnecessary else statements hwmon: (lm90) Use bool for valid flag hwmon: (lm90) Read limit registers only once hwmon: (lm90) Simplify read functions hwmon: (lm90) Use devm_hwmon_device_register_with_groups hwmon: (lm90) Use devm_add_action for cleanup hwmon: (lm75) Convert to use regmap hwmon: (lm75) Add update_interval attribute hwmon: (lm75) Drop lm75_read_value and lm75_write_value hwmon: (lm75) Handle cleanup with devm_add_action ...
This commit is contained in:
commit
dd95069545
27 changed files with 2965 additions and 607 deletions
14
Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt
Normal file
14
Documentation/devicetree/bindings/hwmon/apm-xgene-hwmon.txt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
APM X-Gene hwmon driver
|
||||||
|
|
||||||
|
APM X-Gene SOC sensors are accessed over the "SLIMpro" mailbox.
|
||||||
|
|
||||||
|
Required properties :
|
||||||
|
- compatible : should be "apm,xgene-slimpro-hwmon"
|
||||||
|
- mboxes : use the label reference for the mailbox as the first parameter.
|
||||||
|
The second parameter is the channel number.
|
||||||
|
|
||||||
|
Example :
|
||||||
|
hwmonslimpro {
|
||||||
|
compatible = "apm,xgene-slimpro-hwmon";
|
||||||
|
mboxes = <&mailbox 7>;
|
||||||
|
};
|
42
Documentation/devicetree/bindings/hwmon/jc42.txt
Normal file
42
Documentation/devicetree/bindings/hwmon/jc42.txt
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
Properties for Jedec JC-42.4 compatible temperature sensors
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: May include a device-specific string consisting of the
|
||||||
|
manufacturer and the name of the chip. A list of supported
|
||||||
|
chip names follows.
|
||||||
|
Must include "jedec,jc-42.4-temp" for any Jedec JC-42.4
|
||||||
|
compatible temperature sensor.
|
||||||
|
|
||||||
|
Supported chip names:
|
||||||
|
adi,adt7408
|
||||||
|
atmel,at30ts00
|
||||||
|
atmel,at30tse004
|
||||||
|
onnn,cat6095
|
||||||
|
onnn,cat34ts02
|
||||||
|
maxim,max6604
|
||||||
|
microchip,mcp9804
|
||||||
|
microchip,mcp9805
|
||||||
|
microchip,mcp9808
|
||||||
|
microchip,mcp98243
|
||||||
|
microchip,mcp98244
|
||||||
|
microchip,mcp9843
|
||||||
|
nxp,se97
|
||||||
|
nxp,se98
|
||||||
|
st,stts2002
|
||||||
|
st,stts2004
|
||||||
|
st,stts3000
|
||||||
|
st,stts424
|
||||||
|
st,stts424e
|
||||||
|
idt,tse2002
|
||||||
|
idt,tse2004
|
||||||
|
idt,ts3000
|
||||||
|
idt,ts3001
|
||||||
|
|
||||||
|
- reg: I2C address
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
temp-sensor@1a {
|
||||||
|
compatible = "jedec,jc-42.4-temp";
|
||||||
|
reg = <0x1a>;
|
||||||
|
};
|
|
@ -24,7 +24,7 @@ Supported chips:
|
||||||
AW9D-MAX) (2)
|
AW9D-MAX) (2)
|
||||||
1) For revisions 2 and 3 uGuru's the driver can autodetect the
|
1) For revisions 2 and 3 uGuru's the driver can autodetect the
|
||||||
sensortype (Volt or Temp) for bank1 sensors, for revision 1 uGuru's
|
sensortype (Volt or Temp) for bank1 sensors, for revision 1 uGuru's
|
||||||
this doesnot always work. For these uGuru's the autodection can
|
this does not always work. For these uGuru's the autodetection can
|
||||||
be overridden with the bank1_types module param. For all 3 known
|
be overridden with the bank1_types module param. For all 3 known
|
||||||
revison 1 motherboards the correct use of this param is:
|
revison 1 motherboards the correct use of this param is:
|
||||||
bank1_types=1,1,0,0,0,0,0,2,0,0,0,0,2,0,0,1
|
bank1_types=1,1,0,0,0,0,0,2,0,0,0,0,2,0,0,1
|
||||||
|
|
23
Documentation/hwmon/ftsteutates
Normal file
23
Documentation/hwmon/ftsteutates
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Kernel driver ftsteutates
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
* FTS Teutates
|
||||||
|
Prefix: 'ftsteutates'
|
||||||
|
Addresses scanned: I2C 0x73 (7-Bit)
|
||||||
|
|
||||||
|
Author: Thilo Cestonaro <thilo.cestonaro@ts.fujitsu.com>
|
||||||
|
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
The BMC Teutates is the Eleventh generation of Superior System
|
||||||
|
monitoring and thermal management solution. It is builds on the basic
|
||||||
|
functionality of the BMC Theseus and contains several new features and
|
||||||
|
enhancements. It can monitor up to 4 voltages, 16 temperatures and
|
||||||
|
8 fans. It also contains an integrated watchdog which is currently
|
||||||
|
implemented in this driver.
|
||||||
|
|
||||||
|
Specification of the chip can be found here:
|
||||||
|
ftp:///pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/BMC-Teutates_Specification_V1.21.pdf
|
||||||
|
ftp:///pub/Mainboard-OEM-Sales/Services/Software&Tools/Linux_SystemMonitoring&Watchdog&GPIO/Fujitsu_mainboards-1-Sensors_HowTo-en-US.pdf
|
35
Documentation/hwmon/ina3221
Normal file
35
Documentation/hwmon/ina3221
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
Kernel driver ina3221
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
* Texas Instruments INA3221
|
||||||
|
Prefix: 'ina3221'
|
||||||
|
Addresses: I2C 0x40 - 0x43
|
||||||
|
Datasheet: Publicly available at the Texas Instruments website
|
||||||
|
http://www.ti.com/
|
||||||
|
|
||||||
|
Author: Andrew F. Davis <afd@ti.com>
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The Texas Instruments INA3221 monitors voltage, current, and power on the high
|
||||||
|
side of up to three D.C. power supplies. The INA3221 monitors both shunt drop
|
||||||
|
and supply voltage, with programmable conversion times and averaging, current
|
||||||
|
and power are calculated host-side from these.
|
||||||
|
|
||||||
|
Sysfs entries
|
||||||
|
-------------
|
||||||
|
|
||||||
|
in[123]_input Bus voltage(mV) channels
|
||||||
|
curr[123]_input Current(mA) measurement channels
|
||||||
|
shunt[123]_resistor Shunt resistance(uOhm) channels
|
||||||
|
curr[123]_crit Critical alert current(mA) setting, activates the
|
||||||
|
corresponding alarm when the respective current
|
||||||
|
is above this value
|
||||||
|
curr[123]_crit_alarm Critical alert current limit exceeded
|
||||||
|
curr[123]_max Warning alert current(mA) setting, activates the
|
||||||
|
corresponding alarm when the respective current
|
||||||
|
average is above this value.
|
||||||
|
curr[123]_max_alarm Warning alert current limit exceeded
|
||||||
|
in[456]_input Shunt voltage(uV) for channels 1, 2, and 3 respectively
|
|
@ -18,10 +18,11 @@ Supported chips:
|
||||||
* Maxim MAX6604
|
* Maxim MAX6604
|
||||||
Datasheets:
|
Datasheets:
|
||||||
http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf
|
http://datasheets.maxim-ic.com/en/ds/MAX6604.pdf
|
||||||
* Microchip MCP9804, MCP9805, MCP98242, MCP98243, MCP98244, MCP9843
|
* Microchip MCP9804, MCP9805, MCP9808, MCP98242, MCP98243, MCP98244, MCP9843
|
||||||
Datasheets:
|
Datasheets:
|
||||||
http://ww1.microchip.com/downloads/en/DeviceDoc/22203C.pdf
|
http://ww1.microchip.com/downloads/en/DeviceDoc/22203C.pdf
|
||||||
http://ww1.microchip.com/downloads/en/DeviceDoc/21977b.pdf
|
http://ww1.microchip.com/downloads/en/DeviceDoc/21977b.pdf
|
||||||
|
http://ww1.microchip.com/downloads/en/DeviceDoc/25095A.pdf
|
||||||
http://ww1.microchip.com/downloads/en/DeviceDoc/21996a.pdf
|
http://ww1.microchip.com/downloads/en/DeviceDoc/21996a.pdf
|
||||||
http://ww1.microchip.com/downloads/en/DeviceDoc/22153c.pdf
|
http://ww1.microchip.com/downloads/en/DeviceDoc/22153c.pdf
|
||||||
http://ww1.microchip.com/downloads/en/DeviceDoc/22327A.pdf
|
http://ww1.microchip.com/downloads/en/DeviceDoc/22327A.pdf
|
||||||
|
|
|
@ -17,7 +17,7 @@ This driver implements support for the Maxim MAX1668, MAX1805 and MAX1989
|
||||||
chips.
|
chips.
|
||||||
|
|
||||||
The three devices are very similar, but the MAX1805 has a reduced feature
|
The three devices are very similar, but the MAX1805 has a reduced feature
|
||||||
set; only two remote temperature inputs vs the four avaible on the other
|
set; only two remote temperature inputs vs the four available on the other
|
||||||
two ICs.
|
two ICs.
|
||||||
|
|
||||||
The driver is able to distinguish between the devices and creates sysfs
|
The driver is able to distinguish between the devices and creates sysfs
|
||||||
|
|
76
Documentation/hwmon/sht3x
Normal file
76
Documentation/hwmon/sht3x
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
Kernel driver sht3x
|
||||||
|
===================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
* Sensirion SHT3x-DIS
|
||||||
|
Prefix: 'sht3x'
|
||||||
|
Addresses scanned: none
|
||||||
|
Datasheet: http://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/Humidity/Sensirion_Humidity_Datasheet_SHT3x_DIS.pdf
|
||||||
|
|
||||||
|
Author:
|
||||||
|
David Frey <david.frey@sensirion.com>
|
||||||
|
Pascal Sachs <pascal.sachs@sensirion.com>
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This driver implements support for the Sensirion SHT3x-DIS chip, a humidity
|
||||||
|
and temperature sensor. Temperature is measured in degrees celsius, relative
|
||||||
|
humidity is expressed as a percentage. In the sysfs interface, all values are
|
||||||
|
scaled by 1000, i.e. the value for 31.5 degrees celsius is 31500.
|
||||||
|
|
||||||
|
The device communicates with the I2C protocol. Sensors can have the I2C
|
||||||
|
addresses 0x44 or 0x45, depending on the wiring. See
|
||||||
|
Documentation/i2c/instantiating-devices for methods to instantiate the device.
|
||||||
|
|
||||||
|
There are two options configurable by means of sht3x_platform_data:
|
||||||
|
1. blocking (pull the I2C clock line down while performing the measurement) or
|
||||||
|
non-blocking mode. Blocking mode will guarantee the fastest result but
|
||||||
|
the I2C bus will be busy during that time. By default, non-blocking mode
|
||||||
|
is used. Make sure clock-stretching works properly on your device if you
|
||||||
|
want to use blocking mode.
|
||||||
|
2. high or low accuracy. High accuracy is used by default and using it is
|
||||||
|
strongly recommended.
|
||||||
|
|
||||||
|
The sht3x sensor supports a single shot mode as well as 5 periodic measure
|
||||||
|
modes, which can be controlled with the update_interval sysfs interface.
|
||||||
|
The allowed update_interval in milliseconds are as follows:
|
||||||
|
* 0 single shot mode
|
||||||
|
* 2000 0.5 Hz periodic measurement
|
||||||
|
* 1000 1 Hz periodic measurement
|
||||||
|
* 500 2 Hz periodic measurement
|
||||||
|
* 250 4 Hz periodic measurement
|
||||||
|
* 100 10 Hz periodic measurement
|
||||||
|
|
||||||
|
In the periodic measure mode, the sensor automatically triggers a measurement
|
||||||
|
with the configured update interval on the chip. When a temperature or humidity
|
||||||
|
reading exceeds the configured limits, the alert attribute is set to 1 and
|
||||||
|
the alert pin on the sensor is set to high.
|
||||||
|
When the temperature and humidity readings move back between the hysteresis
|
||||||
|
values, the alert bit is set to 0 and the alert pin on the sensor is set to
|
||||||
|
low.
|
||||||
|
|
||||||
|
sysfs-Interface
|
||||||
|
---------------
|
||||||
|
|
||||||
|
temp1_input: temperature input
|
||||||
|
humidity1_input: humidity input
|
||||||
|
temp1_max: temperature max value
|
||||||
|
temp1_max_hyst: temperature hysteresis value for max limit
|
||||||
|
humidity1_max: humidity max value
|
||||||
|
humidity1_max_hyst: humidity hysteresis value for max limit
|
||||||
|
temp1_min: temperature min value
|
||||||
|
temp1_min_hyst: temperature hysteresis value for min limit
|
||||||
|
humidity1_min: humidity min value
|
||||||
|
humidity1_min_hyst: humidity hysteresis value for min limit
|
||||||
|
temp1_alarm: alarm flag is set to 1 if the temperature is outside the
|
||||||
|
configured limits. Alarm only works in periodic measure mode
|
||||||
|
humidity1_alarm: alarm flag is set to 1 if the humidity is outside the
|
||||||
|
configured limits. Alarm only works in periodic measure mode
|
||||||
|
heater_enable: heater enable, heating element removes excess humidity from
|
||||||
|
sensor
|
||||||
|
0: turned off
|
||||||
|
1: turned on
|
||||||
|
update_interval: update interval, 0 for single shot, interval in msec
|
||||||
|
for periodic measurement. If the interval is not supported
|
||||||
|
by the sensor, the next faster interval is chosen
|
|
@ -15,10 +15,15 @@ increase the chances of your change being accepted.
|
||||||
Documentation/SubmittingPatches
|
Documentation/SubmittingPatches
|
||||||
Documentation/CodingStyle
|
Documentation/CodingStyle
|
||||||
|
|
||||||
* If your patch generates checkpatch warnings, please refrain from explanations
|
* Please run your patch through 'checkpatch --strict'. There should be no
|
||||||
such as "I don't like that coding style". Keep in mind that each unnecessary
|
errors, no warnings, and few if any check messages. If there are any
|
||||||
warning helps hiding a real problem. If you don't like the kernel coding
|
messages, please be prepared to explain.
|
||||||
style, don't write kernel drivers.
|
|
||||||
|
* If your patch generates checkpatch errors, warnings, or check messages,
|
||||||
|
please refrain from explanations such as "I prefer that coding style".
|
||||||
|
Keep in mind that each unnecessary message helps hiding a real problem,
|
||||||
|
and a consistent coding style makes it easier for others to understand
|
||||||
|
and review the code.
|
||||||
|
|
||||||
* Please test your patch thoroughly. We are not your test group.
|
* Please test your patch thoroughly. We are not your test group.
|
||||||
Sometimes a patch can not or not completely be tested because of missing
|
Sometimes a patch can not or not completely be tested because of missing
|
||||||
|
@ -61,15 +66,30 @@ increase the chances of your change being accepted.
|
||||||
|
|
||||||
* Make sure that all dependencies are listed in Kconfig.
|
* Make sure that all dependencies are listed in Kconfig.
|
||||||
|
|
||||||
|
* Please list include files in alphabetic order.
|
||||||
|
|
||||||
|
* Please align continuation lines with '(' on the previous line.
|
||||||
|
|
||||||
* Avoid forward declarations if you can. Rearrange the code if necessary.
|
* Avoid forward declarations if you can. Rearrange the code if necessary.
|
||||||
|
|
||||||
|
* Avoid macros to generate groups of sensor attributes. It not only confuses
|
||||||
|
checkpatch, but also makes it more difficult to review the code.
|
||||||
|
|
||||||
* Avoid calculations in macros and macro-generated functions. While such macros
|
* Avoid calculations in macros and macro-generated functions. While such macros
|
||||||
may save a line or so in the source, it obfuscates the code and makes code
|
may save a line or so in the source, it obfuscates the code and makes code
|
||||||
review more difficult. It may also result in code which is more complicated
|
review more difficult. It may also result in code which is more complicated
|
||||||
than necessary. Use inline functions or just regular functions instead.
|
than necessary. Use inline functions or just regular functions instead.
|
||||||
|
|
||||||
|
* Limit the number of kernel log messages. In general, your driver should not
|
||||||
|
generate an error message just because a runtime operation failed. Report
|
||||||
|
errors to user space instead, using an appropriate error code. Keep in mind
|
||||||
|
that kernel error log messages not only fill up the kernel log, but also are
|
||||||
|
printed synchronously, most likely with interrupt disabled, often to a serial
|
||||||
|
console. Excessive logging can seriously affect system performance.
|
||||||
|
|
||||||
* Use devres functions whenever possible to allocate resources. For rationale
|
* Use devres functions whenever possible to allocate resources. For rationale
|
||||||
and supported functions, please see Documentation/driver-model/devres.txt.
|
and supported functions, please see Documentation/driver-model/devres.txt.
|
||||||
|
If a function is not supported by devres, consider using devm_add_action().
|
||||||
|
|
||||||
* If the driver has a detect function, make sure it is silent. Debug messages
|
* If the driver has a detect function, make sure it is silent. Debug messages
|
||||||
and messages printed after a successful detection are acceptable, but it
|
and messages printed after a successful detection are acceptable, but it
|
||||||
|
@ -96,8 +116,16 @@ increase the chances of your change being accepted.
|
||||||
writing to it might cause a bad misconfiguration.
|
writing to it might cause a bad misconfiguration.
|
||||||
|
|
||||||
* Make sure there are no race conditions in the probe function. Specifically,
|
* Make sure there are no race conditions in the probe function. Specifically,
|
||||||
completely initialize your chip first, then create sysfs entries and register
|
completely initialize your chip and your driver first, then register with
|
||||||
with the hwmon subsystem.
|
the hwmon subsystem.
|
||||||
|
|
||||||
|
* Use devm_hwmon_device_register_with_groups() or, if your driver needs a remove
|
||||||
|
function, hwmon_device_register_with_groups() to register your driver with the
|
||||||
|
hwmon subsystem. Try using devm_add_action() instead of a remove function if
|
||||||
|
possible. Do not use hwmon_device_register().
|
||||||
|
|
||||||
|
* Your driver should be buildable as module. If not, please be prepared to
|
||||||
|
explain why it has to be built into the kernel.
|
||||||
|
|
||||||
* Do not provide support for deprecated sysfs attributes.
|
* Do not provide support for deprecated sysfs attributes.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@ Supported chips:
|
||||||
Prefix: 'tmp435'
|
Prefix: 'tmp435'
|
||||||
Addresses scanned: I2C 0x48 - 0x4f
|
Addresses scanned: I2C 0x48 - 0x4f
|
||||||
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp435.html
|
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp435.html
|
||||||
|
* Texas Instruments TMP461
|
||||||
|
Prefix: 'tmp461'
|
||||||
|
Datasheet: http://www.ti.com/product/tmp461
|
||||||
|
|
||||||
Authors:
|
Authors:
|
||||||
Hans de Goede <hdegoede@redhat.com>
|
Hans de Goede <hdegoede@redhat.com>
|
||||||
|
@ -31,8 +34,8 @@ Description
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
This driver implements support for Texas Instruments TMP401, TMP411,
|
This driver implements support for Texas Instruments TMP401, TMP411,
|
||||||
TMP431, TMP432 and TMP435 chips. These chips implement one or two remote
|
TMP431, TMP432, TMP435, and TMP461 chips. These chips implement one or two
|
||||||
and one local temperature sensors. Temperature is measured in degrees
|
remote and one local temperature sensors. Temperature is measured in degrees
|
||||||
Celsius. Resolution of the remote sensor is 0.0625 degree. Local
|
Celsius. Resolution of the remote sensor is 0.0625 degree. Local
|
||||||
sensor resolution can be set to 0.5, 0.25, 0.125 or 0.0625 degree (not
|
sensor resolution can be set to 0.5, 0.25, 0.125 or 0.0625 degree (not
|
||||||
supported by the driver so far, so using the default resolution of 0.5
|
supported by the driver so far, so using the default resolution of 0.5
|
||||||
|
@ -55,3 +58,10 @@ some additional features.
|
||||||
|
|
||||||
TMP432 is compatible with TMP401 and TMP431. It supports two external
|
TMP432 is compatible with TMP401 and TMP431. It supports two external
|
||||||
temperature sensors.
|
temperature sensors.
|
||||||
|
|
||||||
|
TMP461 is compatible with TMP401. It supports offset correction
|
||||||
|
that is applied to the remote sensor.
|
||||||
|
|
||||||
|
* Sensor offset values are temperature values
|
||||||
|
|
||||||
|
Exported via sysfs attribute tempX_offset
|
||||||
|
|
|
@ -486,6 +486,18 @@ config SENSORS_FSCHMD
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called fschmd.
|
will be called fschmd.
|
||||||
|
|
||||||
|
config SENSORS_FTSTEUTATES
|
||||||
|
tristate "Fujitsu Technology Solutions sensor chip Teutates"
|
||||||
|
depends on I2C && WATCHDOG
|
||||||
|
select WATCHDOG_CORE
|
||||||
|
help
|
||||||
|
If you say yes here you get support for the Fujitsu Technology
|
||||||
|
Solutions (FTS) sensor chip "Teutates" including support for
|
||||||
|
the integrated watchdog.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called ftsteutates.
|
||||||
|
|
||||||
config SENSORS_GL518SM
|
config SENSORS_GL518SM
|
||||||
tristate "Genesys Logic GL518SM"
|
tristate "Genesys Logic GL518SM"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
|
@ -645,8 +657,8 @@ config SENSORS_JC42
|
||||||
temperature sensors, which are used on many DDR3 memory modules for
|
temperature sensors, which are used on many DDR3 memory modules for
|
||||||
mobile devices and servers. Support will include, but not be limited
|
mobile devices and servers. Support will include, but not be limited
|
||||||
to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805,
|
to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805,
|
||||||
MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, STTS424(E),
|
MCP9808, MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98,
|
||||||
STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001.
|
STTS424(E), STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001.
|
||||||
|
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called jc42.
|
will be called jc42.
|
||||||
|
@ -958,6 +970,7 @@ config SENSORS_LM75
|
||||||
tristate "National Semiconductor LM75 and compatibles"
|
tristate "National Semiconductor LM75 and compatibles"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
depends on THERMAL || !THERMAL_OF
|
depends on THERMAL || !THERMAL_OF
|
||||||
|
select REGMAP_I2C
|
||||||
help
|
help
|
||||||
If you say yes here you get support for one common type of
|
If you say yes here you get support for one common type of
|
||||||
temperature sensor chip, with models including:
|
temperature sensor chip, with models including:
|
||||||
|
@ -1265,6 +1278,17 @@ config SENSORS_SHT21
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called sht21.
|
will be called sht21.
|
||||||
|
|
||||||
|
config SENSORS_SHT3x
|
||||||
|
tristate "Sensiron humidity and temperature sensors. SHT3x and compat."
|
||||||
|
depends on I2C
|
||||||
|
select CRC8
|
||||||
|
help
|
||||||
|
If you say yes here you get support for the Sensiron SHT30 and SHT31
|
||||||
|
humidity and temperature sensors.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called sht3x.
|
||||||
|
|
||||||
config SENSORS_SHTC1
|
config SENSORS_SHTC1
|
||||||
tristate "Sensiron humidity and temperature sensors. SHTC1 and compat."
|
tristate "Sensiron humidity and temperature sensors. SHTC1 and compat."
|
||||||
depends on I2C
|
depends on I2C
|
||||||
|
@ -1514,6 +1538,17 @@ config SENSORS_INA2XX
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called ina2xx.
|
will be called ina2xx.
|
||||||
|
|
||||||
|
config SENSORS_INA3221
|
||||||
|
tristate "Texas Instruments INA3221 Triple Power Monitor"
|
||||||
|
depends on I2C
|
||||||
|
select REGMAP_I2C
|
||||||
|
help
|
||||||
|
If you say yes here you get support for the TI INA3221 Triple Power
|
||||||
|
Monitor.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called ina3221.
|
||||||
|
|
||||||
config SENSORS_TC74
|
config SENSORS_TC74
|
||||||
tristate "Microchip TC74"
|
tristate "Microchip TC74"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
|
@ -1538,6 +1573,7 @@ config SENSORS_TMP102
|
||||||
tristate "Texas Instruments TMP102"
|
tristate "Texas Instruments TMP102"
|
||||||
depends on I2C
|
depends on I2C
|
||||||
depends on THERMAL || !THERMAL_OF
|
depends on THERMAL || !THERMAL_OF
|
||||||
|
select REGMAP_I2C
|
||||||
help
|
help
|
||||||
If you say yes here you get support for Texas Instruments TMP102
|
If you say yes here you get support for Texas Instruments TMP102
|
||||||
sensor chips.
|
sensor chips.
|
||||||
|
@ -1561,7 +1597,7 @@ config SENSORS_TMP401
|
||||||
depends on I2C
|
depends on I2C
|
||||||
help
|
help
|
||||||
If you say yes here you get support for Texas Instruments TMP401,
|
If you say yes here you get support for Texas Instruments TMP401,
|
||||||
TMP411, TMP431, TMP432 and TMP435 temperature sensor chips.
|
TMP411, TMP431, TMP432, TMP435, and TMP461 temperature sensor chips.
|
||||||
|
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called tmp401.
|
will be called tmp401.
|
||||||
|
|
|
@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
|
||||||
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
|
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
|
||||||
obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
|
obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
|
||||||
obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
|
obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
|
||||||
|
obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o
|
||||||
obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
||||||
obj-$(CONFIG_SENSORS_G762) += g762.o
|
obj-$(CONFIG_SENSORS_G762) += g762.o
|
||||||
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
||||||
|
@ -77,6 +78,7 @@ obj-$(CONFIG_SENSORS_IBMPOWERNV)+= ibmpowernv.o
|
||||||
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
|
obj-$(CONFIG_SENSORS_IIO_HWMON) += iio_hwmon.o
|
||||||
obj-$(CONFIG_SENSORS_INA209) += ina209.o
|
obj-$(CONFIG_SENSORS_INA209) += ina209.o
|
||||||
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
|
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
|
||||||
|
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
|
||||||
obj-$(CONFIG_SENSORS_IT87) += it87.o
|
obj-$(CONFIG_SENSORS_IT87) += it87.o
|
||||||
obj-$(CONFIG_SENSORS_JC42) += jc42.o
|
obj-$(CONFIG_SENSORS_JC42) += jc42.o
|
||||||
obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
|
obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
|
||||||
|
@ -138,6 +140,7 @@ obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
|
||||||
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o
|
obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o
|
||||||
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
|
obj-$(CONFIG_SENSORS_SHT15) += sht15.o
|
||||||
obj-$(CONFIG_SENSORS_SHT21) += sht21.o
|
obj-$(CONFIG_SENSORS_SHT21) += sht21.o
|
||||||
|
obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o
|
||||||
obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o
|
obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o
|
||||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||||
obj-$(CONFIG_SENSORS_SMM665) += smm665.o
|
obj-$(CONFIG_SENSORS_SMM665) += smm665.o
|
||||||
|
|
|
@ -37,7 +37,6 @@ enum ad7314_variant {
|
||||||
|
|
||||||
struct ad7314_data {
|
struct ad7314_data {
|
||||||
struct spi_device *spi_dev;
|
struct spi_device *spi_dev;
|
||||||
struct device *hwmon_dev;
|
|
||||||
u16 rx ____cacheline_aligned;
|
u16 rx ____cacheline_aligned;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,62 +87,30 @@ static ssize_t ad7314_show_temperature(struct device *dev,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t ad7314_show_name(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
return sprintf(buf, "%s\n", to_spi_device(dev)->modalias);
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEVICE_ATTR(name, S_IRUGO, ad7314_show_name, NULL);
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO,
|
||||||
ad7314_show_temperature, NULL, 0);
|
ad7314_show_temperature, NULL, 0);
|
||||||
|
|
||||||
static struct attribute *ad7314_attributes[] = {
|
static struct attribute *ad7314_attrs[] = {
|
||||||
&dev_attr_name.attr,
|
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group ad7314_group = {
|
ATTRIBUTE_GROUPS(ad7314);
|
||||||
.attrs = ad7314_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int ad7314_probe(struct spi_device *spi_dev)
|
static int ad7314_probe(struct spi_device *spi_dev)
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
struct ad7314_data *chip;
|
struct ad7314_data *chip;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
|
||||||
chip = devm_kzalloc(&spi_dev->dev, sizeof(*chip), GFP_KERNEL);
|
chip = devm_kzalloc(&spi_dev->dev, sizeof(*chip), GFP_KERNEL);
|
||||||
if (chip == NULL)
|
if (chip == NULL)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
spi_set_drvdata(spi_dev, chip);
|
|
||||||
|
|
||||||
ret = sysfs_create_group(&spi_dev->dev.kobj, &ad7314_group);
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
chip->spi_dev = spi_dev;
|
chip->spi_dev = spi_dev;
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_groups(&spi_dev->dev,
|
||||||
return 0;
|
spi_dev->modalias,
|
||||||
error_remove_group:
|
chip, ad7314_groups);
|
||||||
sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ad7314_remove(struct spi_device *spi_dev)
|
|
||||||
{
|
|
||||||
struct ad7314_data *chip = spi_get_drvdata(spi_dev);
|
|
||||||
|
|
||||||
hwmon_device_unregister(chip->hwmon_dev);
|
|
||||||
sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct spi_device_id ad7314_id[] = {
|
static const struct spi_device_id ad7314_id[] = {
|
||||||
|
@ -159,7 +126,6 @@ static struct spi_driver ad7314_driver = {
|
||||||
.name = "ad7314",
|
.name = "ad7314",
|
||||||
},
|
},
|
||||||
.probe = ad7314_probe,
|
.probe = ad7314_probe,
|
||||||
.remove = ad7314_remove,
|
|
||||||
.id_table = ad7314_id,
|
.id_table = ad7314_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -66,14 +66,12 @@
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
#include <linux/hwmon-sysfs.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/mutex.h>
|
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
|
|
||||||
#define DEVICE_NAME "ads7871"
|
#define DEVICE_NAME "ads7871"
|
||||||
|
|
||||||
struct ads7871_data {
|
struct ads7871_data {
|
||||||
struct device *hwmon_dev;
|
struct spi_device *spi;
|
||||||
struct mutex update_lock;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int ads7871_read_reg8(struct spi_device *spi, int reg)
|
static int ads7871_read_reg8(struct spi_device *spi, int reg)
|
||||||
|
@ -101,7 +99,8 @@ static int ads7871_write_reg8(struct spi_device *spi, int reg, u8 val)
|
||||||
static ssize_t show_voltage(struct device *dev,
|
static ssize_t show_voltage(struct device *dev,
|
||||||
struct device_attribute *da, char *buf)
|
struct device_attribute *da, char *buf)
|
||||||
{
|
{
|
||||||
struct spi_device *spi = to_spi_device(dev);
|
struct ads7871_data *pdata = dev_get_drvdata(dev);
|
||||||
|
struct spi_device *spi = pdata->spi;
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
int ret, val, i = 0;
|
int ret, val, i = 0;
|
||||||
uint8_t channel, mux_cnv;
|
uint8_t channel, mux_cnv;
|
||||||
|
@ -139,12 +138,6 @@ static ssize_t show_voltage(struct device *dev,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t ads7871_show_name(struct device *dev,
|
|
||||||
struct device_attribute *devattr, char *buf)
|
|
||||||
{
|
|
||||||
return sprintf(buf, "%s\n", to_spi_device(dev)->modalias);
|
|
||||||
}
|
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_voltage, NULL, 0);
|
static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, show_voltage, NULL, 0);
|
||||||
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 1);
|
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_voltage, NULL, 1);
|
||||||
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 2);
|
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_voltage, NULL, 2);
|
||||||
|
@ -154,9 +147,7 @@ static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO, show_voltage, NULL, 5);
|
||||||
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 6);
|
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO, show_voltage, NULL, 6);
|
||||||
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 7);
|
static SENSOR_DEVICE_ATTR(in7_input, S_IRUGO, show_voltage, NULL, 7);
|
||||||
|
|
||||||
static DEVICE_ATTR(name, S_IRUGO, ads7871_show_name, NULL);
|
static struct attribute *ads7871_attrs[] = {
|
||||||
|
|
||||||
static struct attribute *ads7871_attributes[] = {
|
|
||||||
&sensor_dev_attr_in0_input.dev_attr.attr,
|
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in1_input.dev_attr.attr,
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in2_input.dev_attr.attr,
|
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||||
|
@ -165,21 +156,18 @@ static struct attribute *ads7871_attributes[] = {
|
||||||
&sensor_dev_attr_in5_input.dev_attr.attr,
|
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in6_input.dev_attr.attr,
|
&sensor_dev_attr_in6_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||||
&dev_attr_name.attr,
|
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group ads7871_group = {
|
ATTRIBUTE_GROUPS(ads7871);
|
||||||
.attrs = ads7871_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int ads7871_probe(struct spi_device *spi)
|
static int ads7871_probe(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
int ret, err;
|
struct device *dev = &spi->dev;
|
||||||
|
int ret;
|
||||||
uint8_t val;
|
uint8_t val;
|
||||||
struct ads7871_data *pdata;
|
struct ads7871_data *pdata;
|
||||||
|
struct device *hwmon_dev;
|
||||||
dev_dbg(&spi->dev, "probe\n");
|
|
||||||
|
|
||||||
/* Configure the SPI bus */
|
/* Configure the SPI bus */
|
||||||
spi->mode = (SPI_MODE_0);
|
spi->mode = (SPI_MODE_0);
|
||||||
|
@ -193,7 +181,7 @@ static int ads7871_probe(struct spi_device *spi)
|
||||||
ads7871_write_reg8(spi, REG_OSC_CONTROL, val);
|
ads7871_write_reg8(spi, REG_OSC_CONTROL, val);
|
||||||
ret = ads7871_read_reg8(spi, REG_OSC_CONTROL);
|
ret = ads7871_read_reg8(spi, REG_OSC_CONTROL);
|
||||||
|
|
||||||
dev_dbg(&spi->dev, "REG_OSC_CONTROL write:%x, read:%x\n", val, ret);
|
dev_dbg(dev, "REG_OSC_CONTROL write:%x, read:%x\n", val, ret);
|
||||||
/*
|
/*
|
||||||
* because there is no other error checking on an SPI bus
|
* because there is no other error checking on an SPI bus
|
||||||
* we need to make sure we really have a chip
|
* we need to make sure we really have a chip
|
||||||
|
@ -201,46 +189,23 @@ static int ads7871_probe(struct spi_device *spi)
|
||||||
if (val != ret)
|
if (val != ret)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
pdata = devm_kzalloc(&spi->dev, sizeof(struct ads7871_data),
|
pdata = devm_kzalloc(dev, sizeof(struct ads7871_data), GFP_KERNEL);
|
||||||
GFP_KERNEL);
|
|
||||||
if (!pdata)
|
if (!pdata)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
err = sysfs_create_group(&spi->dev.kobj, &ads7871_group);
|
pdata->spi = spi;
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
spi_set_drvdata(spi, pdata);
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, spi->modalias,
|
||||||
|
pdata,
|
||||||
pdata->hwmon_dev = hwmon_device_register(&spi->dev);
|
ads7871_groups);
|
||||||
if (IS_ERR(pdata->hwmon_dev)) {
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
err = PTR_ERR(pdata->hwmon_dev);
|
|
||||||
goto error_remove;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error_remove:
|
|
||||||
sysfs_remove_group(&spi->dev.kobj, &ads7871_group);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ads7871_remove(struct spi_device *spi)
|
|
||||||
{
|
|
||||||
struct ads7871_data *pdata = spi_get_drvdata(spi);
|
|
||||||
|
|
||||||
hwmon_device_unregister(pdata->hwmon_dev);
|
|
||||||
sysfs_remove_group(&spi->dev.kobj, &ads7871_group);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct spi_driver ads7871_driver = {
|
static struct spi_driver ads7871_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = DEVICE_NAME,
|
.name = DEVICE_NAME,
|
||||||
},
|
},
|
||||||
|
|
||||||
.probe = ads7871_probe,
|
.probe = ads7871_probe,
|
||||||
.remove = ads7871_remove,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module_spi_driver(ads7871_driver);
|
module_spi_driver(ads7871_driver);
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
#define ADT7411_REG_CFG1 0x18
|
#define ADT7411_REG_CFG1 0x18
|
||||||
#define ADT7411_CFG1_START_MONITOR (1 << 0)
|
#define ADT7411_CFG1_START_MONITOR (1 << 0)
|
||||||
|
#define ADT7411_CFG1_RESERVED_BIT3 (1 << 3)
|
||||||
|
|
||||||
#define ADT7411_REG_CFG2 0x19
|
#define ADT7411_REG_CFG2 0x19
|
||||||
#define ADT7411_CFG2_DISABLE_AVG (1 << 5)
|
#define ADT7411_CFG2_DISABLE_AVG (1 << 5)
|
||||||
|
@ -296,8 +297,10 @@ static int adt7411_probe(struct i2c_client *client,
|
||||||
mutex_init(&data->device_lock);
|
mutex_init(&data->device_lock);
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
|
/* According to the datasheet, we must only write 1 to bit 3 */
|
||||||
ret = adt7411_modify_bit(client, ADT7411_REG_CFG1,
|
ret = adt7411_modify_bit(client, ADT7411_REG_CFG1,
|
||||||
ADT7411_CFG1_START_MONITOR, 1);
|
ADT7411_CFG1_RESERVED_BIT3
|
||||||
|
| ADT7411_CFG1_START_MONITOR, 1);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ static bool disallow_fan_type_call;
|
||||||
#define I8K_HWMON_HAVE_TEMP4 (1 << 3)
|
#define I8K_HWMON_HAVE_TEMP4 (1 << 3)
|
||||||
#define I8K_HWMON_HAVE_FAN1 (1 << 4)
|
#define I8K_HWMON_HAVE_FAN1 (1 << 4)
|
||||||
#define I8K_HWMON_HAVE_FAN2 (1 << 5)
|
#define I8K_HWMON_HAVE_FAN2 (1 << 5)
|
||||||
|
#define I8K_HWMON_HAVE_FAN3 (1 << 6)
|
||||||
|
|
||||||
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
|
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
|
||||||
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
|
MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
|
||||||
|
@ -139,6 +140,14 @@ static int i8k_smm(struct smm_regs *regs)
|
||||||
int eax = regs->eax;
|
int eax = regs->eax;
|
||||||
cpumask_var_t old_mask;
|
cpumask_var_t old_mask;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
int ebx = regs->ebx;
|
||||||
|
unsigned long duration;
|
||||||
|
ktime_t calltime, delta, rettime;
|
||||||
|
|
||||||
|
calltime = ktime_get();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* SMM requires CPU 0 */
|
/* SMM requires CPU 0 */
|
||||||
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
|
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -210,6 +219,15 @@ static int i8k_smm(struct smm_regs *regs)
|
||||||
out:
|
out:
|
||||||
set_cpus_allowed_ptr(current, old_mask);
|
set_cpus_allowed_ptr(current, old_mask);
|
||||||
free_cpumask_var(old_mask);
|
free_cpumask_var(old_mask);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
rettime = ktime_get();
|
||||||
|
delta = ktime_sub(rettime, calltime);
|
||||||
|
duration = ktime_to_ns(delta) >> 10;
|
||||||
|
pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx,
|
||||||
|
(rc ? 0xffff : regs->eax & 0xffff), duration);
|
||||||
|
#endif
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +270,7 @@ static int _i8k_get_fan_type(int fan)
|
||||||
static int i8k_get_fan_type(int fan)
|
static int i8k_get_fan_type(int fan)
|
||||||
{
|
{
|
||||||
/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
|
/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
|
||||||
static int types[2] = { INT_MIN, INT_MIN };
|
static int types[3] = { INT_MIN, INT_MIN, INT_MIN };
|
||||||
|
|
||||||
if (types[fan] == INT_MIN)
|
if (types[fan] == INT_MIN)
|
||||||
types[fan] = _i8k_get_fan_type(fan);
|
types[fan] = _i8k_get_fan_type(fan);
|
||||||
|
@ -719,6 +737,12 @@ static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
|
||||||
1);
|
1);
|
||||||
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
|
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
|
||||||
i8k_hwmon_set_pwm, 1);
|
i8k_hwmon_set_pwm, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
|
||||||
|
2);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
|
||||||
|
2);
|
||||||
|
static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
|
||||||
|
i8k_hwmon_set_pwm, 2);
|
||||||
|
|
||||||
static struct attribute *i8k_attrs[] = {
|
static struct attribute *i8k_attrs[] = {
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */
|
&sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */
|
||||||
|
@ -735,6 +759,9 @@ static struct attribute *i8k_attrs[] = {
|
||||||
&sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */
|
&sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */
|
||||||
&sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */
|
&sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */
|
||||||
&sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */
|
&sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */
|
||||||
|
&sensor_dev_attr_fan3_input.dev_attr.attr, /* 14 */
|
||||||
|
&sensor_dev_attr_fan3_label.dev_attr.attr, /* 15 */
|
||||||
|
&sensor_dev_attr_pwm3.dev_attr.attr, /* 16 */
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -742,7 +769,7 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||||
int index)
|
int index)
|
||||||
{
|
{
|
||||||
if (disallow_fan_type_call &&
|
if (disallow_fan_type_call &&
|
||||||
(index == 9 || index == 12))
|
(index == 9 || index == 12 || index == 15))
|
||||||
return 0;
|
return 0;
|
||||||
if (index >= 0 && index <= 1 &&
|
if (index >= 0 && index <= 1 &&
|
||||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
|
!(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
|
||||||
|
@ -762,6 +789,9 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||||
if (index >= 11 && index <= 13 &&
|
if (index >= 11 && index <= 13 &&
|
||||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
|
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
|
||||||
return 0;
|
return 0;
|
||||||
|
if (index >= 14 && index <= 16 &&
|
||||||
|
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3))
|
||||||
|
return 0;
|
||||||
|
|
||||||
return attr->mode;
|
return attr->mode;
|
||||||
}
|
}
|
||||||
|
@ -807,6 +837,13 @@ static int __init i8k_init_hwmon(void)
|
||||||
if (err >= 0)
|
if (err >= 0)
|
||||||
i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
|
i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
|
||||||
|
|
||||||
|
/* Third fan attributes, if fan status or type is OK */
|
||||||
|
err = i8k_get_fan_status(2);
|
||||||
|
if (err < 0)
|
||||||
|
err = i8k_get_fan_type(2);
|
||||||
|
if (err >= 0)
|
||||||
|
i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3;
|
||||||
|
|
||||||
i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm",
|
i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm",
|
||||||
NULL, i8k_groups);
|
NULL, i8k_groups);
|
||||||
if (IS_ERR(i8k_hwmon_dev)) {
|
if (IS_ERR(i8k_hwmon_dev)) {
|
||||||
|
|
|
@ -464,7 +464,7 @@ static int emc6w201_detect(struct i2c_client *client,
|
||||||
if (verstep < 0 || (verstep & 0xF0) != 0xB0)
|
if (verstep < 0 || (verstep & 0xF0) != 0xB0)
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
if ((verstep & 0x0F) > 2) {
|
if ((verstep & 0x0F) > 2) {
|
||||||
dev_dbg(&client->dev, "Unknwown EMC6W201 stepping %d\n",
|
dev_dbg(&client->dev, "Unknown EMC6W201 stepping %d\n",
|
||||||
verstep & 0x0F);
|
verstep & 0x0F);
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
}
|
}
|
||||||
|
|
819
drivers/hwmon/ftsteutates.c
Normal file
819
drivers/hwmon/ftsteutates.c
Normal file
|
@ -0,0 +1,819 @@
|
||||||
|
/*
|
||||||
|
* Support for the FTS Systemmonitoring Chip "Teutates"
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Fujitsu Technology Solutions GmbH,
|
||||||
|
* Thilo Cestonaro <thilo.cestonaro@ts.fujitsu.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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/jiffies.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/sysfs.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <linux/watchdog.h>
|
||||||
|
|
||||||
|
#define FTS_DEVICE_ID_REG 0x0000
|
||||||
|
#define FTS_DEVICE_REVISION_REG 0x0001
|
||||||
|
#define FTS_DEVICE_STATUS_REG 0x0004
|
||||||
|
#define FTS_SATELLITE_STATUS_REG 0x0005
|
||||||
|
#define FTS_EVENT_STATUS_REG 0x0006
|
||||||
|
#define FTS_GLOBAL_CONTROL_REG 0x0007
|
||||||
|
|
||||||
|
#define FTS_SENSOR_EVENT_REG 0x0010
|
||||||
|
|
||||||
|
#define FTS_FAN_EVENT_REG 0x0014
|
||||||
|
#define FTS_FAN_PRESENT_REG 0x0015
|
||||||
|
|
||||||
|
#define FTS_POWER_ON_TIME_COUNTER_A 0x007A
|
||||||
|
#define FTS_POWER_ON_TIME_COUNTER_B 0x007B
|
||||||
|
#define FTS_POWER_ON_TIME_COUNTER_C 0x007C
|
||||||
|
|
||||||
|
#define FTS_PAGE_SELECT_REG 0x007F
|
||||||
|
|
||||||
|
#define FTS_WATCHDOG_TIME_PRESET 0x000B
|
||||||
|
#define FTS_WATCHDOG_CONTROL 0x5081
|
||||||
|
|
||||||
|
#define FTS_NO_FAN_SENSORS 0x08
|
||||||
|
#define FTS_NO_TEMP_SENSORS 0x10
|
||||||
|
#define FTS_NO_VOLT_SENSORS 0x04
|
||||||
|
|
||||||
|
static struct i2c_device_id fts_id[] = {
|
||||||
|
{ "ftsteutates", 0 },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(i2c, fts_id);
|
||||||
|
|
||||||
|
enum WATCHDOG_RESOLUTION {
|
||||||
|
seconds = 1,
|
||||||
|
minutes = 60
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fts_data {
|
||||||
|
struct i2c_client *client;
|
||||||
|
/* update sensor data lock */
|
||||||
|
struct mutex update_lock;
|
||||||
|
/* read/write register lock */
|
||||||
|
struct mutex access_lock;
|
||||||
|
unsigned long last_updated; /* in jiffies */
|
||||||
|
struct watchdog_device wdd;
|
||||||
|
enum WATCHDOG_RESOLUTION resolution;
|
||||||
|
bool valid; /* false until following fields are valid */
|
||||||
|
|
||||||
|
u8 volt[FTS_NO_VOLT_SENSORS];
|
||||||
|
|
||||||
|
u8 temp_input[FTS_NO_TEMP_SENSORS];
|
||||||
|
u8 temp_alarm;
|
||||||
|
|
||||||
|
u8 fan_present;
|
||||||
|
u8 fan_input[FTS_NO_FAN_SENSORS]; /* in rps */
|
||||||
|
u8 fan_source[FTS_NO_FAN_SENSORS];
|
||||||
|
u8 fan_alarm;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FTS_REG_FAN_INPUT(idx) ((idx) + 0x20)
|
||||||
|
#define FTS_REG_FAN_SOURCE(idx) ((idx) + 0x30)
|
||||||
|
#define FTS_REG_FAN_CONTROL(idx) (((idx) << 16) + 0x4881)
|
||||||
|
|
||||||
|
#define FTS_REG_TEMP_INPUT(idx) ((idx) + 0x40)
|
||||||
|
#define FTS_REG_TEMP_CONTROL(idx) (((idx) << 16) + 0x0681)
|
||||||
|
|
||||||
|
#define FTS_REG_VOLT(idx) ((idx) + 0x18)
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* I2C Helper functions */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static int fts_read_byte(struct i2c_client *client, unsigned short reg)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned char page = reg >> 8;
|
||||||
|
struct fts_data *data = dev_get_drvdata(&client->dev);
|
||||||
|
|
||||||
|
mutex_lock(&data->access_lock);
|
||||||
|
|
||||||
|
dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
|
||||||
|
ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
reg &= 0xFF;
|
||||||
|
ret = i2c_smbus_read_byte_data(client, reg);
|
||||||
|
dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret);
|
||||||
|
|
||||||
|
error:
|
||||||
|
mutex_unlock(&data->access_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fts_write_byte(struct i2c_client *client, unsigned short reg,
|
||||||
|
unsigned char value)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned char page = reg >> 8;
|
||||||
|
struct fts_data *data = dev_get_drvdata(&client->dev);
|
||||||
|
|
||||||
|
mutex_lock(&data->access_lock);
|
||||||
|
|
||||||
|
dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page);
|
||||||
|
ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
reg &= 0xFF;
|
||||||
|
dev_dbg(&client->dev,
|
||||||
|
"write - reg: 0x%.02x: val: 0x%.02x\n", reg, value);
|
||||||
|
ret = i2c_smbus_write_byte_data(client, reg, value);
|
||||||
|
|
||||||
|
error:
|
||||||
|
mutex_unlock(&data->access_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* Data Updater Helper function */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static int fts_update_device(struct fts_data *data)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
err = fts_read_byte(data->client, FTS_DEVICE_STATUS_REG);
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
data->valid = !!(err & 0x02); /* Data not ready yet */
|
||||||
|
if (unlikely(!data->valid)) {
|
||||||
|
err = -EAGAIN;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fts_read_byte(data->client, FTS_FAN_PRESENT_REG);
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->fan_present = err;
|
||||||
|
|
||||||
|
err = fts_read_byte(data->client, FTS_FAN_EVENT_REG);
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->fan_alarm = err;
|
||||||
|
|
||||||
|
for (i = 0; i < FTS_NO_FAN_SENSORS; i++) {
|
||||||
|
if (data->fan_present & BIT(i)) {
|
||||||
|
err = fts_read_byte(data->client, FTS_REG_FAN_INPUT(i));
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->fan_input[i] = err;
|
||||||
|
|
||||||
|
err = fts_read_byte(data->client,
|
||||||
|
FTS_REG_FAN_SOURCE(i));
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->fan_source[i] = err;
|
||||||
|
} else {
|
||||||
|
data->fan_input[i] = 0;
|
||||||
|
data->fan_source[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fts_read_byte(data->client, FTS_SENSOR_EVENT_REG);
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->temp_alarm = err;
|
||||||
|
|
||||||
|
for (i = 0; i < FTS_NO_TEMP_SENSORS; i++) {
|
||||||
|
err = fts_read_byte(data->client, FTS_REG_TEMP_INPUT(i));
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->temp_input[i] = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < FTS_NO_VOLT_SENSORS; i++) {
|
||||||
|
err = fts_read_byte(data->client, FTS_REG_VOLT(i));
|
||||||
|
if (err < 0)
|
||||||
|
goto exit;
|
||||||
|
data->volt[i] = err;
|
||||||
|
}
|
||||||
|
data->last_updated = jiffies;
|
||||||
|
err = 0;
|
||||||
|
exit:
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* Watchdog functions */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static int fts_wd_set_resolution(struct fts_data *data,
|
||||||
|
enum WATCHDOG_RESOLUTION resolution)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (data->resolution == resolution)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if ((resolution == seconds && ret & BIT(1)) ||
|
||||||
|
(resolution == minutes && (ret & BIT(1)) == 0)) {
|
||||||
|
data->resolution = resolution;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolution == seconds)
|
||||||
|
set_bit(1, (unsigned long *)&ret);
|
||||||
|
else
|
||||||
|
ret &= ~BIT(1);
|
||||||
|
|
||||||
|
ret = fts_write_byte(data->client, FTS_WATCHDOG_CONTROL, ret);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
data->resolution = resolution;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fts_wd_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
|
||||||
|
{
|
||||||
|
struct fts_data *data;
|
||||||
|
enum WATCHDOG_RESOLUTION resolution = seconds;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
data = watchdog_get_drvdata(wdd);
|
||||||
|
/* switch watchdog resolution to minutes if timeout does not fit
|
||||||
|
* into a byte
|
||||||
|
*/
|
||||||
|
if (timeout > 0xFF) {
|
||||||
|
timeout = DIV_ROUND_UP(timeout, 60) * 60;
|
||||||
|
resolution = minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = fts_wd_set_resolution(data, resolution);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
wdd->timeout = timeout;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fts_wd_start(struct watchdog_device *wdd)
|
||||||
|
{
|
||||||
|
struct fts_data *data = watchdog_get_drvdata(wdd);
|
||||||
|
|
||||||
|
return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET,
|
||||||
|
wdd->timeout / (u8)data->resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fts_wd_stop(struct watchdog_device *wdd)
|
||||||
|
{
|
||||||
|
struct fts_data *data;
|
||||||
|
|
||||||
|
data = watchdog_get_drvdata(wdd);
|
||||||
|
return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct watchdog_info fts_wd_info = {
|
||||||
|
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
||||||
|
.identity = "FTS Teutates Hardware Watchdog",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct watchdog_ops fts_wd_ops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.start = fts_wd_start,
|
||||||
|
.stop = fts_wd_stop,
|
||||||
|
.set_timeout = fts_wd_set_timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int fts_watchdog_init(struct fts_data *data)
|
||||||
|
{
|
||||||
|
int timeout, ret;
|
||||||
|
|
||||||
|
watchdog_set_drvdata(&data->wdd, data);
|
||||||
|
|
||||||
|
timeout = fts_read_byte(data->client, FTS_WATCHDOG_TIME_PRESET);
|
||||||
|
if (timeout < 0)
|
||||||
|
return timeout;
|
||||||
|
|
||||||
|
/* watchdog not running, set timeout to a default of 60 sec. */
|
||||||
|
if (timeout == 0) {
|
||||||
|
ret = fts_wd_set_resolution(data, seconds);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
data->wdd.timeout = 60;
|
||||||
|
} else {
|
||||||
|
ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
data->resolution = ret & BIT(1) ? seconds : minutes;
|
||||||
|
data->wdd.timeout = timeout * (u8)data->resolution;
|
||||||
|
set_bit(WDOG_HW_RUNNING, &data->wdd.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register our watchdog part */
|
||||||
|
data->wdd.info = &fts_wd_info;
|
||||||
|
data->wdd.ops = &fts_wd_ops;
|
||||||
|
data->wdd.parent = &data->client->dev;
|
||||||
|
data->wdd.min_timeout = 1;
|
||||||
|
|
||||||
|
/* max timeout 255 minutes. */
|
||||||
|
data->wdd.max_hw_heartbeat_ms = 0xFF * 60 * MSEC_PER_SEC;
|
||||||
|
|
||||||
|
return watchdog_register_device(&data->wdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* SysFS handler functions */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static ssize_t show_in_value(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->volt[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_temp_value(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->temp_input[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_temp_fault(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
/* 00h Temperature = Sensor Error */
|
||||||
|
return sprintf(buf, "%d\n", data->temp_input[index] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_temp_alarm(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", !!(data->temp_alarm & BIT(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
clear_temp_alarm(struct device *dev, struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
ret = fts_update_device(data);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (kstrtoul(buf, 10, &ret) || ret != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
ret = fts_read_byte(data->client, FTS_REG_TEMP_CONTROL(index));
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(index),
|
||||||
|
ret | 0x1);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
data->valid = false;
|
||||||
|
error:
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_fan_value(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->fan_input[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_fan_source(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->fan_source[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t show_fan_alarm(struct device *dev,
|
||||||
|
struct device_attribute *devattr, char *buf)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = fts_update_device(data);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", !!(data->fan_alarm & BIT(index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
clear_fan_alarm(struct device *dev, struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(dev);
|
||||||
|
int index = to_sensor_dev_attr(devattr)->index;
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
ret = fts_update_device(data);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (kstrtoul(buf, 10, &ret) || ret != 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
mutex_lock(&data->update_lock);
|
||||||
|
ret = fts_read_byte(data->client, FTS_REG_FAN_CONTROL(index));
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(index),
|
||||||
|
ret | 0x1);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
data->valid = false;
|
||||||
|
error:
|
||||||
|
mutex_unlock(&data->update_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* SysFS structs */
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
/* Temprature sensors */
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_value, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_value, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_value, NULL, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_value, NULL, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_value, NULL, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_value, NULL, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp7_input, S_IRUGO, show_temp_value, NULL, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, show_temp_value, NULL, 7);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp9_input, S_IRUGO, show_temp_value, NULL, 8);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp10_input, S_IRUGO, show_temp_value, NULL, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp11_input, S_IRUGO, show_temp_value, NULL, 10);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp12_input, S_IRUGO, show_temp_value, NULL, 11);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp13_input, S_IRUGO, show_temp_value, NULL, 12);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp14_input, S_IRUGO, show_temp_value, NULL, 13);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp15_input, S_IRUGO, show_temp_value, NULL, 14);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp16_input, S_IRUGO, show_temp_value, NULL, 15);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp7_fault, S_IRUGO, show_temp_fault, NULL, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp8_fault, S_IRUGO, show_temp_fault, NULL, 7);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp9_fault, S_IRUGO, show_temp_fault, NULL, 8);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp10_fault, S_IRUGO, show_temp_fault, NULL, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp11_fault, S_IRUGO, show_temp_fault, NULL, 10);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp12_fault, S_IRUGO, show_temp_fault, NULL, 11);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp13_fault, S_IRUGO, show_temp_fault, NULL, 12);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp14_fault, S_IRUGO, show_temp_fault, NULL, 13);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp15_fault, S_IRUGO, show_temp_fault, NULL, 14);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp16_fault, S_IRUGO, show_temp_fault, NULL, 15);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp2_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp3_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp4_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp5_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp6_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp7_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp8_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 7);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp9_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 8);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp10_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 9);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp11_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 10);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp12_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 11);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp13_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 12);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp14_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 13);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp15_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 14);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp16_alarm, S_IRUGO | S_IWUSR, show_temp_alarm,
|
||||||
|
clear_temp_alarm, 15);
|
||||||
|
|
||||||
|
static struct attribute *fts_temp_attrs[] = {
|
||||||
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp5_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp6_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp7_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp8_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp9_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp10_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp11_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp12_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp13_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp14_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp15_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp16_input.dev_attr.attr,
|
||||||
|
|
||||||
|
&sensor_dev_attr_temp1_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp2_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp3_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp4_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp5_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp6_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp7_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp8_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp9_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp10_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp11_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp12_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp13_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp14_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp15_fault.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp16_fault.dev_attr.attr,
|
||||||
|
|
||||||
|
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp2_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp3_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp4_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp5_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp6_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp7_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp8_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp9_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp10_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp11_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp12_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp13_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp14_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp15_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp16_alarm.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Fans */
|
||||||
|
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_value, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_value, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan_value, NULL, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan_value, NULL, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan5_input, S_IRUGO, show_fan_value, NULL, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan6_input, S_IRUGO, show_fan_value, NULL, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan7_input, S_IRUGO, show_fan_value, NULL, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan8_input, S_IRUGO, show_fan_value, NULL, 7);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(fan1_source, S_IRUGO, show_fan_source, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan2_source, S_IRUGO, show_fan_source, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_source, S_IRUGO, show_fan_source, NULL, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan4_source, S_IRUGO, show_fan_source, NULL, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan5_source, S_IRUGO, show_fan_source, NULL, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan6_source, S_IRUGO, show_fan_source, NULL, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan7_source, S_IRUGO, show_fan_source, NULL, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan8_source, S_IRUGO, show_fan_source, NULL, 7);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(fan1_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan2_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan3_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan4_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan5_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan6_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan7_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 6);
|
||||||
|
static SENSOR_DEVICE_ATTR(fan8_alarm, S_IRUGO | S_IWUSR,
|
||||||
|
show_fan_alarm, clear_fan_alarm, 7);
|
||||||
|
|
||||||
|
static struct attribute *fts_fan_attrs[] = {
|
||||||
|
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan4_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan5_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan6_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan7_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan8_input.dev_attr.attr,
|
||||||
|
|
||||||
|
&sensor_dev_attr_fan1_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan4_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan5_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan6_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan7_source.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan8_source.dev_attr.attr,
|
||||||
|
|
||||||
|
&sensor_dev_attr_fan1_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan2_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan3_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan4_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan5_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan6_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan7_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_fan8_alarm.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Voltages */
|
||||||
|
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, show_in_value, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, show_in_value, NULL, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, show_in_value, NULL, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, show_in_value, NULL, 3);
|
||||||
|
static struct attribute *fts_voltage_attrs[] = {
|
||||||
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fts_voltage_attr_group = {
|
||||||
|
.attrs = fts_voltage_attrs
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fts_temp_attr_group = {
|
||||||
|
.attrs = fts_temp_attrs
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group fts_fan_attr_group = {
|
||||||
|
.attrs = fts_fan_attrs
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group *fts_attr_groups[] = {
|
||||||
|
&fts_voltage_attr_group,
|
||||||
|
&fts_temp_attr_group,
|
||||||
|
&fts_fan_attr_group,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* Module initialization / remove functions */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static int fts_remove(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct fts_data *data = dev_get_drvdata(&client->dev);
|
||||||
|
|
||||||
|
watchdog_unregister_device(&data->wdd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fts_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
u8 revision;
|
||||||
|
struct fts_data *data;
|
||||||
|
int err;
|
||||||
|
s8 deviceid;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
|
||||||
|
if (client->addr != 0x73)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Baseboard Management Controller check */
|
||||||
|
deviceid = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG);
|
||||||
|
if (deviceid > 0 && (deviceid & 0xF0) == 0x10) {
|
||||||
|
switch (deviceid & 0x0F) {
|
||||||
|
case 0x01:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_dbg(&client->dev,
|
||||||
|
"No Baseboard Management Controller\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dev_dbg(&client->dev, "No fujitsu board\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = devm_kzalloc(&client->dev, sizeof(struct fts_data),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
mutex_init(&data->update_lock);
|
||||||
|
mutex_init(&data->access_lock);
|
||||||
|
data->client = client;
|
||||||
|
dev_set_drvdata(&client->dev, data);
|
||||||
|
|
||||||
|
err = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
revision = err;
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev,
|
||||||
|
"ftsteutates",
|
||||||
|
data,
|
||||||
|
fts_attr_groups);
|
||||||
|
if (IS_ERR(hwmon_dev))
|
||||||
|
return PTR_ERR(hwmon_dev);
|
||||||
|
|
||||||
|
err = fts_watchdog_init(data);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
dev_info(&client->dev, "Detected FTS Teutates chip, revision: %d.%d\n",
|
||||||
|
(revision & 0xF0) >> 4, revision & 0x0F);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
/* Module Details */
|
||||||
|
/*****************************************************************************/
|
||||||
|
static struct i2c_driver fts_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "ftsteutates",
|
||||||
|
},
|
||||||
|
.id_table = fts_id,
|
||||||
|
.probe = fts_probe,
|
||||||
|
.remove = fts_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(fts_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Thilo Cestonaro <thilo.cestonaro@ts.fujitsu.com>");
|
||||||
|
MODULE_DESCRIPTION("FTS Teutates driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
445
drivers/hwmon/ina3221.c
Normal file
445
drivers/hwmon/ina3221.c
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
/*
|
||||||
|
* INA3221 Triple Current/Voltage Monitor
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.com/
|
||||||
|
* Andrew F. Davis <afd@ti.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* General Public License for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
#define INA3221_DRIVER_NAME "ina3221"
|
||||||
|
|
||||||
|
#define INA3221_CONFIG 0x00
|
||||||
|
#define INA3221_SHUNT1 0x01
|
||||||
|
#define INA3221_BUS1 0x02
|
||||||
|
#define INA3221_SHUNT2 0x03
|
||||||
|
#define INA3221_BUS2 0x04
|
||||||
|
#define INA3221_SHUNT3 0x05
|
||||||
|
#define INA3221_BUS3 0x06
|
||||||
|
#define INA3221_CRIT1 0x07
|
||||||
|
#define INA3221_WARN1 0x08
|
||||||
|
#define INA3221_CRIT2 0x09
|
||||||
|
#define INA3221_WARN2 0x0a
|
||||||
|
#define INA3221_CRIT3 0x0b
|
||||||
|
#define INA3221_WARN3 0x0c
|
||||||
|
#define INA3221_MASK_ENABLE 0x0f
|
||||||
|
|
||||||
|
#define INA3221_CONFIG_MODE_SHUNT BIT(1)
|
||||||
|
#define INA3221_CONFIG_MODE_BUS BIT(2)
|
||||||
|
#define INA3221_CONFIG_MODE_CONTINUOUS BIT(3)
|
||||||
|
|
||||||
|
#define INA3221_RSHUNT_DEFAULT 10000
|
||||||
|
|
||||||
|
enum ina3221_fields {
|
||||||
|
/* Configuration */
|
||||||
|
F_RST,
|
||||||
|
|
||||||
|
/* Alert Flags */
|
||||||
|
F_WF3, F_WF2, F_WF1,
|
||||||
|
F_CF3, F_CF2, F_CF1,
|
||||||
|
|
||||||
|
/* sentinel */
|
||||||
|
F_MAX_FIELDS
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct reg_field ina3221_reg_fields[] = {
|
||||||
|
[F_RST] = REG_FIELD(INA3221_CONFIG, 15, 15),
|
||||||
|
|
||||||
|
[F_WF3] = REG_FIELD(INA3221_MASK_ENABLE, 3, 3),
|
||||||
|
[F_WF2] = REG_FIELD(INA3221_MASK_ENABLE, 4, 4),
|
||||||
|
[F_WF1] = REG_FIELD(INA3221_MASK_ENABLE, 5, 5),
|
||||||
|
[F_CF3] = REG_FIELD(INA3221_MASK_ENABLE, 7, 7),
|
||||||
|
[F_CF2] = REG_FIELD(INA3221_MASK_ENABLE, 8, 8),
|
||||||
|
[F_CF1] = REG_FIELD(INA3221_MASK_ENABLE, 9, 9),
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ina3221_channels {
|
||||||
|
INA3221_CHANNEL1,
|
||||||
|
INA3221_CHANNEL2,
|
||||||
|
INA3221_CHANNEL3,
|
||||||
|
INA3221_NUM_CHANNELS
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned int register_channel[] = {
|
||||||
|
[INA3221_SHUNT1] = INA3221_CHANNEL1,
|
||||||
|
[INA3221_SHUNT2] = INA3221_CHANNEL2,
|
||||||
|
[INA3221_SHUNT3] = INA3221_CHANNEL3,
|
||||||
|
[INA3221_CRIT1] = INA3221_CHANNEL1,
|
||||||
|
[INA3221_CRIT2] = INA3221_CHANNEL2,
|
||||||
|
[INA3221_CRIT3] = INA3221_CHANNEL3,
|
||||||
|
[INA3221_WARN1] = INA3221_CHANNEL1,
|
||||||
|
[INA3221_WARN2] = INA3221_CHANNEL2,
|
||||||
|
[INA3221_WARN3] = INA3221_CHANNEL3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct ina3221_data - device specific information
|
||||||
|
* @regmap: Register map of the device
|
||||||
|
* @fields: Register fields of the device
|
||||||
|
* @shunt_resistors: Array of resistor values per channel
|
||||||
|
*/
|
||||||
|
struct ina3221_data {
|
||||||
|
struct regmap *regmap;
|
||||||
|
struct regmap_field *fields[F_MAX_FIELDS];
|
||||||
|
int shunt_resistors[INA3221_NUM_CHANNELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg,
|
||||||
|
int *val)
|
||||||
|
{
|
||||||
|
unsigned int regval;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_read(ina->regmap, reg, ®val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*val = sign_extend32(regval >> 3, 12);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_show_bus_voltage(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int reg = sd_attr->index;
|
||||||
|
int val, voltage_mv, ret;
|
||||||
|
|
||||||
|
ret = ina3221_read_value(ina, reg, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
voltage_mv = val * 8;
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", voltage_mv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_show_shunt_voltage(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int reg = sd_attr->index;
|
||||||
|
int val, voltage_uv, ret;
|
||||||
|
|
||||||
|
ret = ina3221_read_value(ina, reg, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
voltage_uv = val * 40;
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", voltage_uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_show_current(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int reg = sd_attr->index;
|
||||||
|
unsigned int channel = register_channel[reg];
|
||||||
|
int resistance_uo = ina->shunt_resistors[channel];
|
||||||
|
int val, current_ma, voltage_nv, ret;
|
||||||
|
|
||||||
|
ret = ina3221_read_value(ina, reg, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
voltage_nv = val * 40000;
|
||||||
|
|
||||||
|
current_ma = DIV_ROUND_CLOSEST(voltage_nv, resistance_uo);
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", current_ma);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_set_current(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int reg = sd_attr->index;
|
||||||
|
unsigned int channel = register_channel[reg];
|
||||||
|
int resistance_uo = ina->shunt_resistors[channel];
|
||||||
|
int val, current_ma, voltage_uv, ret;
|
||||||
|
|
||||||
|
ret = kstrtoint(buf, 0, ¤t_ma);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* clamp current */
|
||||||
|
current_ma = clamp_val(current_ma,
|
||||||
|
INT_MIN / resistance_uo,
|
||||||
|
INT_MAX / resistance_uo);
|
||||||
|
|
||||||
|
voltage_uv = DIV_ROUND_CLOSEST(current_ma * resistance_uo, 1000);
|
||||||
|
|
||||||
|
/* clamp voltage */
|
||||||
|
voltage_uv = clamp_val(voltage_uv, -163800, 163800);
|
||||||
|
|
||||||
|
/* 1 / 40uV(scale) << 3(register shift) = 5 */
|
||||||
|
val = DIV_ROUND_CLOSEST(voltage_uv, 5) & 0xfff8;
|
||||||
|
|
||||||
|
ret = regmap_write(ina->regmap, reg, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_show_shunt(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int channel = sd_attr->index;
|
||||||
|
unsigned int resistance_uo;
|
||||||
|
|
||||||
|
resistance_uo = ina->shunt_resistors[channel];
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", resistance_uo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_set_shunt(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int channel = sd_attr->index;
|
||||||
|
int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = kstrtoint(buf, 0, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
val = clamp_val(val, 1, INT_MAX);
|
||||||
|
|
||||||
|
ina->shunt_resistors[channel] = val;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina3221_show_alert(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *sd_attr = to_sensor_dev_attr(attr);
|
||||||
|
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||||
|
unsigned int field = sd_attr->index;
|
||||||
|
unsigned int regval;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_field_read(ina->fields[field], ®val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", regval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bus voltage */
|
||||||
|
static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO,
|
||||||
|
ina3221_show_bus_voltage, NULL, INA3221_BUS1);
|
||||||
|
static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO,
|
||||||
|
ina3221_show_bus_voltage, NULL, INA3221_BUS2);
|
||||||
|
static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO,
|
||||||
|
ina3221_show_bus_voltage, NULL, INA3221_BUS3);
|
||||||
|
|
||||||
|
/* calculated current */
|
||||||
|
static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO,
|
||||||
|
ina3221_show_current, NULL, INA3221_SHUNT1);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO,
|
||||||
|
ina3221_show_current, NULL, INA3221_SHUNT2);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr3_input, S_IRUGO,
|
||||||
|
ina3221_show_current, NULL, INA3221_SHUNT3);
|
||||||
|
|
||||||
|
/* shunt resistance */
|
||||||
|
static SENSOR_DEVICE_ATTR(shunt1_resistor, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL1);
|
||||||
|
static SENSOR_DEVICE_ATTR(shunt2_resistor, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL2);
|
||||||
|
static SENSOR_DEVICE_ATTR(shunt3_resistor, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_shunt, ina3221_set_shunt, INA3221_CHANNEL3);
|
||||||
|
|
||||||
|
/* critical current */
|
||||||
|
static SENSOR_DEVICE_ATTR(curr1_crit, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_CRIT1);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr2_crit, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_CRIT2);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr3_crit, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_CRIT3);
|
||||||
|
|
||||||
|
/* critical current alert */
|
||||||
|
static SENSOR_DEVICE_ATTR(curr1_crit_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_CF1);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr2_crit_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_CF2);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr3_crit_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_CF3);
|
||||||
|
|
||||||
|
/* warning current */
|
||||||
|
static SENSOR_DEVICE_ATTR(curr1_max, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_WARN1);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr2_max, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_WARN2);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr3_max, S_IRUGO | S_IWUSR,
|
||||||
|
ina3221_show_current, ina3221_set_current, INA3221_WARN3);
|
||||||
|
|
||||||
|
/* warning current alert */
|
||||||
|
static SENSOR_DEVICE_ATTR(curr1_max_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_WF1);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr2_max_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_WF2);
|
||||||
|
static SENSOR_DEVICE_ATTR(curr3_max_alarm, S_IRUGO,
|
||||||
|
ina3221_show_alert, NULL, F_WF3);
|
||||||
|
|
||||||
|
/* shunt voltage */
|
||||||
|
static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO,
|
||||||
|
ina3221_show_shunt_voltage, NULL, INA3221_SHUNT1);
|
||||||
|
static SENSOR_DEVICE_ATTR(in5_input, S_IRUGO,
|
||||||
|
ina3221_show_shunt_voltage, NULL, INA3221_SHUNT2);
|
||||||
|
static SENSOR_DEVICE_ATTR(in6_input, S_IRUGO,
|
||||||
|
ina3221_show_shunt_voltage, NULL, INA3221_SHUNT3);
|
||||||
|
|
||||||
|
static struct attribute *ina3221_attrs[] = {
|
||||||
|
/* channel 1 */
|
||||||
|
&sensor_dev_attr_in1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_shunt1_resistor.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr1_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr1_crit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr1_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr1_max_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in4_input.dev_attr.attr,
|
||||||
|
|
||||||
|
/* channel 2 */
|
||||||
|
&sensor_dev_attr_in2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr2_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_shunt2_resistor.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr2_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr2_crit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr2_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr2_max_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in5_input.dev_attr.attr,
|
||||||
|
|
||||||
|
/* channel 3 */
|
||||||
|
&sensor_dev_attr_in3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr3_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_shunt3_resistor.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr3_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr3_crit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr3_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_curr3_max_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in6_input.dev_attr.attr,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
ATTRIBUTE_GROUPS(ina3221);
|
||||||
|
|
||||||
|
static const struct regmap_range ina3221_yes_ranges[] = {
|
||||||
|
regmap_reg_range(INA3221_SHUNT1, INA3221_BUS3),
|
||||||
|
regmap_reg_range(INA3221_MASK_ENABLE, INA3221_MASK_ENABLE),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_access_table ina3221_volatile_table = {
|
||||||
|
.yes_ranges = ina3221_yes_ranges,
|
||||||
|
.n_yes_ranges = ARRAY_SIZE(ina3221_yes_ranges),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_config ina3221_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 16,
|
||||||
|
|
||||||
|
.cache_type = REGCACHE_RBTREE,
|
||||||
|
.volatile_table = &ina3221_volatile_table,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ina3221_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
struct ina3221_data *ina;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
int i, ret;
|
||||||
|
|
||||||
|
ina = devm_kzalloc(dev, sizeof(*ina), GFP_KERNEL);
|
||||||
|
if (!ina)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ina->regmap = devm_regmap_init_i2c(client, &ina3221_regmap_config);
|
||||||
|
if (IS_ERR(ina->regmap)) {
|
||||||
|
dev_err(dev, "Unable to allocate register map\n");
|
||||||
|
return PTR_ERR(ina->regmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < F_MAX_FIELDS; i++) {
|
||||||
|
ina->fields[i] = devm_regmap_field_alloc(dev,
|
||||||
|
ina->regmap,
|
||||||
|
ina3221_reg_fields[i]);
|
||||||
|
if (IS_ERR(ina->fields[i])) {
|
||||||
|
dev_err(dev, "Unable to allocate regmap fields\n");
|
||||||
|
return PTR_ERR(ina->fields[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < INA3221_NUM_CHANNELS; i++)
|
||||||
|
ina->shunt_resistors[i] = INA3221_RSHUNT_DEFAULT;
|
||||||
|
|
||||||
|
ret = regmap_field_write(ina->fields[F_RST], true);
|
||||||
|
if (ret) {
|
||||||
|
dev_err(dev, "Unable to reset device\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev,
|
||||||
|
client->name,
|
||||||
|
ina, ina3221_groups);
|
||||||
|
if (IS_ERR(hwmon_dev)) {
|
||||||
|
dev_err(dev, "Unable to register hwmon device\n");
|
||||||
|
return PTR_ERR(hwmon_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id ina3221_of_match_table[] = {
|
||||||
|
{ .compatible = "ti,ina3221", },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, ina3221_of_match_table);
|
||||||
|
|
||||||
|
static const struct i2c_device_id ina3221_ids[] = {
|
||||||
|
{ "ina3221", 0 },
|
||||||
|
{ /* sentinel */ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(i2c, ina3221_ids);
|
||||||
|
|
||||||
|
static struct i2c_driver ina3221_i2c_driver = {
|
||||||
|
.probe = ina3221_probe,
|
||||||
|
.driver = {
|
||||||
|
.name = INA3221_DRIVER_NAME,
|
||||||
|
.of_match_table = ina3221_of_match_table,
|
||||||
|
},
|
||||||
|
.id_table = ina3221_ids,
|
||||||
|
};
|
||||||
|
module_i2c_driver(ina3221_i2c_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
|
||||||
|
MODULE_DESCRIPTION("Texas Instruments INA3221 HWMon Driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
|
@ -31,6 +31,7 @@
|
||||||
#include <linux/hwmon-sysfs.h>
|
#include <linux/hwmon-sysfs.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
|
||||||
/* Addresses to scan */
|
/* Addresses to scan */
|
||||||
static const unsigned short normal_i2c[] = {
|
static const unsigned short normal_i2c[] = {
|
||||||
|
@ -104,6 +105,9 @@ static const unsigned short normal_i2c[] = {
|
||||||
#define MCP9804_DEVID 0x0200
|
#define MCP9804_DEVID 0x0200
|
||||||
#define MCP9804_DEVID_MASK 0xfffc
|
#define MCP9804_DEVID_MASK 0xfffc
|
||||||
|
|
||||||
|
#define MCP9808_DEVID 0x0400
|
||||||
|
#define MCP9808_DEVID_MASK 0xfffc
|
||||||
|
|
||||||
#define MCP98242_DEVID 0x2000
|
#define MCP98242_DEVID 0x2000
|
||||||
#define MCP98242_DEVID_MASK 0xfffc
|
#define MCP98242_DEVID_MASK 0xfffc
|
||||||
|
|
||||||
|
@ -160,6 +164,7 @@ static struct jc42_chips jc42_chips[] = {
|
||||||
{ IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK },
|
{ IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK },
|
||||||
{ MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK },
|
{ MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK },
|
||||||
{ MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK },
|
{ MCP_MANID, MCP9804_DEVID, MCP9804_DEVID_MASK },
|
||||||
|
{ MCP_MANID, MCP9808_DEVID, MCP9808_DEVID_MASK },
|
||||||
{ MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK },
|
{ MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK },
|
||||||
{ MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK },
|
{ MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK },
|
||||||
{ MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK },
|
{ MCP_MANID, MCP98244_DEVID, MCP98244_DEVID_MASK },
|
||||||
|
@ -537,11 +542,20 @@ static const struct i2c_device_id jc42_id[] = {
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, jc42_id);
|
MODULE_DEVICE_TABLE(i2c, jc42_id);
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id jc42_of_ids[] = {
|
||||||
|
{ .compatible = "jedec,jc-42.4-temp", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, jc42_of_ids);
|
||||||
|
#endif
|
||||||
|
|
||||||
static struct i2c_driver jc42_driver = {
|
static struct i2c_driver jc42_driver = {
|
||||||
.class = I2C_CLASS_SPD,
|
.class = I2C_CLASS_SPD | I2C_CLASS_HWMON,
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "jc42",
|
.name = "jc42",
|
||||||
.pm = JC42_DEV_PM_OPS,
|
.pm = JC42_DEV_PM_OPS,
|
||||||
|
.of_match_table = of_match_ptr(jc42_of_ids),
|
||||||
},
|
},
|
||||||
.probe = jc42_probe,
|
.probe = jc42_probe,
|
||||||
.remove = jc42_remove,
|
.remove = jc42_remove,
|
||||||
|
|
|
@ -29,23 +29,13 @@
|
||||||
|
|
||||||
struct jz4740_hwmon {
|
struct jz4740_hwmon {
|
||||||
void __iomem *base;
|
void __iomem *base;
|
||||||
|
|
||||||
int irq;
|
int irq;
|
||||||
|
|
||||||
const struct mfd_cell *cell;
|
const struct mfd_cell *cell;
|
||||||
struct device *hwmon;
|
struct platform_device *pdev;
|
||||||
|
|
||||||
struct completion read_completion;
|
struct completion read_completion;
|
||||||
|
|
||||||
struct mutex lock;
|
struct mutex lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
static ssize_t jz4740_hwmon_show_name(struct device *dev,
|
|
||||||
struct device_attribute *dev_attr, char *buf)
|
|
||||||
{
|
|
||||||
return sprintf(buf, "jz4740\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
|
static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
|
||||||
{
|
{
|
||||||
struct jz4740_hwmon *hwmon = data;
|
struct jz4740_hwmon *hwmon = data;
|
||||||
|
@ -58,6 +48,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||||
struct device_attribute *dev_attr, char *buf)
|
struct device_attribute *dev_attr, char *buf)
|
||||||
{
|
{
|
||||||
struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
|
struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
|
||||||
|
struct platform_device *pdev = hwmon->pdev;
|
||||||
struct completion *completion = &hwmon->read_completion;
|
struct completion *completion = &hwmon->read_completion;
|
||||||
long t;
|
long t;
|
||||||
unsigned long val;
|
unsigned long val;
|
||||||
|
@ -68,7 +59,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||||
reinit_completion(completion);
|
reinit_completion(completion);
|
||||||
|
|
||||||
enable_irq(hwmon->irq);
|
enable_irq(hwmon->irq);
|
||||||
hwmon->cell->enable(to_platform_device(dev));
|
hwmon->cell->enable(pdev);
|
||||||
|
|
||||||
t = wait_for_completion_interruptible_timeout(completion, HZ);
|
t = wait_for_completion_interruptible_timeout(completion, HZ);
|
||||||
|
|
||||||
|
@ -80,7 +71,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||||
ret = t ? t : -ETIMEDOUT;
|
ret = t ? t : -ETIMEDOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
hwmon->cell->disable(to_platform_device(dev));
|
hwmon->cell->disable(pdev);
|
||||||
disable_irq(hwmon->irq);
|
disable_irq(hwmon->irq);
|
||||||
|
|
||||||
mutex_unlock(&hwmon->lock);
|
mutex_unlock(&hwmon->lock);
|
||||||
|
@ -88,26 +79,24 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL);
|
|
||||||
static DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL);
|
static DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL);
|
||||||
|
|
||||||
static struct attribute *jz4740_hwmon_attributes[] = {
|
static struct attribute *jz4740_attrs[] = {
|
||||||
&dev_attr_name.attr,
|
|
||||||
&dev_attr_in0_input.attr,
|
&dev_attr_in0_input.attr,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct attribute_group jz4740_hwmon_attr_group = {
|
ATTRIBUTE_GROUPS(jz4740);
|
||||||
.attrs = jz4740_hwmon_attributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int jz4740_hwmon_probe(struct platform_device *pdev)
|
static int jz4740_hwmon_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
struct jz4740_hwmon *hwmon;
|
struct jz4740_hwmon *hwmon;
|
||||||
|
struct device *hwmon_dev;
|
||||||
struct resource *mem;
|
struct resource *mem;
|
||||||
|
|
||||||
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
|
hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
|
||||||
if (!hwmon)
|
if (!hwmon)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
@ -125,12 +114,11 @@ static int jz4740_hwmon_probe(struct platform_device *pdev)
|
||||||
if (IS_ERR(hwmon->base))
|
if (IS_ERR(hwmon->base))
|
||||||
return PTR_ERR(hwmon->base);
|
return PTR_ERR(hwmon->base);
|
||||||
|
|
||||||
|
hwmon->pdev = pdev;
|
||||||
init_completion(&hwmon->read_completion);
|
init_completion(&hwmon->read_completion);
|
||||||
mutex_init(&hwmon->lock);
|
mutex_init(&hwmon->lock);
|
||||||
|
|
||||||
platform_set_drvdata(pdev, hwmon);
|
ret = devm_request_irq(dev, hwmon->irq, jz4740_hwmon_irq, 0,
|
||||||
|
|
||||||
ret = devm_request_irq(&pdev->dev, hwmon->irq, jz4740_hwmon_irq, 0,
|
|
||||||
pdev->name, hwmon);
|
pdev->name, hwmon);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
|
dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
|
||||||
|
@ -138,38 +126,13 @@ static int jz4740_hwmon_probe(struct platform_device *pdev)
|
||||||
}
|
}
|
||||||
disable_irq(hwmon->irq);
|
disable_irq(hwmon->irq);
|
||||||
|
|
||||||
ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jz4740", hwmon,
|
||||||
if (ret) {
|
jz4740_groups);
|
||||||
dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret);
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
hwmon->hwmon = hwmon_device_register(&pdev->dev);
|
|
||||||
if (IS_ERR(hwmon->hwmon)) {
|
|
||||||
ret = PTR_ERR(hwmon->hwmon);
|
|
||||||
goto err_remove_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_remove_file:
|
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int jz4740_hwmon_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
|
|
||||||
|
|
||||||
hwmon_device_unregister(hwmon->hwmon);
|
|
||||||
sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct platform_driver jz4740_hwmon_driver = {
|
static struct platform_driver jz4740_hwmon_driver = {
|
||||||
.probe = jz4740_hwmon_probe,
|
.probe = jz4740_hwmon_probe,
|
||||||
.remove = jz4740_hwmon_remove,
|
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "jz4740-hwmon",
|
.name = "jz4740-hwmon",
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/hwmon-sysfs.h>
|
#include <linux/hwmon-sysfs.h>
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/mutex.h>
|
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
#include <linux/thermal.h>
|
#include <linux/thermal.h>
|
||||||
#include "lm75.h"
|
#include "lm75.h"
|
||||||
|
|
||||||
|
@ -66,35 +66,21 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
||||||
|
|
||||||
|
|
||||||
/* The LM75 registers */
|
/* The LM75 registers */
|
||||||
|
#define LM75_REG_TEMP 0x00
|
||||||
#define LM75_REG_CONF 0x01
|
#define LM75_REG_CONF 0x01
|
||||||
static const u8 LM75_REG_TEMP[3] = {
|
#define LM75_REG_HYST 0x02
|
||||||
0x00, /* input */
|
#define LM75_REG_MAX 0x03
|
||||||
0x03, /* max */
|
|
||||||
0x02, /* hyst */
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Each client has this additional data */
|
/* Each client has this additional data */
|
||||||
struct lm75_data {
|
struct lm75_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct device *hwmon_dev;
|
struct regmap *regmap;
|
||||||
struct mutex update_lock;
|
|
||||||
u8 orig_conf;
|
u8 orig_conf;
|
||||||
u8 resolution; /* In bits, between 9 and 12 */
|
u8 resolution; /* In bits, between 9 and 12 */
|
||||||
u8 resolution_limits;
|
u8 resolution_limits;
|
||||||
char valid; /* !=0 if registers are valid */
|
unsigned int sample_time; /* In ms */
|
||||||
unsigned long last_updated; /* In jiffies */
|
|
||||||
unsigned long sample_time; /* In jiffies */
|
|
||||||
s16 temp[3]; /* Register values,
|
|
||||||
0 = input
|
|
||||||
1 = max
|
|
||||||
2 = hyst */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static int lm75_read_value(struct i2c_client *client, u8 reg);
|
|
||||||
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value);
|
|
||||||
static struct lm75_data *lm75_update_device(struct device *dev);
|
|
||||||
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------*/
|
||||||
|
|
||||||
static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
||||||
|
@ -106,12 +92,15 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
||||||
|
|
||||||
static int lm75_read_temp(void *dev, int *temp)
|
static int lm75_read_temp(void *dev, int *temp)
|
||||||
{
|
{
|
||||||
struct lm75_data *data = lm75_update_device(dev);
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
|
unsigned int _temp;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (IS_ERR(data))
|
err = regmap_read(data->regmap, LM75_REG_TEMP, &_temp);
|
||||||
return PTR_ERR(data);
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
*temp = lm75_reg_to_mc(data->temp[0], data->resolution);
|
*temp = lm75_reg_to_mc(_temp, data->resolution);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -120,13 +109,15 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
struct lm75_data *data = lm75_update_device(dev);
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
|
unsigned int temp = 0;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (IS_ERR(data))
|
err = regmap_read(data->regmap, attr->index, &temp);
|
||||||
return PTR_ERR(data);
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
return sprintf(buf, "%ld\n", lm75_reg_to_mc(data->temp[attr->index],
|
return sprintf(buf, "%ld\n", lm75_reg_to_mc(temp, data->resolution));
|
||||||
data->resolution));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||||
|
@ -134,8 +125,6 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
int nr = attr->index;
|
|
||||||
long temp;
|
long temp;
|
||||||
int error;
|
int error;
|
||||||
u8 resolution;
|
u8 resolution;
|
||||||
|
@ -153,25 +142,36 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||||
else
|
else
|
||||||
resolution = data->resolution;
|
resolution = data->resolution;
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||||
data->temp[nr] = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
temp = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
||||||
1000) << (16 - resolution);
|
1000) << (16 - resolution);
|
||||||
lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
|
error = regmap_write(data->regmap, attr->index, temp);
|
||||||
mutex_unlock(&data->update_lock);
|
if (error < 0)
|
||||||
|
return error;
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t show_update_interval(struct device *dev,
|
||||||
|
struct device_attribute *da, char *buf)
|
||||||
|
{
|
||||||
|
struct lm75_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->sample_time);
|
||||||
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,
|
||||||
show_temp, set_temp, 1);
|
show_temp, set_temp, LM75_REG_MAX);
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
||||||
show_temp, set_temp, 2);
|
show_temp, set_temp, LM75_REG_HYST);
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, LM75_REG_TEMP);
|
||||||
|
static DEVICE_ATTR(update_interval, S_IRUGO, show_update_interval, NULL);
|
||||||
|
|
||||||
static struct attribute *lm75_attrs[] = {
|
static struct attribute *lm75_attrs[] = {
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||||
|
&dev_attr_update_interval.attr,
|
||||||
|
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
@ -185,10 +185,40 @@ static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = {
|
||||||
|
|
||||||
/* device probe and removal */
|
/* device probe and removal */
|
||||||
|
|
||||||
|
static bool lm75_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
return reg != LM75_REG_TEMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool lm75_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
return reg == LM75_REG_TEMP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct regmap_config lm75_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 16,
|
||||||
|
.max_register = LM75_REG_MAX,
|
||||||
|
.writeable_reg = lm75_is_writeable_reg,
|
||||||
|
.volatile_reg = lm75_is_volatile_reg,
|
||||||
|
.val_format_endian = REGMAP_ENDIAN_BIG,
|
||||||
|
.cache_type = REGCACHE_RBTREE,
|
||||||
|
.use_single_rw = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void lm75_remove(void *data)
|
||||||
|
{
|
||||||
|
struct lm75_data *lm75 = data;
|
||||||
|
struct i2c_client *client = lm75->client;
|
||||||
|
|
||||||
|
i2c_smbus_write_byte_data(client, LM75_REG_CONF, lm75->orig_conf);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
|
struct device *hwmon_dev;
|
||||||
struct lm75_data *data;
|
struct lm75_data *data;
|
||||||
int status;
|
int status;
|
||||||
u8 set_mask, clr_mask;
|
u8 set_mask, clr_mask;
|
||||||
|
@ -204,8 +234,10 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
data->client = client;
|
data->client = client;
|
||||||
i2c_set_clientdata(client, data);
|
|
||||||
mutex_init(&data->update_lock);
|
data->regmap = devm_regmap_init_i2c(client, &lm75_regmap_config);
|
||||||
|
if (IS_ERR(data->regmap))
|
||||||
|
return PTR_ERR(data->regmap);
|
||||||
|
|
||||||
/* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
|
/* Set to LM75 resolution (9 bits, 1/2 degree C) and range.
|
||||||
* Then tweak to be more precise when appropriate.
|
* Then tweak to be more precise when appropriate.
|
||||||
|
@ -217,7 +249,7 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
case adt75:
|
case adt75:
|
||||||
clr_mask |= 1 << 5; /* not one-shot mode */
|
clr_mask |= 1 << 5; /* not one-shot mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ / 8;
|
data->sample_time = MSEC_PER_SEC / 8;
|
||||||
break;
|
break;
|
||||||
case ds1775:
|
case ds1775:
|
||||||
case ds75:
|
case ds75:
|
||||||
|
@ -225,35 +257,35 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
clr_mask |= 3 << 5;
|
clr_mask |= 3 << 5;
|
||||||
set_mask |= 2 << 5; /* 11-bit mode */
|
set_mask |= 2 << 5; /* 11-bit mode */
|
||||||
data->resolution = 11;
|
data->resolution = 11;
|
||||||
data->sample_time = HZ;
|
data->sample_time = MSEC_PER_SEC;
|
||||||
break;
|
break;
|
||||||
case ds7505:
|
case ds7505:
|
||||||
set_mask |= 3 << 5; /* 12-bit mode */
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
case g751:
|
case g751:
|
||||||
case lm75:
|
case lm75:
|
||||||
case lm75a:
|
case lm75a:
|
||||||
data->resolution = 9;
|
data->resolution = 9;
|
||||||
data->sample_time = HZ / 2;
|
data->sample_time = MSEC_PER_SEC / 2;
|
||||||
break;
|
break;
|
||||||
case lm75b:
|
case lm75b:
|
||||||
data->resolution = 11;
|
data->resolution = 11;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
case max6625:
|
case max6625:
|
||||||
data->resolution = 9;
|
data->resolution = 9;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
case max6626:
|
case max6626:
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->resolution_limits = 9;
|
data->resolution_limits = 9;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
case tcn75:
|
case tcn75:
|
||||||
data->resolution = 9;
|
data->resolution = 9;
|
||||||
data->sample_time = HZ / 8;
|
data->sample_time = MSEC_PER_SEC / 8;
|
||||||
break;
|
break;
|
||||||
case mcp980x:
|
case mcp980x:
|
||||||
data->resolution_limits = 9;
|
data->resolution_limits = 9;
|
||||||
|
@ -262,14 +294,14 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
case tmp101:
|
case tmp101:
|
||||||
set_mask |= 3 << 5; /* 12-bit mode */
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ;
|
data->sample_time = MSEC_PER_SEC;
|
||||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
break;
|
break;
|
||||||
case tmp112:
|
case tmp112:
|
||||||
set_mask |= 3 << 5; /* 12-bit mode */
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
case tmp105:
|
case tmp105:
|
||||||
case tmp175:
|
case tmp175:
|
||||||
|
@ -278,17 +310,17 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
set_mask |= 3 << 5; /* 12-bit mode */
|
set_mask |= 3 << 5; /* 12-bit mode */
|
||||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ / 2;
|
data->sample_time = MSEC_PER_SEC / 2;
|
||||||
break;
|
break;
|
||||||
case tmp75c:
|
case tmp75c:
|
||||||
clr_mask |= 1 << 5; /* not one-shot mode */
|
clr_mask |= 1 << 5; /* not one-shot mode */
|
||||||
data->resolution = 12;
|
data->resolution = 12;
|
||||||
data->sample_time = HZ / 4;
|
data->sample_time = MSEC_PER_SEC / 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* configure as specified */
|
/* configure as specified */
|
||||||
status = lm75_read_value(client, LM75_REG_CONF);
|
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
dev_dbg(dev, "Can't read config? %d\n", status);
|
dev_dbg(dev, "Can't read config? %d\n", status);
|
||||||
return status;
|
return status;
|
||||||
|
@ -297,33 +329,26 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||||
new = status & ~clr_mask;
|
new = status & ~clr_mask;
|
||||||
new |= set_mask;
|
new |= set_mask;
|
||||||
if (status != new)
|
if (status != new)
|
||||||
lm75_write_value(client, LM75_REG_CONF, new);
|
i2c_smbus_write_byte_data(client, LM75_REG_CONF, new);
|
||||||
|
|
||||||
|
devm_add_action(dev, lm75_remove, data);
|
||||||
|
|
||||||
dev_dbg(dev, "Config %02x\n", new);
|
dev_dbg(dev, "Config %02x\n", new);
|
||||||
|
|
||||||
data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||||
data, lm75_groups);
|
data, lm75_groups);
|
||||||
if (IS_ERR(data->hwmon_dev))
|
if (IS_ERR(hwmon_dev))
|
||||||
return PTR_ERR(data->hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
|
|
||||||
devm_thermal_zone_of_sensor_register(data->hwmon_dev, 0,
|
devm_thermal_zone_of_sensor_register(hwmon_dev, 0,
|
||||||
data->hwmon_dev,
|
hwmon_dev,
|
||||||
&lm75_of_thermal_ops);
|
&lm75_of_thermal_ops);
|
||||||
|
|
||||||
dev_info(dev, "%s: sensor '%s'\n",
|
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
|
||||||
dev_name(data->hwmon_dev), client->name);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lm75_remove(struct i2c_client *client)
|
|
||||||
{
|
|
||||||
struct lm75_data *data = i2c_get_clientdata(client);
|
|
||||||
|
|
||||||
hwmon_device_unregister(data->hwmon_dev);
|
|
||||||
lm75_write_value(client, LM75_REG_CONF, data->orig_conf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct i2c_device_id lm75_ids[] = {
|
static const struct i2c_device_id lm75_ids[] = {
|
||||||
{ "adt75", adt75, },
|
{ "adt75", adt75, },
|
||||||
{ "ds1775", ds1775, },
|
{ "ds1775", ds1775, },
|
||||||
|
@ -449,13 +474,13 @@ static int lm75_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
status = lm75_read_value(client, LM75_REG_CONF);
|
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = status | LM75_SHUTDOWN;
|
status = status | LM75_SHUTDOWN;
|
||||||
lm75_write_value(client, LM75_REG_CONF, status);
|
i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,13 +488,13 @@ static int lm75_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
status = lm75_read_value(client, LM75_REG_CONF);
|
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = status & ~LM75_SHUTDOWN;
|
status = status & ~LM75_SHUTDOWN;
|
||||||
lm75_write_value(client, LM75_REG_CONF, status);
|
i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,73 +514,11 @@ static struct i2c_driver lm75_driver = {
|
||||||
.pm = LM75_DEV_PM_OPS,
|
.pm = LM75_DEV_PM_OPS,
|
||||||
},
|
},
|
||||||
.probe = lm75_probe,
|
.probe = lm75_probe,
|
||||||
.remove = lm75_remove,
|
|
||||||
.id_table = lm75_ids,
|
.id_table = lm75_ids,
|
||||||
.detect = lm75_detect,
|
.detect = lm75_detect,
|
||||||
.address_list = normal_i2c,
|
.address_list = normal_i2c,
|
||||||
};
|
};
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/* register access */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* All registers are word-sized, except for the configuration register.
|
|
||||||
* LM75 uses a high-byte first convention, which is exactly opposite to
|
|
||||||
* the SMBus standard.
|
|
||||||
*/
|
|
||||||
static int lm75_read_value(struct i2c_client *client, u8 reg)
|
|
||||||
{
|
|
||||||
if (reg == LM75_REG_CONF)
|
|
||||||
return i2c_smbus_read_byte_data(client, reg);
|
|
||||||
else
|
|
||||||
return i2c_smbus_read_word_swapped(client, reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lm75_write_value(struct i2c_client *client, u8 reg, u16 value)
|
|
||||||
{
|
|
||||||
if (reg == LM75_REG_CONF)
|
|
||||||
return i2c_smbus_write_byte_data(client, reg, value);
|
|
||||||
else
|
|
||||||
return i2c_smbus_write_word_swapped(client, reg, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct lm75_data *lm75_update_device(struct device *dev)
|
|
||||||
{
|
|
||||||
struct lm75_data *data = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = data->client;
|
|
||||||
struct lm75_data *ret = data;
|
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
|
||||||
|
|
||||||
if (time_after(jiffies, data->last_updated + data->sample_time)
|
|
||||||
|| !data->valid) {
|
|
||||||
int i;
|
|
||||||
dev_dbg(&client->dev, "Starting lm75 update\n");
|
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(data->temp); i++) {
|
|
||||||
int status;
|
|
||||||
|
|
||||||
status = lm75_read_value(client, LM75_REG_TEMP[i]);
|
|
||||||
if (unlikely(status < 0)) {
|
|
||||||
dev_dbg(dev,
|
|
||||||
"LM75: Failed to read value: reg %d, error %d\n",
|
|
||||||
LM75_REG_TEMP[i], status);
|
|
||||||
ret = ERR_PTR(status);
|
|
||||||
data->valid = 0;
|
|
||||||
goto abort;
|
|
||||||
}
|
|
||||||
data->temp[i] = status;
|
|
||||||
}
|
|
||||||
data->last_updated = jiffies;
|
|
||||||
data->valid = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
abort:
|
|
||||||
mutex_unlock(&data->update_lock);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
module_i2c_driver(lm75_driver);
|
module_i2c_driver(lm75_driver);
|
||||||
|
|
||||||
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl>");
|
||||||
|
|
|
@ -171,7 +171,6 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
||||||
|
|
||||||
#define SA56004_REG_R_LOCAL_TEMPL 0x22
|
#define SA56004_REG_R_LOCAL_TEMPL 0x22
|
||||||
|
|
||||||
#define LM90_DEF_CONVRATE_RVAL 6 /* Def conversion rate register value */
|
|
||||||
#define LM90_MAX_CONVRATE_MS 16000 /* Maximum conversion rate in ms */
|
#define LM90_MAX_CONVRATE_MS 16000 /* Maximum conversion rate in ms */
|
||||||
|
|
||||||
/* TMP451 registers */
|
/* TMP451 registers */
|
||||||
|
@ -366,11 +365,9 @@ enum lm90_temp11_reg_index {
|
||||||
|
|
||||||
struct lm90_data {
|
struct lm90_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct device *hwmon_dev;
|
|
||||||
const struct attribute_group *groups[6];
|
const struct attribute_group *groups[6];
|
||||||
struct mutex update_lock;
|
struct mutex update_lock;
|
||||||
struct regulator *regulator;
|
bool valid; /* true if register values are valid */
|
||||||
char valid; /* zero until following fields are valid */
|
|
||||||
unsigned long last_updated; /* in jiffies */
|
unsigned long last_updated; /* in jiffies */
|
||||||
int kind;
|
int kind;
|
||||||
u32 flags;
|
u32 flags;
|
||||||
|
@ -412,7 +409,7 @@ static inline s32 adm1032_write_byte(struct i2c_client *client, u8 value)
|
||||||
* because we don't want the address pointer to change between the write
|
* because we don't want the address pointer to change between the write
|
||||||
* byte and the read byte transactions.
|
* byte and the read byte transactions.
|
||||||
*/
|
*/
|
||||||
static int lm90_read_reg(struct i2c_client *client, u8 reg, u8 *value)
|
static int lm90_read_reg(struct i2c_client *client, u8 reg)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
@ -423,20 +420,12 @@ static int lm90_read_reg(struct i2c_client *client, u8 reg, u8 *value)
|
||||||
} else
|
} else
|
||||||
err = i2c_smbus_read_byte_data(client, reg);
|
err = i2c_smbus_read_byte_data(client, reg);
|
||||||
|
|
||||||
if (err < 0) {
|
return err;
|
||||||
dev_warn(&client->dev, "Register %#02x read failed (%d)\n",
|
|
||||||
reg, err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
*value = err;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value)
|
static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl)
|
||||||
{
|
{
|
||||||
int err;
|
int oldh, newh, l;
|
||||||
u8 oldh, newh, l;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* There is a trick here. We have to read two registers to have the
|
* There is a trick here. We have to read two registers to have the
|
||||||
|
@ -451,18 +440,21 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value)
|
||||||
* we have to read the low byte again, and now we believe we have a
|
* we have to read the low byte again, and now we believe we have a
|
||||||
* correct reading.
|
* correct reading.
|
||||||
*/
|
*/
|
||||||
if ((err = lm90_read_reg(client, regh, &oldh))
|
oldh = lm90_read_reg(client, regh);
|
||||||
|| (err = lm90_read_reg(client, regl, &l))
|
if (oldh < 0)
|
||||||
|| (err = lm90_read_reg(client, regh, &newh)))
|
return oldh;
|
||||||
return err;
|
l = lm90_read_reg(client, regl);
|
||||||
|
if (l < 0)
|
||||||
|
return l;
|
||||||
|
newh = lm90_read_reg(client, regh);
|
||||||
|
if (newh < 0)
|
||||||
|
return newh;
|
||||||
if (oldh != newh) {
|
if (oldh != newh) {
|
||||||
err = lm90_read_reg(client, regl, &l);
|
l = lm90_read_reg(client, regl);
|
||||||
if (err)
|
if (l < 0)
|
||||||
return err;
|
return l;
|
||||||
}
|
}
|
||||||
*value = (newh << 8) | l;
|
return (newh << 8) | l;
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -473,20 +465,23 @@ static int lm90_read16(struct i2c_client *client, u8 regh, u8 regl, u16 *value)
|
||||||
* various registers have different meanings as a result of selecting a
|
* various registers have different meanings as a result of selecting a
|
||||||
* non-default remote channel.
|
* non-default remote channel.
|
||||||
*/
|
*/
|
||||||
static inline void lm90_select_remote_channel(struct i2c_client *client,
|
static inline int lm90_select_remote_channel(struct i2c_client *client,
|
||||||
struct lm90_data *data,
|
struct lm90_data *data,
|
||||||
int channel)
|
int channel)
|
||||||
{
|
{
|
||||||
u8 config;
|
int config;
|
||||||
|
|
||||||
if (data->kind == max6696) {
|
if (data->kind == max6696) {
|
||||||
lm90_read_reg(client, LM90_REG_R_CONFIG1, &config);
|
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||||
|
if (config < 0)
|
||||||
|
return config;
|
||||||
config &= ~0x08;
|
config &= ~0x08;
|
||||||
if (channel)
|
if (channel)
|
||||||
config |= 0x08;
|
config |= 0x08;
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
||||||
config);
|
config);
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -513,118 +508,204 @@ static void lm90_set_convrate(struct i2c_client *client, struct lm90_data *data,
|
||||||
data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64);
|
data->update_interval = DIV_ROUND_CLOSEST(update_interval, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int lm90_update_limits(struct device *dev)
|
||||||
|
{
|
||||||
|
struct lm90_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[LOCAL_CRIT] = val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[REMOTE_CRIT] = val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_TCRIT_HYST);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp_hyst = val;
|
||||||
|
|
||||||
|
lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE_LOW] = val << 8;
|
||||||
|
|
||||||
|
if (data->flags & LM90_HAVE_REM_LIMIT_EXT) {
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE_LOW] |= val;
|
||||||
|
}
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE_HIGH] = val << 8;
|
||||||
|
|
||||||
|
if (data->flags & LM90_HAVE_REM_LIMIT_EXT) {
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE_HIGH] |= val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->flags & LM90_HAVE_OFFSET) {
|
||||||
|
val = lm90_read16(client, LM90_REG_R_REMOTE_OFFSH,
|
||||||
|
LM90_REG_R_REMOTE_OFFSL);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE_OFFSET] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->flags & LM90_HAVE_EMERGENCY) {
|
||||||
|
val = lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[LOCAL_EMERG] = val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[REMOTE_EMERG] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data->kind == max6696) {
|
||||||
|
val = lm90_select_remote_channel(client, data, 1);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[REMOTE2_CRIT] = val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp8[REMOTE2_EMERG] = val;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE2_LOW] = val << 8;
|
||||||
|
|
||||||
|
val = lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
data->temp11[REMOTE2_HIGH] = val << 8;
|
||||||
|
|
||||||
|
lm90_select_remote_channel(client, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static struct lm90_data *lm90_update_device(struct device *dev)
|
static struct lm90_data *lm90_update_device(struct device *dev)
|
||||||
{
|
{
|
||||||
struct lm90_data *data = dev_get_drvdata(dev);
|
struct lm90_data *data = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = data->client;
|
struct i2c_client *client = data->client;
|
||||||
unsigned long next_update;
|
unsigned long next_update;
|
||||||
|
int val = 0;
|
||||||
|
|
||||||
mutex_lock(&data->update_lock);
|
mutex_lock(&data->update_lock);
|
||||||
|
|
||||||
|
if (!data->valid) {
|
||||||
|
val = lm90_update_limits(dev);
|
||||||
|
if (val < 0)
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
next_update = data->last_updated +
|
next_update = data->last_updated +
|
||||||
msecs_to_jiffies(data->update_interval);
|
msecs_to_jiffies(data->update_interval);
|
||||||
if (time_after(jiffies, next_update) || !data->valid) {
|
if (time_after(jiffies, next_update) || !data->valid) {
|
||||||
u8 h, l;
|
|
||||||
u8 alarms;
|
|
||||||
|
|
||||||
dev_dbg(&client->dev, "Updating lm90 data.\n");
|
dev_dbg(&client->dev, "Updating lm90 data.\n");
|
||||||
lm90_read_reg(client, LM90_REG_R_LOCAL_LOW,
|
|
||||||
&data->temp8[LOCAL_LOW]);
|
data->valid = false;
|
||||||
lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH,
|
|
||||||
&data->temp8[LOCAL_HIGH]);
|
val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW);
|
||||||
lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT,
|
if (val < 0)
|
||||||
&data->temp8[LOCAL_CRIT]);
|
goto error;
|
||||||
lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT,
|
data->temp8[LOCAL_LOW] = val;
|
||||||
&data->temp8[REMOTE_CRIT]);
|
|
||||||
lm90_read_reg(client, LM90_REG_R_TCRIT_HYST, &data->temp_hyst);
|
val = lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH);
|
||||||
|
if (val < 0)
|
||||||
|
goto error;
|
||||||
|
data->temp8[LOCAL_HIGH] = val;
|
||||||
|
|
||||||
if (data->reg_local_ext) {
|
if (data->reg_local_ext) {
|
||||||
lm90_read16(client, LM90_REG_R_LOCAL_TEMP,
|
val = lm90_read16(client, LM90_REG_R_LOCAL_TEMP,
|
||||||
data->reg_local_ext,
|
data->reg_local_ext);
|
||||||
&data->temp11[LOCAL_TEMP]);
|
if (val < 0)
|
||||||
|
goto error;
|
||||||
|
data->temp11[LOCAL_TEMP] = val;
|
||||||
} else {
|
} else {
|
||||||
if (lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP,
|
val = lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP);
|
||||||
&h) == 0)
|
if (val < 0)
|
||||||
data->temp11[LOCAL_TEMP] = h << 8;
|
goto error;
|
||||||
|
data->temp11[LOCAL_TEMP] = val << 8;
|
||||||
}
|
}
|
||||||
lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||||
LM90_REG_R_REMOTE_TEMPL,
|
LM90_REG_R_REMOTE_TEMPL);
|
||||||
&data->temp11[REMOTE_TEMP]);
|
if (val < 0)
|
||||||
|
goto error;
|
||||||
|
data->temp11[REMOTE_TEMP] = val;
|
||||||
|
|
||||||
if (lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h) == 0) {
|
val = lm90_read_reg(client, LM90_REG_R_STATUS);
|
||||||
data->temp11[REMOTE_LOW] = h << 8;
|
if (val < 0)
|
||||||
if ((data->flags & LM90_HAVE_REM_LIMIT_EXT)
|
goto error;
|
||||||
&& lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL,
|
data->alarms = val; /* lower 8 bit of alarms */
|
||||||
&l) == 0)
|
|
||||||
data->temp11[REMOTE_LOW] |= l;
|
|
||||||
}
|
|
||||||
if (lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &h) == 0) {
|
|
||||||
data->temp11[REMOTE_HIGH] = h << 8;
|
|
||||||
if ((data->flags & LM90_HAVE_REM_LIMIT_EXT)
|
|
||||||
&& lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHL,
|
|
||||||
&l) == 0)
|
|
||||||
data->temp11[REMOTE_HIGH] |= l;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data->flags & LM90_HAVE_OFFSET) {
|
|
||||||
if (lm90_read_reg(client, LM90_REG_R_REMOTE_OFFSH,
|
|
||||||
&h) == 0
|
|
||||||
&& lm90_read_reg(client, LM90_REG_R_REMOTE_OFFSL,
|
|
||||||
&l) == 0)
|
|
||||||
data->temp11[REMOTE_OFFSET] = (h << 8) | l;
|
|
||||||
}
|
|
||||||
if (data->flags & LM90_HAVE_EMERGENCY) {
|
|
||||||
lm90_read_reg(client, MAX6659_REG_R_LOCAL_EMERG,
|
|
||||||
&data->temp8[LOCAL_EMERG]);
|
|
||||||
lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG,
|
|
||||||
&data->temp8[REMOTE_EMERG]);
|
|
||||||
}
|
|
||||||
lm90_read_reg(client, LM90_REG_R_STATUS, &alarms);
|
|
||||||
data->alarms = alarms; /* save as 16 bit value */
|
|
||||||
|
|
||||||
if (data->kind == max6696) {
|
if (data->kind == max6696) {
|
||||||
lm90_select_remote_channel(client, data, 1);
|
val = lm90_select_remote_channel(client, data, 1);
|
||||||
lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT,
|
if (val < 0)
|
||||||
&data->temp8[REMOTE2_CRIT]);
|
goto error;
|
||||||
lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG,
|
|
||||||
&data->temp8[REMOTE2_EMERG]);
|
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||||
lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
LM90_REG_R_REMOTE_TEMPL);
|
||||||
LM90_REG_R_REMOTE_TEMPL,
|
if (val < 0)
|
||||||
&data->temp11[REMOTE2_TEMP]);
|
goto error;
|
||||||
if (!lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h))
|
data->temp11[REMOTE2_TEMP] = val;
|
||||||
data->temp11[REMOTE2_LOW] = h << 8;
|
|
||||||
if (!lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &h))
|
|
||||||
data->temp11[REMOTE2_HIGH] = h << 8;
|
|
||||||
lm90_select_remote_channel(client, data, 0);
|
lm90_select_remote_channel(client, data, 0);
|
||||||
|
|
||||||
if (!lm90_read_reg(client, MAX6696_REG_R_STATUS2,
|
val = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
||||||
&alarms))
|
if (val < 0)
|
||||||
data->alarms |= alarms << 8;
|
goto error;
|
||||||
|
data->alarms |= val << 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Re-enable ALERT# output if it was originally enabled and
|
* Re-enable ALERT# output if it was originally enabled and
|
||||||
* relevant alarms are all clear
|
* relevant alarms are all clear
|
||||||
*/
|
*/
|
||||||
if ((data->config_orig & 0x80) == 0
|
if (!(data->config_orig & 0x80) &&
|
||||||
&& (data->alarms & data->alert_alarms) == 0) {
|
!(data->alarms & data->alert_alarms)) {
|
||||||
u8 config;
|
val = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||||
|
if (val < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
lm90_read_reg(client, LM90_REG_R_CONFIG1, &config);
|
if (val & 0x80) {
|
||||||
if (config & 0x80) {
|
|
||||||
dev_dbg(&client->dev, "Re-enabling ALERT#\n");
|
dev_dbg(&client->dev, "Re-enabling ALERT#\n");
|
||||||
i2c_smbus_write_byte_data(client,
|
i2c_smbus_write_byte_data(client,
|
||||||
LM90_REG_W_CONFIG1,
|
LM90_REG_W_CONFIG1,
|
||||||
config & ~0x80);
|
val & ~0x80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data->last_updated = jiffies;
|
data->last_updated = jiffies;
|
||||||
data->valid = 1;
|
data->valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error:
|
||||||
mutex_unlock(&data->update_lock);
|
mutex_unlock(&data->update_lock);
|
||||||
|
|
||||||
|
if (val < 0)
|
||||||
|
return ERR_PTR(val);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,16 +790,14 @@ static inline int temp_from_u8_adt7461(struct lm90_data *data, u8 val)
|
||||||
{
|
{
|
||||||
if (data->flags & LM90_FLAG_ADT7461_EXT)
|
if (data->flags & LM90_FLAG_ADT7461_EXT)
|
||||||
return (val - 64) * 1000;
|
return (val - 64) * 1000;
|
||||||
else
|
return temp_from_s8(val);
|
||||||
return temp_from_s8(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int temp_from_u16_adt7461(struct lm90_data *data, u16 val)
|
static inline int temp_from_u16_adt7461(struct lm90_data *data, u16 val)
|
||||||
{
|
{
|
||||||
if (data->flags & LM90_FLAG_ADT7461_EXT)
|
if (data->flags & LM90_FLAG_ADT7461_EXT)
|
||||||
return (val - 0x4000) / 64 * 250;
|
return (val - 0x4000) / 64 * 250;
|
||||||
else
|
return temp_from_s16(val);
|
||||||
return temp_from_s16(val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 temp_to_u8_adt7461(struct lm90_data *data, long val)
|
static u8 temp_to_u8_adt7461(struct lm90_data *data, long val)
|
||||||
|
@ -729,13 +808,12 @@ static u8 temp_to_u8_adt7461(struct lm90_data *data, long val)
|
||||||
if (val >= 191000)
|
if (val >= 191000)
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
return (val + 500 + 64000) / 1000;
|
return (val + 500 + 64000) / 1000;
|
||||||
} else {
|
|
||||||
if (val <= 0)
|
|
||||||
return 0;
|
|
||||||
if (val >= 127000)
|
|
||||||
return 127;
|
|
||||||
return (val + 500) / 1000;
|
|
||||||
}
|
}
|
||||||
|
if (val <= 0)
|
||||||
|
return 0;
|
||||||
|
if (val >= 127000)
|
||||||
|
return 127;
|
||||||
|
return (val + 500) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u16 temp_to_u16_adt7461(struct lm90_data *data, long val)
|
static u16 temp_to_u16_adt7461(struct lm90_data *data, long val)
|
||||||
|
@ -746,13 +824,12 @@ static u16 temp_to_u16_adt7461(struct lm90_data *data, long val)
|
||||||
if (val >= 191750)
|
if (val >= 191750)
|
||||||
return 0xFFC0;
|
return 0xFFC0;
|
||||||
return (val + 64000 + 125) / 250 * 64;
|
return (val + 64000 + 125) / 250 * 64;
|
||||||
} else {
|
|
||||||
if (val <= 0)
|
|
||||||
return 0;
|
|
||||||
if (val >= 127750)
|
|
||||||
return 0x7FC0;
|
|
||||||
return (val + 125) / 250 * 64;
|
|
||||||
}
|
}
|
||||||
|
if (val <= 0)
|
||||||
|
return 0;
|
||||||
|
if (val >= 127750)
|
||||||
|
return 0x7FC0;
|
||||||
|
return (val + 125) / 250 * 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -766,6 +843,9 @@ static ssize_t show_temp8(struct device *dev, struct device_attribute *devattr,
|
||||||
struct lm90_data *data = lm90_update_device(dev);
|
struct lm90_data *data = lm90_update_device(dev);
|
||||||
int temp;
|
int temp;
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
if (data->kind == adt7461 || data->kind == tmp451)
|
if (data->kind == adt7461 || data->kind == tmp451)
|
||||||
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
||||||
else if (data->kind == max6646)
|
else if (data->kind == max6646)
|
||||||
|
@ -832,6 +912,9 @@ static ssize_t show_temp11(struct device *dev, struct device_attribute *devattr,
|
||||||
struct lm90_data *data = lm90_update_device(dev);
|
struct lm90_data *data = lm90_update_device(dev);
|
||||||
int temp;
|
int temp;
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
if (data->kind == adt7461 || data->kind == tmp451)
|
if (data->kind == adt7461 || data->kind == tmp451)
|
||||||
temp = temp_from_u16_adt7461(data, data->temp11[attr->index]);
|
temp = temp_from_u16_adt7461(data, data->temp11[attr->index]);
|
||||||
else if (data->kind == max6646)
|
else if (data->kind == max6646)
|
||||||
|
@ -907,6 +990,9 @@ static ssize_t show_temphyst(struct device *dev,
|
||||||
struct lm90_data *data = lm90_update_device(dev);
|
struct lm90_data *data = lm90_update_device(dev);
|
||||||
int temp;
|
int temp;
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
if (data->kind == adt7461 || data->kind == tmp451)
|
if (data->kind == adt7461 || data->kind == tmp451)
|
||||||
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
||||||
else if (data->kind == max6646)
|
else if (data->kind == max6646)
|
||||||
|
@ -953,6 +1039,10 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct lm90_data *data = lm90_update_device(dev);
|
struct lm90_data *data = lm90_update_device(dev);
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", data->alarms);
|
return sprintf(buf, "%d\n", data->alarms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,6 +1053,9 @@ static ssize_t show_alarm(struct device *dev, struct device_attribute
|
||||||
struct lm90_data *data = lm90_update_device(dev);
|
struct lm90_data *data = lm90_update_device(dev);
|
||||||
int bitnr = attr->index;
|
int bitnr = attr->index;
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
|
return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,8 +1497,11 @@ static int lm90_detect(struct i2c_client *client,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lm90_restore_conf(struct i2c_client *client, struct lm90_data *data)
|
static void lm90_restore_conf(void *_data)
|
||||||
{
|
{
|
||||||
|
struct lm90_data *data = _data;
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
|
||||||
/* Restore initial configuration */
|
/* Restore initial configuration */
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE,
|
i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE,
|
||||||
data->convrate_orig);
|
data->convrate_orig);
|
||||||
|
@ -1413,24 +1509,22 @@ static void lm90_restore_conf(struct i2c_client *client, struct lm90_data *data)
|
||||||
data->config_orig);
|
data->config_orig);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||||
{
|
{
|
||||||
u8 config, convrate;
|
int config, convrate;
|
||||||
|
|
||||||
if (lm90_read_reg(client, LM90_REG_R_CONVRATE, &convrate) < 0) {
|
convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE);
|
||||||
dev_warn(&client->dev, "Failed to read convrate register!\n");
|
if (convrate < 0)
|
||||||
convrate = LM90_DEF_CONVRATE_RVAL;
|
return convrate;
|
||||||
}
|
|
||||||
data->convrate_orig = convrate;
|
data->convrate_orig = convrate;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start the conversions.
|
* Start the conversions.
|
||||||
*/
|
*/
|
||||||
lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */
|
lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */
|
||||||
if (lm90_read_reg(client, LM90_REG_R_CONFIG1, &config) < 0) {
|
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||||
dev_warn(&client->dev, "Initialization failed!\n");
|
if (config < 0)
|
||||||
return;
|
return config;
|
||||||
}
|
|
||||||
data->config_orig = config;
|
data->config_orig = config;
|
||||||
|
|
||||||
/* Check Temperature Range Select */
|
/* Check Temperature Range Select */
|
||||||
|
@ -1456,17 +1550,26 @@ static void lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||||
config &= 0xBF; /* run */
|
config &= 0xBF; /* run */
|
||||||
if (config != data->config_orig) /* Only write if changed */
|
if (config != data->config_orig) /* Only write if changed */
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config);
|
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1, config);
|
||||||
|
|
||||||
|
devm_add_action(&client->dev, lm90_restore_conf, data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool lm90_is_tripped(struct i2c_client *client, u16 *status)
|
static bool lm90_is_tripped(struct i2c_client *client, u16 *status)
|
||||||
{
|
{
|
||||||
struct lm90_data *data = i2c_get_clientdata(client);
|
struct lm90_data *data = i2c_get_clientdata(client);
|
||||||
u8 st, st2 = 0;
|
int st, st2 = 0;
|
||||||
|
|
||||||
lm90_read_reg(client, LM90_REG_R_STATUS, &st);
|
st = lm90_read_reg(client, LM90_REG_R_STATUS);
|
||||||
|
if (st < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (data->kind == max6696)
|
if (data->kind == max6696) {
|
||||||
lm90_read_reg(client, MAX6696_REG_R_STATUS2, &st2);
|
st2 = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
||||||
|
if (st2 < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
*status = st | (st2 << 8);
|
*status = st | (st2 << 8);
|
||||||
|
|
||||||
|
@ -1506,6 +1609,16 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id)
|
||||||
return IRQ_NONE;
|
return IRQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void lm90_remove_pec(void *dev)
|
||||||
|
{
|
||||||
|
device_remove_file(dev, &dev_attr_pec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lm90_regulator_disable(void *regulator)
|
||||||
|
{
|
||||||
|
regulator_disable(regulator);
|
||||||
|
}
|
||||||
|
|
||||||
static int lm90_probe(struct i2c_client *client,
|
static int lm90_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
|
@ -1513,6 +1626,7 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
struct i2c_adapter *adapter = to_i2c_adapter(dev->parent);
|
struct i2c_adapter *adapter = to_i2c_adapter(dev->parent);
|
||||||
struct lm90_data *data;
|
struct lm90_data *data;
|
||||||
struct regulator *regulator;
|
struct regulator *regulator;
|
||||||
|
struct device *hwmon_dev;
|
||||||
int groups = 0;
|
int groups = 0;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
@ -1526,6 +1640,8 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devm_add_action(dev, lm90_regulator_disable, regulator);
|
||||||
|
|
||||||
data = devm_kzalloc(dev, sizeof(struct lm90_data), GFP_KERNEL);
|
data = devm_kzalloc(dev, sizeof(struct lm90_data), GFP_KERNEL);
|
||||||
if (!data)
|
if (!data)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
@ -1534,8 +1650,6 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
i2c_set_clientdata(client, data);
|
i2c_set_clientdata(client, data);
|
||||||
mutex_init(&data->update_lock);
|
mutex_init(&data->update_lock);
|
||||||
|
|
||||||
data->regulator = regulator;
|
|
||||||
|
|
||||||
/* Set the device type */
|
/* Set the device type */
|
||||||
data->kind = id->driver_data;
|
data->kind = id->driver_data;
|
||||||
if (data->kind == adm1032) {
|
if (data->kind == adm1032) {
|
||||||
|
@ -1557,7 +1671,11 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
data->max_convrate = lm90_params[data->kind].max_convrate;
|
data->max_convrate = lm90_params[data->kind].max_convrate;
|
||||||
|
|
||||||
/* Initialize the LM90 chip */
|
/* Initialize the LM90 chip */
|
||||||
lm90_init_client(client, data);
|
err = lm90_init_client(client, data);
|
||||||
|
if (err < 0) {
|
||||||
|
dev_err(dev, "Failed to initialize device\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/* Register sysfs hooks */
|
/* Register sysfs hooks */
|
||||||
data->groups[groups++] = &lm90_group;
|
data->groups[groups++] = &lm90_group;
|
||||||
|
@ -1577,15 +1695,14 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
if (client->flags & I2C_CLIENT_PEC) {
|
if (client->flags & I2C_CLIENT_PEC) {
|
||||||
err = device_create_file(dev, &dev_attr_pec);
|
err = device_create_file(dev, &dev_attr_pec);
|
||||||
if (err)
|
if (err)
|
||||||
goto exit_restore;
|
return err;
|
||||||
|
devm_add_action(dev, lm90_remove_pec, dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||||
data, data->groups);
|
data, data->groups);
|
||||||
if (IS_ERR(data->hwmon_dev)) {
|
if (IS_ERR(hwmon_dev))
|
||||||
err = PTR_ERR(data->hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
goto exit_remove_pec;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->irq) {
|
if (client->irq) {
|
||||||
dev_dbg(dev, "IRQ: %d\n", client->irq);
|
dev_dbg(dev, "IRQ: %d\n", client->irq);
|
||||||
|
@ -1595,32 +1712,10 @@ static int lm90_probe(struct i2c_client *client,
|
||||||
"lm90", client);
|
"lm90", client);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(dev, "cannot request IRQ %d\n", client->irq);
|
dev_err(dev, "cannot request IRQ %d\n", client->irq);
|
||||||
goto exit_unregister;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
exit_unregister:
|
|
||||||
hwmon_device_unregister(data->hwmon_dev);
|
|
||||||
exit_remove_pec:
|
|
||||||
device_remove_file(dev, &dev_attr_pec);
|
|
||||||
exit_restore:
|
|
||||||
lm90_restore_conf(client, data);
|
|
||||||
regulator_disable(data->regulator);
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int lm90_remove(struct i2c_client *client)
|
|
||||||
{
|
|
||||||
struct lm90_data *data = i2c_get_clientdata(client);
|
|
||||||
|
|
||||||
hwmon_device_unregister(data->hwmon_dev);
|
|
||||||
device_remove_file(&client->dev, &dev_attr_pec);
|
|
||||||
lm90_restore_conf(client, data);
|
|
||||||
regulator_disable(data->regulator);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1636,13 +1731,16 @@ static void lm90_alert(struct i2c_client *client, unsigned int flag)
|
||||||
*/
|
*/
|
||||||
struct lm90_data *data = i2c_get_clientdata(client);
|
struct lm90_data *data = i2c_get_clientdata(client);
|
||||||
|
|
||||||
if ((data->flags & LM90_HAVE_BROKEN_ALERT)
|
if ((data->flags & LM90_HAVE_BROKEN_ALERT) &&
|
||||||
&& (alarms & data->alert_alarms)) {
|
(alarms & data->alert_alarms)) {
|
||||||
u8 config;
|
int config;
|
||||||
|
|
||||||
dev_dbg(&client->dev, "Disabling ALERT#\n");
|
dev_dbg(&client->dev, "Disabling ALERT#\n");
|
||||||
lm90_read_reg(client, LM90_REG_R_CONFIG1, &config);
|
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
if (config >= 0)
|
||||||
config | 0x80);
|
i2c_smbus_write_byte_data(client,
|
||||||
|
LM90_REG_W_CONFIG1,
|
||||||
|
config | 0x80);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dev_info(&client->dev, "Everything OK\n");
|
dev_info(&client->dev, "Everything OK\n");
|
||||||
|
@ -1655,7 +1753,6 @@ static struct i2c_driver lm90_driver = {
|
||||||
.name = "lm90",
|
.name = "lm90",
|
||||||
},
|
},
|
||||||
.probe = lm90_probe,
|
.probe = lm90_probe,
|
||||||
.remove = lm90_remove,
|
|
||||||
.alert = lm90_alert,
|
.alert = lm90_alert,
|
||||||
.id_table = lm90_id,
|
.id_table = lm90_id,
|
||||||
.detect = lm90_detect,
|
.detect = lm90_detect,
|
||||||
|
|
775
drivers/hwmon/sht3x.c
Normal file
775
drivers/hwmon/sht3x.c
Normal file
|
@ -0,0 +1,775 @@
|
||||||
|
/* Sensirion SHT3x-DIS humidity and temperature sensor driver.
|
||||||
|
* The SHT3x comes in many different versions, this driver is for the
|
||||||
|
* I2C version only.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Sensirion AG, Switzerland
|
||||||
|
* Author: David Frey <david.frey@sensirion.com>
|
||||||
|
* Author: Pascal Sachs <pascal.sachs@sensirion.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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <asm/page.h>
|
||||||
|
#include <linux/crc8.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/jiffies.h>
|
||||||
|
#include <linux/platform_data/sht3x.h>
|
||||||
|
|
||||||
|
/* commands (high precision mode) */
|
||||||
|
static const unsigned char sht3x_cmd_measure_blocking_hpm[] = { 0x2c, 0x06 };
|
||||||
|
static const unsigned char sht3x_cmd_measure_nonblocking_hpm[] = { 0x24, 0x00 };
|
||||||
|
|
||||||
|
/* commands (low power mode) */
|
||||||
|
static const unsigned char sht3x_cmd_measure_blocking_lpm[] = { 0x2c, 0x10 };
|
||||||
|
static const unsigned char sht3x_cmd_measure_nonblocking_lpm[] = { 0x24, 0x16 };
|
||||||
|
|
||||||
|
/* commands for periodic mode */
|
||||||
|
static const unsigned char sht3x_cmd_measure_periodic_mode[] = { 0xe0, 0x00 };
|
||||||
|
static const unsigned char sht3x_cmd_break[] = { 0x30, 0x93 };
|
||||||
|
|
||||||
|
/* commands for heater control */
|
||||||
|
static const unsigned char sht3x_cmd_heater_on[] = { 0x30, 0x6d };
|
||||||
|
static const unsigned char sht3x_cmd_heater_off[] = { 0x30, 0x66 };
|
||||||
|
|
||||||
|
/* other commands */
|
||||||
|
static const unsigned char sht3x_cmd_read_status_reg[] = { 0xf3, 0x2d };
|
||||||
|
static const unsigned char sht3x_cmd_clear_status_reg[] = { 0x30, 0x41 };
|
||||||
|
|
||||||
|
/* delays for non-blocking i2c commands, both in us */
|
||||||
|
#define SHT3X_NONBLOCKING_WAIT_TIME_HPM 15000
|
||||||
|
#define SHT3X_NONBLOCKING_WAIT_TIME_LPM 4000
|
||||||
|
|
||||||
|
#define SHT3X_WORD_LEN 2
|
||||||
|
#define SHT3X_CMD_LENGTH 2
|
||||||
|
#define SHT3X_CRC8_LEN 1
|
||||||
|
#define SHT3X_RESPONSE_LENGTH 6
|
||||||
|
#define SHT3X_CRC8_POLYNOMIAL 0x31
|
||||||
|
#define SHT3X_CRC8_INIT 0xFF
|
||||||
|
#define SHT3X_MIN_TEMPERATURE -45000
|
||||||
|
#define SHT3X_MAX_TEMPERATURE 130000
|
||||||
|
#define SHT3X_MIN_HUMIDITY 0
|
||||||
|
#define SHT3X_MAX_HUMIDITY 100000
|
||||||
|
|
||||||
|
enum sht3x_chips {
|
||||||
|
sht3x,
|
||||||
|
sts3x,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum sht3x_limits {
|
||||||
|
limit_max = 0,
|
||||||
|
limit_max_hyst,
|
||||||
|
limit_min,
|
||||||
|
limit_min_hyst,
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_CRC8_TABLE(sht3x_crc8_table);
|
||||||
|
|
||||||
|
/* periodic measure commands (high precision mode) */
|
||||||
|
static const char periodic_measure_commands_hpm[][SHT3X_CMD_LENGTH] = {
|
||||||
|
/* 0.5 measurements per second */
|
||||||
|
{0x20, 0x32},
|
||||||
|
/* 1 measurements per second */
|
||||||
|
{0x21, 0x30},
|
||||||
|
/* 2 measurements per second */
|
||||||
|
{0x22, 0x36},
|
||||||
|
/* 4 measurements per second */
|
||||||
|
{0x23, 0x34},
|
||||||
|
/* 10 measurements per second */
|
||||||
|
{0x27, 0x37},
|
||||||
|
};
|
||||||
|
|
||||||
|
/* periodic measure commands (low power mode) */
|
||||||
|
static const char periodic_measure_commands_lpm[][SHT3X_CMD_LENGTH] = {
|
||||||
|
/* 0.5 measurements per second */
|
||||||
|
{0x20, 0x2f},
|
||||||
|
/* 1 measurements per second */
|
||||||
|
{0x21, 0x2d},
|
||||||
|
/* 2 measurements per second */
|
||||||
|
{0x22, 0x2b},
|
||||||
|
/* 4 measurements per second */
|
||||||
|
{0x23, 0x29},
|
||||||
|
/* 10 measurements per second */
|
||||||
|
{0x27, 0x2a},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sht3x_limit_commands {
|
||||||
|
const char read_command[SHT3X_CMD_LENGTH];
|
||||||
|
const char write_command[SHT3X_CMD_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct sht3x_limit_commands limit_commands[] = {
|
||||||
|
/* temp1_max, humidity1_max */
|
||||||
|
[limit_max] = { {0xe1, 0x1f}, {0x61, 0x1d} },
|
||||||
|
/* temp_1_max_hyst, humidity1_max_hyst */
|
||||||
|
[limit_max_hyst] = { {0xe1, 0x14}, {0x61, 0x16} },
|
||||||
|
/* temp1_min, humidity1_min */
|
||||||
|
[limit_min] = { {0xe1, 0x02}, {0x61, 0x00} },
|
||||||
|
/* temp_1_min_hyst, humidity1_min_hyst */
|
||||||
|
[limit_min_hyst] = { {0xe1, 0x09}, {0x61, 0x0B} },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SHT3X_NUM_LIMIT_CMD ARRAY_SIZE(limit_commands)
|
||||||
|
|
||||||
|
static const u16 mode_to_update_interval[] = {
|
||||||
|
0,
|
||||||
|
2000,
|
||||||
|
1000,
|
||||||
|
500,
|
||||||
|
250,
|
||||||
|
100,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sht3x_data {
|
||||||
|
struct i2c_client *client;
|
||||||
|
struct mutex i2c_lock; /* lock for sending i2c commands */
|
||||||
|
struct mutex data_lock; /* lock for updating driver data */
|
||||||
|
|
||||||
|
u8 mode;
|
||||||
|
const unsigned char *command;
|
||||||
|
u32 wait_time; /* in us*/
|
||||||
|
unsigned long last_update; /* last update in periodic mode*/
|
||||||
|
|
||||||
|
struct sht3x_platform_data setup;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* cached values for temperature and humidity and limits
|
||||||
|
* the limits arrays have the following order:
|
||||||
|
* max, max_hyst, min, min_hyst
|
||||||
|
*/
|
||||||
|
int temperature;
|
||||||
|
int temperature_limits[SHT3X_NUM_LIMIT_CMD];
|
||||||
|
u32 humidity;
|
||||||
|
u32 humidity_limits[SHT3X_NUM_LIMIT_CMD];
|
||||||
|
};
|
||||||
|
|
||||||
|
static u8 get_mode_from_update_interval(u16 value)
|
||||||
|
{
|
||||||
|
size_t index;
|
||||||
|
u8 number_of_modes = ARRAY_SIZE(mode_to_update_interval);
|
||||||
|
|
||||||
|
if (value == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* find next faster update interval */
|
||||||
|
for (index = 1; index < number_of_modes; index++) {
|
||||||
|
if (mode_to_update_interval[index] <= value)
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return number_of_modes - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sht3x_read_from_command(struct i2c_client *client,
|
||||||
|
struct sht3x_data *data,
|
||||||
|
const char *command,
|
||||||
|
char *buf, int length, u32 wait_time)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&data->i2c_lock);
|
||||||
|
ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH);
|
||||||
|
|
||||||
|
if (ret != SHT3X_CMD_LENGTH) {
|
||||||
|
ret = ret < 0 ? ret : -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wait_time)
|
||||||
|
usleep_range(wait_time, wait_time + 1000);
|
||||||
|
|
||||||
|
ret = i2c_master_recv(client, buf, length);
|
||||||
|
if (ret != length) {
|
||||||
|
ret = ret < 0 ? ret : -EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
out:
|
||||||
|
mutex_unlock(&data->i2c_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sht3x_extract_temperature(u16 raw)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* From datasheet:
|
||||||
|
* T = -45 + 175 * ST / 2^16
|
||||||
|
* Adapted for integer fixed point (3 digit) arithmetic.
|
||||||
|
*/
|
||||||
|
return ((21875 * (int)raw) >> 13) - 45000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 sht3x_extract_humidity(u16 raw)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* From datasheet:
|
||||||
|
* RH = 100 * SRH / 2^16
|
||||||
|
* Adapted for integer fixed point (3 digit) arithmetic.
|
||||||
|
*/
|
||||||
|
return (12500 * (u32)raw) >> 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct sht3x_data *sht3x_update_client(struct device *dev)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
u16 interval_ms = mode_to_update_interval[data->mode];
|
||||||
|
unsigned long interval_jiffies = msecs_to_jiffies(interval_ms);
|
||||||
|
unsigned char buf[SHT3X_RESPONSE_LENGTH];
|
||||||
|
u16 val;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
mutex_lock(&data->data_lock);
|
||||||
|
/*
|
||||||
|
* Only update cached readings once per update interval in periodic
|
||||||
|
* mode. In single shot mode the sensor measures values on demand, so
|
||||||
|
* every time the sysfs interface is called, a measurement is triggered.
|
||||||
|
* In periodic mode however, the measurement process is handled
|
||||||
|
* internally by the sensor and reading out sensor values only makes
|
||||||
|
* sense if a new reading is available.
|
||||||
|
*/
|
||||||
|
if (time_after(jiffies, data->last_update + interval_jiffies)) {
|
||||||
|
ret = sht3x_read_from_command(client, data, data->command, buf,
|
||||||
|
sizeof(buf), data->wait_time);
|
||||||
|
if (ret)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
val = be16_to_cpup((__be16 *)buf);
|
||||||
|
data->temperature = sht3x_extract_temperature(val);
|
||||||
|
val = be16_to_cpup((__be16 *)(buf + 3));
|
||||||
|
data->humidity = sht3x_extract_humidity(val);
|
||||||
|
data->last_update = jiffies;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&data->data_lock);
|
||||||
|
if (ret)
|
||||||
|
return ERR_PTR(ret);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sysfs attributes */
|
||||||
|
static ssize_t temp1_input_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = sht3x_update_client(dev);
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", data->temperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t humidity1_input_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = sht3x_update_client(dev);
|
||||||
|
|
||||||
|
if (IS_ERR(data))
|
||||||
|
return PTR_ERR(data);
|
||||||
|
|
||||||
|
return sprintf(buf, "%u\n", data->humidity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* limits_update must only be called from probe or with data_lock held
|
||||||
|
*/
|
||||||
|
static int limits_update(struct sht3x_data *data)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
u8 index;
|
||||||
|
int temperature;
|
||||||
|
u32 humidity;
|
||||||
|
u16 raw;
|
||||||
|
char buffer[SHT3X_RESPONSE_LENGTH];
|
||||||
|
const struct sht3x_limit_commands *commands;
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
|
||||||
|
for (index = 0; index < SHT3X_NUM_LIMIT_CMD; index++) {
|
||||||
|
commands = &limit_commands[index];
|
||||||
|
ret = sht3x_read_from_command(client, data,
|
||||||
|
commands->read_command, buffer,
|
||||||
|
SHT3X_RESPONSE_LENGTH, 0);
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
raw = be16_to_cpup((__be16 *)buffer);
|
||||||
|
temperature = sht3x_extract_temperature((raw & 0x01ff) << 7);
|
||||||
|
humidity = sht3x_extract_humidity(raw & 0xfe00);
|
||||||
|
data->temperature_limits[index] = temperature;
|
||||||
|
data->humidity_limits[index] = humidity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t temp1_limit_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
u8 index = to_sensor_dev_attr(attr)->index;
|
||||||
|
int temperature_limit = data->temperature_limits[index];
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", temperature_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t humidity1_limit_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
u8 index = to_sensor_dev_attr(attr)->index;
|
||||||
|
u32 humidity_limit = data->humidity_limits[index];
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%u\n", humidity_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* limit_store must only be called with data_lock held
|
||||||
|
*/
|
||||||
|
static size_t limit_store(struct device *dev,
|
||||||
|
size_t count,
|
||||||
|
u8 index,
|
||||||
|
int temperature,
|
||||||
|
u32 humidity)
|
||||||
|
{
|
||||||
|
char buffer[SHT3X_CMD_LENGTH + SHT3X_WORD_LEN + SHT3X_CRC8_LEN];
|
||||||
|
char *position = buffer;
|
||||||
|
int ret;
|
||||||
|
u16 raw;
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
const struct sht3x_limit_commands *commands;
|
||||||
|
|
||||||
|
commands = &limit_commands[index];
|
||||||
|
|
||||||
|
memcpy(position, commands->write_command, SHT3X_CMD_LENGTH);
|
||||||
|
position += SHT3X_CMD_LENGTH;
|
||||||
|
/*
|
||||||
|
* ST = (T + 45) / 175 * 2^16
|
||||||
|
* SRH = RH / 100 * 2^16
|
||||||
|
* adapted for fixed point arithmetic and packed the same as
|
||||||
|
* in limit_show()
|
||||||
|
*/
|
||||||
|
raw = ((u32)(temperature + 45000) * 24543) >> (16 + 7);
|
||||||
|
raw |= ((humidity * 42950) >> 16) & 0xfe00;
|
||||||
|
|
||||||
|
*((__be16 *)position) = cpu_to_be16(raw);
|
||||||
|
position += SHT3X_WORD_LEN;
|
||||||
|
*position = crc8(sht3x_crc8_table,
|
||||||
|
position - SHT3X_WORD_LEN,
|
||||||
|
SHT3X_WORD_LEN,
|
||||||
|
SHT3X_CRC8_INIT);
|
||||||
|
|
||||||
|
mutex_lock(&data->i2c_lock);
|
||||||
|
ret = i2c_master_send(client, buffer, sizeof(buffer));
|
||||||
|
mutex_unlock(&data->i2c_lock);
|
||||||
|
|
||||||
|
if (ret != sizeof(buffer))
|
||||||
|
return ret < 0 ? ret : -EIO;
|
||||||
|
|
||||||
|
data->temperature_limits[index] = temperature;
|
||||||
|
data->humidity_limits[index] = humidity;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t temp1_limit_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
int temperature;
|
||||||
|
int ret;
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
u8 index = to_sensor_dev_attr(attr)->index;
|
||||||
|
|
||||||
|
ret = kstrtoint(buf, 0, &temperature);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
temperature = clamp_val(temperature, SHT3X_MIN_TEMPERATURE,
|
||||||
|
SHT3X_MAX_TEMPERATURE);
|
||||||
|
mutex_lock(&data->data_lock);
|
||||||
|
ret = limit_store(dev, count, index, temperature,
|
||||||
|
data->humidity_limits[index]);
|
||||||
|
mutex_unlock(&data->data_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t humidity1_limit_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
u32 humidity;
|
||||||
|
int ret;
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
u8 index = to_sensor_dev_attr(attr)->index;
|
||||||
|
|
||||||
|
ret = kstrtou32(buf, 0, &humidity);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
humidity = clamp_val(humidity, SHT3X_MIN_HUMIDITY, SHT3X_MAX_HUMIDITY);
|
||||||
|
mutex_lock(&data->data_lock);
|
||||||
|
ret = limit_store(dev, count, index, data->temperature_limits[index],
|
||||||
|
humidity);
|
||||||
|
mutex_unlock(&data->data_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sht3x_select_command(struct sht3x_data *data)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* In blocking mode (clock stretching mode) the I2C bus
|
||||||
|
* is blocked for other traffic, thus the call to i2c_master_recv()
|
||||||
|
* will wait until the data is ready. For non blocking mode, we
|
||||||
|
* have to wait ourselves.
|
||||||
|
*/
|
||||||
|
if (data->mode > 0) {
|
||||||
|
data->command = sht3x_cmd_measure_periodic_mode;
|
||||||
|
data->wait_time = 0;
|
||||||
|
} else if (data->setup.blocking_io) {
|
||||||
|
data->command = data->setup.high_precision ?
|
||||||
|
sht3x_cmd_measure_blocking_hpm :
|
||||||
|
sht3x_cmd_measure_blocking_lpm;
|
||||||
|
data->wait_time = 0;
|
||||||
|
} else {
|
||||||
|
if (data->setup.high_precision) {
|
||||||
|
data->command = sht3x_cmd_measure_nonblocking_hpm;
|
||||||
|
data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_HPM;
|
||||||
|
} else {
|
||||||
|
data->command = sht3x_cmd_measure_nonblocking_lpm;
|
||||||
|
data->wait_time = SHT3X_NONBLOCKING_WAIT_TIME_LPM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int status_register_read(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buffer, int length)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
|
||||||
|
ret = sht3x_read_from_command(client, data, sht3x_cmd_read_status_reg,
|
||||||
|
buffer, length, 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t temp1_alarm_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = status_register_read(dev, attr, buffer,
|
||||||
|
SHT3X_WORD_LEN + SHT3X_CRC8_LEN);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x04));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t humidity1_alarm_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = status_register_read(dev, attr, buffer,
|
||||||
|
SHT3X_WORD_LEN + SHT3X_CRC8_LEN);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x08));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t heater_enable_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
char buffer[SHT3X_WORD_LEN + SHT3X_CRC8_LEN];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = status_register_read(dev, attr, buffer,
|
||||||
|
SHT3X_WORD_LEN + SHT3X_CRC8_LEN);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n", !!(buffer[0] & 0x20));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t heater_enable_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
int ret;
|
||||||
|
bool status;
|
||||||
|
|
||||||
|
ret = kstrtobool(buf, &status);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mutex_lock(&data->i2c_lock);
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_on,
|
||||||
|
SHT3X_CMD_LENGTH);
|
||||||
|
else
|
||||||
|
ret = i2c_master_send(client, (char *)&sht3x_cmd_heater_off,
|
||||||
|
SHT3X_CMD_LENGTH);
|
||||||
|
|
||||||
|
mutex_unlock(&data->i2c_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t update_interval_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%u\n",
|
||||||
|
mode_to_update_interval[data->mode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t update_interval_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf,
|
||||||
|
size_t count)
|
||||||
|
{
|
||||||
|
u16 update_interval;
|
||||||
|
u8 mode;
|
||||||
|
int ret;
|
||||||
|
const char *command;
|
||||||
|
struct sht3x_data *data = dev_get_drvdata(dev);
|
||||||
|
struct i2c_client *client = data->client;
|
||||||
|
|
||||||
|
ret = kstrtou16(buf, 0, &update_interval);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
mode = get_mode_from_update_interval(update_interval);
|
||||||
|
|
||||||
|
mutex_lock(&data->data_lock);
|
||||||
|
/* mode did not change */
|
||||||
|
if (mode == data->mode) {
|
||||||
|
mutex_unlock(&data->data_lock);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&data->i2c_lock);
|
||||||
|
/*
|
||||||
|
* Abort periodic measure mode.
|
||||||
|
* To do any changes to the configuration while in periodic mode, we
|
||||||
|
* have to send a break command to the sensor, which then falls back
|
||||||
|
* to single shot (mode = 0).
|
||||||
|
*/
|
||||||
|
if (data->mode > 0) {
|
||||||
|
ret = i2c_master_send(client, sht3x_cmd_break,
|
||||||
|
SHT3X_CMD_LENGTH);
|
||||||
|
if (ret != SHT3X_CMD_LENGTH)
|
||||||
|
goto out;
|
||||||
|
data->mode = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode > 0) {
|
||||||
|
if (data->setup.high_precision)
|
||||||
|
command = periodic_measure_commands_hpm[mode - 1];
|
||||||
|
else
|
||||||
|
command = periodic_measure_commands_lpm[mode - 1];
|
||||||
|
|
||||||
|
/* select mode */
|
||||||
|
ret = i2c_master_send(client, command, SHT3X_CMD_LENGTH);
|
||||||
|
if (ret != SHT3X_CMD_LENGTH)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* select mode and command */
|
||||||
|
data->mode = mode;
|
||||||
|
sht3x_select_command(data);
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&data->i2c_lock);
|
||||||
|
mutex_unlock(&data->data_lock);
|
||||||
|
if (ret != SHT3X_CMD_LENGTH)
|
||||||
|
return ret < 0 ? ret : -EIO;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, temp1_input_show, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_input, S_IRUGO, humidity1_input_show,
|
||||||
|
NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR,
|
||||||
|
temp1_limit_show, temp1_limit_store,
|
||||||
|
limit_max);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_max, S_IRUGO | S_IWUSR,
|
||||||
|
humidity1_limit_show, humidity1_limit_store,
|
||||||
|
limit_max);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IRUGO | S_IWUSR,
|
||||||
|
temp1_limit_show, temp1_limit_store,
|
||||||
|
limit_max_hyst);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_max_hyst, S_IRUGO | S_IWUSR,
|
||||||
|
humidity1_limit_show, humidity1_limit_store,
|
||||||
|
limit_max_hyst);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR,
|
||||||
|
temp1_limit_show, temp1_limit_store,
|
||||||
|
limit_min);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_min, S_IRUGO | S_IWUSR,
|
||||||
|
humidity1_limit_show, humidity1_limit_store,
|
||||||
|
limit_min);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_min_hyst, S_IRUGO | S_IWUSR,
|
||||||
|
temp1_limit_show, temp1_limit_store,
|
||||||
|
limit_min_hyst);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_min_hyst, S_IRUGO | S_IWUSR,
|
||||||
|
humidity1_limit_show, humidity1_limit_store,
|
||||||
|
limit_min_hyst);
|
||||||
|
static SENSOR_DEVICE_ATTR(temp1_alarm, S_IRUGO, temp1_alarm_show, NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(humidity1_alarm, S_IRUGO, humidity1_alarm_show,
|
||||||
|
NULL, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(heater_enable, S_IRUGO | S_IWUSR,
|
||||||
|
heater_enable_show, heater_enable_store, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR(update_interval, S_IRUGO | S_IWUSR,
|
||||||
|
update_interval_show, update_interval_store, 0);
|
||||||
|
|
||||||
|
static struct attribute *sht3x_attrs[] = {
|
||||||
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_input.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_max.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_max_hyst.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_min_hyst.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_min.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_min_hyst.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_temp1_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_humidity1_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_heater_enable.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_update_interval.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute *sts3x_attrs[] = {
|
||||||
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
ATTRIBUTE_GROUPS(sht3x);
|
||||||
|
ATTRIBUTE_GROUPS(sts3x);
|
||||||
|
|
||||||
|
static int sht3x_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct sht3x_data *data;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
struct i2c_adapter *adap = client->adapter;
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
const struct attribute_group **attribute_groups;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* we require full i2c support since the sht3x uses multi-byte read and
|
||||||
|
* writes as well as multi-byte commands which are not supported by
|
||||||
|
* the smbus protocol
|
||||||
|
*/
|
||||||
|
if (!i2c_check_functionality(adap, I2C_FUNC_I2C))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
ret = i2c_master_send(client, sht3x_cmd_clear_status_reg,
|
||||||
|
SHT3X_CMD_LENGTH);
|
||||||
|
if (ret != SHT3X_CMD_LENGTH)
|
||||||
|
return ret < 0 ? ret : -ENODEV;
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data->setup.blocking_io = false;
|
||||||
|
data->setup.high_precision = true;
|
||||||
|
data->mode = 0;
|
||||||
|
data->last_update = 0;
|
||||||
|
data->client = client;
|
||||||
|
crc8_populate_msb(sht3x_crc8_table, SHT3X_CRC8_POLYNOMIAL);
|
||||||
|
|
||||||
|
if (client->dev.platform_data)
|
||||||
|
data->setup = *(struct sht3x_platform_data *)dev->platform_data;
|
||||||
|
|
||||||
|
sht3x_select_command(data);
|
||||||
|
|
||||||
|
mutex_init(&data->i2c_lock);
|
||||||
|
mutex_init(&data->data_lock);
|
||||||
|
|
||||||
|
ret = limits_update(data);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (id->driver_data == sts3x)
|
||||||
|
attribute_groups = sts3x_groups;
|
||||||
|
else
|
||||||
|
attribute_groups = sht3x_groups;
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev,
|
||||||
|
client->name,
|
||||||
|
data,
|
||||||
|
attribute_groups);
|
||||||
|
|
||||||
|
if (IS_ERR(hwmon_dev))
|
||||||
|
dev_dbg(dev, "unable to register hwmon device\n");
|
||||||
|
|
||||||
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* device ID table */
|
||||||
|
static const struct i2c_device_id sht3x_ids[] = {
|
||||||
|
{"sht3x", sht3x},
|
||||||
|
{"sts3x", sts3x},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(i2c, sht3x_ids);
|
||||||
|
|
||||||
|
static struct i2c_driver sht3x_i2c_driver = {
|
||||||
|
.driver.name = "sht3x",
|
||||||
|
.probe = sht3x_probe,
|
||||||
|
.id_table = sht3x_ids,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(sht3x_i2c_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("David Frey <david.frey@sensirion.com>");
|
||||||
|
MODULE_AUTHOR("Pascal Sachs <pascal.sachs@sensirion.com>");
|
||||||
|
MODULE_DESCRIPTION("Sensirion SHT3x humidity and temperature sensor driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -11,12 +11,9 @@
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/delay.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
@ -27,6 +24,7 @@
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
#include <linux/thermal.h>
|
#include <linux/thermal.h>
|
||||||
#include <linux/of.h>
|
#include <linux/of.h>
|
||||||
|
|
||||||
|
@ -50,14 +48,23 @@
|
||||||
#define TMP102_TLOW_REG 0x02
|
#define TMP102_TLOW_REG 0x02
|
||||||
#define TMP102_THIGH_REG 0x03
|
#define TMP102_THIGH_REG 0x03
|
||||||
|
|
||||||
|
#define TMP102_CONFREG_MASK (TMP102_CONF_SD | TMP102_CONF_TM | \
|
||||||
|
TMP102_CONF_POL | TMP102_CONF_F0 | \
|
||||||
|
TMP102_CONF_F1 | TMP102_CONF_OS | \
|
||||||
|
TMP102_CONF_EM | TMP102_CONF_AL | \
|
||||||
|
TMP102_CONF_CR0 | TMP102_CONF_CR1)
|
||||||
|
|
||||||
|
#define TMP102_CONFIG_CLEAR (TMP102_CONF_SD | TMP102_CONF_OS | \
|
||||||
|
TMP102_CONF_CR0)
|
||||||
|
#define TMP102_CONFIG_SET (TMP102_CONF_TM | TMP102_CONF_EM | \
|
||||||
|
TMP102_CONF_CR1)
|
||||||
|
|
||||||
|
#define CONVERSION_TIME_MS 35 /* in milli-seconds */
|
||||||
|
|
||||||
struct tmp102 {
|
struct tmp102 {
|
||||||
struct i2c_client *client;
|
struct regmap *regmap;
|
||||||
struct device *hwmon_dev;
|
|
||||||
struct mutex lock;
|
|
||||||
u16 config_orig;
|
u16 config_orig;
|
||||||
unsigned long last_update;
|
unsigned long ready_time;
|
||||||
int temp[3];
|
|
||||||
bool first_time;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* convert left adjusted 13-bit TMP102 register value to milliCelsius */
|
/* convert left adjusted 13-bit TMP102 register value to milliCelsius */
|
||||||
|
@ -72,44 +79,22 @@ static inline u16 tmp102_mC_to_reg(int val)
|
||||||
return (val * 128) / 1000;
|
return (val * 128) / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const u8 tmp102_reg[] = {
|
|
||||||
TMP102_TEMP_REG,
|
|
||||||
TMP102_TLOW_REG,
|
|
||||||
TMP102_THIGH_REG,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct tmp102 *tmp102_update_device(struct device *dev)
|
|
||||||
{
|
|
||||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
|
||||||
struct i2c_client *client = tmp102->client;
|
|
||||||
|
|
||||||
mutex_lock(&tmp102->lock);
|
|
||||||
if (time_after(jiffies, tmp102->last_update + HZ / 3)) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < ARRAY_SIZE(tmp102->temp); ++i) {
|
|
||||||
int status = i2c_smbus_read_word_swapped(client,
|
|
||||||
tmp102_reg[i]);
|
|
||||||
if (status > -1)
|
|
||||||
tmp102->temp[i] = tmp102_reg_to_mC(status);
|
|
||||||
}
|
|
||||||
tmp102->last_update = jiffies;
|
|
||||||
tmp102->first_time = false;
|
|
||||||
}
|
|
||||||
mutex_unlock(&tmp102->lock);
|
|
||||||
return tmp102;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tmp102_read_temp(void *dev, int *temp)
|
static int tmp102_read_temp(void *dev, int *temp)
|
||||||
{
|
{
|
||||||
struct tmp102 *tmp102 = tmp102_update_device(dev);
|
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||||
|
unsigned int reg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
/* Is it too early even to return a conversion? */
|
if (time_before(jiffies, tmp102->ready_time)) {
|
||||||
if (tmp102->first_time) {
|
|
||||||
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
|
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
*temp = tmp102->temp[0];
|
ret = regmap_read(tmp102->regmap, TMP102_TEMP_REG, ®);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
*temp = tmp102_reg_to_mC(reg);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -119,13 +104,20 @@ static ssize_t tmp102_show_temp(struct device *dev,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
||||||
struct tmp102 *tmp102 = tmp102_update_device(dev);
|
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||||
|
int regaddr = sda->index;
|
||||||
|
unsigned int reg;
|
||||||
|
int err;
|
||||||
|
|
||||||
/* Is it too early even to return a read? */
|
if (regaddr == TMP102_TEMP_REG &&
|
||||||
if (tmp102->first_time)
|
time_before(jiffies, tmp102->ready_time))
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
|
|
||||||
return sprintf(buf, "%d\n", tmp102->temp[sda->index]);
|
err = regmap_read(tmp102->regmap, regaddr, ®);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", tmp102_reg_to_mC(reg));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t tmp102_set_temp(struct device *dev,
|
static ssize_t tmp102_set_temp(struct device *dev,
|
||||||
|
@ -134,29 +126,26 @@ static ssize_t tmp102_set_temp(struct device *dev,
|
||||||
{
|
{
|
||||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
||||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||||
struct i2c_client *client = tmp102->client;
|
int reg = sda->index;
|
||||||
long val;
|
long val;
|
||||||
int status;
|
int err;
|
||||||
|
|
||||||
if (kstrtol(buf, 10, &val) < 0)
|
if (kstrtol(buf, 10, &val) < 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
val = clamp_val(val, -256000, 255000);
|
val = clamp_val(val, -256000, 255000);
|
||||||
|
|
||||||
mutex_lock(&tmp102->lock);
|
err = regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val));
|
||||||
tmp102->temp[sda->index] = val;
|
return err ? : count;
|
||||||
status = i2c_smbus_write_word_swapped(client, tmp102_reg[sda->index],
|
|
||||||
tmp102_mC_to_reg(val));
|
|
||||||
mutex_unlock(&tmp102->lock);
|
|
||||||
return status ? : count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp102_show_temp, NULL , 0);
|
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, tmp102_show_temp, NULL,
|
||||||
|
TMP102_TEMP_REG);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
||||||
tmp102_set_temp, 1);
|
tmp102_set_temp, TMP102_TLOW_REG);
|
||||||
|
|
||||||
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, tmp102_show_temp,
|
||||||
tmp102_set_temp, 2);
|
tmp102_set_temp, TMP102_THIGH_REG);
|
||||||
|
|
||||||
static struct attribute *tmp102_attrs[] = {
|
static struct attribute *tmp102_attrs[] = {
|
||||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||||
|
@ -166,20 +155,46 @@ static struct attribute *tmp102_attrs[] = {
|
||||||
};
|
};
|
||||||
ATTRIBUTE_GROUPS(tmp102);
|
ATTRIBUTE_GROUPS(tmp102);
|
||||||
|
|
||||||
#define TMP102_CONFIG (TMP102_CONF_TM | TMP102_CONF_EM | TMP102_CONF_CR1)
|
|
||||||
#define TMP102_CONFIG_RD_ONLY (TMP102_CONF_R0 | TMP102_CONF_R1 | TMP102_CONF_AL)
|
|
||||||
|
|
||||||
static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = {
|
static const struct thermal_zone_of_device_ops tmp102_of_thermal_ops = {
|
||||||
.get_temp = tmp102_read_temp,
|
.get_temp = tmp102_read_temp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void tmp102_restore_config(void *data)
|
||||||
|
{
|
||||||
|
struct tmp102 *tmp102 = data;
|
||||||
|
|
||||||
|
regmap_write(tmp102->regmap, TMP102_CONF_REG, tmp102->config_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tmp102_is_writeable_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
return reg != TMP102_TEMP_REG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tmp102_is_volatile_reg(struct device *dev, unsigned int reg)
|
||||||
|
{
|
||||||
|
return reg == TMP102_TEMP_REG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct regmap_config tmp102_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 16,
|
||||||
|
.max_register = TMP102_THIGH_REG,
|
||||||
|
.writeable_reg = tmp102_is_writeable_reg,
|
||||||
|
.volatile_reg = tmp102_is_volatile_reg,
|
||||||
|
.val_format_endian = REGMAP_ENDIAN_BIG,
|
||||||
|
.cache_type = REGCACHE_RBTREE,
|
||||||
|
.use_single_rw = true,
|
||||||
|
};
|
||||||
|
|
||||||
static int tmp102_probe(struct i2c_client *client,
|
static int tmp102_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
struct tmp102 *tmp102;
|
struct tmp102 *tmp102;
|
||||||
int status;
|
unsigned int regval;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (!i2c_check_functionality(client->adapter,
|
if (!i2c_check_functionality(client->adapter,
|
||||||
I2C_FUNC_SMBUS_WORD_DATA)) {
|
I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||||
|
@ -193,73 +208,57 @@ static int tmp102_probe(struct i2c_client *client,
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
i2c_set_clientdata(client, tmp102);
|
i2c_set_clientdata(client, tmp102);
|
||||||
tmp102->client = client;
|
|
||||||
|
|
||||||
status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config);
|
||||||
if (status < 0) {
|
if (IS_ERR(tmp102->regmap))
|
||||||
|
return PTR_ERR(tmp102->regmap);
|
||||||
|
|
||||||
|
err = regmap_read(tmp102->regmap, TMP102_CONF_REG, ®val);
|
||||||
|
if (err < 0) {
|
||||||
dev_err(dev, "error reading config register\n");
|
dev_err(dev, "error reading config register\n");
|
||||||
return status;
|
return err;
|
||||||
}
|
}
|
||||||
tmp102->config_orig = status;
|
|
||||||
status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG,
|
if ((regval & ~TMP102_CONFREG_MASK) !=
|
||||||
TMP102_CONFIG);
|
(TMP102_CONF_R0 | TMP102_CONF_R1)) {
|
||||||
if (status < 0) {
|
dev_err(dev, "unexpected config register value\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp102->config_orig = regval;
|
||||||
|
|
||||||
|
devm_add_action(dev, tmp102_restore_config, tmp102);
|
||||||
|
|
||||||
|
regval &= ~TMP102_CONFIG_CLEAR;
|
||||||
|
regval |= TMP102_CONFIG_SET;
|
||||||
|
|
||||||
|
err = regmap_write(tmp102->regmap, TMP102_CONF_REG, regval);
|
||||||
|
if (err < 0) {
|
||||||
dev_err(dev, "error writing config register\n");
|
dev_err(dev, "error writing config register\n");
|
||||||
goto fail_restore_config;
|
return err;
|
||||||
}
|
}
|
||||||
status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
|
||||||
if (status < 0) {
|
|
||||||
dev_err(dev, "error reading config register\n");
|
|
||||||
goto fail_restore_config;
|
|
||||||
}
|
|
||||||
status &= ~TMP102_CONFIG_RD_ONLY;
|
|
||||||
if (status != TMP102_CONFIG) {
|
|
||||||
dev_err(dev, "config settings did not stick\n");
|
|
||||||
status = -ENODEV;
|
|
||||||
goto fail_restore_config;
|
|
||||||
}
|
|
||||||
tmp102->last_update = jiffies;
|
|
||||||
/* Mark that we are not ready with data until conversion is complete */
|
|
||||||
tmp102->first_time = true;
|
|
||||||
mutex_init(&tmp102->lock);
|
|
||||||
|
|
||||||
hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
tmp102->ready_time = jiffies;
|
||||||
tmp102, tmp102_groups);
|
if (tmp102->config_orig & TMP102_CONF_SD) {
|
||||||
|
/*
|
||||||
|
* Mark that we are not ready with data until the first
|
||||||
|
* conversion is complete
|
||||||
|
*/
|
||||||
|
tmp102->ready_time += msecs_to_jiffies(CONVERSION_TIME_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||||
|
tmp102,
|
||||||
|
tmp102_groups);
|
||||||
if (IS_ERR(hwmon_dev)) {
|
if (IS_ERR(hwmon_dev)) {
|
||||||
dev_dbg(dev, "unable to register hwmon device\n");
|
dev_dbg(dev, "unable to register hwmon device\n");
|
||||||
status = PTR_ERR(hwmon_dev);
|
return PTR_ERR(hwmon_dev);
|
||||||
goto fail_restore_config;
|
|
||||||
}
|
}
|
||||||
tmp102->hwmon_dev = hwmon_dev;
|
|
||||||
devm_thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev,
|
devm_thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev,
|
||||||
&tmp102_of_thermal_ops);
|
&tmp102_of_thermal_ops);
|
||||||
|
|
||||||
dev_info(dev, "initialized\n");
|
dev_info(dev, "initialized\n");
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
fail_restore_config:
|
|
||||||
i2c_smbus_write_word_swapped(client, TMP102_CONF_REG,
|
|
||||||
tmp102->config_orig);
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int tmp102_remove(struct i2c_client *client)
|
|
||||||
{
|
|
||||||
struct tmp102 *tmp102 = i2c_get_clientdata(client);
|
|
||||||
|
|
||||||
hwmon_device_unregister(tmp102->hwmon_dev);
|
|
||||||
|
|
||||||
/* Stop monitoring if device was stopped originally */
|
|
||||||
if (tmp102->config_orig & TMP102_CONF_SD) {
|
|
||||||
int config;
|
|
||||||
|
|
||||||
config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
|
||||||
if (config >= 0)
|
|
||||||
i2c_smbus_write_word_swapped(client, TMP102_CONF_REG,
|
|
||||||
config | TMP102_CONF_SD);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,27 +266,24 @@ static int tmp102_remove(struct i2c_client *client)
|
||||||
static int tmp102_suspend(struct device *dev)
|
static int tmp102_suspend(struct device *dev)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
int config;
|
struct tmp102 *tmp102 = i2c_get_clientdata(client);
|
||||||
|
|
||||||
config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
return regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
|
||||||
if (config < 0)
|
TMP102_CONF_SD, TMP102_CONF_SD);
|
||||||
return config;
|
|
||||||
|
|
||||||
config |= TMP102_CONF_SD;
|
|
||||||
return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int tmp102_resume(struct device *dev)
|
static int tmp102_resume(struct device *dev)
|
||||||
{
|
{
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
int config;
|
struct tmp102 *tmp102 = i2c_get_clientdata(client);
|
||||||
|
int err;
|
||||||
|
|
||||||
config = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
|
||||||
if (config < 0)
|
TMP102_CONF_SD, 0);
|
||||||
return config;
|
|
||||||
|
|
||||||
config &= ~TMP102_CONF_SD;
|
tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS);
|
||||||
return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config);
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_PM */
|
#endif /* CONFIG_PM */
|
||||||
|
|
||||||
|
@ -303,7 +299,6 @@ static struct i2c_driver tmp102_driver = {
|
||||||
.driver.name = DRIVER_NAME,
|
.driver.name = DRIVER_NAME,
|
||||||
.driver.pm = &tmp102_dev_pm_ops,
|
.driver.pm = &tmp102_dev_pm_ops,
|
||||||
.probe = tmp102_probe,
|
.probe = tmp102_probe,
|
||||||
.remove = tmp102_remove,
|
|
||||||
.id_table = tmp102_id,
|
.id_table = tmp102_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d,
|
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d,
|
||||||
0x4e, 0x4f, I2C_CLIENT_END };
|
0x4e, 0x4f, I2C_CLIENT_END };
|
||||||
|
|
||||||
enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 };
|
enum chips { tmp401, tmp411, tmp431, tmp432, tmp435, tmp461 };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The TMP401 registers, note some registers have different addresses for
|
* The TMP401 registers, note some registers have different addresses for
|
||||||
|
@ -62,31 +62,34 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 };
|
||||||
#define TMP401_MANUFACTURER_ID_REG 0xFE
|
#define TMP401_MANUFACTURER_ID_REG 0xFE
|
||||||
#define TMP401_DEVICE_ID_REG 0xFF
|
#define TMP401_DEVICE_ID_REG 0xFF
|
||||||
|
|
||||||
static const u8 TMP401_TEMP_MSB_READ[6][2] = {
|
static const u8 TMP401_TEMP_MSB_READ[7][2] = {
|
||||||
{ 0x00, 0x01 }, /* temp */
|
{ 0x00, 0x01 }, /* temp */
|
||||||
{ 0x06, 0x08 }, /* low limit */
|
{ 0x06, 0x08 }, /* low limit */
|
||||||
{ 0x05, 0x07 }, /* high limit */
|
{ 0x05, 0x07 }, /* high limit */
|
||||||
{ 0x20, 0x19 }, /* therm (crit) limit */
|
{ 0x20, 0x19 }, /* therm (crit) limit */
|
||||||
{ 0x30, 0x34 }, /* lowest */
|
{ 0x30, 0x34 }, /* lowest */
|
||||||
{ 0x32, 0x36 }, /* highest */
|
{ 0x32, 0x36 }, /* highest */
|
||||||
|
{ 0, 0x11 }, /* offset */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u8 TMP401_TEMP_MSB_WRITE[6][2] = {
|
static const u8 TMP401_TEMP_MSB_WRITE[7][2] = {
|
||||||
{ 0, 0 }, /* temp (unused) */
|
{ 0, 0 }, /* temp (unused) */
|
||||||
{ 0x0C, 0x0E }, /* low limit */
|
{ 0x0C, 0x0E }, /* low limit */
|
||||||
{ 0x0B, 0x0D }, /* high limit */
|
{ 0x0B, 0x0D }, /* high limit */
|
||||||
{ 0x20, 0x19 }, /* therm (crit) limit */
|
{ 0x20, 0x19 }, /* therm (crit) limit */
|
||||||
{ 0x30, 0x34 }, /* lowest */
|
{ 0x30, 0x34 }, /* lowest */
|
||||||
{ 0x32, 0x36 }, /* highest */
|
{ 0x32, 0x36 }, /* highest */
|
||||||
|
{ 0, 0x11 }, /* offset */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u8 TMP401_TEMP_LSB[6][2] = {
|
static const u8 TMP401_TEMP_LSB[7][2] = {
|
||||||
{ 0x15, 0x10 }, /* temp */
|
{ 0x15, 0x10 }, /* temp */
|
||||||
{ 0x17, 0x14 }, /* low limit */
|
{ 0x17, 0x14 }, /* low limit */
|
||||||
{ 0x16, 0x13 }, /* high limit */
|
{ 0x16, 0x13 }, /* high limit */
|
||||||
{ 0, 0 }, /* therm (crit) limit (unused) */
|
{ 0, 0 }, /* therm (crit) limit (unused) */
|
||||||
{ 0x31, 0x35 }, /* lowest */
|
{ 0x31, 0x35 }, /* lowest */
|
||||||
{ 0x33, 0x37 }, /* highest */
|
{ 0x33, 0x37 }, /* highest */
|
||||||
|
{ 0, 0x12 }, /* offset */
|
||||||
};
|
};
|
||||||
|
|
||||||
static const u8 TMP432_TEMP_MSB_READ[4][3] = {
|
static const u8 TMP432_TEMP_MSB_READ[4][3] = {
|
||||||
|
@ -149,6 +152,7 @@ static const struct i2c_device_id tmp401_id[] = {
|
||||||
{ "tmp431", tmp431 },
|
{ "tmp431", tmp431 },
|
||||||
{ "tmp432", tmp432 },
|
{ "tmp432", tmp432 },
|
||||||
{ "tmp435", tmp435 },
|
{ "tmp435", tmp435 },
|
||||||
|
{ "tmp461", tmp461 },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, tmp401_id);
|
MODULE_DEVICE_TABLE(i2c, tmp401_id);
|
||||||
|
@ -170,7 +174,7 @@ struct tmp401_data {
|
||||||
/* register values */
|
/* register values */
|
||||||
u8 status[4];
|
u8 status[4];
|
||||||
u8 config;
|
u8 config;
|
||||||
u16 temp[6][3];
|
u16 temp[7][3];
|
||||||
u8 temp_crit_hyst;
|
u8 temp_crit_hyst;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -612,6 +616,22 @@ static const struct attribute_group tmp432_group = {
|
||||||
.attrs = tmp432_attributes,
|
.attrs = tmp432_attributes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Additional features of the TMP461 chip.
|
||||||
|
* The TMP461 temperature offset for the remote channel.
|
||||||
|
*/
|
||||||
|
static SENSOR_DEVICE_ATTR_2(temp2_offset, S_IWUSR | S_IRUGO, show_temp,
|
||||||
|
store_temp, 6, 1);
|
||||||
|
|
||||||
|
static struct attribute *tmp461_attributes[] = {
|
||||||
|
&sensor_dev_attr_temp2_offset.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group tmp461_group = {
|
||||||
|
.attrs = tmp461_attributes,
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Begin non sysfs callback code (aka Real code)
|
* Begin non sysfs callback code (aka Real code)
|
||||||
*/
|
*/
|
||||||
|
@ -714,7 +734,7 @@ static int tmp401_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
static const char * const names[] = {
|
static const char * const names[] = {
|
||||||
"TMP401", "TMP411", "TMP431", "TMP432", "TMP435"
|
"TMP401", "TMP411", "TMP431", "TMP432", "TMP435", "TMP461"
|
||||||
};
|
};
|
||||||
struct device *dev = &client->dev;
|
struct device *dev = &client->dev;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
|
@ -745,6 +765,9 @@ static int tmp401_probe(struct i2c_client *client,
|
||||||
if (data->kind == tmp432)
|
if (data->kind == tmp432)
|
||||||
data->groups[groups++] = &tmp432_group;
|
data->groups[groups++] = &tmp432_group;
|
||||||
|
|
||||||
|
if (data->kind == tmp461)
|
||||||
|
data->groups[groups++] = &tmp461_group;
|
||||||
|
|
||||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||||
data, data->groups);
|
data, data->groups);
|
||||||
if (IS_ERR(hwmon_dev))
|
if (IS_ERR(hwmon_dev))
|
||||||
|
|
25
include/linux/platform_data/sht3x.h
Normal file
25
include/linux/platform_data/sht3x.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Sensirion AG, Switzerland
|
||||||
|
* Author: David Frey <david.frey@sensirion.com>
|
||||||
|
* Author: Pascal Sachs <pascal.sachs@sensirion.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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SHT3X_H_
|
||||||
|
#define __SHT3X_H_
|
||||||
|
|
||||||
|
struct sht3x_platform_data {
|
||||||
|
bool blocking_io;
|
||||||
|
bool high_precision;
|
||||||
|
};
|
||||||
|
#endif /* __SHT3X_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue