mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-22 20:23:57 +08: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
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)
|
||||
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
|
||||
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
|
||||
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
|
||||
|
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
|
||||
Datasheets:
|
||||
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:
|
||||
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/25095A.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/22327A.pdf
|
||||
|
@ -17,7 +17,7 @@ This driver implements support for the Maxim MAX1668, MAX1805 and MAX1989
|
||||
chips.
|
||||
|
||||
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.
|
||||
|
||||
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/CodingStyle
|
||||
|
||||
* If your patch generates checkpatch warnings, please refrain from explanations
|
||||
such as "I don't like that coding style". Keep in mind that each unnecessary
|
||||
warning helps hiding a real problem. If you don't like the kernel coding
|
||||
style, don't write kernel drivers.
|
||||
* Please run your patch through 'checkpatch --strict'. There should be no
|
||||
errors, no warnings, and few if any check messages. If there are any
|
||||
messages, please be prepared to explain.
|
||||
|
||||
* 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.
|
||||
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.
|
||||
|
||||
* 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 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
* Make sure there are no race conditions in the probe function. Specifically,
|
||||
completely initialize your chip first, then create sysfs entries and register
|
||||
with the hwmon subsystem.
|
||||
completely initialize your chip and your driver first, then register with
|
||||
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.
|
||||
|
||||
|
@ -22,6 +22,9 @@ Supported chips:
|
||||
Prefix: 'tmp435'
|
||||
Addresses scanned: I2C 0x48 - 0x4f
|
||||
Datasheet: http://focus.ti.com/docs/prod/folders/print/tmp435.html
|
||||
* Texas Instruments TMP461
|
||||
Prefix: 'tmp461'
|
||||
Datasheet: http://www.ti.com/product/tmp461
|
||||
|
||||
Authors:
|
||||
Hans de Goede <hdegoede@redhat.com>
|
||||
@ -31,8 +34,8 @@ Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Texas Instruments TMP401, TMP411,
|
||||
TMP431, TMP432 and TMP435 chips. These chips implement one or two remote
|
||||
and one local temperature sensors. Temperature is measured in degrees
|
||||
TMP431, TMP432, TMP435, and TMP461 chips. These chips implement one or two
|
||||
remote and one local temperature sensors. Temperature is measured in degrees
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
tristate "Genesys Logic GL518SM"
|
||||
depends on I2C
|
||||
@ -645,8 +657,8 @@ config SENSORS_JC42
|
||||
temperature sensors, which are used on many DDR3 memory modules for
|
||||
mobile devices and servers. Support will include, but not be limited
|
||||
to, ADT7408, AT30TS00, CAT34TS02, CAT6095, MAX6604, MCP9804, MCP9805,
|
||||
MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98, STTS424(E),
|
||||
STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001.
|
||||
MCP9808, MCP98242, MCP98243, MCP98244, MCP9843, SE97, SE98,
|
||||
STTS424(E), STTS2002, STTS3000, TSE2002, TSE2004, TS3000, and TS3001.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called jc42.
|
||||
@ -958,6 +970,7 @@ config SENSORS_LM75
|
||||
tristate "National Semiconductor LM75 and compatibles"
|
||||
depends on I2C
|
||||
depends on THERMAL || !THERMAL_OF
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for one common type of
|
||||
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
|
||||
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
|
||||
tristate "Sensiron humidity and temperature sensors. SHTC1 and compat."
|
||||
depends on I2C
|
||||
@ -1514,6 +1538,17 @@ config SENSORS_INA2XX
|
||||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Microchip TC74"
|
||||
depends on I2C
|
||||
@ -1538,6 +1573,7 @@ config SENSORS_TMP102
|
||||
tristate "Texas Instruments TMP102"
|
||||
depends on I2C
|
||||
depends on THERMAL || !THERMAL_OF
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments TMP102
|
||||
sensor chips.
|
||||
@ -1561,7 +1597,7 @@ config SENSORS_TMP401
|
||||
depends on I2C
|
||||
help
|
||||
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
|
||||
will be called tmp401.
|
||||
|
@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o
|
||||
obj-$(CONFIG_SENSORS_F75375S) += f75375s.o
|
||||
obj-$(CONFIG_SENSORS_FAM15H_POWER) += fam15h_power.o
|
||||
obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o
|
||||
obj-$(CONFIG_SENSORS_FTSTEUTATES) += ftsteutates.o
|
||||
obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
||||
obj-$(CONFIG_SENSORS_G762) += g762.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_INA209) += ina209.o
|
||||
obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o
|
||||
obj-$(CONFIG_SENSORS_INA3221) += ina3221.o
|
||||
obj-$(CONFIG_SENSORS_IT87) += it87.o
|
||||
obj-$(CONFIG_SENSORS_JC42) += jc42.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_SHT15) += sht15.o
|
||||
obj-$(CONFIG_SENSORS_SHT21) += sht21.o
|
||||
obj-$(CONFIG_SENSORS_SHT3x) += sht3x.o
|
||||
obj-$(CONFIG_SENSORS_SHTC1) += shtc1.o
|
||||
obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o
|
||||
obj-$(CONFIG_SENSORS_SMM665) += smm665.o
|
||||
|
@ -37,7 +37,6 @@ enum ad7314_variant {
|
||||
|
||||
struct ad7314_data {
|
||||
struct spi_device *spi_dev;
|
||||
struct device *hwmon_dev;
|
||||
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,
|
||||
ad7314_show_temperature, NULL, 0);
|
||||
|
||||
static struct attribute *ad7314_attributes[] = {
|
||||
&dev_attr_name.attr,
|
||||
static struct attribute *ad7314_attrs[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group ad7314_group = {
|
||||
.attrs = ad7314_attributes,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ad7314);
|
||||
|
||||
static int ad7314_probe(struct spi_device *spi_dev)
|
||||
{
|
||||
int ret;
|
||||
struct ad7314_data *chip;
|
||||
struct device *hwmon_dev;
|
||||
|
||||
chip = devm_kzalloc(&spi_dev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (chip == NULL)
|
||||
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;
|
||||
|
||||
return 0;
|
||||
error_remove_group:
|
||||
sysfs_remove_group(&spi_dev->dev.kobj, &ad7314_group);
|
||||
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;
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(&spi_dev->dev,
|
||||
spi_dev->modalias,
|
||||
chip, ad7314_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct spi_device_id ad7314_id[] = {
|
||||
@ -159,7 +126,6 @@ static struct spi_driver ad7314_driver = {
|
||||
.name = "ad7314",
|
||||
},
|
||||
.probe = ad7314_probe,
|
||||
.remove = ad7314_remove,
|
||||
.id_table = ad7314_id,
|
||||
};
|
||||
|
||||
|
@ -66,14 +66,12 @@
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define DEVICE_NAME "ads7871"
|
||||
|
||||
struct ads7871_data {
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
struct spi_device *spi;
|
||||
};
|
||||
|
||||
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,
|
||||
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);
|
||||
int ret, val, i = 0;
|
||||
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(in1_input, S_IRUGO, show_voltage, NULL, 1);
|
||||
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(in7_input, S_IRUGO, show_voltage, NULL, 7);
|
||||
|
||||
static DEVICE_ATTR(name, S_IRUGO, ads7871_show_name, NULL);
|
||||
|
||||
static struct attribute *ads7871_attributes[] = {
|
||||
static struct attribute *ads7871_attrs[] = {
|
||||
&sensor_dev_attr_in0_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in1_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_in6_input.dev_attr.attr,
|
||||
&sensor_dev_attr_in7_input.dev_attr.attr,
|
||||
&dev_attr_name.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group ads7871_group = {
|
||||
.attrs = ads7871_attributes,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ads7871);
|
||||
|
||||
static int ads7871_probe(struct spi_device *spi)
|
||||
{
|
||||
int ret, err;
|
||||
struct device *dev = &spi->dev;
|
||||
int ret;
|
||||
uint8_t val;
|
||||
struct ads7871_data *pdata;
|
||||
|
||||
dev_dbg(&spi->dev, "probe\n");
|
||||
struct device *hwmon_dev;
|
||||
|
||||
/* Configure the SPI bus */
|
||||
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);
|
||||
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
|
||||
* 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)
|
||||
return -ENODEV;
|
||||
|
||||
pdata = devm_kzalloc(&spi->dev, sizeof(struct ads7871_data),
|
||||
GFP_KERNEL);
|
||||
pdata = devm_kzalloc(dev, sizeof(struct ads7871_data), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
err = sysfs_create_group(&spi->dev.kobj, &ads7871_group);
|
||||
if (err < 0)
|
||||
return err;
|
||||
pdata->spi = spi;
|
||||
|
||||
spi_set_drvdata(spi, pdata);
|
||||
|
||||
pdata->hwmon_dev = hwmon_device_register(&spi->dev);
|
||||
if (IS_ERR(pdata->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;
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, spi->modalias,
|
||||
pdata,
|
||||
ads7871_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static struct spi_driver ads7871_driver = {
|
||||
.driver = {
|
||||
.name = DEVICE_NAME,
|
||||
},
|
||||
|
||||
.probe = ads7871_probe,
|
||||
.remove = ads7871_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(ads7871_driver);
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#define ADT7411_REG_CFG1 0x18
|
||||
#define ADT7411_CFG1_START_MONITOR (1 << 0)
|
||||
#define ADT7411_CFG1_RESERVED_BIT3 (1 << 3)
|
||||
|
||||
#define ADT7411_REG_CFG2 0x19
|
||||
#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->update_lock);
|
||||
|
||||
/* According to the datasheet, we must only write 1 to bit 3 */
|
||||
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)
|
||||
return ret;
|
||||
|
||||
|
@ -81,6 +81,7 @@ static bool disallow_fan_type_call;
|
||||
#define I8K_HWMON_HAVE_TEMP4 (1 << 3)
|
||||
#define I8K_HWMON_HAVE_FAN1 (1 << 4)
|
||||
#define I8K_HWMON_HAVE_FAN2 (1 << 5)
|
||||
#define I8K_HWMON_HAVE_FAN3 (1 << 6)
|
||||
|
||||
MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
|
||||
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;
|
||||
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 */
|
||||
if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
|
||||
return -ENOMEM;
|
||||
@ -210,6 +219,15 @@ static int i8k_smm(struct smm_regs *regs)
|
||||
out:
|
||||
set_cpus_allowed_ptr(current, 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;
|
||||
}
|
||||
|
||||
@ -252,7 +270,7 @@ 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 */
|
||||
static int types[2] = { INT_MIN, INT_MIN };
|
||||
static int types[3] = { INT_MIN, INT_MIN, INT_MIN };
|
||||
|
||||
if (types[fan] == INT_MIN)
|
||||
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);
|
||||
static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
|
||||
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[] = {
|
||||
&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_label.dev_attr.attr, /* 12 */
|
||||
&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
|
||||
};
|
||||
|
||||
@ -742,7 +769,7 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||
int index)
|
||||
{
|
||||
if (disallow_fan_type_call &&
|
||||
(index == 9 || index == 12))
|
||||
(index == 9 || index == 12 || index == 15))
|
||||
return 0;
|
||||
if (index >= 0 && index <= 1 &&
|
||||
!(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 &&
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
|
||||
return 0;
|
||||
if (index >= 14 && index <= 16 &&
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3))
|
||||
return 0;
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
@ -807,6 +837,13 @@ static int __init i8k_init_hwmon(void)
|
||||
if (err >= 0)
|
||||
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",
|
||||
NULL, i8k_groups);
|
||||
if (IS_ERR(i8k_hwmon_dev)) {
|
||||
|
@ -464,7 +464,7 @@ static int emc6w201_detect(struct i2c_client *client,
|
||||
if (verstep < 0 || (verstep & 0xF0) != 0xB0)
|
||||
return -ENODEV;
|
||||
if ((verstep & 0x0F) > 2) {
|
||||
dev_dbg(&client->dev, "Unknwown EMC6W201 stepping %d\n",
|
||||
dev_dbg(&client->dev, "Unknown EMC6W201 stepping %d\n",
|
||||
verstep & 0x0F);
|
||||
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/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static const unsigned short normal_i2c[] = {
|
||||
@ -104,6 +105,9 @@ static const unsigned short normal_i2c[] = {
|
||||
#define MCP9804_DEVID 0x0200
|
||||
#define MCP9804_DEVID_MASK 0xfffc
|
||||
|
||||
#define MCP9808_DEVID 0x0400
|
||||
#define MCP9808_DEVID_MASK 0xfffc
|
||||
|
||||
#define MCP98242_DEVID 0x2000
|
||||
#define MCP98242_DEVID_MASK 0xfffc
|
||||
|
||||
@ -160,6 +164,7 @@ static struct jc42_chips jc42_chips[] = {
|
||||
{ IDT_MANID, TS3001_DEVID, TS3001_DEVID_MASK },
|
||||
{ MAX_MANID, MAX6604_DEVID, MAX6604_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, MCP98243_DEVID, MCP98243_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);
|
||||
|
||||
#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 = {
|
||||
.class = I2C_CLASS_SPD,
|
||||
.class = I2C_CLASS_SPD | I2C_CLASS_HWMON,
|
||||
.driver = {
|
||||
.name = "jc42",
|
||||
.pm = JC42_DEV_PM_OPS,
|
||||
.of_match_table = of_match_ptr(jc42_of_ids),
|
||||
},
|
||||
.probe = jc42_probe,
|
||||
.remove = jc42_remove,
|
||||
|
@ -29,23 +29,13 @@
|
||||
|
||||
struct jz4740_hwmon {
|
||||
void __iomem *base;
|
||||
|
||||
int irq;
|
||||
|
||||
const struct mfd_cell *cell;
|
||||
struct device *hwmon;
|
||||
|
||||
struct platform_device *pdev;
|
||||
struct completion read_completion;
|
||||
|
||||
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)
|
||||
{
|
||||
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 jz4740_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
struct platform_device *pdev = hwmon->pdev;
|
||||
struct completion *completion = &hwmon->read_completion;
|
||||
long t;
|
||||
unsigned long val;
|
||||
@ -68,7 +59,7 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||
reinit_completion(completion);
|
||||
|
||||
enable_irq(hwmon->irq);
|
||||
hwmon->cell->enable(to_platform_device(dev));
|
||||
hwmon->cell->enable(pdev);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hwmon->cell->disable(to_platform_device(dev));
|
||||
hwmon->cell->disable(pdev);
|
||||
disable_irq(hwmon->irq);
|
||||
|
||||
mutex_unlock(&hwmon->lock);
|
||||
@ -88,26 +79,24 @@ static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
|
||||
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 struct attribute *jz4740_hwmon_attributes[] = {
|
||||
&dev_attr_name.attr,
|
||||
static struct attribute *jz4740_attrs[] = {
|
||||
&dev_attr_in0_input.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group jz4740_hwmon_attr_group = {
|
||||
.attrs = jz4740_hwmon_attributes,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(jz4740);
|
||||
|
||||
static int jz4740_hwmon_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct jz4740_hwmon *hwmon;
|
||||
struct device *hwmon_dev;
|
||||
struct resource *mem;
|
||||
|
||||
hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL);
|
||||
hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
|
||||
if (!hwmon)
|
||||
return -ENOMEM;
|
||||
|
||||
@ -125,12 +114,11 @@ static int jz4740_hwmon_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(hwmon->base))
|
||||
return PTR_ERR(hwmon->base);
|
||||
|
||||
hwmon->pdev = pdev;
|
||||
init_completion(&hwmon->read_completion);
|
||||
mutex_init(&hwmon->lock);
|
||||
|
||||
platform_set_drvdata(pdev, hwmon);
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, hwmon->irq, jz4740_hwmon_irq, 0,
|
||||
ret = devm_request_irq(dev, hwmon->irq, jz4740_hwmon_irq, 0,
|
||||
pdev->name, hwmon);
|
||||
if (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);
|
||||
|
||||
ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret);
|
||||
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;
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, "jz4740", hwmon,
|
||||
jz4740_groups);
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static struct platform_driver jz4740_hwmon_driver = {
|
||||
.probe = jz4740_hwmon_probe,
|
||||
.remove = jz4740_hwmon_remove,
|
||||
.driver = {
|
||||
.name = "jz4740-hwmon",
|
||||
},
|
||||
|
@ -26,8 +26,8 @@
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/thermal.h>
|
||||
#include "lm75.h"
|
||||
|
||||
@ -66,35 +66,21 @@ static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c,
|
||||
|
||||
|
||||
/* The LM75 registers */
|
||||
#define LM75_REG_TEMP 0x00
|
||||
#define LM75_REG_CONF 0x01
|
||||
static const u8 LM75_REG_TEMP[3] = {
|
||||
0x00, /* input */
|
||||
0x03, /* max */
|
||||
0x02, /* hyst */
|
||||
};
|
||||
#define LM75_REG_HYST 0x02
|
||||
#define LM75_REG_MAX 0x03
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct lm75_data {
|
||||
struct i2c_client *client;
|
||||
struct device *hwmon_dev;
|
||||
struct mutex update_lock;
|
||||
struct regmap *regmap;
|
||||
u8 orig_conf;
|
||||
u8 resolution; /* In bits, between 9 and 12 */
|
||||
u8 resolution_limits;
|
||||
char valid; /* !=0 if registers are valid */
|
||||
unsigned long last_updated; /* In jiffies */
|
||||
unsigned long sample_time; /* In jiffies */
|
||||
s16 temp[3]; /* Register values,
|
||||
0 = input
|
||||
1 = max
|
||||
2 = hyst */
|
||||
unsigned int sample_time; /* In ms */
|
||||
};
|
||||
|
||||
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)
|
||||
@ -106,12 +92,15 @@ static inline long lm75_reg_to_mc(s16 temp, u8 resolution)
|
||||
|
||||
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))
|
||||
return PTR_ERR(data);
|
||||
err = regmap_read(data->regmap, LM75_REG_TEMP, &_temp);
|
||||
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;
|
||||
}
|
||||
@ -120,13 +109,15 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *da,
|
||||
char *buf)
|
||||
{
|
||||
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))
|
||||
return PTR_ERR(data);
|
||||
err = regmap_read(data->regmap, attr->index, &temp);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return sprintf(buf, "%ld\n", lm75_reg_to_mc(data->temp[attr->index],
|
||||
data->resolution));
|
||||
return sprintf(buf, "%ld\n", lm75_reg_to_mc(temp, data->resolution));
|
||||
}
|
||||
|
||||
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 lm75_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
int nr = attr->index;
|
||||
long temp;
|
||||
int error;
|
||||
u8 resolution;
|
||||
@ -153,25 +142,36 @@ static ssize_t set_temp(struct device *dev, struct device_attribute *da,
|
||||
else
|
||||
resolution = data->resolution;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
temp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||
data->temp[nr] = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
||||
1000) << (16 - resolution);
|
||||
lm75_write_value(client, LM75_REG_TEMP[nr], data->temp[nr]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
temp = DIV_ROUND_CLOSEST(temp << (resolution - 8),
|
||||
1000) << (16 - resolution);
|
||||
error = regmap_write(data->regmap, attr->index, temp);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
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,
|
||||
show_temp, set_temp, 1);
|
||||
show_temp, set_temp, LM75_REG_MAX);
|
||||
static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,
|
||||
show_temp, set_temp, 2);
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0);
|
||||
show_temp, set_temp, LM75_REG_HYST);
|
||||
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[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
&dev_attr_update_interval.attr,
|
||||
|
||||
NULL
|
||||
};
|
||||
@ -185,10 +185,40 @@ static const struct thermal_zone_of_device_ops lm75_of_thermal_ops = {
|
||||
|
||||
/* 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
|
||||
lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct lm75_data *data;
|
||||
int status;
|
||||
u8 set_mask, clr_mask;
|
||||
@ -204,8 +234,10 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
return -ENOMEM;
|
||||
|
||||
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.
|
||||
* 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:
|
||||
clr_mask |= 1 << 5; /* not one-shot mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ / 8;
|
||||
data->sample_time = MSEC_PER_SEC / 8;
|
||||
break;
|
||||
case ds1775:
|
||||
case ds75:
|
||||
@ -225,35 +257,35 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
clr_mask |= 3 << 5;
|
||||
set_mask |= 2 << 5; /* 11-bit mode */
|
||||
data->resolution = 11;
|
||||
data->sample_time = HZ;
|
||||
data->sample_time = MSEC_PER_SEC;
|
||||
break;
|
||||
case ds7505:
|
||||
set_mask |= 3 << 5; /* 12-bit mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
case g751:
|
||||
case lm75:
|
||||
case lm75a:
|
||||
data->resolution = 9;
|
||||
data->sample_time = HZ / 2;
|
||||
data->sample_time = MSEC_PER_SEC / 2;
|
||||
break;
|
||||
case lm75b:
|
||||
data->resolution = 11;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
case max6625:
|
||||
data->resolution = 9;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
case max6626:
|
||||
data->resolution = 12;
|
||||
data->resolution_limits = 9;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
case tcn75:
|
||||
data->resolution = 9;
|
||||
data->sample_time = HZ / 8;
|
||||
data->sample_time = MSEC_PER_SEC / 8;
|
||||
break;
|
||||
case mcp980x:
|
||||
data->resolution_limits = 9;
|
||||
@ -262,14 +294,14 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
case tmp101:
|
||||
set_mask |= 3 << 5; /* 12-bit mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ;
|
||||
data->sample_time = MSEC_PER_SEC;
|
||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||
break;
|
||||
case tmp112:
|
||||
set_mask |= 3 << 5; /* 12-bit mode */
|
||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
case tmp105:
|
||||
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 */
|
||||
clr_mask |= 1 << 7; /* not one-shot mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ / 2;
|
||||
data->sample_time = MSEC_PER_SEC / 2;
|
||||
break;
|
||||
case tmp75c:
|
||||
clr_mask |= 1 << 5; /* not one-shot mode */
|
||||
data->resolution = 12;
|
||||
data->sample_time = HZ / 4;
|
||||
data->sample_time = MSEC_PER_SEC / 4;
|
||||
break;
|
||||
}
|
||||
|
||||
/* configure as specified */
|
||||
status = lm75_read_value(client, LM75_REG_CONF);
|
||||
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||
if (status < 0) {
|
||||
dev_dbg(dev, "Can't read config? %d\n", status);
|
||||
return status;
|
||||
@ -297,33 +329,26 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
new = status & ~clr_mask;
|
||||
new |= set_mask;
|
||||
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);
|
||||
|
||||
data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name,
|
||||
data, lm75_groups);
|
||||
if (IS_ERR(data->hwmon_dev))
|
||||
return PTR_ERR(data->hwmon_dev);
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||
data, lm75_groups);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
devm_thermal_zone_of_sensor_register(data->hwmon_dev, 0,
|
||||
data->hwmon_dev,
|
||||
devm_thermal_zone_of_sensor_register(hwmon_dev, 0,
|
||||
hwmon_dev,
|
||||
&lm75_of_thermal_ops);
|
||||
|
||||
dev_info(dev, "%s: sensor '%s'\n",
|
||||
dev_name(data->hwmon_dev), client->name);
|
||||
dev_info(dev, "%s: sensor '%s'\n", dev_name(hwmon_dev), client->name);
|
||||
|
||||
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[] = {
|
||||
{ "adt75", adt75, },
|
||||
{ "ds1775", ds1775, },
|
||||
@ -449,13 +474,13 @@ static int lm75_suspend(struct device *dev)
|
||||
{
|
||||
int status;
|
||||
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) {
|
||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||
return status;
|
||||
}
|
||||
status = status | LM75_SHUTDOWN;
|
||||
lm75_write_value(client, LM75_REG_CONF, status);
|
||||
i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -463,13 +488,13 @@ static int lm75_resume(struct device *dev)
|
||||
{
|
||||
int status;
|
||||
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) {
|
||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||
return status;
|
||||
}
|
||||
status = status & ~LM75_SHUTDOWN;
|
||||
lm75_write_value(client, LM75_REG_CONF, status);
|
||||
i2c_smbus_write_byte_data(client, LM75_REG_CONF, status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -489,73 +514,11 @@ static struct i2c_driver lm75_driver = {
|
||||
.pm = LM75_DEV_PM_OPS,
|
||||
},
|
||||
.probe = lm75_probe,
|
||||
.remove = lm75_remove,
|
||||
.id_table = lm75_ids,
|
||||
.detect = lm75_detect,
|
||||
.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_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 LM90_DEF_CONVRATE_RVAL 6 /* Def conversion rate register value */
|
||||
#define LM90_MAX_CONVRATE_MS 16000 /* Maximum conversion rate in ms */
|
||||
|
||||
/* TMP451 registers */
|
||||
@ -366,11 +365,9 @@ enum lm90_temp11_reg_index {
|
||||
|
||||
struct lm90_data {
|
||||
struct i2c_client *client;
|
||||
struct device *hwmon_dev;
|
||||
const struct attribute_group *groups[6];
|
||||
struct mutex update_lock;
|
||||
struct regulator *regulator;
|
||||
char valid; /* zero until following fields are valid */
|
||||
bool valid; /* true if register values are valid */
|
||||
unsigned long last_updated; /* in jiffies */
|
||||
int kind;
|
||||
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
|
||||
* 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;
|
||||
|
||||
@ -423,20 +420,12 @@ static int lm90_read_reg(struct i2c_client *client, u8 reg, u8 *value)
|
||||
} else
|
||||
err = i2c_smbus_read_byte_data(client, reg);
|
||||
|
||||
if (err < 0) {
|
||||
dev_warn(&client->dev, "Register %#02x read failed (%d)\n",
|
||||
reg, err);
|
||||
return err;
|
||||
}
|
||||
*value = err;
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
}
|
||||
|
||||
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;
|
||||
u8 oldh, newh, l;
|
||||
int oldh, newh, l;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* correct reading.
|
||||
*/
|
||||
if ((err = lm90_read_reg(client, regh, &oldh))
|
||||
|| (err = lm90_read_reg(client, regl, &l))
|
||||
|| (err = lm90_read_reg(client, regh, &newh)))
|
||||
return err;
|
||||
oldh = lm90_read_reg(client, regh);
|
||||
if (oldh < 0)
|
||||
return oldh;
|
||||
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) {
|
||||
err = lm90_read_reg(client, regl, &l);
|
||||
if (err)
|
||||
return err;
|
||||
l = lm90_read_reg(client, regl);
|
||||
if (l < 0)
|
||||
return l;
|
||||
}
|
||||
*value = (newh << 8) | l;
|
||||
|
||||
return 0;
|
||||
return (newh << 8) | l;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -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
|
||||
* non-default remote channel.
|
||||
*/
|
||||
static inline void lm90_select_remote_channel(struct i2c_client *client,
|
||||
struct lm90_data *data,
|
||||
int channel)
|
||||
static inline int lm90_select_remote_channel(struct i2c_client *client,
|
||||
struct lm90_data *data,
|
||||
int channel)
|
||||
{
|
||||
u8 config;
|
||||
int config;
|
||||
|
||||
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;
|
||||
if (channel)
|
||||
config |= 0x08;
|
||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct lm90_data *data = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = data->client;
|
||||
unsigned long next_update;
|
||||
int val = 0;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (!data->valid) {
|
||||
val = lm90_update_limits(dev);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
next_update = data->last_updated +
|
||||
msecs_to_jiffies(data->update_interval);
|
||||
if (time_after(jiffies, next_update) || !data->valid) {
|
||||
u8 h, l;
|
||||
u8 alarms;
|
||||
|
||||
dev_dbg(&client->dev, "Updating lm90 data.\n");
|
||||
lm90_read_reg(client, LM90_REG_R_LOCAL_LOW,
|
||||
&data->temp8[LOCAL_LOW]);
|
||||
lm90_read_reg(client, LM90_REG_R_LOCAL_HIGH,
|
||||
&data->temp8[LOCAL_HIGH]);
|
||||
lm90_read_reg(client, LM90_REG_R_LOCAL_CRIT,
|
||||
&data->temp8[LOCAL_CRIT]);
|
||||
lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT,
|
||||
&data->temp8[REMOTE_CRIT]);
|
||||
lm90_read_reg(client, LM90_REG_R_TCRIT_HYST, &data->temp_hyst);
|
||||
|
||||
data->valid = false;
|
||||
|
||||
val = lm90_read_reg(client, LM90_REG_R_LOCAL_LOW);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->temp8[LOCAL_LOW] = val;
|
||||
|
||||
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) {
|
||||
lm90_read16(client, LM90_REG_R_LOCAL_TEMP,
|
||||
data->reg_local_ext,
|
||||
&data->temp11[LOCAL_TEMP]);
|
||||
val = lm90_read16(client, LM90_REG_R_LOCAL_TEMP,
|
||||
data->reg_local_ext);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->temp11[LOCAL_TEMP] = val;
|
||||
} else {
|
||||
if (lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP,
|
||||
&h) == 0)
|
||||
data->temp11[LOCAL_TEMP] = h << 8;
|
||||
val = lm90_read_reg(client, LM90_REG_R_LOCAL_TEMP);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->temp11[LOCAL_TEMP] = val << 8;
|
||||
}
|
||||
lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||
LM90_REG_R_REMOTE_TEMPL,
|
||||
&data->temp11[REMOTE_TEMP]);
|
||||
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||
LM90_REG_R_REMOTE_TEMPL);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->temp11[REMOTE_TEMP] = val;
|
||||
|
||||
if (lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h) == 0) {
|
||||
data->temp11[REMOTE_LOW] = h << 8;
|
||||
if ((data->flags & LM90_HAVE_REM_LIMIT_EXT)
|
||||
&& lm90_read_reg(client, LM90_REG_R_REMOTE_LOWL,
|
||||
&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 */
|
||||
val = lm90_read_reg(client, LM90_REG_R_STATUS);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->alarms = val; /* lower 8 bit of alarms */
|
||||
|
||||
if (data->kind == max6696) {
|
||||
lm90_select_remote_channel(client, data, 1);
|
||||
lm90_read_reg(client, LM90_REG_R_REMOTE_CRIT,
|
||||
&data->temp8[REMOTE2_CRIT]);
|
||||
lm90_read_reg(client, MAX6659_REG_R_REMOTE_EMERG,
|
||||
&data->temp8[REMOTE2_EMERG]);
|
||||
lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||
LM90_REG_R_REMOTE_TEMPL,
|
||||
&data->temp11[REMOTE2_TEMP]);
|
||||
if (!lm90_read_reg(client, LM90_REG_R_REMOTE_LOWH, &h))
|
||||
data->temp11[REMOTE2_LOW] = h << 8;
|
||||
if (!lm90_read_reg(client, LM90_REG_R_REMOTE_HIGHH, &h))
|
||||
data->temp11[REMOTE2_HIGH] = h << 8;
|
||||
val = lm90_select_remote_channel(client, data, 1);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
|
||||
val = lm90_read16(client, LM90_REG_R_REMOTE_TEMPH,
|
||||
LM90_REG_R_REMOTE_TEMPL);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->temp11[REMOTE2_TEMP] = val;
|
||||
|
||||
lm90_select_remote_channel(client, data, 0);
|
||||
|
||||
if (!lm90_read_reg(client, MAX6696_REG_R_STATUS2,
|
||||
&alarms))
|
||||
data->alarms |= alarms << 8;
|
||||
val = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
data->alarms |= val << 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-enable ALERT# output if it was originally enabled and
|
||||
* relevant alarms are all clear
|
||||
*/
|
||||
if ((data->config_orig & 0x80) == 0
|
||||
&& (data->alarms & data->alert_alarms) == 0) {
|
||||
u8 config;
|
||||
if (!(data->config_orig & 0x80) &&
|
||||
!(data->alarms & data->alert_alarms)) {
|
||||
val = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||
if (val < 0)
|
||||
goto error;
|
||||
|
||||
lm90_read_reg(client, LM90_REG_R_CONFIG1, &config);
|
||||
if (config & 0x80) {
|
||||
if (val & 0x80) {
|
||||
dev_dbg(&client->dev, "Re-enabling ALERT#\n");
|
||||
i2c_smbus_write_byte_data(client,
|
||||
LM90_REG_W_CONFIG1,
|
||||
config & ~0x80);
|
||||
val & ~0x80);
|
||||
}
|
||||
}
|
||||
|
||||
data->last_updated = jiffies;
|
||||
data->valid = 1;
|
||||
data->valid = true;
|
||||
}
|
||||
|
||||
error:
|
||||
mutex_unlock(&data->update_lock);
|
||||
|
||||
if (val < 0)
|
||||
return ERR_PTR(val);
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
if (data->flags & LM90_FLAG_ADT7461_EXT)
|
||||
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)
|
||||
@ -729,13 +808,12 @@ static u8 temp_to_u8_adt7461(struct lm90_data *data, long val)
|
||||
if (val >= 191000)
|
||||
return 0xFF;
|
||||
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)
|
||||
@ -746,13 +824,12 @@ static u16 temp_to_u16_adt7461(struct lm90_data *data, long val)
|
||||
if (val >= 191750)
|
||||
return 0xFFC0;
|
||||
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);
|
||||
int temp;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (data->kind == adt7461 || data->kind == tmp451)
|
||||
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
||||
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);
|
||||
int temp;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (data->kind == adt7461 || data->kind == tmp451)
|
||||
temp = temp_from_u16_adt7461(data, data->temp11[attr->index]);
|
||||
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);
|
||||
int temp;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
if (data->kind == adt7461 || data->kind == tmp451)
|
||||
temp = temp_from_u8_adt7461(data, data->temp8[attr->index]);
|
||||
else if (data->kind == max6646)
|
||||
@ -953,6 +1039,10 @@ static ssize_t show_alarms(struct device *dev, struct device_attribute *dummy,
|
||||
char *buf)
|
||||
{
|
||||
struct lm90_data *data = lm90_update_device(dev);
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
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);
|
||||
int bitnr = attr->index;
|
||||
|
||||
if (IS_ERR(data))
|
||||
return PTR_ERR(data);
|
||||
|
||||
return sprintf(buf, "%d\n", (data->alarms >> bitnr) & 1);
|
||||
}
|
||||
|
||||
@ -1404,8 +1497,11 @@ static int lm90_detect(struct i2c_client *client,
|
||||
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 */
|
||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONVRATE,
|
||||
data->convrate_orig);
|
||||
@ -1413,24 +1509,22 @@ static void lm90_restore_conf(struct i2c_client *client, struct lm90_data *data)
|
||||
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) {
|
||||
dev_warn(&client->dev, "Failed to read convrate register!\n");
|
||||
convrate = LM90_DEF_CONVRATE_RVAL;
|
||||
}
|
||||
convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE);
|
||||
if (convrate < 0)
|
||||
return convrate;
|
||||
data->convrate_orig = convrate;
|
||||
|
||||
/*
|
||||
* Start the conversions.
|
||||
*/
|
||||
lm90_set_convrate(client, data, 500); /* 500ms; 2Hz conversion rate */
|
||||
if (lm90_read_reg(client, LM90_REG_R_CONFIG1, &config) < 0) {
|
||||
dev_warn(&client->dev, "Initialization failed!\n");
|
||||
return;
|
||||
}
|
||||
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||
if (config < 0)
|
||||
return config;
|
||||
data->config_orig = config;
|
||||
|
||||
/* Check Temperature Range Select */
|
||||
@ -1456,17 +1550,26 @@ static void lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
||||
config &= 0xBF; /* run */
|
||||
if (config != data->config_orig) /* Only write if changed */
|
||||
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)
|
||||
{
|
||||
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)
|
||||
lm90_read_reg(client, MAX6696_REG_R_STATUS2, &st2);
|
||||
if (data->kind == max6696) {
|
||||
st2 = lm90_read_reg(client, MAX6696_REG_R_STATUS2);
|
||||
if (st2 < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
*status = st | (st2 << 8);
|
||||
|
||||
@ -1506,6 +1609,16 @@ static irqreturn_t lm90_irq_thread(int irq, void *dev_id)
|
||||
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,
|
||||
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 lm90_data *data;
|
||||
struct regulator *regulator;
|
||||
struct device *hwmon_dev;
|
||||
int groups = 0;
|
||||
int err;
|
||||
|
||||
@ -1526,6 +1640,8 @@ static int lm90_probe(struct i2c_client *client,
|
||||
return err;
|
||||
}
|
||||
|
||||
devm_add_action(dev, lm90_regulator_disable, regulator);
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(struct lm90_data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
@ -1534,8 +1650,6 @@ static int lm90_probe(struct i2c_client *client,
|
||||
i2c_set_clientdata(client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
data->regulator = regulator;
|
||||
|
||||
/* Set the device type */
|
||||
data->kind = id->driver_data;
|
||||
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;
|
||||
|
||||
/* 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 */
|
||||
data->groups[groups++] = &lm90_group;
|
||||
@ -1577,15 +1695,14 @@ static int lm90_probe(struct i2c_client *client,
|
||||
if (client->flags & I2C_CLIENT_PEC) {
|
||||
err = device_create_file(dev, &dev_attr_pec);
|
||||
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,
|
||||
data, data->groups);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
goto exit_remove_pec;
|
||||
}
|
||||
hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name,
|
||||
data, data->groups);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
if (client->irq) {
|
||||
dev_dbg(dev, "IRQ: %d\n", client->irq);
|
||||
@ -1595,32 +1712,10 @@ static int lm90_probe(struct i2c_client *client,
|
||||
"lm90", client);
|
||||
if (err < 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1636,13 +1731,16 @@ static void lm90_alert(struct i2c_client *client, unsigned int flag)
|
||||
*/
|
||||
struct lm90_data *data = i2c_get_clientdata(client);
|
||||
|
||||
if ((data->flags & LM90_HAVE_BROKEN_ALERT)
|
||||
&& (alarms & data->alert_alarms)) {
|
||||
u8 config;
|
||||
if ((data->flags & LM90_HAVE_BROKEN_ALERT) &&
|
||||
(alarms & data->alert_alarms)) {
|
||||
int config;
|
||||
|
||||
dev_dbg(&client->dev, "Disabling ALERT#\n");
|
||||
lm90_read_reg(client, LM90_REG_R_CONFIG1, &config);
|
||||
i2c_smbus_write_byte_data(client, LM90_REG_W_CONFIG1,
|
||||
config | 0x80);
|
||||
config = lm90_read_reg(client, LM90_REG_R_CONFIG1);
|
||||
if (config >= 0)
|
||||
i2c_smbus_write_byte_data(client,
|
||||
LM90_REG_W_CONFIG1,
|
||||
config | 0x80);
|
||||
}
|
||||
} else {
|
||||
dev_info(&client->dev, "Everything OK\n");
|
||||
@ -1655,7 +1753,6 @@ static struct i2c_driver lm90_driver = {
|
||||
.name = "lm90",
|
||||
},
|
||||
.probe = lm90_probe,
|
||||
.remove = lm90_remove,
|
||||
.alert = lm90_alert,
|
||||
.id_table = lm90_id,
|
||||
.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
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
@ -27,6 +24,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
@ -50,14 +48,23 @@
|
||||
#define TMP102_TLOW_REG 0x02
|
||||
#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 i2c_client *client;
|
||||
struct device *hwmon_dev;
|
||||
struct mutex lock;
|
||||
struct regmap *regmap;
|
||||
u16 config_orig;
|
||||
unsigned long last_update;
|
||||
int temp[3];
|
||||
bool first_time;
|
||||
unsigned long ready_time;
|
||||
};
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 (tmp102->first_time) {
|
||||
if (time_before(jiffies, tmp102->ready_time)) {
|
||||
dev_dbg(dev, "%s: Conversion not ready yet..\n", __func__);
|
||||
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;
|
||||
}
|
||||
@ -119,13 +104,20 @@ static ssize_t tmp102_show_temp(struct device *dev,
|
||||
char *buf)
|
||||
{
|
||||
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 (tmp102->first_time)
|
||||
if (regaddr == TMP102_TEMP_REG &&
|
||||
time_before(jiffies, tmp102->ready_time))
|
||||
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,
|
||||
@ -134,29 +126,26 @@ static ssize_t tmp102_set_temp(struct device *dev,
|
||||
{
|
||||
struct sensor_device_attribute *sda = to_sensor_dev_attr(attr);
|
||||
struct tmp102 *tmp102 = dev_get_drvdata(dev);
|
||||
struct i2c_client *client = tmp102->client;
|
||||
int reg = sda->index;
|
||||
long val;
|
||||
int status;
|
||||
int err;
|
||||
|
||||
if (kstrtol(buf, 10, &val) < 0)
|
||||
return -EINVAL;
|
||||
val = clamp_val(val, -256000, 255000);
|
||||
|
||||
mutex_lock(&tmp102->lock);
|
||||
tmp102->temp[sda->index] = val;
|
||||
status = i2c_smbus_write_word_swapped(client, tmp102_reg[sda->index],
|
||||
tmp102_mC_to_reg(val));
|
||||
mutex_unlock(&tmp102->lock);
|
||||
return status ? : count;
|
||||
err = regmap_write(tmp102->regmap, reg, tmp102_mC_to_reg(val));
|
||||
return err ? : 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,
|
||||
tmp102_set_temp, 1);
|
||||
tmp102_set_temp, TMP102_TLOW_REG);
|
||||
|
||||
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[] = {
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
@ -166,20 +155,46 @@ static struct attribute *tmp102_attrs[] = {
|
||||
};
|
||||
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 = {
|
||||
.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,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct tmp102 *tmp102;
|
||||
int status;
|
||||
unsigned int regval;
|
||||
int err;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WORD_DATA)) {
|
||||
@ -193,73 +208,57 @@ static int tmp102_probe(struct i2c_client *client,
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(client, tmp102);
|
||||
tmp102->client = client;
|
||||
|
||||
status = i2c_smbus_read_word_swapped(client, TMP102_CONF_REG);
|
||||
if (status < 0) {
|
||||
tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config);
|
||||
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");
|
||||
return status;
|
||||
return err;
|
||||
}
|
||||
tmp102->config_orig = status;
|
||||
status = i2c_smbus_write_word_swapped(client, TMP102_CONF_REG,
|
||||
TMP102_CONFIG);
|
||||
if (status < 0) {
|
||||
|
||||
if ((regval & ~TMP102_CONFREG_MASK) !=
|
||||
(TMP102_CONF_R0 | TMP102_CONF_R1)) {
|
||||
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");
|
||||
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, tmp102_groups);
|
||||
tmp102->ready_time = jiffies;
|
||||
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)) {
|
||||
dev_dbg(dev, "unable to register hwmon device\n");
|
||||
status = PTR_ERR(hwmon_dev);
|
||||
goto fail_restore_config;
|
||||
return PTR_ERR(hwmon_dev);
|
||||
}
|
||||
tmp102->hwmon_dev = hwmon_dev;
|
||||
devm_thermal_zone_of_sensor_register(hwmon_dev, 0, hwmon_dev,
|
||||
&tmp102_of_thermal_ops);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -267,27 +266,24 @@ static int tmp102_remove(struct i2c_client *client)
|
||||
static int tmp102_suspend(struct device *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);
|
||||
if (config < 0)
|
||||
return config;
|
||||
|
||||
config |= TMP102_CONF_SD;
|
||||
return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config);
|
||||
return regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
|
||||
TMP102_CONF_SD, TMP102_CONF_SD);
|
||||
}
|
||||
|
||||
static int tmp102_resume(struct device *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);
|
||||
if (config < 0)
|
||||
return config;
|
||||
err = regmap_update_bits(tmp102->regmap, TMP102_CONF_REG,
|
||||
TMP102_CONF_SD, 0);
|
||||
|
||||
config &= ~TMP102_CONF_SD;
|
||||
return i2c_smbus_write_word_swapped(client, TMP102_CONF_REG, config);
|
||||
tmp102->ready_time = jiffies + msecs_to_jiffies(CONVERSION_TIME_MS);
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
@ -303,7 +299,6 @@ static struct i2c_driver tmp102_driver = {
|
||||
.driver.name = DRIVER_NAME,
|
||||
.driver.pm = &tmp102_dev_pm_ops,
|
||||
.probe = tmp102_probe,
|
||||
.remove = tmp102_remove,
|
||||
.id_table = tmp102_id,
|
||||
};
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
||||
static const unsigned short normal_i2c[] = { 0x48, 0x49, 0x4a, 0x4c, 0x4d,
|
||||
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
|
||||
@ -62,31 +62,34 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 };
|
||||
#define TMP401_MANUFACTURER_ID_REG 0xFE
|
||||
#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 */
|
||||
{ 0x06, 0x08 }, /* low limit */
|
||||
{ 0x05, 0x07 }, /* high limit */
|
||||
{ 0x20, 0x19 }, /* therm (crit) limit */
|
||||
{ 0x30, 0x34 }, /* lowest */
|
||||
{ 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) */
|
||||
{ 0x0C, 0x0E }, /* low limit */
|
||||
{ 0x0B, 0x0D }, /* high limit */
|
||||
{ 0x20, 0x19 }, /* therm (crit) limit */
|
||||
{ 0x30, 0x34 }, /* lowest */
|
||||
{ 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 */
|
||||
{ 0x17, 0x14 }, /* low limit */
|
||||
{ 0x16, 0x13 }, /* high limit */
|
||||
{ 0, 0 }, /* therm (crit) limit (unused) */
|
||||
{ 0x31, 0x35 }, /* lowest */
|
||||
{ 0x33, 0x37 }, /* highest */
|
||||
{ 0, 0x12 }, /* offset */
|
||||
};
|
||||
|
||||
static const u8 TMP432_TEMP_MSB_READ[4][3] = {
|
||||
@ -149,6 +152,7 @@ static const struct i2c_device_id tmp401_id[] = {
|
||||
{ "tmp431", tmp431 },
|
||||
{ "tmp432", tmp432 },
|
||||
{ "tmp435", tmp435 },
|
||||
{ "tmp461", tmp461 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tmp401_id);
|
||||
@ -170,7 +174,7 @@ struct tmp401_data {
|
||||
/* register values */
|
||||
u8 status[4];
|
||||
u8 config;
|
||||
u16 temp[6][3];
|
||||
u16 temp[7][3];
|
||||
u8 temp_crit_hyst;
|
||||
};
|
||||
|
||||
@ -612,6 +616,22 @@ static const struct attribute_group tmp432_group = {
|
||||
.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)
|
||||
*/
|
||||
@ -714,7 +734,7 @@ static int tmp401_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
static const char * const names[] = {
|
||||
"TMP401", "TMP411", "TMP431", "TMP432", "TMP435"
|
||||
"TMP401", "TMP411", "TMP431", "TMP432", "TMP435", "TMP461"
|
||||
};
|
||||
struct device *dev = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
@ -745,6 +765,9 @@ static int tmp401_probe(struct i2c_client *client,
|
||||
if (data->kind == tmp432)
|
||||
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,
|
||||
data, data->groups);
|
||||
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…
Reference in New Issue
Block a user