2
0
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:
Linus Torvalds 2019-11-27 09:58:49 -08:00
commit 3d9e3501a0
28 changed files with 3288 additions and 78 deletions

View 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>;
};
};
...

View File

@ -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.

View 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>;
};
};

View 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.

View 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.

View File

@ -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:

View File

@ -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

View 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)
======================= ==========================================

View 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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 };

View File

@ -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);
}

View File

@ -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);

View File

@ -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(&regs) ? : (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(&regs);
}
/*
* 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

View File

@ -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, &regval);
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

File diff suppressed because it is too large Load Diff

View 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, &ltc2947_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 = &ltc2947_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");

View 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, &ltc2947_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 = &ltc2947_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
View 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

View File

@ -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

View File

@ -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

View 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");

View File

@ -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);

View File

@ -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
View 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, &regval);
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, &regval);
}
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");

View File

@ -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;