mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-18 02:04:05 +08:00
hwmon updates for v5.5
- Added support for Texas Instruments TMP512/513 - Added support for LTC2947 - Added support for BEL PFE1100 and PFE3000 - Various minor improvements and fixes -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAl3dGjsACgkQyx8mb86f mYHdxxAAiMSLIy9+0F+bpDY9LPkASVTEBNKf50aCihS1HLfMcHxN4SaPU9xfoa64 x8Oq3D0g3FVHoJYLjX0NFKx7w1u+B3Mud0bEKt09KHcJjQQFFSAwkGGyxpk28+IH XzLTYgYSqitYEmcT/AMsyNg1mRAyEJCmdF9yQyGqpirWkR1GxRYVmidZEgblWEZD IQYhy8omjyWOrJEHFT6oQ+Ki+kZkEQNCB22ies/R4qVC3u3zunnOK/xmT8abCSMh i+2lVpMyo/oo/loOWzRFwOYuQAXB072vpTwRRxo/77n4j2pVwg7Pb0AWrUMCR3Ec Nj3qd/phUMhL3iBDqBNy5VfeMiRGdGdnigHDMHQoJjYlyXI3Jc6MNFdluhm1VRSb +8aurQsS56XVfsk+FOr6wxD5q9vppIIS0RXlZ6TVD2RznEX5ta8GJ71y1FQHQ3FD KNp2aaAElx/Y6QAKswQ8BKA/138lTVFgZ4Pi4h9O4yptCzhxMOeo01Wlp+CsIQPF WCNvC2D8Lp5d9fU9z9Rr5e4iKDQLYBVEfkDQs/bL04h2+rPnddM9rJYcWE3rOSPk JZ6y3oj1iJxMF2eK5Q0x9qgUhmRURJMelFNshEukO7F+ismRiQPnXSOiPHJM+Qcj JGeSp8kPusOPGAt+/Zh4knKPGG41PfL03MMJQVwF5vQzXbwlDcs= =HNfe -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: - Add support for Texas Instruments TMP512/513 - Add support for LTC2947 - Add support for BEL PFE1100 and PFE3000 - Various minor improvements and fixes * tag 'hwmon-for-v5.5' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: dell-smm-hwmon: Add documentation hwmon: (dell-smm) Add support for disabling automatic BIOS fan control hwmon: Add driver for Texas Instruments TMP512/513 sensor chips. dt-bindings: hwmon: Add TMP512/513 docs: hwmon: Document bel-pfe pmbus driver hwmon: (pmbus) add driver for BEL PFE1100 and PFE3000 dt-bindings: hwmon: Add ltc2947 documentation hwmon: Add support for ltc2947 hwmon: (ina3221) Add summation feature support hwmon: (tmp421) Allow reading at 2Hz instead of 0.5Hz hwmon: (w83793d) remove redundant assignment to variable res hwmon: (pmbus/ibm-cffps) Add version detection capability dt-bindings: hwmon: Document ibm,cffps compatible string hwmon: abituguru: make array probe_order static, makes object smaller hwmon: (applesmc) switch to using input device polling mode hwmon: (aspeed-pwm-tacho) Use devm_platform_ioremap_resource() in aspeed_pwm_tacho_probe() hwmon: (pmbus/ibm-cffps) Fix LED blink behavior hwmon: (pmbus/ibm-cffps) Switch LEDs to blocking brightness call
This commit is contained in:
commit
3d9e3501a0
104
Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
Normal file
104
Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
Normal file
@ -0,0 +1,104 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/bindings/hwmon/adi,ltc2947.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Analog Devices LTC2947 high precision power and energy monitor
|
||||
|
||||
maintainers:
|
||||
- Nuno Sá <nuno.sa@analog.com>
|
||||
|
||||
description: |
|
||||
Analog Devices LTC2947 high precision power and energy monitor over SPI or I2C.
|
||||
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- adi,ltc2947
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
clocks:
|
||||
description:
|
||||
The LTC2947 uses either a trimmed internal oscillator or an external clock
|
||||
as the time base for determining the integration period to represent time,
|
||||
charge and energy. When an external clock is used, this property must be
|
||||
set accordingly.
|
||||
maxItems: 1
|
||||
|
||||
adi,accumulator-ctl-pol:
|
||||
description:
|
||||
This property controls the polarity of current that is accumulated to
|
||||
calculate charge and energy so that, they can be only accumulated for
|
||||
positive current for example. Since there are two sets of registers for
|
||||
the accumulated values, this entry can also have two items which sets
|
||||
energy1/charge1 and energy2/charger2 respectively. Check table 12 of the
|
||||
datasheet for more information on the supported options.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
- minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
enum: [0, 1, 2, 3]
|
||||
default: 0
|
||||
|
||||
adi,accumulation-deadband-microamp:
|
||||
description:
|
||||
This property controls the Accumulation Dead band which allows to set the
|
||||
level of current below which no accumulation takes place.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
maximum: 255
|
||||
default: 0
|
||||
|
||||
adi,gpio-out-pol:
|
||||
description:
|
||||
This property controls the GPIO polarity. Setting it to one makes the GPIO
|
||||
active high, setting it to zero makets it active low. When this property
|
||||
is present, the GPIO is automatically configured as output and set to
|
||||
control a fan as a function of measured temperature.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [0, 1]
|
||||
default: 0
|
||||
|
||||
adi,gpio-in-accum:
|
||||
description:
|
||||
When set, this property sets the GPIO as input. It is then used to control
|
||||
the accumulation of charge, energy and time. This function can be
|
||||
enabled/configured separately for each of the two sets of accumulation
|
||||
registers. Check table 13 of the datasheet for more information on the
|
||||
supported options. This property cannot be used together with
|
||||
adi,gpio-out-pol.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
- minItems: 2
|
||||
maxItems: 2
|
||||
items:
|
||||
enum: [0, 1, 2]
|
||||
default: 0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
|
||||
examples:
|
||||
- |
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
ltc2947_spi: ltc2947@0 {
|
||||
compatible = "adi,ltc2947";
|
||||
reg = <0>;
|
||||
/* accumulation takes place always for energ1/charge1. */
|
||||
/* accumulation only on positive current for energy2/charge2. */
|
||||
adi,accumulator-ctl-pol = <0 1>;
|
||||
};
|
||||
};
|
||||
...
|
@ -5,6 +5,9 @@ Required properties:
|
||||
- compatible : Must be one of the following:
|
||||
"ibm,cffps1"
|
||||
"ibm,cffps2"
|
||||
or "ibm,cffps" if the system
|
||||
must support any version of the
|
||||
power supply
|
||||
- reg = < I2C bus address >; : Address of the power supply on the
|
||||
I2C bus.
|
||||
|
||||
|
93
Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml
Normal file
93
Documentation/devicetree/bindings/hwmon/ti,tmp513.yaml
Normal file
@ -0,0 +1,93 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
|
||||
$id: http://devicetree.org/schemas/hwmon/ti,tmp513.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TMP513/512 system monitor sensor
|
||||
|
||||
maintainers:
|
||||
- Eric Tremblay <etremblay@distech-controls.com>
|
||||
|
||||
description: |
|
||||
The TMP512 (dual-channel) and TMP513 (triple-channel) are system monitors
|
||||
that include remote sensors, a local temperature sensor, and a high-side
|
||||
current shunt monitor. These system monitors have the capability of measuring
|
||||
remote temperatures, on-chip temperatures, and system voltage/power/current
|
||||
consumption.
|
||||
|
||||
Datasheets:
|
||||
http://www.ti.com/lit/gpn/tmp513
|
||||
http://www.ti.com/lit/gpn/tmp512
|
||||
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,tmp512
|
||||
- ti,tmp513
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
shunt-resistor-micro-ohms:
|
||||
description: |
|
||||
If 0, the calibration process will be skiped and the current and power
|
||||
measurement engine will not work. Temperature and voltage measurement
|
||||
will continue to work. The shunt value also need to respect:
|
||||
rshunt <= pga-gain * 40 * 1000 * 1000.
|
||||
If not, it's not possible to compute a valid calibration value.
|
||||
default: 1000
|
||||
|
||||
ti,pga-gain:
|
||||
description: |
|
||||
The gain value for the PGA function. This is 8, 4, 2 or 1.
|
||||
The PGA gain affect the shunt voltage range.
|
||||
The range will be equal to: pga-gain * 40mV
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [1, 2, 4, 8]
|
||||
default: 8
|
||||
|
||||
ti,bus-range-microvolt:
|
||||
description: |
|
||||
This is the operating range of the bus voltage in microvolt
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#/definitions/uint32
|
||||
enum: [16000000, 32000000]
|
||||
default: 32000000
|
||||
|
||||
ti,nfactor:
|
||||
description: |
|
||||
Array of three(TMP513) or two(TMP512) n-Factor value for each remote
|
||||
temperature channel.
|
||||
See datasheet Table 11 for n-Factor range list and value interpretation.
|
||||
allOf:
|
||||
- $ref: /schemas/types.yaml#definitions/uint32-array
|
||||
- minItems: 2
|
||||
maxItems: 3
|
||||
items:
|
||||
default: 0x00
|
||||
minimum: 0x00
|
||||
maximum: 0xFF
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
tmp513@5c {
|
||||
compatible = "ti,tmp513";
|
||||
reg = <0x5C>;
|
||||
shunt-resistor-micro-ohms = <330000>;
|
||||
ti,bus-range-microvolt = <32000000>;
|
||||
ti,pga-gain = <8>;
|
||||
ti,nfactor = <0x1 0xF3 0x00>;
|
||||
};
|
||||
};
|
112
Documentation/hwmon/bel-pfe.rst
Normal file
112
Documentation/hwmon/bel-pfe.rst
Normal file
@ -0,0 +1,112 @@
|
||||
Kernel driver bel-pfe
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* BEL PFE1100
|
||||
|
||||
Prefixes: 'pfe1100'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://www.belfuse.com/resources/datasheets/powersolutions/ds-bps-pfe1100-12-054xa.pdf
|
||||
|
||||
* BEL PFE3000
|
||||
|
||||
Prefixes: 'pfe3000'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: https://www.belfuse.com/resources/datasheets/powersolutions/ds-bps-pfe3000-series.pdf
|
||||
|
||||
Author: Tao Ren <rentao.bupt@gmail.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for below power supply devices
|
||||
which support PMBus Protocol:
|
||||
|
||||
* BEL PFE1100
|
||||
|
||||
1100 Watt AC to DC power-factor-corrected (PFC) power supply.
|
||||
PMBus Communication Manual is not publicly available.
|
||||
|
||||
* BEL PFE3000
|
||||
|
||||
3000 Watt AC/DC power-factor-corrected (PFC) and DC-DC power supply.
|
||||
PMBus Communication Manual is not publicly available.
|
||||
|
||||
The driver is a client driver to the core PMBus driver. Please see
|
||||
Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not auto-detect devices. You will have to instantiate the
|
||||
devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
|
||||
details.
|
||||
|
||||
Example: the following will load the driver for an PFE3000 at address 0x20
|
||||
on I2C bus #1::
|
||||
|
||||
$ modprobe bel-pfe
|
||||
$ echo pfe3000 0x20 > /sys/bus/i2c/devices/i2c-1/new_device
|
||||
|
||||
|
||||
Platform data support
|
||||
---------------------
|
||||
|
||||
The driver supports standard PMBus driver platform data.
|
||||
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
======================= =======================================================
|
||||
curr1_label "iin"
|
||||
curr1_input Measured input current
|
||||
curr1_max Input current max value
|
||||
curr1_max_alarm Input current max alarm
|
||||
|
||||
curr[2-3]_label "iout[1-2]"
|
||||
curr[2-3]_input Measured output current
|
||||
curr[2-3]_max Output current max value
|
||||
curr[2-3]_max_alarm Output current max alarm
|
||||
|
||||
fan[1-2]_input Fan 1 and 2 speed in RPM
|
||||
fan1_target Set fan speed reference for both fans
|
||||
|
||||
in1_label "vin"
|
||||
in1_input Measured input voltage
|
||||
in1_crit Input voltage critical max value
|
||||
in1_crit_alarm Input voltage critical max alarm
|
||||
in1_lcrit Input voltage critical min value
|
||||
in1_lcrit_alarm Input voltage critical min alarm
|
||||
in1_max Input voltage max value
|
||||
in1_max_alarm Input voltage max alarm
|
||||
|
||||
in2_label "vcap"
|
||||
in2_input Hold up capacitor voltage
|
||||
|
||||
in[3-8]_label "vout[1-3,5-7]"
|
||||
in[3-8]_input Measured output voltage
|
||||
in[3-4]_alarm vout[1-2] output voltage alarm
|
||||
|
||||
power[1-2]_label "pin[1-2]"
|
||||
power[1-2]_input Measured input power
|
||||
power[1-2]_alarm Input power high alarm
|
||||
|
||||
power[3-4]_label "pout[1-2]"
|
||||
power[3-4]_input Measured output power
|
||||
|
||||
temp[1-3]_input Measured temperature
|
||||
temp[1-3]_alarm Temperature alarm
|
||||
======================= =======================================================
|
||||
|
||||
.. note::
|
||||
|
||||
- curr3, fan2, vout[2-7], vcap, pin2, pout2 and temp3 attributes only
|
||||
exist for PFE3000.
|
164
Documentation/hwmon/dell-smm-hwmon.rst
Normal file
164
Documentation/hwmon/dell-smm-hwmon.rst
Normal file
@ -0,0 +1,164 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
.. include:: <isonum.txt>
|
||||
|
||||
Kernel driver dell-smm-hwmon
|
||||
============================
|
||||
|
||||
:Copyright: |copy| 2002-2005 Massimo Dal Zotto <dz@debian.org>
|
||||
:Copyright: |copy| 2019 Giovanni Mascellani <gio@debian.org>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
On many Dell laptops the System Management Mode (SMM) BIOS can be
|
||||
queried for the status of fans and temperature sensors. Userspace
|
||||
utilities like ``sensors`` can be used to return the readings. The
|
||||
userspace suite `i8kutils`__ can also be used to read the sensors and
|
||||
automatically adjust fan speed (please notice that it currently uses
|
||||
the deprecated ``/proc/i8k`` interface).
|
||||
|
||||
__ https://github.com/vitorafsr/i8kutils
|
||||
|
||||
``sysfs`` interface
|
||||
-------------------
|
||||
|
||||
Temperature sensors and fans can be queried and set via the standard
|
||||
``hwmon`` interface on ``sysfs``, under the directory
|
||||
``/sys/class/hwmon/hwmonX`` for some value of ``X`` (search for the
|
||||
``X`` such that ``/sys/class/hwmon/hwmonX/name`` has content
|
||||
``dell_smm``). A number of other attributes can be read or written:
|
||||
|
||||
=============================== ======= =======================================
|
||||
Name Perm Description
|
||||
=============================== ======= =======================================
|
||||
fan[1-3]_input RO Fan speed in RPM.
|
||||
fan[1-3]_label RO Fan label.
|
||||
pwm[1-3] RW Control the fan PWM duty-cycle.
|
||||
pwm1_enable WO Enable or disable automatic BIOS fan
|
||||
control (not supported on all laptops,
|
||||
see below for details).
|
||||
temp[1-10]_input RO Temperature reading in milli-degrees
|
||||
Celsius.
|
||||
temp[1-10]_label RO Temperature sensor label.
|
||||
=============================== ======= =======================================
|
||||
|
||||
Disabling automatic BIOS fan control
|
||||
------------------------------------
|
||||
|
||||
On some laptops the BIOS automatically sets fan speed every few
|
||||
seconds. Therefore the fan speed set by mean of this driver is quickly
|
||||
overwritten.
|
||||
|
||||
There is experimental support for disabling automatic BIOS fan
|
||||
control, at least on laptops where the corresponding SMM command is
|
||||
known, by writing the value ``1`` in the attribute ``pwm1_enable``
|
||||
(writing ``2`` enables automatic BIOS control again). Even if you have
|
||||
more than one fan, all of them are set to either enabled or disabled
|
||||
automatic fan control at the same time and, notwithstanding the name,
|
||||
``pwm1_enable`` sets automatic control for all fans.
|
||||
|
||||
If ``pwm1_enable`` is not available, then it means that SMM codes for
|
||||
enabling and disabling automatic BIOS fan control are not whitelisted
|
||||
for your hardware. It is possible that codes that work for other
|
||||
laptops actually work for yours as well, or that you have to discover
|
||||
new codes.
|
||||
|
||||
Check the list ``i8k_whitelist_fan_control`` in file
|
||||
``drivers/hwmon/dell-smm-hwmon.c`` in the kernel tree: as a first
|
||||
attempt you can try to add your machine and use an already-known code
|
||||
pair. If, after recompiling the kernel, you see that ``pwm1_enable``
|
||||
is present and works (i.e., you can manually control the fan speed),
|
||||
then please submit your finding as a kernel patch, so that other users
|
||||
can benefit from it. Please see
|
||||
:ref:`Documentation/process/submitting-patches.rst <submittingpatches>`
|
||||
for information on submitting patches.
|
||||
|
||||
If no known code works on your machine, you need to resort to do some
|
||||
probing, because unfortunately Dell does not publish datasheets for
|
||||
its SMM. You can experiment with the code in `this repository`__ to
|
||||
probe the BIOS on your machine and discover the appropriate codes.
|
||||
|
||||
__ https://github.com/clopez/dellfan/
|
||||
|
||||
Again, when you find new codes, we'd be happy to have your patches!
|
||||
|
||||
Module parameters
|
||||
-----------------
|
||||
|
||||
* force:bool
|
||||
Force loading without checking for supported
|
||||
models. (default: 0)
|
||||
|
||||
* ignore_dmi:bool
|
||||
Continue probing hardware even if DMI data does not
|
||||
match. (default: 0)
|
||||
|
||||
* restricted:bool
|
||||
Allow fan control only to processes with the
|
||||
``CAP_SYS_ADMIN`` capability set or processes run
|
||||
as root when using the legacy ``/proc/i8k``
|
||||
interface. In this case normal users will be able
|
||||
to read temperature and fan status but not to
|
||||
control the fan. If your notebook is shared with
|
||||
other users and you don't trust them you may want
|
||||
to use this option. (default: 1, only available
|
||||
with ``CONFIG_I8K``)
|
||||
|
||||
* power_status:bool
|
||||
Report AC status in ``/proc/i8k``. (default: 0,
|
||||
only available with ``CONFIG_I8K``)
|
||||
|
||||
* fan_mult:uint
|
||||
Factor to multiply fan speed with. (default:
|
||||
autodetect)
|
||||
|
||||
* fan_max:uint
|
||||
Maximum configurable fan speed. (default:
|
||||
autodetect)
|
||||
|
||||
Legacy ``/proc`` interface
|
||||
--------------------------
|
||||
|
||||
.. warning:: This interface is obsolete and deprecated and should not
|
||||
used in new applications. This interface is only
|
||||
available when kernel is compiled with option
|
||||
``CONFIG_I8K``.
|
||||
|
||||
The information provided by the kernel driver can be accessed by
|
||||
simply reading the ``/proc/i8k`` file. For example::
|
||||
|
||||
$ cat /proc/i8k
|
||||
1.0 A17 2J59L02 52 2 1 8040 6420 1 2
|
||||
|
||||
The fields read from ``/proc/i8k`` are::
|
||||
|
||||
1.0 A17 2J59L02 52 2 1 8040 6420 1 2
|
||||
| | | | | | | | | |
|
||||
| | | | | | | | | +------- 10. buttons status
|
||||
| | | | | | | | +--------- 9. AC status
|
||||
| | | | | | | +-------------- 8. fan0 RPM
|
||||
| | | | | | +------------------- 7. fan1 RPM
|
||||
| | | | | +--------------------- 6. fan0 status
|
||||
| | | | +----------------------- 5. fan1 status
|
||||
| | | +-------------------------- 4. temp0 reading (Celsius)
|
||||
| | +---------------------------------- 3. Dell service tag (later known as 'serial number')
|
||||
| +-------------------------------------- 2. BIOS version
|
||||
+------------------------------------------ 1. /proc/i8k format version
|
||||
|
||||
A negative value, for example -22, indicates that the BIOS doesn't
|
||||
return the corresponding information. This is normal on some
|
||||
models/BIOSes.
|
||||
|
||||
For performance reasons the ``/proc/i8k`` doesn't report by default
|
||||
the AC status since this SMM call takes a long time to execute and is
|
||||
not really needed. If you want to see the ac status in ``/proc/i8k``
|
||||
you must explictitly enable this option by passing the
|
||||
``power_status=1`` parameter to insmod. If AC status is not
|
||||
available -1 is printed instead.
|
||||
|
||||
The driver provides also an ioctl interface which can be used to
|
||||
obtain the same information and to control the fan status. The ioctl
|
||||
interface can be accessed from C programs or from shell using the
|
||||
i8kctl utility. See the source file of ``i8kutils`` for more
|
||||
information on how to use the ioctl interface.
|
@ -41,6 +41,18 @@ curr[123]_max Warning alert current(mA) setting, activates the
|
||||
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
|
||||
in7_input Sum of shunt voltage(uV) channels
|
||||
in7_label Channel label for sum of shunt voltage
|
||||
curr4_input Sum of current(mA) measurement channels,
|
||||
(only available when all channels use the same resistor
|
||||
value for their shunt resistors)
|
||||
curr4_crit Critical alert current(mA) setting for sum of current
|
||||
measurements, activates the corresponding alarm
|
||||
when the respective current is above this value
|
||||
(only effective when all channels use the same resistor
|
||||
value for their shunt resistors)
|
||||
curr4_crit_alarm Critical alert current limit exceeded for sum of
|
||||
current measurements.
|
||||
samples Number of samples using in the averaging mode.
|
||||
|
||||
Supports the list of number of samples:
|
||||
|
@ -41,9 +41,11 @@ Hardware Monitoring Kernel Drivers
|
||||
asb100
|
||||
asc7621
|
||||
aspeed-pwm-tacho
|
||||
bel-pfe
|
||||
coretemp
|
||||
da9052
|
||||
da9055
|
||||
dell-smm-hwmon
|
||||
dme1737
|
||||
ds1621
|
||||
ds620
|
||||
@ -90,6 +92,7 @@ Hardware Monitoring Kernel Drivers
|
||||
lm95245
|
||||
lochnagar
|
||||
ltc2945
|
||||
ltc2947
|
||||
ltc2978
|
||||
ltc2990
|
||||
ltc3815
|
||||
@ -153,6 +156,7 @@ Hardware Monitoring Kernel Drivers
|
||||
tmp108
|
||||
tmp401
|
||||
tmp421
|
||||
tmp513
|
||||
tps40422
|
||||
twl4030-madc-hwmon
|
||||
ucd9000
|
||||
|
100
Documentation/hwmon/ltc2947.rst
Normal file
100
Documentation/hwmon/ltc2947.rst
Normal file
@ -0,0 +1,100 @@
|
||||
Kernel drivers ltc2947-i2c and ltc2947-spi
|
||||
==========================================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Analog Devices LTC2947
|
||||
|
||||
Prefix: 'ltc2947'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet:
|
||||
|
||||
https://www.analog.com/media/en/technical-documentation/data-sheets/LTC2947.pdf
|
||||
|
||||
Author: Nuno Sá <nuno.sa@analog.com>
|
||||
|
||||
Description
|
||||
___________
|
||||
|
||||
The LTC2947 is a high precision power and energy monitor that measures current,
|
||||
voltage, power, temperature, charge and energy. The device supports both SPI
|
||||
and I2C depending on the chip configuration.
|
||||
The device also measures accumulated quantities as energy. It has two banks of
|
||||
register's to read/set energy related values. These banks can be configured
|
||||
independently to have setups like: energy1 accumulates always and enrgy2 only
|
||||
accumulates if current is positive (to check battery charging efficiency for
|
||||
example). The device also supports a GPIO pin that can be configured as output
|
||||
to control a fan as a function of measured temperature. Then, the GPIO becomes
|
||||
active as soon as a temperature reading is higher than a defined threshold. The
|
||||
temp2 channel is used to control this thresholds and to read the respective
|
||||
alarms.
|
||||
|
||||
Sysfs entries
|
||||
_____________
|
||||
|
||||
The following attributes are supported. Limits are read-write, reset_history
|
||||
is write-only and all the other attributes are read-only.
|
||||
|
||||
======================= ==========================================
|
||||
in0_input VP-VM voltage (mV).
|
||||
in0_min Undervoltage threshold
|
||||
in0_max Overvoltage threshold
|
||||
in0_lowest Lowest measured voltage
|
||||
in0_highest Highest measured voltage
|
||||
in0_reset_history Write 1 to reset in1 history
|
||||
in0_min_alarm Undervoltage alarm
|
||||
in0_max_alarm Overvoltage alarm
|
||||
in0_label Channel label (VP-VM)
|
||||
|
||||
in1_input DVCC voltage (mV)
|
||||
in1_min Undervoltage threshold
|
||||
in1_max Overvoltage threshold
|
||||
in1_lowest Lowest measured voltage
|
||||
in1_highest Highest measured voltage
|
||||
in1_reset_history Write 1 to reset in2 history
|
||||
in1_min_alarm Undervoltage alarm
|
||||
in1_max_alarm Overvoltage alarm
|
||||
in1_label Channel label (DVCC)
|
||||
|
||||
curr1_input IP-IM Sense current (mA)
|
||||
curr1_min Undercurrent threshold
|
||||
curr1_max Overcurrent threshold
|
||||
curr1_lowest Lowest measured current
|
||||
curr1_highest Highest measured current
|
||||
curr1_reset_history Write 1 to reset curr1 history
|
||||
curr1_min_alarm Undercurrent alarm
|
||||
curr1_max_alarm Overcurrent alarm
|
||||
curr1_label Channel label (IP-IM)
|
||||
|
||||
power1_input Power (in uW)
|
||||
power1_min Low power threshold
|
||||
power1_max High power threshold
|
||||
power1_input_lowest Historical minimum power use
|
||||
power1_input_highest Historical maximum power use
|
||||
power1_reset_history Write 1 to reset power1 history
|
||||
power1_min_alarm Low power alarm
|
||||
power1_max_alarm High power alarm
|
||||
power1_label Channel label (Power)
|
||||
|
||||
temp1_input Chip Temperature (in milliC)
|
||||
temp1_min Low temperature threshold
|
||||
temp1_max High temperature threshold
|
||||
temp1_input_lowest Historical minimum temperature use
|
||||
temp1_input_highest Historical maximum temperature use
|
||||
temp1_reset_history Write 1 to reset temp1 history
|
||||
temp1_min_alarm Low temperature alarm
|
||||
temp1_max_alarm High temperature alarm
|
||||
temp1_label Channel label (Ambient)
|
||||
|
||||
temp2_min Low temperature threshold for fan control
|
||||
temp2_max High temperature threshold for fan control
|
||||
temp2_min_alarm Low temperature fan control alarm
|
||||
temp2_max_alarm High temperature fan control alarm
|
||||
temp2_label Channel label (TEMPFAN)
|
||||
|
||||
energy1_input Measured energy over time (in microJoule)
|
||||
|
||||
energy2_input Measured energy over time (in microJoule)
|
||||
======================= ==========================================
|
103
Documentation/hwmon/tmp513.rst
Normal file
103
Documentation/hwmon/tmp513.rst
Normal file
@ -0,0 +1,103 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver tmp513
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Texas Instruments TMP512
|
||||
|
||||
Prefix: 'tmp512'
|
||||
|
||||
Datasheet: http://www.ti.com/lit/ds/symlink/tmp512.pdf
|
||||
|
||||
* Texas Instruments TMP513
|
||||
|
||||
Prefix: 'tmp513'
|
||||
|
||||
Datasheet: http://www.ti.com/lit/ds/symlink/tmp513.pdf
|
||||
|
||||
Authors:
|
||||
|
||||
Eric Tremblay <etremblay@distech-controls.com>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver implements support for Texas Instruments TMP512, and TMP513.
|
||||
The TMP512 (dual-channel) and TMP513 (triple-channel) are system monitors
|
||||
that include remote sensors, a local temperature sensor, and a high-side current
|
||||
shunt monitor. These system monitors have the capability of measuring remote
|
||||
temperatures, on-chip temperatures, and system voltage/power/current
|
||||
consumption.
|
||||
|
||||
The temperatures are measured in degrees Celsius with a range of
|
||||
-40 to + 125 degrees with a resolution of 0.0625 degree C.
|
||||
|
||||
For hysteresis value, only the first channel is writable. Writing to it
|
||||
will affect all other values since each channels are sharing the same
|
||||
hysteresis value. The hysteresis is in degrees Celsius with a range of
|
||||
0 to 127.5 degrees with a resolution of 0.5 degree.
|
||||
|
||||
The driver exports the temperature values via the following sysfs files:
|
||||
|
||||
**temp[1-4]_input**
|
||||
|
||||
**temp[1-4]_crit**
|
||||
|
||||
**temp[1-4]_crit_alarm**
|
||||
|
||||
**temp[1-4]_crit_hyst**
|
||||
|
||||
The driver read the shunt voltage from the chip and convert it to current.
|
||||
The readable range depends on the "ti,pga-gain" property (default to 8) and the
|
||||
shunt resistor value. The value resolution will be equal to 10uV/Rshunt.
|
||||
|
||||
The driver exports the shunt currents values via the following sysFs files:
|
||||
|
||||
**curr1_input**
|
||||
|
||||
**curr1_lcrit**
|
||||
|
||||
**curr1_lcrit_alarm**
|
||||
|
||||
**curr1_crit**
|
||||
|
||||
**curr1_crit_alarm**
|
||||
|
||||
The bus voltage range is read from the chip with a resolution of 4mV. The chip
|
||||
can be configurable in two different range (32V or 16V) using the
|
||||
ti,bus-range-microvolt property in the devicetree.
|
||||
|
||||
The driver exports the bus voltage values via the following sysFs files:
|
||||
|
||||
**in0_input**
|
||||
|
||||
**in0_lcrit**
|
||||
|
||||
**in0_lcrit_alarm**
|
||||
|
||||
**in0_crit**
|
||||
|
||||
**in0_crit_alarm**
|
||||
|
||||
The bus power and bus currents range and resolution depends on the calibration
|
||||
register value. Those values are calculate by the hardware using those
|
||||
formulas:
|
||||
|
||||
Current = (ShuntVoltage * CalibrationRegister) / 4096
|
||||
Power = (Current * BusVoltage) / 5000
|
||||
|
||||
The driver exports the bus current and bus power values via the following
|
||||
sysFs files:
|
||||
|
||||
**curr2_input**
|
||||
|
||||
**power1_input**
|
||||
|
||||
**power1_crit**
|
||||
|
||||
**power1_crit_alarm**
|
||||
|
||||
The calibration process follow the procedure of the datasheet (without overflow)
|
||||
and depend on the shunt resistor value and the pga_gain value.
|
18
MAINTAINERS
18
MAINTAINERS
@ -9717,6 +9717,17 @@ S: Maintained
|
||||
F: Documentation/hwmon/ltc4261.rst
|
||||
F: drivers/hwmon/ltc4261.c
|
||||
|
||||
LTC2947 HARDWARE MONITOR DRIVER
|
||||
M: Nuno Sá <nuno.sa@analog.com>
|
||||
W: http://ez.analog.com/community/linux-device-drivers
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/hwmon/ltc2947-core.c
|
||||
F: drivers/hwmon/ltc2947-spi.c
|
||||
F: drivers/hwmon/ltc2947-i2c.c
|
||||
F: drivers/hwmon/ltc2947.h
|
||||
F: Documentation/devicetree/bindings/hwmon/adi,ltc2947.yaml
|
||||
|
||||
LTC4306 I2C MULTIPLEXER DRIVER
|
||||
M: Michael Hennerich <michael.hennerich@analog.com>
|
||||
W: http://ez.analog.com/community/linux-device-drivers
|
||||
@ -16493,6 +16504,13 @@ S: Maintained
|
||||
F: Documentation/hwmon/tmp401.rst
|
||||
F: drivers/hwmon/tmp401.c
|
||||
|
||||
TMP513 HARDWARE MONITOR DRIVER
|
||||
M: Eric Tremblay <etremblay@distech-controls.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/hwmon/tmp513.rst
|
||||
F: drivers/hwmon/tmp513.c
|
||||
|
||||
TMPFS (SHMEM FILESYSTEM)
|
||||
M: Hugh Dickins <hughd@google.com>
|
||||
L: linux-mm@kvack.org
|
||||
|
@ -310,7 +310,6 @@ config SENSORS_APPLESMC
|
||||
depends on INPUT && X86
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
select INPUT_POLLDEV
|
||||
help
|
||||
This driver provides support for the Apple System Management
|
||||
Controller, which provides an accelerometer (Apple Sudden Motion
|
||||
@ -728,6 +727,33 @@ config SENSORS_LTC2945
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2945.
|
||||
|
||||
config SENSORS_LTC2947
|
||||
tristate
|
||||
|
||||
config SENSORS_LTC2947_I2C
|
||||
tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over I2C"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
select SENSORS_LTC2947
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC2947
|
||||
I2C High Precision Power and Energy Monitor
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2947-i2c.
|
||||
|
||||
config SENSORS_LTC2947_SPI
|
||||
tristate "Analog Devices LTC2947 High Precision Power and Energy Monitor over SPI"
|
||||
depends on SPI_MASTER
|
||||
select REGMAP_SPI
|
||||
select SENSORS_LTC2947
|
||||
help
|
||||
If you say yes here you get support for Linear Technology LTC2947
|
||||
SPI High Precision Power and Energy Monitor
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called ltc2947-spi.
|
||||
|
||||
config SENSORS_LTC2990
|
||||
tristate "Linear Technology LTC2990"
|
||||
depends on I2C
|
||||
@ -1710,6 +1736,16 @@ config SENSORS_TMP421
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tmp421.
|
||||
|
||||
config SENSORS_TMP513
|
||||
tristate "Texas Instruments TMP513 and compatibles"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments TMP512,
|
||||
and TMP513 temperature and power supply sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tmp513.
|
||||
|
||||
config SENSORS_VEXPRESS
|
||||
tristate "Versatile Express"
|
||||
depends on VEXPRESS_CONFIG
|
||||
|
@ -106,6 +106,9 @@ obj-$(CONFIG_SENSORS_LM95234) += lm95234.o
|
||||
obj-$(CONFIG_SENSORS_LM95241) += lm95241.o
|
||||
obj-$(CONFIG_SENSORS_LM95245) += lm95245.o
|
||||
obj-$(CONFIG_SENSORS_LTC2945) += ltc2945.o
|
||||
obj-$(CONFIG_SENSORS_LTC2947) += ltc2947-core.o
|
||||
obj-$(CONFIG_SENSORS_LTC2947_I2C) += ltc2947-i2c.o
|
||||
obj-$(CONFIG_SENSORS_LTC2947_SPI) += ltc2947-spi.o
|
||||
obj-$(CONFIG_SENSORS_LTC2990) += ltc2990.o
|
||||
obj-$(CONFIG_SENSORS_LTC4151) += ltc4151.o
|
||||
obj-$(CONFIG_SENSORS_LTC4215) += ltc4215.o
|
||||
@ -166,6 +169,7 @@ obj-$(CONFIG_SENSORS_TMP103) += tmp103.o
|
||||
obj-$(CONFIG_SENSORS_TMP108) += tmp108.o
|
||||
obj-$(CONFIG_SENSORS_TMP401) += tmp401.o
|
||||
obj-$(CONFIG_SENSORS_TMP421) += tmp421.o
|
||||
obj-$(CONFIG_SENSORS_TMP513) += tmp513.o
|
||||
obj-$(CONFIG_SENSORS_VEXPRESS) += vexpress-hwmon.o
|
||||
obj-$(CONFIG_SENSORS_VIA_CPUTEMP)+= via-cputemp.o
|
||||
obj-$(CONFIG_SENSORS_VIA686A) += via686a.o
|
||||
|
@ -1264,7 +1264,7 @@ static int abituguru_probe(struct platform_device *pdev)
|
||||
* El weirdo probe order, to keep the sysfs order identical to the
|
||||
* BIOS and window-appliction listing order.
|
||||
*/
|
||||
const u8 probe_order[ABIT_UGURU_MAX_BANK1_SENSORS] = {
|
||||
static const u8 probe_order[ABIT_UGURU_MAX_BANK1_SENSORS] = {
|
||||
0x00, 0x01, 0x03, 0x04, 0x0A, 0x08, 0x0E, 0x02,
|
||||
0x09, 0x06, 0x05, 0x0B, 0x0F, 0x0D, 0x07, 0x0C };
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/input-polldev.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
@ -140,7 +140,7 @@ static s16 rest_y;
|
||||
static u8 backlight_state[2];
|
||||
|
||||
static struct device *hwmon_dev;
|
||||
static struct input_polled_dev *applesmc_idev;
|
||||
static struct input_dev *applesmc_idev;
|
||||
|
||||
/*
|
||||
* Last index written to key_at_index sysfs file, and value to use for all other
|
||||
@ -681,9 +681,8 @@ static void applesmc_calibrate(void)
|
||||
rest_x = -rest_x;
|
||||
}
|
||||
|
||||
static void applesmc_idev_poll(struct input_polled_dev *dev)
|
||||
static void applesmc_idev_poll(struct input_dev *idev)
|
||||
{
|
||||
struct input_dev *idev = dev->input;
|
||||
s16 x, y;
|
||||
|
||||
if (applesmc_read_s16(MOTION_SENSOR_X_KEY, &x))
|
||||
@ -1134,7 +1133,6 @@ out:
|
||||
/* Create accelerometer resources */
|
||||
static int applesmc_create_accelerometer(void)
|
||||
{
|
||||
struct input_dev *idev;
|
||||
int ret;
|
||||
|
||||
if (!smcreg.has_accelerometer)
|
||||
@ -1144,37 +1142,38 @@ static int applesmc_create_accelerometer(void)
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
applesmc_idev = input_allocate_polled_device();
|
||||
applesmc_idev = input_allocate_device();
|
||||
if (!applesmc_idev) {
|
||||
ret = -ENOMEM;
|
||||
goto out_sysfs;
|
||||
}
|
||||
|
||||
applesmc_idev->poll = applesmc_idev_poll;
|
||||
applesmc_idev->poll_interval = APPLESMC_POLL_INTERVAL;
|
||||
|
||||
/* initial calibrate for the input device */
|
||||
applesmc_calibrate();
|
||||
|
||||
/* initialize the input device */
|
||||
idev = applesmc_idev->input;
|
||||
idev->name = "applesmc";
|
||||
idev->id.bustype = BUS_HOST;
|
||||
idev->dev.parent = &pdev->dev;
|
||||
idev->evbit[0] = BIT_MASK(EV_ABS);
|
||||
input_set_abs_params(idev, ABS_X,
|
||||
applesmc_idev->name = "applesmc";
|
||||
applesmc_idev->id.bustype = BUS_HOST;
|
||||
applesmc_idev->dev.parent = &pdev->dev;
|
||||
input_set_abs_params(applesmc_idev, ABS_X,
|
||||
-256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT);
|
||||
input_set_abs_params(idev, ABS_Y,
|
||||
input_set_abs_params(applesmc_idev, ABS_Y,
|
||||
-256, 256, APPLESMC_INPUT_FUZZ, APPLESMC_INPUT_FLAT);
|
||||
|
||||
ret = input_register_polled_device(applesmc_idev);
|
||||
ret = input_setup_polling(applesmc_idev, applesmc_idev_poll);
|
||||
if (ret)
|
||||
goto out_idev;
|
||||
|
||||
input_set_poll_interval(applesmc_idev, APPLESMC_POLL_INTERVAL);
|
||||
|
||||
ret = input_register_device(applesmc_idev);
|
||||
if (ret)
|
||||
goto out_idev;
|
||||
|
||||
return 0;
|
||||
|
||||
out_idev:
|
||||
input_free_polled_device(applesmc_idev);
|
||||
input_free_device(applesmc_idev);
|
||||
|
||||
out_sysfs:
|
||||
applesmc_destroy_nodes(accelerometer_group);
|
||||
@ -1189,8 +1188,7 @@ static void applesmc_release_accelerometer(void)
|
||||
{
|
||||
if (!smcreg.has_accelerometer)
|
||||
return;
|
||||
input_unregister_polled_device(applesmc_idev);
|
||||
input_free_polled_device(applesmc_idev);
|
||||
input_unregister_device(applesmc_idev);
|
||||
applesmc_destroy_nodes(accelerometer_group);
|
||||
}
|
||||
|
||||
|
@ -891,17 +891,12 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
|
||||
struct device_node *np, *child;
|
||||
struct aspeed_pwm_tacho_data *priv;
|
||||
void __iomem *regs;
|
||||
struct resource *res;
|
||||
struct device *hwmon;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
np = dev->of_node;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -ENOENT;
|
||||
regs = devm_ioremap_resource(dev, res);
|
||||
regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
|
@ -68,6 +68,8 @@ static uint i8k_pwm_mult;
|
||||
static uint i8k_fan_max = I8K_FAN_HIGH;
|
||||
static bool disallow_fan_type_call;
|
||||
static bool disallow_fan_support;
|
||||
static unsigned int manual_fan;
|
||||
static unsigned int auto_fan;
|
||||
|
||||
#define I8K_HWMON_HAVE_TEMP1 (1 << 0)
|
||||
#define I8K_HWMON_HAVE_TEMP2 (1 << 1)
|
||||
@ -300,6 +302,20 @@ static int i8k_get_fan_nominal_speed(int fan, int speed)
|
||||
return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable or disable automatic BIOS fan control support
|
||||
*/
|
||||
static int i8k_enable_fan_auto_mode(bool enable)
|
||||
{
|
||||
struct smm_regs regs = { };
|
||||
|
||||
if (disallow_fan_support)
|
||||
return -EINVAL;
|
||||
|
||||
regs.eax = enable ? auto_fan : manual_fan;
|
||||
return i8k_smm(®s);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the fan speed (off, low, high). Returns the new fan status.
|
||||
*/
|
||||
@ -726,6 +742,35 @@ static ssize_t i8k_hwmon_pwm_store(struct device *dev,
|
||||
return err < 0 ? -EIO : count;
|
||||
}
|
||||
|
||||
static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int err;
|
||||
bool enable;
|
||||
unsigned long val;
|
||||
|
||||
if (!auto_fan)
|
||||
return -ENODEV;
|
||||
|
||||
err = kstrtoul(buf, 10, &val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (val == 1)
|
||||
enable = false;
|
||||
else if (val == 2)
|
||||
enable = true;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&i8k_mutex);
|
||||
err = i8k_enable_fan_auto_mode(enable);
|
||||
mutex_unlock(&i8k_mutex);
|
||||
|
||||
return err ? err : count;
|
||||
}
|
||||
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_input, i8k_hwmon_temp, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_label, i8k_hwmon_temp_label, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_input, i8k_hwmon_temp, 1);
|
||||
@ -749,6 +794,7 @@ static SENSOR_DEVICE_ATTR_RO(temp10_label, i8k_hwmon_temp_label, 9);
|
||||
static SENSOR_DEVICE_ATTR_RO(fan1_input, i8k_hwmon_fan, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(fan1_label, i8k_hwmon_fan_label, 0);
|
||||
static SENSOR_DEVICE_ATTR_RW(pwm1, i8k_hwmon_pwm, 0);
|
||||
static SENSOR_DEVICE_ATTR_WO(pwm1_enable, i8k_hwmon_pwm_enable, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(fan2_input, i8k_hwmon_fan, 1);
|
||||
static SENSOR_DEVICE_ATTR_RO(fan2_label, i8k_hwmon_fan_label, 1);
|
||||
static SENSOR_DEVICE_ATTR_RW(pwm2, i8k_hwmon_pwm, 1);
|
||||
@ -780,12 +826,13 @@ static struct attribute *i8k_attrs[] = {
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr, /* 20 */
|
||||
&sensor_dev_attr_fan1_label.dev_attr.attr, /* 21 */
|
||||
&sensor_dev_attr_pwm1.dev_attr.attr, /* 22 */
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr, /* 23 */
|
||||
&sensor_dev_attr_fan2_label.dev_attr.attr, /* 24 */
|
||||
&sensor_dev_attr_pwm2.dev_attr.attr, /* 25 */
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr, /* 26 */
|
||||
&sensor_dev_attr_fan3_label.dev_attr.attr, /* 27 */
|
||||
&sensor_dev_attr_pwm3.dev_attr.attr, /* 28 */
|
||||
&sensor_dev_attr_pwm1_enable.dev_attr.attr, /* 23 */
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr, /* 24 */
|
||||
&sensor_dev_attr_fan2_label.dev_attr.attr, /* 25 */
|
||||
&sensor_dev_attr_pwm2.dev_attr.attr, /* 26 */
|
||||
&sensor_dev_attr_fan3_input.dev_attr.attr, /* 27 */
|
||||
&sensor_dev_attr_fan3_label.dev_attr.attr, /* 28 */
|
||||
&sensor_dev_attr_pwm3.dev_attr.attr, /* 29 */
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -828,16 +875,19 @@ static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10))
|
||||
return 0;
|
||||
|
||||
if (index >= 20 && index <= 22 &&
|
||||
if (index >= 20 && index <= 23 &&
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
|
||||
return 0;
|
||||
if (index >= 23 && index <= 25 &&
|
||||
if (index >= 24 && index <= 26 &&
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
|
||||
return 0;
|
||||
if (index >= 26 && index <= 28 &&
|
||||
if (index >= 27 && index <= 29 &&
|
||||
!(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3))
|
||||
return 0;
|
||||
|
||||
if (index == 23 && !auto_fan)
|
||||
return 0;
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
@ -1135,12 +1185,48 @@ static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
|
||||
{ }
|
||||
};
|
||||
|
||||
struct i8k_fan_control_data {
|
||||
unsigned int manual_fan;
|
||||
unsigned int auto_fan;
|
||||
};
|
||||
|
||||
enum i8k_fan_controls {
|
||||
I8K_FAN_34A3_35A3,
|
||||
};
|
||||
|
||||
static const struct i8k_fan_control_data i8k_fan_control_data[] = {
|
||||
[I8K_FAN_34A3_35A3] = {
|
||||
.manual_fan = 0x34a3,
|
||||
.auto_fan = 0x35a3,
|
||||
},
|
||||
};
|
||||
|
||||
static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = {
|
||||
{
|
||||
.ident = "Dell Precision 5530",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
|
||||
},
|
||||
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
|
||||
},
|
||||
{
|
||||
.ident = "Dell Latitude E6440",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
|
||||
},
|
||||
.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
/*
|
||||
* Probe for the presence of a supported laptop.
|
||||
*/
|
||||
static int __init i8k_probe(void)
|
||||
{
|
||||
const struct dmi_system_id *id;
|
||||
const struct dmi_system_id *id, *fan_control;
|
||||
int fan, ret;
|
||||
|
||||
/*
|
||||
@ -1200,6 +1286,15 @@ static int __init i8k_probe(void)
|
||||
i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */
|
||||
i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
|
||||
|
||||
fan_control = dmi_first_match(i8k_whitelist_fan_control);
|
||||
if (fan_control && fan_control->driver_data) {
|
||||
const struct i8k_fan_control_data *data = fan_control->driver_data;
|
||||
|
||||
manual_fan = data->manual_fan;
|
||||
auto_fan = data->auto_fan;
|
||||
pr_info("enabling support for setting automatic/manual fan control\n");
|
||||
}
|
||||
|
||||
if (!fan_mult) {
|
||||
/*
|
||||
* Autodetect fan multiplier based on nominal rpm
|
||||
|
@ -31,6 +31,8 @@
|
||||
#define INA3221_WARN2 0x0a
|
||||
#define INA3221_CRIT3 0x0b
|
||||
#define INA3221_WARN3 0x0c
|
||||
#define INA3221_SHUNT_SUM 0x0d
|
||||
#define INA3221_CRIT_SUM 0x0e
|
||||
#define INA3221_MASK_ENABLE 0x0f
|
||||
|
||||
#define INA3221_CONFIG_MODE_MASK GENMASK(2, 0)
|
||||
@ -50,6 +52,8 @@
|
||||
#define INA3221_CONFIG_CHs_EN_MASK GENMASK(14, 12)
|
||||
#define INA3221_CONFIG_CHx_EN(x) BIT(14 - (x))
|
||||
|
||||
#define INA3221_MASK_ENABLE_SCC_MASK GENMASK(14, 12)
|
||||
|
||||
#define INA3221_CONFIG_DEFAULT 0x7127
|
||||
#define INA3221_RSHUNT_DEFAULT 10000
|
||||
|
||||
@ -60,9 +64,11 @@ enum ina3221_fields {
|
||||
/* Status Flags */
|
||||
F_CVRF,
|
||||
|
||||
/* Alert Flags */
|
||||
/* Warning Flags */
|
||||
F_WF3, F_WF2, F_WF1,
|
||||
F_CF3, F_CF2, F_CF1,
|
||||
|
||||
/* Alert Flags: SF is the summation-alert flag */
|
||||
F_SF, F_CF3, F_CF2, F_CF1,
|
||||
|
||||
/* sentinel */
|
||||
F_MAX_FIELDS
|
||||
@ -75,6 +81,7 @@ static const struct reg_field ina3221_reg_fields[] = {
|
||||
[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_SF] = REG_FIELD(INA3221_MASK_ENABLE, 6, 6),
|
||||
[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),
|
||||
@ -107,6 +114,7 @@ struct ina3221_input {
|
||||
* @inputs: Array of channel input source specific structures
|
||||
* @lock: mutex lock to serialize sysfs attribute accesses
|
||||
* @reg_config: Register value of INA3221_CONFIG
|
||||
* @summation_shunt_resistor: equivalent shunt resistor value for summation
|
||||
* @single_shot: running in single-shot operating mode
|
||||
*/
|
||||
struct ina3221_data {
|
||||
@ -116,16 +124,51 @@ struct ina3221_data {
|
||||
struct ina3221_input inputs[INA3221_NUM_CHANNELS];
|
||||
struct mutex lock;
|
||||
u32 reg_config;
|
||||
int summation_shunt_resistor;
|
||||
|
||||
bool single_shot;
|
||||
};
|
||||
|
||||
static inline bool ina3221_is_enabled(struct ina3221_data *ina, int channel)
|
||||
{
|
||||
/* Summation channel checks shunt resistor values */
|
||||
if (channel > INA3221_CHANNEL3)
|
||||
return ina->summation_shunt_resistor != 0;
|
||||
|
||||
return pm_runtime_active(ina->pm_dev) &&
|
||||
(ina->reg_config & INA3221_CONFIG_CHx_EN(channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the resistor value for current summation.
|
||||
*
|
||||
* There is a condition to calculate current summation -- all the shunt
|
||||
* resistor values should be the same, so as to simply fit the formula:
|
||||
* current summation = shunt voltage summation / shunt resistor
|
||||
*
|
||||
* Returns the equivalent shunt resistor value on success or 0 on failure
|
||||
*/
|
||||
static inline int ina3221_summation_shunt_resistor(struct ina3221_data *ina)
|
||||
{
|
||||
struct ina3221_input *input = ina->inputs;
|
||||
int i, shunt_resistor = 0;
|
||||
|
||||
for (i = 0; i < INA3221_NUM_CHANNELS; i++) {
|
||||
if (input[i].disconnected || !input[i].shunt_resistor)
|
||||
continue;
|
||||
if (!shunt_resistor) {
|
||||
/* Found the reference shunt resistor value */
|
||||
shunt_resistor = input[i].shunt_resistor;
|
||||
} else {
|
||||
/* No summation if resistor values are different */
|
||||
if (shunt_resistor != input[i].shunt_resistor)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return shunt_resistor;
|
||||
}
|
||||
|
||||
/* Lookup table for Bus and Shunt conversion times in usec */
|
||||
static const u16 ina3221_conv_time[] = {
|
||||
140, 204, 332, 588, 1100, 2116, 4156, 8244,
|
||||
@ -183,7 +226,14 @@ static int ina3221_read_value(struct ina3221_data *ina, unsigned int reg,
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = sign_extend32(regval >> 3, 12);
|
||||
/*
|
||||
* Shunt Voltage Sum register has 14-bit value with 1-bit shift
|
||||
* Other Shunt Voltage registers have 12 bits with 3-bit shift
|
||||
*/
|
||||
if (reg == INA3221_SHUNT_SUM)
|
||||
*val = sign_extend32(regval >> 1, 14);
|
||||
else
|
||||
*val = sign_extend32(regval >> 3, 12);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -195,6 +245,7 @@ static const u8 ina3221_in_reg[] = {
|
||||
INA3221_SHUNT1,
|
||||
INA3221_SHUNT2,
|
||||
INA3221_SHUNT3,
|
||||
INA3221_SHUNT_SUM,
|
||||
};
|
||||
|
||||
static int ina3221_read_chip(struct device *dev, u32 attr, long *val)
|
||||
@ -224,8 +275,12 @@ static int ina3221_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
u8 reg = ina3221_in_reg[channel];
|
||||
int regval, ret;
|
||||
|
||||
/* Translate shunt channel index to sensor channel index */
|
||||
channel %= INA3221_NUM_CHANNELS;
|
||||
/*
|
||||
* Translate shunt channel index to sensor channel index except
|
||||
* the 7th channel (6 since being 0-aligned) is for summation.
|
||||
*/
|
||||
if (channel != 6)
|
||||
channel %= INA3221_NUM_CHANNELS;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
@ -259,22 +314,29 @@ static int ina3221_read_in(struct device *dev, u32 attr, int channel, long *val)
|
||||
}
|
||||
}
|
||||
|
||||
static const u8 ina3221_curr_reg[][INA3221_NUM_CHANNELS] = {
|
||||
[hwmon_curr_input] = { INA3221_SHUNT1, INA3221_SHUNT2, INA3221_SHUNT3 },
|
||||
[hwmon_curr_max] = { INA3221_WARN1, INA3221_WARN2, INA3221_WARN3 },
|
||||
[hwmon_curr_crit] = { INA3221_CRIT1, INA3221_CRIT2, INA3221_CRIT3 },
|
||||
[hwmon_curr_max_alarm] = { F_WF1, F_WF2, F_WF3 },
|
||||
[hwmon_curr_crit_alarm] = { F_CF1, F_CF2, F_CF3 },
|
||||
static const u8 ina3221_curr_reg[][INA3221_NUM_CHANNELS + 1] = {
|
||||
[hwmon_curr_input] = { INA3221_SHUNT1, INA3221_SHUNT2,
|
||||
INA3221_SHUNT3, INA3221_SHUNT_SUM },
|
||||
[hwmon_curr_max] = { INA3221_WARN1, INA3221_WARN2, INA3221_WARN3, 0 },
|
||||
[hwmon_curr_crit] = { INA3221_CRIT1, INA3221_CRIT2,
|
||||
INA3221_CRIT3, INA3221_CRIT_SUM },
|
||||
[hwmon_curr_max_alarm] = { F_WF1, F_WF2, F_WF3, 0 },
|
||||
[hwmon_curr_crit_alarm] = { F_CF1, F_CF2, F_CF3, F_SF },
|
||||
};
|
||||
|
||||
static int ina3221_read_curr(struct device *dev, u32 attr,
|
||||
int channel, long *val)
|
||||
{
|
||||
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||
struct ina3221_input *input = &ina->inputs[channel];
|
||||
int resistance_uo = input->shunt_resistor;
|
||||
struct ina3221_input *input = ina->inputs;
|
||||
u8 reg = ina3221_curr_reg[attr][channel];
|
||||
int regval, voltage_nv, ret;
|
||||
int resistance_uo, voltage_nv;
|
||||
int regval, ret;
|
||||
|
||||
if (channel > INA3221_CHANNEL3)
|
||||
resistance_uo = ina->summation_shunt_resistor;
|
||||
else
|
||||
resistance_uo = input[channel].shunt_resistor;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
@ -293,6 +355,9 @@ static int ina3221_read_curr(struct device *dev, u32 attr,
|
||||
/* fall through */
|
||||
case hwmon_curr_crit:
|
||||
case hwmon_curr_max:
|
||||
if (!resistance_uo)
|
||||
return -ENODATA;
|
||||
|
||||
ret = ina3221_read_value(ina, reg, ®val);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -366,10 +431,18 @@ static int ina3221_write_curr(struct device *dev, u32 attr,
|
||||
int channel, long val)
|
||||
{
|
||||
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||
struct ina3221_input *input = &ina->inputs[channel];
|
||||
int resistance_uo = input->shunt_resistor;
|
||||
struct ina3221_input *input = ina->inputs;
|
||||
u8 reg = ina3221_curr_reg[attr][channel];
|
||||
int regval, current_ma, voltage_uv;
|
||||
int resistance_uo, current_ma, voltage_uv;
|
||||
int regval;
|
||||
|
||||
if (channel > INA3221_CHANNEL3)
|
||||
resistance_uo = ina->summation_shunt_resistor;
|
||||
else
|
||||
resistance_uo = input[channel].shunt_resistor;
|
||||
|
||||
if (!resistance_uo)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* clamp current */
|
||||
current_ma = clamp_val(val,
|
||||
@ -381,8 +454,21 @@ static int ina3221_write_curr(struct device *dev, u32 attr,
|
||||
/* clamp voltage */
|
||||
voltage_uv = clamp_val(voltage_uv, -163800, 163800);
|
||||
|
||||
/* 1 / 40uV(scale) << 3(register shift) = 5 */
|
||||
regval = DIV_ROUND_CLOSEST(voltage_uv, 5) & 0xfff8;
|
||||
/*
|
||||
* Formula to convert voltage_uv to register value:
|
||||
* regval = (voltage_uv / scale) << shift
|
||||
* Note:
|
||||
* The scale is 40uV for all shunt voltage registers
|
||||
* Shunt Voltage Sum register left-shifts 1 bit
|
||||
* All other Shunt Voltage registers shift 3 bits
|
||||
* Results:
|
||||
* SHUNT_SUM: (1 / 40uV) << 1 = 1 / 20uV
|
||||
* SHUNT[1-3]: (1 / 40uV) << 3 = 1 / 5uV
|
||||
*/
|
||||
if (reg == INA3221_SHUNT_SUM)
|
||||
regval = DIV_ROUND_CLOSEST(voltage_uv, 20) & 0xfffe;
|
||||
else
|
||||
regval = DIV_ROUND_CLOSEST(voltage_uv, 5) & 0xfff8;
|
||||
|
||||
return regmap_write(ina->regmap, reg, regval);
|
||||
}
|
||||
@ -499,7 +585,10 @@ static int ina3221_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||
struct ina3221_data *ina = dev_get_drvdata(dev);
|
||||
int index = channel - 1;
|
||||
|
||||
*str = ina->inputs[index].label;
|
||||
if (channel == 7)
|
||||
*str = "sum of shunt voltages";
|
||||
else
|
||||
*str = ina->inputs[index].label;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -529,6 +618,8 @@ static umode_t ina3221_is_visible(const void *drvdata,
|
||||
case hwmon_in_label:
|
||||
if (channel - 1 <= INA3221_CHANNEL3)
|
||||
input = &ina->inputs[channel - 1];
|
||||
else if (channel == 7)
|
||||
return 0444;
|
||||
/* Hide label node if label is not provided */
|
||||
return (input && input->label) ? 0444 : 0;
|
||||
case hwmon_in_input:
|
||||
@ -573,11 +664,16 @@ static const struct hwmon_channel_info *ina3221_info[] = {
|
||||
/* 4-6: shunt voltage Channels */
|
||||
HWMON_I_INPUT,
|
||||
HWMON_I_INPUT,
|
||||
HWMON_I_INPUT),
|
||||
HWMON_I_INPUT,
|
||||
/* 7: summation of shunt voltage channels */
|
||||
HWMON_I_INPUT | HWMON_I_LABEL),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
/* 1-3: current channels*/
|
||||
INA3221_HWMON_CURR_CONFIG,
|
||||
INA3221_HWMON_CURR_CONFIG,
|
||||
INA3221_HWMON_CURR_CONFIG),
|
||||
INA3221_HWMON_CURR_CONFIG,
|
||||
/* 4: summation of current channels */
|
||||
HWMON_C_INPUT | HWMON_C_CRIT | HWMON_C_CRIT_ALARM),
|
||||
NULL
|
||||
};
|
||||
|
||||
@ -624,6 +720,9 @@ static ssize_t ina3221_shunt_store(struct device *dev,
|
||||
|
||||
input->shunt_resistor = val;
|
||||
|
||||
/* Update summation_shunt_resistor for summation channel */
|
||||
ina->summation_shunt_resistor = ina3221_summation_shunt_resistor(ina);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -642,6 +741,7 @@ ATTRIBUTE_GROUPS(ina3221);
|
||||
|
||||
static const struct regmap_range ina3221_yes_ranges[] = {
|
||||
regmap_reg_range(INA3221_CONFIG, INA3221_BUS3),
|
||||
regmap_reg_range(INA3221_SHUNT_SUM, INA3221_SHUNT_SUM),
|
||||
regmap_reg_range(INA3221_MASK_ENABLE, INA3221_MASK_ENABLE),
|
||||
};
|
||||
|
||||
@ -772,6 +872,9 @@ static int ina3221_probe(struct i2c_client *client,
|
||||
ina->reg_config &= ~INA3221_CONFIG_CHx_EN(i);
|
||||
}
|
||||
|
||||
/* Initialize summation_shunt_resistor for summation channel control */
|
||||
ina->summation_shunt_resistor = ina3221_summation_shunt_resistor(ina);
|
||||
|
||||
ina->pm_dev = dev;
|
||||
mutex_init(&ina->lock);
|
||||
dev_set_drvdata(dev, ina);
|
||||
@ -875,6 +978,22 @@ static int __maybe_unused ina3221_resume(struct device *dev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initialize summation channel control */
|
||||
if (ina->summation_shunt_resistor) {
|
||||
/*
|
||||
* Take all three channels into summation by default
|
||||
* Shunt measurements of disconnected channels should
|
||||
* be 0, so it does not matter for summation.
|
||||
*/
|
||||
ret = regmap_update_bits(ina->regmap, INA3221_MASK_ENABLE,
|
||||
INA3221_MASK_ENABLE_SCC_MASK,
|
||||
INA3221_MASK_ENABLE_SCC_MASK);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to control summation channel\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
1183
drivers/hwmon/ltc2947-core.c
Normal file
1183
drivers/hwmon/ltc2947-core.c
Normal file
File diff suppressed because it is too large
Load Diff
49
drivers/hwmon/ltc2947-i2c.c
Normal file
49
drivers/hwmon/ltc2947-i2c.c
Normal file
@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Analog Devices LTC2947 high precision power and energy monitor over I2C
|
||||
*
|
||||
* Copyright 2019 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "ltc2947.h"
|
||||
|
||||
static const struct regmap_config ltc2947_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int ltc2947_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct regmap *map;
|
||||
|
||||
map = devm_regmap_init_i2c(i2c, <c2947_regmap_config);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
return ltc2947_core_probe(map, i2c->name);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id ltc2947_id[] = {
|
||||
{"ltc2947", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ltc2947_id);
|
||||
|
||||
static struct i2c_driver ltc2947_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2947",
|
||||
.of_match_table = ltc2947_of_match,
|
||||
.pm = <c2947_pm_ops,
|
||||
},
|
||||
.probe = ltc2947_probe,
|
||||
.id_table = ltc2947_id,
|
||||
};
|
||||
module_i2c_driver(ltc2947_driver);
|
||||
|
||||
MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
|
||||
MODULE_DESCRIPTION("LTC2947 I2C power and energy monitor driver");
|
||||
MODULE_LICENSE("GPL");
|
50
drivers/hwmon/ltc2947-spi.c
Normal file
50
drivers/hwmon/ltc2947-spi.c
Normal file
@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Analog Devices LTC2947 high precision power and energy monitor over SPI
|
||||
*
|
||||
* Copyright 2019 Analog Devices Inc.
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include "ltc2947.h"
|
||||
|
||||
static const struct regmap_config ltc2947_regmap_config = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
.read_flag_mask = BIT(0),
|
||||
};
|
||||
|
||||
static int ltc2947_probe(struct spi_device *spi)
|
||||
{
|
||||
struct regmap *map;
|
||||
|
||||
map = devm_regmap_init_spi(spi, <c2947_regmap_config);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
return ltc2947_core_probe(map, spi_get_device_id(spi)->name);
|
||||
}
|
||||
|
||||
static const struct spi_device_id ltc2947_id[] = {
|
||||
{"ltc2947", 0},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, ltc2947_id);
|
||||
|
||||
static struct spi_driver ltc2947_driver = {
|
||||
.driver = {
|
||||
.name = "ltc2947",
|
||||
.of_match_table = ltc2947_of_match,
|
||||
.pm = <c2947_pm_ops,
|
||||
},
|
||||
.probe = ltc2947_probe,
|
||||
.id_table = ltc2947_id,
|
||||
};
|
||||
module_spi_driver(ltc2947_driver);
|
||||
|
||||
MODULE_AUTHOR("Nuno Sa <nuno.sa@analog.com>");
|
||||
MODULE_DESCRIPTION("LTC2947 SPI power and energy monitor driver");
|
||||
MODULE_LICENSE("GPL");
|
12
drivers/hwmon/ltc2947.h
Normal file
12
drivers/hwmon/ltc2947.h
Normal file
@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _LINUX_LTC2947_H
|
||||
#define _LINUX_LTC2947_H
|
||||
|
||||
struct regmap;
|
||||
|
||||
extern const struct of_device_id ltc2947_of_match[];
|
||||
extern const struct dev_pm_ops ltc2947_pm_ops;
|
||||
|
||||
int ltc2947_core_probe(struct regmap *map, const char *name);
|
||||
|
||||
#endif
|
@ -36,6 +36,15 @@ config SENSORS_ADM1275
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called adm1275.
|
||||
|
||||
config SENSORS_BEL_PFE
|
||||
tristate "Bel PFE Compatible Power Supplies"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for BEL
|
||||
PFE1100 and PFE3000 Power Supplies.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called bel-pfe.
|
||||
|
||||
config SENSORS_IBM_CFFPS
|
||||
tristate "IBM Common Form Factor Power Supply"
|
||||
depends on LEDS_CLASS
|
||||
|
@ -6,6 +6,7 @@
|
||||
obj-$(CONFIG_PMBUS) += pmbus_core.o
|
||||
obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o
|
||||
obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o
|
||||
obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
|
||||
obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
|
||||
obj-$(CONFIG_SENSORS_INSPUR_IPSPS) += inspur-ipsps.o
|
||||
obj-$(CONFIG_SENSORS_IR35221) += ir35221.o
|
||||
|
131
drivers/hwmon/pmbus/bel-pfe.c
Normal file
131
drivers/hwmon/pmbus/bel-pfe.c
Normal file
@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Hardware monitoring driver for BEL PFE family power supplies.
|
||||
*
|
||||
* Copyright (c) 2019 Facebook Inc.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pmbus.h>
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips {pfe1100, pfe3000};
|
||||
|
||||
/*
|
||||
* Disable status check for pfe3000 devices, because some devices report
|
||||
* communication error (invalid command) for VOUT_MODE command (0x20)
|
||||
* although correct VOUT_MODE (0x16) is returned: it leads to incorrect
|
||||
* exponent in linear mode.
|
||||
*/
|
||||
static struct pmbus_platform_data pfe3000_plat_data = {
|
||||
.flags = PMBUS_SKIP_STATUS_CHECK,
|
||||
};
|
||||
|
||||
static struct pmbus_driver_info pfe_driver_info[] = {
|
||||
[pfe1100] = {
|
||||
.pages = 1,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
.format[PSC_CURRENT_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_POWER] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_FAN] = linear,
|
||||
|
||||
.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_POUT |
|
||||
PMBUS_HAVE_VIN | PMBUS_HAVE_IIN |
|
||||
PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
|
||||
PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_FAN12,
|
||||
},
|
||||
|
||||
[pfe3000] = {
|
||||
.pages = 7,
|
||||
.format[PSC_VOLTAGE_IN] = linear,
|
||||
.format[PSC_VOLTAGE_OUT] = linear,
|
||||
.format[PSC_CURRENT_IN] = linear,
|
||||
.format[PSC_CURRENT_OUT] = linear,
|
||||
.format[PSC_POWER] = linear,
|
||||
.format[PSC_TEMPERATURE] = linear,
|
||||
.format[PSC_FAN] = linear,
|
||||
|
||||
/* Page 0: V1. */
|
||||
.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_POUT | PMBUS_HAVE_FAN12 |
|
||||
PMBUS_HAVE_VIN | PMBUS_HAVE_IIN |
|
||||
PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
|
||||
PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_TEMP |
|
||||
PMBUS_HAVE_VCAP,
|
||||
|
||||
/* Page 1: Vsb. */
|
||||
.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||
PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT |
|
||||
PMBUS_HAVE_POUT,
|
||||
|
||||
/*
|
||||
* Page 2: V1 Ishare.
|
||||
* Page 3: Reserved.
|
||||
* Page 4: V1 Cathode.
|
||||
* Page 5: Vsb Cathode.
|
||||
* Page 6: V1 Sense.
|
||||
*/
|
||||
.func[2] = PMBUS_HAVE_VOUT,
|
||||
.func[4] = PMBUS_HAVE_VOUT,
|
||||
.func[5] = PMBUS_HAVE_VOUT,
|
||||
.func[6] = PMBUS_HAVE_VOUT,
|
||||
},
|
||||
};
|
||||
|
||||
static int pfe_pmbus_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int model;
|
||||
|
||||
model = (int)id->driver_data;
|
||||
|
||||
/*
|
||||
* PFE3000-12-069RA devices may not stay in page 0 during device
|
||||
* probe which leads to probe failure (read status word failed).
|
||||
* So let's set the device to page 0 at the beginning.
|
||||
*/
|
||||
if (model == pfe3000) {
|
||||
client->dev.platform_data = &pfe3000_plat_data;
|
||||
i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0);
|
||||
}
|
||||
|
||||
return pmbus_do_probe(client, id, &pfe_driver_info[model]);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id pfe_device_id[] = {
|
||||
{"pfe1100", pfe1100},
|
||||
{"pfe3000", pfe3000},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, pfe_device_id);
|
||||
|
||||
static struct i2c_driver pfe_pmbus_driver = {
|
||||
.driver = {
|
||||
.name = "bel-pfe",
|
||||
},
|
||||
.probe = pfe_pmbus_probe,
|
||||
.remove = pmbus_do_remove,
|
||||
.id_table = pfe_device_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(pfe_pmbus_driver);
|
||||
|
||||
MODULE_AUTHOR("Tao Ren <rentao.bupt@gmail.com>");
|
||||
MODULE_DESCRIPTION("PMBus driver for BEL PFE Family Power Supplies");
|
||||
MODULE_LICENSE("GPL");
|
@ -3,6 +3,7 @@
|
||||
* Copyright 2017 IBM Corp.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/device.h>
|
||||
@ -29,6 +30,10 @@
|
||||
#define CFFPS_INPUT_HISTORY_CMD 0xD6
|
||||
#define CFFPS_INPUT_HISTORY_SIZE 100
|
||||
|
||||
#define CFFPS_CCIN_VERSION GENMASK(15, 8)
|
||||
#define CFFPS_CCIN_VERSION_1 0x2b
|
||||
#define CFFPS_CCIN_VERSION_2 0x2e
|
||||
|
||||
/* STATUS_MFR_SPECIFIC bits */
|
||||
#define CFFPS_MFR_FAN_FAULT BIT(0)
|
||||
#define CFFPS_MFR_THERMAL_FAULT BIT(1)
|
||||
@ -39,9 +44,13 @@
|
||||
#define CFFPS_MFR_VAUX_FAULT BIT(6)
|
||||
#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7)
|
||||
|
||||
/*
|
||||
* LED off state actually relinquishes LED control to PSU firmware, so it can
|
||||
* turn on the LED for faults.
|
||||
*/
|
||||
#define CFFPS_LED_OFF 0
|
||||
#define CFFPS_LED_BLINK BIT(0)
|
||||
#define CFFPS_LED_ON BIT(1)
|
||||
#define CFFPS_LED_OFF BIT(2)
|
||||
#define CFFPS_BLINK_RATE_MS 250
|
||||
|
||||
enum {
|
||||
@ -54,7 +63,7 @@ enum {
|
||||
CFFPS_DEBUGFS_NUM_ENTRIES
|
||||
};
|
||||
|
||||
enum versions { cffps1, cffps2 };
|
||||
enum versions { cffps1, cffps2, cffps_unknown };
|
||||
|
||||
struct ibm_cffps_input_history {
|
||||
struct mutex update_lock;
|
||||
@ -292,28 +301,38 @@ static int ibm_cffps_read_word_data(struct i2c_client *client, int page,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void ibm_cffps_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
static int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
int rc;
|
||||
u8 next_led_state;
|
||||
struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
|
||||
|
||||
if (brightness == LED_OFF) {
|
||||
psu->led_state = CFFPS_LED_OFF;
|
||||
next_led_state = CFFPS_LED_OFF;
|
||||
} else {
|
||||
brightness = LED_FULL;
|
||||
|
||||
if (psu->led_state != CFFPS_LED_BLINK)
|
||||
psu->led_state = CFFPS_LED_ON;
|
||||
next_led_state = CFFPS_LED_ON;
|
||||
else
|
||||
next_led_state = CFFPS_LED_BLINK;
|
||||
}
|
||||
|
||||
dev_dbg(&psu->client->dev, "LED brightness set: %d. Command: %d.\n",
|
||||
brightness, next_led_state);
|
||||
|
||||
pmbus_set_page(psu->client, 0);
|
||||
|
||||
rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD,
|
||||
psu->led_state);
|
||||
next_led_state);
|
||||
if (rc < 0)
|
||||
return;
|
||||
return rc;
|
||||
|
||||
psu->led_state = next_led_state;
|
||||
led_cdev->brightness = brightness;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev,
|
||||
@ -323,10 +342,7 @@ static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev,
|
||||
int rc;
|
||||
struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
|
||||
|
||||
psu->led_state = CFFPS_LED_BLINK;
|
||||
|
||||
if (led_cdev->brightness == LED_OFF)
|
||||
return 0;
|
||||
dev_dbg(&psu->client->dev, "LED blink set.\n");
|
||||
|
||||
pmbus_set_page(psu->client, 0);
|
||||
|
||||
@ -335,6 +351,8 @@ static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev,
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
psu->led_state = CFFPS_LED_BLINK;
|
||||
led_cdev->brightness = LED_FULL;
|
||||
*delay_on = CFFPS_BLINK_RATE_MS;
|
||||
*delay_off = CFFPS_BLINK_RATE_MS;
|
||||
|
||||
@ -351,7 +369,7 @@ static void ibm_cffps_create_led_class(struct ibm_cffps *psu)
|
||||
client->addr);
|
||||
psu->led.name = psu->led_name;
|
||||
psu->led.max_brightness = LED_FULL;
|
||||
psu->led.brightness_set = ibm_cffps_led_brightness_set;
|
||||
psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set;
|
||||
psu->led.blink_set = ibm_cffps_led_blink_set;
|
||||
|
||||
rc = devm_led_classdev_register(dev, &psu->led);
|
||||
@ -395,7 +413,7 @@ static int ibm_cffps_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int i, rc;
|
||||
enum versions vs;
|
||||
enum versions vs = cffps_unknown;
|
||||
struct dentry *debugfs;
|
||||
struct dentry *ibm_cffps_dir;
|
||||
struct ibm_cffps *psu;
|
||||
@ -405,8 +423,27 @@ static int ibm_cffps_probe(struct i2c_client *client,
|
||||
vs = (enum versions)md;
|
||||
else if (id)
|
||||
vs = (enum versions)id->driver_data;
|
||||
else
|
||||
vs = cffps1;
|
||||
|
||||
if (vs == cffps_unknown) {
|
||||
u16 ccin_version = CFFPS_CCIN_VERSION_1;
|
||||
int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD);
|
||||
|
||||
if (ccin > 0)
|
||||
ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin);
|
||||
|
||||
switch (ccin_version) {
|
||||
default:
|
||||
case CFFPS_CCIN_VERSION_1:
|
||||
vs = cffps1;
|
||||
break;
|
||||
case CFFPS_CCIN_VERSION_2:
|
||||
vs = cffps2;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set the client name to include the version number. */
|
||||
snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1);
|
||||
}
|
||||
|
||||
client->dev.platform_data = &ibm_cffps_pdata;
|
||||
rc = pmbus_do_probe(client, id, &ibm_cffps_info[vs]);
|
||||
@ -465,6 +502,7 @@ static int ibm_cffps_probe(struct i2c_client *client,
|
||||
static const struct i2c_device_id ibm_cffps_id[] = {
|
||||
{ "ibm_cffps1", cffps1 },
|
||||
{ "ibm_cffps2", cffps2 },
|
||||
{ "ibm_cffps", cffps_unknown },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, ibm_cffps_id);
|
||||
@ -478,6 +516,10 @@ static const struct of_device_id ibm_cffps_of_match[] = {
|
||||
.compatible = "ibm,cffps2",
|
||||
.data = (void *)cffps2
|
||||
},
|
||||
{
|
||||
.compatible = "ibm,cffps",
|
||||
.data = (void *)cffps_unknown
|
||||
},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, ibm_cffps_of_match);
|
||||
|
@ -127,7 +127,8 @@ static struct tmp421_data *tmp421_update_device(struct device *dev)
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) {
|
||||
if (time_after(jiffies, data->last_updated + (HZ / 2)) ||
|
||||
!data->valid) {
|
||||
data->config = i2c_smbus_read_byte_data(client,
|
||||
TMP421_CONFIG_REG_1);
|
||||
|
||||
|
772
drivers/hwmon/tmp513.c
Normal file
772
drivers/hwmon/tmp513.c
Normal file
@ -0,0 +1,772 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for Texas Instruments TMP512, TMP513 power monitor chips
|
||||
*
|
||||
* TMP513:
|
||||
* Thermal/Power Management with Triple Remote and
|
||||
* Local Temperature Sensor and Current Shunt Monitor
|
||||
* Datasheet: http://www.ti.com/lit/gpn/tmp513
|
||||
*
|
||||
* TMP512:
|
||||
* Thermal/Power Management with Dual Remote
|
||||
* and Local Temperature Sensor and Current Shunt Monitor
|
||||
* Datasheet: http://www.ti.com/lit/gpn/tmp512
|
||||
*
|
||||
* Copyright (C) 2019 Eric Tremblay <etremblay@distech-controls.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; version 2 of the License.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/util_macros.h>
|
||||
|
||||
// Common register definition
|
||||
#define TMP51X_SHUNT_CONFIG 0x00
|
||||
#define TMP51X_TEMP_CONFIG 0x01
|
||||
#define TMP51X_STATUS 0x02
|
||||
#define TMP51X_SMBUS_ALERT 0x03
|
||||
#define TMP51X_SHUNT_CURRENT_RESULT 0x04
|
||||
#define TMP51X_BUS_VOLTAGE_RESULT 0x05
|
||||
#define TMP51X_POWER_RESULT 0x06
|
||||
#define TMP51X_BUS_CURRENT_RESULT 0x07
|
||||
#define TMP51X_LOCAL_TEMP_RESULT 0x08
|
||||
#define TMP51X_REMOTE_TEMP_RESULT_1 0x09
|
||||
#define TMP51X_REMOTE_TEMP_RESULT_2 0x0A
|
||||
#define TMP51X_SHUNT_CURRENT_H_LIMIT 0x0C
|
||||
#define TMP51X_SHUNT_CURRENT_L_LIMIT 0x0D
|
||||
#define TMP51X_BUS_VOLTAGE_H_LIMIT 0x0E
|
||||
#define TMP51X_BUS_VOLTAGE_L_LIMIT 0x0F
|
||||
#define TMP51X_POWER_LIMIT 0x10
|
||||
#define TMP51X_LOCAL_TEMP_LIMIT 0x11
|
||||
#define TMP51X_REMOTE_TEMP_LIMIT_1 0x12
|
||||
#define TMP51X_REMOTE_TEMP_LIMIT_2 0x13
|
||||
#define TMP51X_SHUNT_CALIBRATION 0x15
|
||||
#define TMP51X_N_FACTOR_AND_HYST_1 0x16
|
||||
#define TMP51X_N_FACTOR_2 0x17
|
||||
#define TMP51X_MAN_ID_REG 0xFE
|
||||
#define TMP51X_DEVICE_ID_REG 0xFF
|
||||
|
||||
// TMP513 specific register definition
|
||||
#define TMP513_REMOTE_TEMP_RESULT_3 0x0B
|
||||
#define TMP513_REMOTE_TEMP_LIMIT_3 0x14
|
||||
#define TMP513_N_FACTOR_3 0x18
|
||||
|
||||
// Common attrs, and NULL
|
||||
#define TMP51X_MANUFACTURER_ID 0x55FF
|
||||
|
||||
#define TMP512_DEVICE_ID 0x22FF
|
||||
#define TMP513_DEVICE_ID 0x23FF
|
||||
|
||||
// Default config
|
||||
#define TMP51X_SHUNT_CONFIG_DEFAULT 0x399F
|
||||
#define TMP51X_SHUNT_VALUE_DEFAULT 1000
|
||||
#define TMP51X_VBUS_RANGE_DEFAULT TMP51X_VBUS_RANGE_32V
|
||||
#define TMP51X_PGA_DEFAULT 8
|
||||
#define TMP51X_MAX_REGISTER_ADDR 0xFF
|
||||
|
||||
#define TMP512_TEMP_CONFIG_DEFAULT 0xBF80
|
||||
#define TMP513_TEMP_CONFIG_DEFAULT 0xFF80
|
||||
|
||||
// Mask and shift
|
||||
#define CURRENT_SENSE_VOLTAGE_320_MASK 0x1800
|
||||
#define CURRENT_SENSE_VOLTAGE_160_MASK 0x1000
|
||||
#define CURRENT_SENSE_VOLTAGE_80_MASK 0x0800
|
||||
#define CURRENT_SENSE_VOLTAGE_40_MASK 0
|
||||
|
||||
#define TMP51X_BUS_VOLTAGE_MASK 0x2000
|
||||
#define TMP51X_NFACTOR_MASK 0xFF00
|
||||
#define TMP51X_HYST_MASK 0x00FF
|
||||
|
||||
#define TMP51X_BUS_VOLTAGE_SHIFT 3
|
||||
#define TMP51X_TEMP_SHIFT 3
|
||||
|
||||
// Alarms
|
||||
#define TMP51X_SHUNT_CURRENT_H_LIMIT_POS 15
|
||||
#define TMP51X_SHUNT_CURRENT_L_LIMIT_POS 14
|
||||
#define TMP51X_BUS_VOLTAGE_H_LIMIT_POS 13
|
||||
#define TMP51X_BUS_VOLTAGE_L_LIMIT_POS 12
|
||||
#define TMP51X_POWER_LIMIT_POS 11
|
||||
#define TMP51X_LOCAL_TEMP_LIMIT_POS 10
|
||||
#define TMP51X_REMOTE_TEMP_LIMIT_1_POS 9
|
||||
#define TMP51X_REMOTE_TEMP_LIMIT_2_POS 8
|
||||
#define TMP513_REMOTE_TEMP_LIMIT_3_POS 7
|
||||
|
||||
#define TMP51X_VBUS_RANGE_32V 32000000
|
||||
#define TMP51X_VBUS_RANGE_16V 16000000
|
||||
|
||||
// Max and Min value
|
||||
#define MAX_BUS_VOLTAGE_32_LIMIT 32764
|
||||
#define MAX_BUS_VOLTAGE_16_LIMIT 16382
|
||||
|
||||
// Max possible value is -256 to +256 but datasheet indicated -40 to 125.
|
||||
#define MAX_TEMP_LIMIT 125000
|
||||
#define MIN_TEMP_LIMIT -40000
|
||||
|
||||
#define MAX_TEMP_HYST 127500
|
||||
|
||||
static const u8 TMP51X_TEMP_INPUT[4] = {
|
||||
TMP51X_LOCAL_TEMP_RESULT,
|
||||
TMP51X_REMOTE_TEMP_RESULT_1,
|
||||
TMP51X_REMOTE_TEMP_RESULT_2,
|
||||
TMP513_REMOTE_TEMP_RESULT_3
|
||||
};
|
||||
|
||||
static const u8 TMP51X_TEMP_CRIT[4] = {
|
||||
TMP51X_LOCAL_TEMP_LIMIT,
|
||||
TMP51X_REMOTE_TEMP_LIMIT_1,
|
||||
TMP51X_REMOTE_TEMP_LIMIT_2,
|
||||
TMP513_REMOTE_TEMP_LIMIT_3
|
||||
};
|
||||
|
||||
static const u8 TMP51X_TEMP_CRIT_ALARM[4] = {
|
||||
TMP51X_LOCAL_TEMP_LIMIT_POS,
|
||||
TMP51X_REMOTE_TEMP_LIMIT_1_POS,
|
||||
TMP51X_REMOTE_TEMP_LIMIT_2_POS,
|
||||
TMP513_REMOTE_TEMP_LIMIT_3_POS
|
||||
};
|
||||
|
||||
static const u8 TMP51X_TEMP_CRIT_HYST[4] = {
|
||||
TMP51X_N_FACTOR_AND_HYST_1,
|
||||
TMP51X_N_FACTOR_AND_HYST_1,
|
||||
TMP51X_N_FACTOR_AND_HYST_1,
|
||||
TMP51X_N_FACTOR_AND_HYST_1
|
||||
};
|
||||
|
||||
static const u8 TMP51X_CURR_INPUT[2] = {
|
||||
TMP51X_SHUNT_CURRENT_RESULT,
|
||||
TMP51X_BUS_CURRENT_RESULT
|
||||
};
|
||||
|
||||
static struct regmap_config tmp51x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
.max_register = TMP51X_MAX_REGISTER_ADDR,
|
||||
};
|
||||
|
||||
enum tmp51x_ids {
|
||||
tmp512, tmp513
|
||||
};
|
||||
|
||||
struct tmp51x_data {
|
||||
u16 shunt_config;
|
||||
u16 pga_gain;
|
||||
u32 vbus_range_uvolt;
|
||||
|
||||
u16 temp_config;
|
||||
u32 nfactor[3];
|
||||
|
||||
u32 shunt_uohms;
|
||||
|
||||
u32 curr_lsb_ua;
|
||||
u32 pwr_lsb_uw;
|
||||
|
||||
enum tmp51x_ids id;
|
||||
struct regmap *regmap;
|
||||
};
|
||||
|
||||
// Set the shift based on the gain 8=4, 4=3, 2=2, 1=1
|
||||
static inline u8 tmp51x_get_pga_shift(struct tmp51x_data *data)
|
||||
{
|
||||
return 5 - ffs(data->pga_gain);
|
||||
}
|
||||
|
||||
static int tmp51x_get_value(struct tmp51x_data *data, u8 reg, u8 pos,
|
||||
unsigned int regval, long *val)
|
||||
{
|
||||
switch (reg) {
|
||||
case TMP51X_STATUS:
|
||||
*val = (regval >> pos) & 1;
|
||||
break;
|
||||
case TMP51X_SHUNT_CURRENT_RESULT:
|
||||
case TMP51X_SHUNT_CURRENT_H_LIMIT:
|
||||
case TMP51X_SHUNT_CURRENT_L_LIMIT:
|
||||
/*
|
||||
* The valus is read in voltage in the chip but reported as
|
||||
* current to the user.
|
||||
* 2's compliment number shifted by one to four depending
|
||||
* on the pga gain setting. 1lsb = 10uV
|
||||
*/
|
||||
*val = sign_extend32(regval, 17 - tmp51x_get_pga_shift(data));
|
||||
*val = DIV_ROUND_CLOSEST(*val * 10000, data->shunt_uohms);
|
||||
break;
|
||||
case TMP51X_BUS_VOLTAGE_RESULT:
|
||||
case TMP51X_BUS_VOLTAGE_H_LIMIT:
|
||||
case TMP51X_BUS_VOLTAGE_L_LIMIT:
|
||||
// 1lsb = 4mV
|
||||
*val = (regval >> TMP51X_BUS_VOLTAGE_SHIFT) * 4;
|
||||
break;
|
||||
case TMP51X_POWER_RESULT:
|
||||
case TMP51X_POWER_LIMIT:
|
||||
// Power = (current * BusVoltage) / 5000
|
||||
*val = regval * data->pwr_lsb_uw;
|
||||
break;
|
||||
case TMP51X_BUS_CURRENT_RESULT:
|
||||
// Current = (ShuntVoltage * CalibrationRegister) / 4096
|
||||
*val = sign_extend32(regval, 16) * data->curr_lsb_ua;
|
||||
*val = DIV_ROUND_CLOSEST(*val, 1000);
|
||||
break;
|
||||
case TMP51X_LOCAL_TEMP_RESULT:
|
||||
case TMP51X_REMOTE_TEMP_RESULT_1:
|
||||
case TMP51X_REMOTE_TEMP_RESULT_2:
|
||||
case TMP513_REMOTE_TEMP_RESULT_3:
|
||||
case TMP51X_LOCAL_TEMP_LIMIT:
|
||||
case TMP51X_REMOTE_TEMP_LIMIT_1:
|
||||
case TMP51X_REMOTE_TEMP_LIMIT_2:
|
||||
case TMP513_REMOTE_TEMP_LIMIT_3:
|
||||
// 1lsb = 0.0625 degrees centigrade
|
||||
*val = sign_extend32(regval, 16) >> TMP51X_TEMP_SHIFT;
|
||||
*val = DIV_ROUND_CLOSEST(*val * 625, 10);
|
||||
break;
|
||||
case TMP51X_N_FACTOR_AND_HYST_1:
|
||||
// 1lsb = 0.5 degrees centigrade
|
||||
*val = (regval & TMP51X_HYST_MASK) * 500;
|
||||
break;
|
||||
default:
|
||||
// Programmer goofed
|
||||
WARN_ON_ONCE(1);
|
||||
*val = 0;
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp51x_set_value(struct tmp51x_data *data, u8 reg, long val)
|
||||
{
|
||||
int regval, max_val;
|
||||
u32 mask = 0;
|
||||
|
||||
switch (reg) {
|
||||
case TMP51X_SHUNT_CURRENT_H_LIMIT:
|
||||
case TMP51X_SHUNT_CURRENT_L_LIMIT:
|
||||
/*
|
||||
* The user enter current value and we convert it to
|
||||
* voltage. 1lsb = 10uV
|
||||
*/
|
||||
val = DIV_ROUND_CLOSEST(val * data->shunt_uohms, 10000);
|
||||
max_val = U16_MAX >> tmp51x_get_pga_shift(data);
|
||||
regval = clamp_val(val, -max_val, max_val);
|
||||
break;
|
||||
case TMP51X_BUS_VOLTAGE_H_LIMIT:
|
||||
case TMP51X_BUS_VOLTAGE_L_LIMIT:
|
||||
// 1lsb = 4mV
|
||||
max_val = (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_32V) ?
|
||||
MAX_BUS_VOLTAGE_32_LIMIT : MAX_BUS_VOLTAGE_16_LIMIT;
|
||||
|
||||
val = clamp_val(DIV_ROUND_CLOSEST(val, 4), 0, max_val);
|
||||
regval = val << TMP51X_BUS_VOLTAGE_SHIFT;
|
||||
break;
|
||||
case TMP51X_POWER_LIMIT:
|
||||
regval = clamp_val(DIV_ROUND_CLOSEST(val, data->pwr_lsb_uw), 0,
|
||||
U16_MAX);
|
||||
break;
|
||||
case TMP51X_LOCAL_TEMP_LIMIT:
|
||||
case TMP51X_REMOTE_TEMP_LIMIT_1:
|
||||
case TMP51X_REMOTE_TEMP_LIMIT_2:
|
||||
case TMP513_REMOTE_TEMP_LIMIT_3:
|
||||
// 1lsb = 0.0625 degrees centigrade
|
||||
val = clamp_val(val, MIN_TEMP_LIMIT, MAX_TEMP_LIMIT);
|
||||
regval = DIV_ROUND_CLOSEST(val * 10, 625) << TMP51X_TEMP_SHIFT;
|
||||
break;
|
||||
case TMP51X_N_FACTOR_AND_HYST_1:
|
||||
// 1lsb = 0.5 degrees centigrade
|
||||
val = clamp_val(val, 0, MAX_TEMP_HYST);
|
||||
regval = DIV_ROUND_CLOSEST(val, 500);
|
||||
mask = TMP51X_HYST_MASK;
|
||||
break;
|
||||
default:
|
||||
// Programmer goofed
|
||||
WARN_ON_ONCE(1);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
if (mask == 0)
|
||||
return regmap_write(data->regmap, reg, regval);
|
||||
else
|
||||
return regmap_update_bits(data->regmap, reg, mask, regval);
|
||||
}
|
||||
|
||||
static u8 tmp51x_get_reg(enum hwmon_sensor_types type, u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
return TMP51X_TEMP_INPUT[channel];
|
||||
case hwmon_temp_crit_alarm:
|
||||
return TMP51X_STATUS;
|
||||
case hwmon_temp_crit:
|
||||
return TMP51X_TEMP_CRIT[channel];
|
||||
case hwmon_temp_crit_hyst:
|
||||
return TMP51X_TEMP_CRIT_HYST[channel];
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
return TMP51X_BUS_VOLTAGE_RESULT;
|
||||
case hwmon_in_lcrit_alarm:
|
||||
case hwmon_in_crit_alarm:
|
||||
return TMP51X_STATUS;
|
||||
case hwmon_in_lcrit:
|
||||
return TMP51X_BUS_VOLTAGE_L_LIMIT;
|
||||
case hwmon_in_crit:
|
||||
return TMP51X_BUS_VOLTAGE_H_LIMIT;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
return TMP51X_CURR_INPUT[channel];
|
||||
case hwmon_curr_lcrit_alarm:
|
||||
case hwmon_curr_crit_alarm:
|
||||
return TMP51X_STATUS;
|
||||
case hwmon_curr_lcrit:
|
||||
return TMP51X_SHUNT_CURRENT_L_LIMIT;
|
||||
case hwmon_curr_crit:
|
||||
return TMP51X_SHUNT_CURRENT_H_LIMIT;
|
||||
}
|
||||
break;
|
||||
case hwmon_power:
|
||||
switch (attr) {
|
||||
case hwmon_power_input:
|
||||
return TMP51X_POWER_RESULT;
|
||||
case hwmon_power_crit_alarm:
|
||||
return TMP51X_STATUS;
|
||||
case hwmon_power_crit:
|
||||
return TMP51X_POWER_LIMIT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u8 tmp51x_get_status_pos(enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_crit_alarm:
|
||||
return TMP51X_TEMP_CRIT_ALARM[channel];
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_lcrit_alarm:
|
||||
return TMP51X_BUS_VOLTAGE_L_LIMIT_POS;
|
||||
case hwmon_in_crit_alarm:
|
||||
return TMP51X_BUS_VOLTAGE_H_LIMIT_POS;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_lcrit_alarm:
|
||||
return TMP51X_SHUNT_CURRENT_L_LIMIT_POS;
|
||||
case hwmon_curr_crit_alarm:
|
||||
return TMP51X_SHUNT_CURRENT_H_LIMIT_POS;
|
||||
}
|
||||
break;
|
||||
case hwmon_power:
|
||||
switch (attr) {
|
||||
case hwmon_power_crit_alarm:
|
||||
return TMP51X_POWER_LIMIT_POS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp51x_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct tmp51x_data *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
u32 regval;
|
||||
u8 pos = 0, reg = 0;
|
||||
|
||||
reg = tmp51x_get_reg(type, attr, channel);
|
||||
if (reg == 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (reg == TMP51X_STATUS)
|
||||
pos = tmp51x_get_status_pos(type, attr, channel);
|
||||
|
||||
ret = regmap_read(data->regmap, reg, ®val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return tmp51x_get_value(data, reg, pos, regval, val);
|
||||
}
|
||||
|
||||
static int tmp51x_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
u8 reg = 0;
|
||||
|
||||
reg = tmp51x_get_reg(type, attr, channel);
|
||||
if (reg == 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return tmp51x_set_value(dev_get_drvdata(dev), reg, val);
|
||||
}
|
||||
|
||||
static umode_t tmp51x_is_visible(const void *_data,
|
||||
enum hwmon_sensor_types type, u32 attr,
|
||||
int channel)
|
||||
{
|
||||
const struct tmp51x_data *data = _data;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
if (data->id == tmp512 && channel == 4)
|
||||
return 0;
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_crit_alarm:
|
||||
return 0444;
|
||||
case hwmon_temp_crit:
|
||||
return 0644;
|
||||
case hwmon_temp_crit_hyst:
|
||||
if (channel == 0)
|
||||
return 0644;
|
||||
return 0444;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_lcrit_alarm:
|
||||
case hwmon_in_crit_alarm:
|
||||
return 0444;
|
||||
case hwmon_in_lcrit:
|
||||
case hwmon_in_crit:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
if (!data->shunt_uohms)
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
case hwmon_curr_lcrit_alarm:
|
||||
case hwmon_curr_crit_alarm:
|
||||
return 0444;
|
||||
case hwmon_curr_lcrit:
|
||||
case hwmon_curr_crit:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
case hwmon_power:
|
||||
if (!data->shunt_uohms)
|
||||
return 0;
|
||||
|
||||
switch (attr) {
|
||||
case hwmon_power_input:
|
||||
case hwmon_power_crit_alarm:
|
||||
return 0444;
|
||||
case hwmon_power_crit:
|
||||
return 0644;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *tmp51x_info[] = {
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
|
||||
HWMON_T_CRIT_HYST,
|
||||
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
|
||||
HWMON_T_CRIT_HYST,
|
||||
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
|
||||
HWMON_T_CRIT_HYST,
|
||||
HWMON_T_INPUT | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
|
||||
HWMON_T_CRIT_HYST),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_LCRIT | HWMON_I_LCRIT_ALARM |
|
||||
HWMON_I_CRIT | HWMON_I_CRIT_ALARM),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_LCRIT | HWMON_C_LCRIT_ALARM |
|
||||
HWMON_C_CRIT | HWMON_C_CRIT_ALARM,
|
||||
HWMON_C_INPUT),
|
||||
HWMON_CHANNEL_INFO(power,
|
||||
HWMON_P_INPUT | HWMON_P_CRIT | HWMON_P_CRIT_ALARM),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops tmp51x_hwmon_ops = {
|
||||
.is_visible = tmp51x_is_visible,
|
||||
.read = tmp51x_read,
|
||||
.write = tmp51x_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info tmp51x_chip_info = {
|
||||
.ops = &tmp51x_hwmon_ops,
|
||||
.info = tmp51x_info,
|
||||
};
|
||||
|
||||
/*
|
||||
* Calibrate the tmp51x following the datasheet method
|
||||
*/
|
||||
static int tmp51x_calibrate(struct tmp51x_data *data)
|
||||
{
|
||||
int vshunt_max = data->pga_gain * 40;
|
||||
u64 max_curr_ma;
|
||||
u32 div;
|
||||
|
||||
/*
|
||||
* If shunt_uohms is equal to 0, the calibration should be set to 0.
|
||||
* The consequence will be that the current and power measurement engine
|
||||
* of the sensor will not work. Temperature and voltage sensing will
|
||||
* continue to work.
|
||||
*/
|
||||
if (data->shunt_uohms == 0)
|
||||
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION, 0);
|
||||
|
||||
max_curr_ma = DIV_ROUND_CLOSEST_ULL(vshunt_max * 1000 * 1000,
|
||||
data->shunt_uohms);
|
||||
|
||||
/*
|
||||
* Calculate the minimal bit resolution for the current and the power.
|
||||
* Those values will be used during register interpretation.
|
||||
*/
|
||||
data->curr_lsb_ua = DIV_ROUND_CLOSEST_ULL(max_curr_ma * 1000, 32767);
|
||||
data->pwr_lsb_uw = 20 * data->curr_lsb_ua;
|
||||
|
||||
div = DIV_ROUND_CLOSEST_ULL(data->curr_lsb_ua * data->shunt_uohms,
|
||||
1000 * 1000);
|
||||
|
||||
return regmap_write(data->regmap, TMP51X_SHUNT_CALIBRATION,
|
||||
DIV_ROUND_CLOSEST(40960, div));
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the configuration and calibration registers.
|
||||
*/
|
||||
static int tmp51x_init(struct tmp51x_data *data)
|
||||
{
|
||||
unsigned int regval;
|
||||
int ret = regmap_write(data->regmap, TMP51X_SHUNT_CONFIG,
|
||||
data->shunt_config);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, TMP51X_TEMP_CONFIG, data->temp_config);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
// nFactor configuration
|
||||
ret = regmap_update_bits(data->regmap, TMP51X_N_FACTOR_AND_HYST_1,
|
||||
TMP51X_NFACTOR_MASK, data->nfactor[0] << 8);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = regmap_write(data->regmap, TMP51X_N_FACTOR_2,
|
||||
data->nfactor[1] << 8);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (data->id == tmp513) {
|
||||
ret = regmap_write(data->regmap, TMP513_N_FACTOR_3,
|
||||
data->nfactor[2] << 8);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = tmp51x_calibrate(data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
// Read the status register before using as the datasheet propose
|
||||
return regmap_read(data->regmap, TMP51X_STATUS, ®val);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id tmp51x_id[] = {
|
||||
{ "tmp512", tmp512 },
|
||||
{ "tmp513", tmp513 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, tmp51x_id);
|
||||
|
||||
static const struct of_device_id tmp51x_of_match[] = {
|
||||
{
|
||||
.compatible = "ti,tmp512",
|
||||
.data = (void *)tmp512
|
||||
},
|
||||
{
|
||||
.compatible = "ti,tmp513",
|
||||
.data = (void *)tmp513
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tmp51x_of_match);
|
||||
|
||||
static int tmp51x_vbus_range_to_reg(struct device *dev,
|
||||
struct tmp51x_data *data)
|
||||
{
|
||||
if (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_32V) {
|
||||
data->shunt_config |= TMP51X_BUS_VOLTAGE_MASK;
|
||||
} else if (data->vbus_range_uvolt == TMP51X_VBUS_RANGE_16V) {
|
||||
data->shunt_config &= ~TMP51X_BUS_VOLTAGE_MASK;
|
||||
} else {
|
||||
dev_err(dev, "ti,bus-range-microvolt is invalid: %u\n",
|
||||
data->vbus_range_uvolt);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp51x_pga_gain_to_reg(struct device *dev, struct tmp51x_data *data)
|
||||
{
|
||||
if (data->pga_gain == 8) {
|
||||
data->shunt_config |= CURRENT_SENSE_VOLTAGE_320_MASK;
|
||||
} else if (data->pga_gain == 4) {
|
||||
data->shunt_config |= CURRENT_SENSE_VOLTAGE_160_MASK;
|
||||
} else if (data->pga_gain == 2) {
|
||||
data->shunt_config |= CURRENT_SENSE_VOLTAGE_80_MASK;
|
||||
} else if (data->pga_gain == 1) {
|
||||
data->shunt_config |= CURRENT_SENSE_VOLTAGE_40_MASK;
|
||||
} else {
|
||||
dev_err(dev, "ti,pga-gain is invalid: %u\n", data->pga_gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp51x_read_properties(struct device *dev, struct tmp51x_data *data)
|
||||
{
|
||||
int ret;
|
||||
u32 nfactor[3];
|
||||
u32 val;
|
||||
|
||||
ret = device_property_read_u32(dev, "shunt-resistor-micro-ohms", &val);
|
||||
data->shunt_uohms = (ret >= 0) ? val : TMP51X_SHUNT_VALUE_DEFAULT;
|
||||
|
||||
ret = device_property_read_u32(dev, "ti,bus-range-microvolt", &val);
|
||||
data->vbus_range_uvolt = (ret >= 0) ? val : TMP51X_VBUS_RANGE_DEFAULT;
|
||||
ret = tmp51x_vbus_range_to_reg(dev, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = device_property_read_u32(dev, "ti,pga-gain", &val);
|
||||
data->pga_gain = (ret >= 0) ? val : TMP51X_PGA_DEFAULT;
|
||||
ret = tmp51x_pga_gain_to_reg(dev, data);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = device_property_read_u32_array(dev, "ti,nfactor", nfactor,
|
||||
(data->id == tmp513) ? 3 : 2);
|
||||
if (ret >= 0)
|
||||
memcpy(data->nfactor, nfactor, (data->id == tmp513) ? 3 : 2);
|
||||
|
||||
// Check if shunt value is compatible with pga-gain
|
||||
if (data->shunt_uohms > data->pga_gain * 40 * 1000 * 1000) {
|
||||
dev_err(dev, "shunt-resistor: %u too big for pga_gain: %u\n",
|
||||
data->shunt_uohms, data->pga_gain);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tmp51x_use_default(struct tmp51x_data *data)
|
||||
{
|
||||
data->vbus_range_uvolt = TMP51X_VBUS_RANGE_DEFAULT;
|
||||
data->pga_gain = TMP51X_PGA_DEFAULT;
|
||||
data->shunt_uohms = TMP51X_SHUNT_VALUE_DEFAULT;
|
||||
}
|
||||
|
||||
static int tmp51x_configure(struct device *dev, struct tmp51x_data *data)
|
||||
{
|
||||
data->shunt_config = TMP51X_SHUNT_CONFIG_DEFAULT;
|
||||
data->temp_config = (data->id == tmp513) ?
|
||||
TMP513_TEMP_CONFIG_DEFAULT : TMP512_TEMP_CONFIG_DEFAULT;
|
||||
|
||||
if (dev->of_node)
|
||||
return tmp51x_read_properties(dev, data);
|
||||
|
||||
tmp51x_use_default(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tmp51x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct tmp51x_data *data;
|
||||
struct device *hwmon_dev;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (client->dev.of_node)
|
||||
data->id = (enum tmp51x_ids)device_get_match_data(&client->dev);
|
||||
else
|
||||
data->id = id->driver_data;
|
||||
|
||||
ret = tmp51x_configure(dev, data);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &tmp51x_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
ret = tmp51x_init(data);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "error configuring the device: %d\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
data,
|
||||
&tmp51x_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
dev_dbg(dev, "power monitor %s\n", id->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver tmp51x_driver = {
|
||||
.driver = {
|
||||
.name = "tmp51x",
|
||||
.of_match_table = of_match_ptr(tmp51x_of_match),
|
||||
},
|
||||
.probe = tmp51x_probe,
|
||||
.id_table = tmp51x_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(tmp51x_driver);
|
||||
|
||||
MODULE_AUTHOR("Eric Tremblay <etremblay@distechcontrols.com>");
|
||||
MODULE_DESCRIPTION("tmp51x driver");
|
||||
MODULE_LICENSE("GPL");
|
@ -2096,7 +2096,7 @@ END:
|
||||
static u8 w83793_read_value(struct i2c_client *client, u16 reg)
|
||||
{
|
||||
struct w83793_data *data = i2c_get_clientdata(client);
|
||||
u8 res = 0xff;
|
||||
u8 res;
|
||||
u8 new_bank = reg >> 8;
|
||||
|
||||
new_bank |= data->bank & 0xfc;
|
||||
|
Loading…
Reference in New Issue
Block a user