mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-22 20:23:57 +08:00
hwmon updates for v5.8
Infrastructure - Add notification support New drivers - Baikal-T1 PVT sensor driver - amd_energy driver to report energy counters - Driver for Maxim MAX16601 - Gateworks System Controller Various - applesmc: avoid overlong udelay() - dell-smm: Use one DMI match for all XPS models - ina2xx: Implement alert functions - lm70: Add support for ACPI - lm75: Fix coding-style warnings - lm90: Add max6654 support to lm90 driver - nct7802: Replace container_of() API - nct7904: Set default timeout - nct7904: Add watchdog function - pmbus: Improve initialization of 'currpage' and 'currphase' -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEiHPvMQj9QTOCiqgVyx8mb86fmYEFAl7UYTAACgkQyx8mb86f mYHRexAAqqP6Wc0ntc3vJXD9wyjW3KDtZI7BLmhq82VCLoxIel7mhWUCOQvSINtS OtW5VsXWo1UeKZnc00sHf91cv3IO2ANBAdgP8RM3lMeUq2ZwTCfNB5G9tf6CsRjJ yagrJgLWrQYTJWiD9VkdUE2vYbsK4xuNv6sqJIdYhdlEMzdJbiK1E9gQIYUBdHEx EKHiopQbpxYG/t1hwhkPVfWDdCK1D5jjQ6YVYlC2Y4L3b//vcOrpHvqKuNPTCBSI EemAyOOGIJBsvDdTc697+6ilA9GxNuDjlnQb8WZdv8gODQP60Bq6YZvZ//jlJMY8 V6qEdTZJNqzist0MMKzuf2X0mgQ1ZavpBizMu1G90Ri60lhYfMdciYmGkI63WtL4 /RMdppLhuG5CQNEmyquM6ZalZMx1ja48jqB76GQeSNS5b2L+oT6iCowyA0LN9Vio FAhGTuQ8/f/5af6mapGKR6G9Ua8D6+KbeQmMUV+2puBfDA+FuP1A1y6fZ5RTT4kV qBiGdD5XQJid6uTGuof4Q49iCNzwQhugB/f2oO3Go84S8PRvsqMb2JLWFsXm9MtB 90PuV2vLca6WdNVG3QZSHKm2nhr/PRFu3DvU1Y6hDA9zmKFBhoaeVqW2L3JEEZzL uaNchz6Uni4rUNf9f+E+s4CosaeRnAsgXi/DUVS7wGhd1K48ylo= =QZKO -----END PGP SIGNATURE----- Merge tag 'hwmon-for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging Pull hwmon updates from Guenter Roeck: "Infrastructure: - Add notification support New drivers: - Baikal-T1 PVT sensor driver - amd_energy driver to report energy counters - Driver for Maxim MAX16601 - Gateworks System Controller Various: - applesmc: avoid overlong udelay() - dell-smm: Use one DMI match for all XPS models - ina2xx: Implement alert functions - lm70: Add support for ACPI - lm75: Fix coding-style warnings - lm90: Add max6654 support to lm90 driver - nct7802: Replace container_of() API - nct7904: Set default timeout - nct7904: Add watchdog function - pmbus: Improve initialization of 'currpage' and 'currphase'" * tag 'hwmon-for-v5.8' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (24 commits) hwmon: Add Baikal-T1 PVT sensor driver hwmon: Add notification support dt-bindings: hwmon: Add Baikal-T1 PVT sensor binding hwmon: (applesmc) avoid overlong udelay() hwmon: (nct7904) Set default timeout hwmon: (amd_energy) Missing platform_driver_unregister() on error in amd_energy_init() MAINTAINERS: add entry for AMD energy driver hwmon: (amd_energy) Add documentation hwmon: Add amd_energy driver to report energy counters hwmon: (nct7802) Replace container_of() API hwmon: (lm90) Add max6654 support to lm90 driver hwmon : (nct6775) Use kobj_to_dev() API hwmon: (pmbus) Driver for Maxim MAX16601 hwmon: (pmbus) Improve initialization of 'currpage' and 'currphase' hwmon: (adt7411) update contact email hwmon: (lm75) Fix all coding-style warnings on lm75 driver hwmon: Reduce indentation level in __hwmon_device_register() hwmon: (ina2xx) Implement alert functions hwmon: (lm70) Add support for ACPI hwmon: (dell-smm) Use one DMI match for all XPS models ...
This commit is contained in:
commit
129b9a5c40
107
Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml
Normal file
107
Documentation/devicetree/bindings/hwmon/baikal,bt1-pvt.yaml
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||||
|
# Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
|
||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
$id: http://devicetree.org/schemas/hwmon/baikal,bt1-pvt.yaml#
|
||||||
|
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||||
|
|
||||||
|
title: Baikal-T1 PVT Sensor
|
||||||
|
|
||||||
|
maintainers:
|
||||||
|
- Serge Semin <fancer.lancer@gmail.com>
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Baikal-T1 SoC provides an embedded process, voltage and temperature
|
||||||
|
sensor to monitor an internal SoC environment (chip temperature, supply
|
||||||
|
voltage and process monitor) and on time detect critical situations,
|
||||||
|
which may cause the system instability and even damages. The IP-block
|
||||||
|
is based on the Analog Bits PVT sensor, but is equipped with a dedicated
|
||||||
|
control wrapper, which provides a MMIO registers-based access to the
|
||||||
|
sensor core functionality (APB3-bus based) and exposes an additional
|
||||||
|
functions like thresholds/data ready interrupts, its status and masks,
|
||||||
|
measurements timeout. Its internal structure is depicted on the next
|
||||||
|
diagram:
|
||||||
|
|
||||||
|
Analog Bits core Bakal-T1 PVT control block
|
||||||
|
+--------------------+ +------------------------+
|
||||||
|
| Temperature sensor |-+ +------| Sensors control |
|
||||||
|
|--------------------| |<---En---| |------------------------|
|
||||||
|
| Voltage sensor |-|<--Mode--| +--->| Sampled data |
|
||||||
|
|--------------------| |<--Trim--+ | |------------------------|
|
||||||
|
| Low-Vt sensor |-| | +--| Thresholds comparator |
|
||||||
|
|--------------------| |---Data----| | |------------------------|
|
||||||
|
| High-Vt sensor |-| | +->| Interrupts status |
|
||||||
|
|--------------------| |--Valid--+-+ | |------------------------|
|
||||||
|
| Standard-Vt sensor |-+ +---+--| Interrupts mask |
|
||||||
|
+--------------------+ |------------------------|
|
||||||
|
^ | Interrupts timeout |
|
||||||
|
| +------------------------+
|
||||||
|
| ^ ^
|
||||||
|
Rclk-----+----------------------------------------+ |
|
||||||
|
APB3-------------------------------------------------+
|
||||||
|
|
||||||
|
This bindings describes the external Baikal-T1 PVT control interfaces
|
||||||
|
like MMIO registers space, interrupt request number and clocks source.
|
||||||
|
These are then used by the corresponding hwmon device driver to
|
||||||
|
implement the sysfs files-based access to the sensors functionality.
|
||||||
|
|
||||||
|
properties:
|
||||||
|
compatible:
|
||||||
|
const: baikal,bt1-pvt
|
||||||
|
|
||||||
|
reg:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
interrupts:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
clocks:
|
||||||
|
items:
|
||||||
|
- description: PVT reference clock
|
||||||
|
- description: APB3 interface clock
|
||||||
|
|
||||||
|
clock-names:
|
||||||
|
items:
|
||||||
|
- const: ref
|
||||||
|
- const: pclk
|
||||||
|
|
||||||
|
"#thermal-sensor-cells":
|
||||||
|
description: Baikal-T1 can be referenced as the CPU thermal-sensor
|
||||||
|
const: 0
|
||||||
|
|
||||||
|
baikal,pvt-temp-offset-millicelsius:
|
||||||
|
description: |
|
||||||
|
Temperature sensor trimming factor. It can be used to manually adjust the
|
||||||
|
temperature measurements within 7.130 degrees Celsius.
|
||||||
|
maxItems: 1
|
||||||
|
items:
|
||||||
|
default: 0
|
||||||
|
minimum: 0
|
||||||
|
maximum: 7130
|
||||||
|
|
||||||
|
unevaluatedProperties: false
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- reg
|
||||||
|
- interrupts
|
||||||
|
- clocks
|
||||||
|
- clock-names
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- |
|
||||||
|
#include <dt-bindings/interrupt-controller/mips-gic.h>
|
||||||
|
|
||||||
|
pvt@1f200000 {
|
||||||
|
compatible = "baikal,bt1-pvt";
|
||||||
|
reg = <0x1f200000 0x1000>;
|
||||||
|
#thermal-sensor-cells = <0>;
|
||||||
|
|
||||||
|
interrupts = <GIC_SHARED 31 IRQ_TYPE_LEVEL_HIGH>;
|
||||||
|
|
||||||
|
baikal,pvt-temp-trim-millicelsius = <1000>;
|
||||||
|
|
||||||
|
clocks = <&ccu_sys>, <&ccu_sys>;
|
||||||
|
clock-names = "ref", "pclk";
|
||||||
|
};
|
||||||
|
...
|
196
Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml
Normal file
196
Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
|
||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
$id: http://devicetree.org/schemas/mfd/gateworks-gsc.yaml#
|
||||||
|
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||||
|
|
||||||
|
title: Gateworks System Controller
|
||||||
|
|
||||||
|
description: |
|
||||||
|
The Gateworks System Controller (GSC) is a device present across various
|
||||||
|
Gateworks product families that provides a set of system related features
|
||||||
|
such as the following (refer to the board hardware user manuals to see what
|
||||||
|
features are present)
|
||||||
|
- Watchdog Timer
|
||||||
|
- GPIO
|
||||||
|
- Pushbutton controller
|
||||||
|
- Hardware monitor with ADC's for temperature and voltage rails and
|
||||||
|
fan controller
|
||||||
|
|
||||||
|
maintainers:
|
||||||
|
- Tim Harvey <tharvey@gateworks.com>
|
||||||
|
- Robert Jones <rjones@gateworks.com>
|
||||||
|
|
||||||
|
properties:
|
||||||
|
$nodename:
|
||||||
|
pattern: "gsc@[0-9a-f]{1,2}"
|
||||||
|
compatible:
|
||||||
|
const: gw,gsc
|
||||||
|
|
||||||
|
reg:
|
||||||
|
description: I2C device address
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
interrupts:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
interrupt-controller: true
|
||||||
|
|
||||||
|
"#interrupt-cells":
|
||||||
|
const: 1
|
||||||
|
|
||||||
|
"#address-cells":
|
||||||
|
const: 1
|
||||||
|
|
||||||
|
"#size-cells":
|
||||||
|
const: 0
|
||||||
|
|
||||||
|
adc:
|
||||||
|
type: object
|
||||||
|
description: Optional hardware monitoring module
|
||||||
|
|
||||||
|
properties:
|
||||||
|
compatible:
|
||||||
|
const: gw,gsc-adc
|
||||||
|
|
||||||
|
"#address-cells":
|
||||||
|
const: 1
|
||||||
|
|
||||||
|
"#size-cells":
|
||||||
|
const: 0
|
||||||
|
|
||||||
|
patternProperties:
|
||||||
|
"^channel@[0-9]+$":
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Properties for a single ADC which can report cooked values
|
||||||
|
(i.e. temperature sensor based on thermister), raw values
|
||||||
|
(i.e. voltage rail with a pre-scaling resistor divider).
|
||||||
|
|
||||||
|
properties:
|
||||||
|
reg:
|
||||||
|
description: Register of the ADC
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
label:
|
||||||
|
description: Name of the ADC input
|
||||||
|
|
||||||
|
gw,mode:
|
||||||
|
description: |
|
||||||
|
conversion mode:
|
||||||
|
0 - temperature, in C*10
|
||||||
|
1 - pre-scaled voltage value
|
||||||
|
2 - scaled voltage based on an optional resistor divider
|
||||||
|
and optional offset
|
||||||
|
$ref: /schemas/types.yaml#/definitions/uint32
|
||||||
|
enum: [0, 1, 2]
|
||||||
|
|
||||||
|
gw,voltage-divider-ohms:
|
||||||
|
description: Values of resistors for divider on raw ADC input
|
||||||
|
maxItems: 2
|
||||||
|
items:
|
||||||
|
minimum: 1000
|
||||||
|
maximum: 1000000
|
||||||
|
|
||||||
|
gw,voltage-offset-microvolt:
|
||||||
|
description: |
|
||||||
|
A positive voltage offset to apply to a raw ADC
|
||||||
|
(i.e. to compensate for a diode drop).
|
||||||
|
minimum: 0
|
||||||
|
maximum: 1000000
|
||||||
|
|
||||||
|
required:
|
||||||
|
- gw,mode
|
||||||
|
- reg
|
||||||
|
- label
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- "#address-cells"
|
||||||
|
- "#size-cells"
|
||||||
|
|
||||||
|
patternProperties:
|
||||||
|
"^fan-controller@[0-9a-f]+$":
|
||||||
|
type: object
|
||||||
|
description: Optional fan controller
|
||||||
|
|
||||||
|
properties:
|
||||||
|
compatible:
|
||||||
|
const: gw,gsc-fan
|
||||||
|
|
||||||
|
"#address-cells":
|
||||||
|
const: 1
|
||||||
|
|
||||||
|
"#size-cells":
|
||||||
|
const: 0
|
||||||
|
|
||||||
|
reg:
|
||||||
|
description: The fan controller base address
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- reg
|
||||||
|
- "#address-cells"
|
||||||
|
- "#size-cells"
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- reg
|
||||||
|
- interrupts
|
||||||
|
- interrupt-controller
|
||||||
|
- "#interrupt-cells"
|
||||||
|
- "#address-cells"
|
||||||
|
- "#size-cells"
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- |
|
||||||
|
#include <dt-bindings/gpio/gpio.h>
|
||||||
|
i2c {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
gsc@20 {
|
||||||
|
compatible = "gw,gsc";
|
||||||
|
reg = <0x20>;
|
||||||
|
interrupt-parent = <&gpio1>;
|
||||||
|
interrupts = <4 GPIO_ACTIVE_LOW>;
|
||||||
|
interrupt-controller;
|
||||||
|
#interrupt-cells = <1>;
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
adc {
|
||||||
|
compatible = "gw,gsc-adc";
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
channel@0 { /* A0: Board Temperature */
|
||||||
|
reg = <0x00>;
|
||||||
|
label = "temp";
|
||||||
|
gw,mode = <0>;
|
||||||
|
};
|
||||||
|
|
||||||
|
channel@2 { /* A1: Input Voltage (raw ADC) */
|
||||||
|
reg = <0x02>;
|
||||||
|
label = "vdd_vin";
|
||||||
|
gw,mode = <1>;
|
||||||
|
gw,voltage-divider-ohms = <22100 1000>;
|
||||||
|
gw,voltage-offset-microvolt = <800000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
channel@b { /* A2: Battery voltage */
|
||||||
|
reg = <0x0b>;
|
||||||
|
label = "vdd_bat";
|
||||||
|
gw,mode = <1>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fan-controller@2c {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
compatible = "gw,gsc-fan";
|
||||||
|
reg = <0x2c>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
109
Documentation/hwmon/amd_energy.rst
Normal file
109
Documentation/hwmon/amd_energy.rst
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
Kernel driver amd_energy
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
|
||||||
|
* AMD Family 17h Processors
|
||||||
|
|
||||||
|
Prefix: 'amd_energy'
|
||||||
|
|
||||||
|
Addresses used: RAPL MSRs
|
||||||
|
|
||||||
|
Datasheets:
|
||||||
|
|
||||||
|
- Processor Programming Reference (PPR) for AMD Family 17h Model 01h, Revision B1 Processors
|
||||||
|
|
||||||
|
https://developer.amd.com/wp-content/resources/55570-B1_PUB.zip
|
||||||
|
|
||||||
|
- Preliminary Processor Programming Reference (PPR) for AMD Family 17h Model 31h, Revision B0 Processors
|
||||||
|
|
||||||
|
https://developer.amd.com/wp-content/resources/56176_ppr_Family_17h_Model_71h_B0_pub_Rev_3.06.zip
|
||||||
|
|
||||||
|
Author: Naveen Krishna Chatradhi <nchatrad@amd.com>
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The Energy driver exposes the energy counters that are
|
||||||
|
reported via the Running Average Power Limit (RAPL)
|
||||||
|
Model-specific Registers (MSRs) via the hardware monitor
|
||||||
|
(HWMON) sysfs interface.
|
||||||
|
|
||||||
|
1. Power, Energy and Time Units
|
||||||
|
MSR_RAPL_POWER_UNIT/ C001_0299:
|
||||||
|
shared with all cores in the socket
|
||||||
|
|
||||||
|
2. Energy consumed by each Core
|
||||||
|
MSR_CORE_ENERGY_STATUS/ C001_029A:
|
||||||
|
32-bitRO, Accumulator, core-level power reporting
|
||||||
|
|
||||||
|
3. Energy consumed by Socket
|
||||||
|
MSR_PACKAGE_ENERGY_STATUS/ C001_029B:
|
||||||
|
32-bitRO, Accumulator, socket-level power reporting,
|
||||||
|
shared with all cores in socket
|
||||||
|
|
||||||
|
These registers are updated every 1ms and cleared on
|
||||||
|
reset of the system.
|
||||||
|
|
||||||
|
Note: If SMT is enabled, Linux enumerates all threads as cpus.
|
||||||
|
Since, the energy status registers are accessed at core level,
|
||||||
|
reading those registers from the sibling threads would result
|
||||||
|
in duplicate values. Hence, energy counter entries are not
|
||||||
|
populated for the siblings.
|
||||||
|
|
||||||
|
Energy Caluclation
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Energy information (in Joules) is based on the multiplier,
|
||||||
|
1/2^ESU; where ESU is an unsigned integer read from
|
||||||
|
MSR_RAPL_POWER_UNIT register. Default value is 10000b,
|
||||||
|
indicating energy status unit is 15.3 micro-Joules increment.
|
||||||
|
|
||||||
|
Reported values are scaled as per the formula
|
||||||
|
|
||||||
|
scaled value = ((1/2^ESU) * (Raw value) * 1000000UL) in uJoules
|
||||||
|
|
||||||
|
Users calculate power for a given domain by calculating
|
||||||
|
dEnergy/dTime for that domain.
|
||||||
|
|
||||||
|
Energy accumulation
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Current, Socket energy status register is 32bit, assuming a 240W
|
||||||
|
2P system, the register would wrap around in
|
||||||
|
|
||||||
|
2^32*15.3 e-6/240 * 2 = 547.60833024 secs to wrap(~9 mins)
|
||||||
|
|
||||||
|
The Core energy register may wrap around after several days.
|
||||||
|
|
||||||
|
To improve the wrap around time, a kernel thread is implemented
|
||||||
|
to accumulate the socket energy counters and one core energy counter
|
||||||
|
per run to a respective 64-bit counter. The kernel thread starts
|
||||||
|
running during probe, wakes up every 100secs and stops running
|
||||||
|
when driver is removed.
|
||||||
|
|
||||||
|
A socket and core energy read would return the current register
|
||||||
|
value added to the respective energy accumulator.
|
||||||
|
|
||||||
|
Sysfs attributes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
=============== ======== =====================================
|
||||||
|
Attribute Label Description
|
||||||
|
=============== ======== =====================================
|
||||||
|
|
||||||
|
* For index N between [1] and [nr_cpus]
|
||||||
|
|
||||||
|
=============== ======== ======================================
|
||||||
|
energy[N]_input EcoreX Core Energy X = [0] to [nr_cpus - 1]
|
||||||
|
Measured input core energy
|
||||||
|
=============== ======== ======================================
|
||||||
|
|
||||||
|
* For N between [nr_cpus] and [nr_cpus + nr_socks]
|
||||||
|
|
||||||
|
=============== ======== ======================================
|
||||||
|
energy[N]_input EsocketX Socket Energy X = [0] to [nr_socks -1]
|
||||||
|
Measured input socket energy
|
||||||
|
=============== ======== ======================================
|
117
Documentation/hwmon/bt1-pvt.rst
Normal file
117
Documentation/hwmon/bt1-pvt.rst
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
|
Kernel driver bt1-pvt
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
|
||||||
|
* Baikal-T1 PVT sensor (in SoC)
|
||||||
|
|
||||||
|
Prefix: 'bt1-pvt'
|
||||||
|
|
||||||
|
Addresses scanned: -
|
||||||
|
|
||||||
|
Datasheet: Provided by BAIKAL ELECTRONICS upon request and under NDA
|
||||||
|
|
||||||
|
Authors:
|
||||||
|
Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
|
||||||
|
Serge Semin <Sergey.Semin@baikalelectronics.ru>
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This driver implements support for the hardware monitoring capabilities of the
|
||||||
|
embedded into Baikal-T1 process, voltage and temperature sensors. PVT IP-core
|
||||||
|
consists of one temperature and four voltage sensors, which can be used to
|
||||||
|
monitor the chip internal environment like heating, supply voltage and
|
||||||
|
transistors performance. The driver can optionally provide the hwmon alarms
|
||||||
|
for each sensor the PVT controller supports. The alarms functionality is made
|
||||||
|
compile-time configurable due to the hardware interface implementation
|
||||||
|
peculiarity, which is connected with an ability to convert data from only one
|
||||||
|
sensor at a time. Additional limitation is that the controller performs the
|
||||||
|
thresholds checking synchronously with the data conversion procedure. Due to
|
||||||
|
these in order to have the hwmon alarms automatically detected the driver code
|
||||||
|
must switch from one sensor to another, read converted data and manually check
|
||||||
|
the threshold status bits. Depending on the measurements timeout settings
|
||||||
|
(update_interval sysfs node value) this design may cause additional burden on
|
||||||
|
the system performance. So in case if alarms are unnecessary in your system
|
||||||
|
design it's recommended to have them disabled to prevent the PVT IRQs being
|
||||||
|
periodically raised to get the data cache/alarms status up to date. By default
|
||||||
|
in alarm-less configuration the data conversion is performed by the driver
|
||||||
|
on demand when read operation is requested via corresponding _input-file.
|
||||||
|
|
||||||
|
Temperature Monitoring
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Temperature is measured with 10-bit resolution and reported in millidegree
|
||||||
|
Celsius. The driver performs all the scaling by itself therefore reports true
|
||||||
|
temperatures that don't need any user-space adjustments. While the data
|
||||||
|
translation formulae isn't linear, which gives us non-linear discreteness,
|
||||||
|
it's close to one, but giving a bit better accuracy for higher temperatures.
|
||||||
|
The temperature input is mapped as follows (the last column indicates the input
|
||||||
|
ranges)::
|
||||||
|
|
||||||
|
temp1: CPU embedded diode -48.38C - +147.438C
|
||||||
|
|
||||||
|
In case if the alarms kernel config is enabled in the driver the temperature input
|
||||||
|
has associated min and max limits which trigger an alarm when crossed.
|
||||||
|
|
||||||
|
Voltage Monitoring
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The voltage inputs are also sampled with 10-bit resolution and reported in
|
||||||
|
millivolts. But in this case the data translation formulae is linear, which
|
||||||
|
provides a constant measurements discreteness. The data scaling is also
|
||||||
|
performed by the driver, so returning true millivolts. The voltage inputs are
|
||||||
|
mapped as follows (the last column indicates the input ranges)::
|
||||||
|
|
||||||
|
in0: VDD (processor core) 0.62V - 1.168V
|
||||||
|
in1: Low-Vt (low voltage threshold) 0.62V - 1.168V
|
||||||
|
in2: High-Vt (high voltage threshold) 0.62V - 1.168V
|
||||||
|
in3: Standard-Vt (standard voltage threshold) 0.62V - 1.168V
|
||||||
|
|
||||||
|
In case if the alarms config is enabled in the driver the voltage inputs
|
||||||
|
have associated min and max limits which trigger an alarm when crossed.
|
||||||
|
|
||||||
|
Sysfs Attributes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Following is a list of all sysfs attributes that the driver provides, their
|
||||||
|
permissions and a short description:
|
||||||
|
|
||||||
|
=============================== ======= =======================================
|
||||||
|
Name Perm Description
|
||||||
|
=============================== ======= =======================================
|
||||||
|
update_interval RW Measurements update interval per
|
||||||
|
sensor.
|
||||||
|
temp1_type RO Sensor type (always 1 as CPU embedded
|
||||||
|
diode).
|
||||||
|
temp1_label RO CPU Core Temperature sensor.
|
||||||
|
temp1_input RO Measured temperature in millidegree
|
||||||
|
Celsius.
|
||||||
|
temp1_min RW Low limit for temp input.
|
||||||
|
temp1_max RW High limit for temp input.
|
||||||
|
temp1_min_alarm RO Temperature input alarm. Returns 1 if
|
||||||
|
temperature input went below min limit,
|
||||||
|
0 otherwise.
|
||||||
|
temp1_max_alarm RO Temperature input alarm. Returns 1 if
|
||||||
|
temperature input went above max limit,
|
||||||
|
0 otherwise.
|
||||||
|
temp1_offset RW Temperature offset in millidegree
|
||||||
|
Celsius which is added to the
|
||||||
|
temperature reading by the chip. It can
|
||||||
|
be used to manually adjust the
|
||||||
|
temperature measurements within 7.130
|
||||||
|
degrees Celsius.
|
||||||
|
in[0-3]_label RO CPU Voltage sensor (either core or
|
||||||
|
low/high/standard thresholds).
|
||||||
|
in[0-3]_input RO Measured voltage in millivolts.
|
||||||
|
in[0-3]_min RW Low limit for voltage input.
|
||||||
|
in[0-3]_max RW High limit for voltage input.
|
||||||
|
in[0-3]_min_alarm RO Voltage input alarm. Returns 1 if
|
||||||
|
voltage input went below min limit,
|
||||||
|
0 otherwise.
|
||||||
|
in[0-3]_max_alarm RO Voltage input alarm. Returns 1 if
|
||||||
|
voltage input went above max limit,
|
||||||
|
0 otherwise.
|
||||||
|
=============================== ======= =======================================
|
53
Documentation/hwmon/gsc-hwmon.rst
Normal file
53
Documentation/hwmon/gsc-hwmon.rst
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
Kernel driver gsc-hwmon
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Supported chips: Gateworks GSC
|
||||||
|
Datasheet: http://trac.gateworks.com/wiki/gsc
|
||||||
|
Author: Tim Harvey <tharvey@gateworks.com>
|
||||||
|
|
||||||
|
Description:
|
||||||
|
------------
|
||||||
|
|
||||||
|
This driver supports hardware monitoring for the temperature sensor,
|
||||||
|
various ADC's connected to the GSC, and optional FAN controller available
|
||||||
|
on some boards.
|
||||||
|
|
||||||
|
|
||||||
|
Voltage Monitoring
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The voltage inputs are scaled either internally or by the driver depending
|
||||||
|
on the GSC version and firmware. The values returned by the driver do not need
|
||||||
|
further scaling. The voltage input labels provide the voltage rail name:
|
||||||
|
|
||||||
|
inX_input Measured voltage (mV).
|
||||||
|
inX_label Name of voltage rail.
|
||||||
|
|
||||||
|
|
||||||
|
Temperature Monitoring
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Temperatures are measured with 12-bit or 10-bit resolution and are scaled
|
||||||
|
either internally or by the driver depending on the GSC version and firmware.
|
||||||
|
The values returned by the driver reflect millidegree Celcius:
|
||||||
|
|
||||||
|
tempX_input Measured temperature.
|
||||||
|
tempX_label Name of temperature input.
|
||||||
|
|
||||||
|
|
||||||
|
PWM Output Control
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The GSC features 1 PWM output that operates in automatic mode where the
|
||||||
|
PWM value will be scalled depending on 6 temperature boundaries.
|
||||||
|
The tempeature boundaries are read-write and in millidegree Celcius and the
|
||||||
|
read-only PWM values range from 0 (off) to 255 (full speed).
|
||||||
|
Fan speed will be set to minimum (off) when the temperature sensor reads
|
||||||
|
less than pwm1_auto_point1_temp and maximum when the temperature sensor
|
||||||
|
equals or exceeds pwm1_auto_point6_temp.
|
||||||
|
|
||||||
|
pwm1_auto_point[1-6]_pwm PWM value.
|
||||||
|
pwm1_auto_point[1-6]_temp Temperature boundary.
|
||||||
|
|
@ -99,6 +99,25 @@ Sysfs entries for ina226, ina230 and ina231 only
|
|||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
======================= ====================================================
|
======================= ====================================================
|
||||||
|
in0_lcrit Critical low shunt voltage
|
||||||
|
in0_crit Critical high shunt voltage
|
||||||
|
in0_lcrit_alarm Shunt voltage critical low alarm
|
||||||
|
in0_crit_alarm Shunt voltage critical high alarm
|
||||||
|
in1_lcrit Critical low bus voltage
|
||||||
|
in1_crit Critical high bus voltage
|
||||||
|
in1_lcrit_alarm Bus voltage critical low alarm
|
||||||
|
in1_crit_alarm Bus voltage critical high alarm
|
||||||
|
power1_crit Critical high power
|
||||||
|
power1_crit_alarm Power critical high alarm
|
||||||
update_interval data conversion time; affects number of samples used
|
update_interval data conversion time; affects number of samples used
|
||||||
to average results for shunt and bus voltages.
|
to average results for shunt and bus voltages.
|
||||||
======================= ====================================================
|
======================= ====================================================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
- Configure `shunt_resistor` before configure `power1_crit`, because power
|
||||||
|
value is calculated based on `shunt_resistor` set.
|
||||||
|
- Because of the underlying register implementation, only one `*crit` setting
|
||||||
|
and its `alarm` can be active. Writing to one `*crit` setting clears other
|
||||||
|
`*crit` settings and alarms. Writing 0 to any `*crit` setting clears all
|
||||||
|
`*crit` settings and alarms.
|
||||||
|
@ -39,10 +39,12 @@ Hardware Monitoring Kernel Drivers
|
|||||||
adt7470
|
adt7470
|
||||||
adt7475
|
adt7475
|
||||||
amc6821
|
amc6821
|
||||||
|
amd_energy
|
||||||
asb100
|
asb100
|
||||||
asc7621
|
asc7621
|
||||||
aspeed-pwm-tacho
|
aspeed-pwm-tacho
|
||||||
bel-pfe
|
bel-pfe
|
||||||
|
bt1-pvt
|
||||||
coretemp
|
coretemp
|
||||||
da9052
|
da9052
|
||||||
da9055
|
da9055
|
||||||
@ -60,6 +62,7 @@ Hardware Monitoring Kernel Drivers
|
|||||||
ftsteutates
|
ftsteutates
|
||||||
g760a
|
g760a
|
||||||
g762
|
g762
|
||||||
|
gsc-hwmon
|
||||||
gl518sm
|
gl518sm
|
||||||
hih6130
|
hih6130
|
||||||
ibmaem
|
ibmaem
|
||||||
@ -106,6 +109,7 @@ Hardware Monitoring Kernel Drivers
|
|||||||
max16064
|
max16064
|
||||||
max16065
|
max16065
|
||||||
max1619
|
max1619
|
||||||
|
max16601
|
||||||
max1668
|
max1668
|
||||||
max197
|
max197
|
||||||
max20730
|
max20730
|
||||||
|
@ -123,6 +123,18 @@ Supported chips:
|
|||||||
|
|
||||||
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3497
|
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3497
|
||||||
|
|
||||||
|
* Maxim MAX6654
|
||||||
|
|
||||||
|
Prefix: 'max6654'
|
||||||
|
|
||||||
|
Addresses scanned: I2C 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b,
|
||||||
|
|
||||||
|
0x4c, 0x4d and 0x4e
|
||||||
|
|
||||||
|
Datasheet: Publicly available at the Maxim website
|
||||||
|
|
||||||
|
https://www.maximintegrated.com/en/products/sensors/MAX6654.html
|
||||||
|
|
||||||
* Maxim MAX6657
|
* Maxim MAX6657
|
||||||
|
|
||||||
Prefix: 'max6657'
|
Prefix: 'max6657'
|
||||||
@ -301,6 +313,13 @@ ADT7461, ADT7461A, NCT1008:
|
|||||||
* Extended temperature range (breaks compatibility)
|
* Extended temperature range (breaks compatibility)
|
||||||
* Lower resolution for remote temperature
|
* Lower resolution for remote temperature
|
||||||
|
|
||||||
|
MAX6654:
|
||||||
|
* Better local resolution
|
||||||
|
* Selectable address
|
||||||
|
* Remote sensor type selection
|
||||||
|
* Extended temperature range
|
||||||
|
* Extended resolution only available when conversion rate <= 1 Hz
|
||||||
|
|
||||||
MAX6657 and MAX6658:
|
MAX6657 and MAX6658:
|
||||||
* Better local resolution
|
* Better local resolution
|
||||||
* Remote sensor type selection
|
* Remote sensor type selection
|
||||||
@ -336,8 +355,8 @@ SA56004X:
|
|||||||
|
|
||||||
All temperature values are given in degrees Celsius. Resolution
|
All temperature values are given in degrees Celsius. Resolution
|
||||||
is 1.0 degree for the local temperature, 0.125 degree for the remote
|
is 1.0 degree for the local temperature, 0.125 degree for the remote
|
||||||
temperature, except for the MAX6657, MAX6658 and MAX6659 which have a
|
temperature, except for the MAX6654, MAX6657, MAX6658 and MAX6659 which have
|
||||||
resolution of 0.125 degree for both temperatures.
|
a resolution of 0.125 degree for both temperatures.
|
||||||
|
|
||||||
Each sensor has its own high and low limits, plus a critical limit.
|
Each sensor has its own high and low limits, plus a critical limit.
|
||||||
Additionally, there is a relative hysteresis value common to both critical
|
Additionally, there is a relative hysteresis value common to both critical
|
||||||
|
159
Documentation/hwmon/max16601.rst
Normal file
159
Documentation/hwmon/max16601.rst
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
Kernel driver max16601
|
||||||
|
======================
|
||||||
|
|
||||||
|
Supported chips:
|
||||||
|
|
||||||
|
* Maxim MAX16601
|
||||||
|
|
||||||
|
Prefix: 'max16601'
|
||||||
|
|
||||||
|
Addresses scanned: -
|
||||||
|
|
||||||
|
Datasheet: Not published
|
||||||
|
|
||||||
|
Author: Guenter Roeck <linux@roeck-us.net>
|
||||||
|
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
|
||||||
|
Chipset.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Platform data support
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
The driver supports standard PMBus driver platform data.
|
||||||
|
|
||||||
|
|
||||||
|
Sysfs entries
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The following attributes are supported.
|
||||||
|
|
||||||
|
======================= =======================================================
|
||||||
|
in1_label "vin1"
|
||||||
|
in1_input VCORE input voltage.
|
||||||
|
in1_alarm Input voltage alarm.
|
||||||
|
|
||||||
|
in2_label "vout1"
|
||||||
|
in2_input VCORE output voltage.
|
||||||
|
in2_alarm Output voltage alarm.
|
||||||
|
|
||||||
|
curr1_label "iin1"
|
||||||
|
curr1_input VCORE input current, derived from duty cycle and output
|
||||||
|
current.
|
||||||
|
curr1_max Maximum input current.
|
||||||
|
curr1_max_alarm Current high alarm.
|
||||||
|
|
||||||
|
curr2_label "iin1.0"
|
||||||
|
curr2_input VCORE phase 0 input current.
|
||||||
|
|
||||||
|
curr3_label "iin1.1"
|
||||||
|
curr3_input VCORE phase 1 input current.
|
||||||
|
|
||||||
|
curr4_label "iin1.2"
|
||||||
|
curr4_input VCORE phase 2 input current.
|
||||||
|
|
||||||
|
curr5_label "iin1.3"
|
||||||
|
curr5_input VCORE phase 3 input current.
|
||||||
|
|
||||||
|
curr6_label "iin1.4"
|
||||||
|
curr6_input VCORE phase 4 input current.
|
||||||
|
|
||||||
|
curr7_label "iin1.5"
|
||||||
|
curr7_input VCORE phase 5 input current.
|
||||||
|
|
||||||
|
curr8_label "iin1.6"
|
||||||
|
curr8_input VCORE phase 6 input current.
|
||||||
|
|
||||||
|
curr9_label "iin1.7"
|
||||||
|
curr9_input VCORE phase 7 input current.
|
||||||
|
|
||||||
|
curr10_label "iin2"
|
||||||
|
curr10_input VCORE input current, derived from sensor element.
|
||||||
|
|
||||||
|
curr11_label "iin3"
|
||||||
|
curr11_input VSA input current.
|
||||||
|
|
||||||
|
curr12_label "iout1"
|
||||||
|
curr12_input VCORE output current.
|
||||||
|
curr12_crit Critical output current.
|
||||||
|
curr12_crit_alarm Output current critical alarm.
|
||||||
|
curr12_max Maximum output current.
|
||||||
|
curr12_max_alarm Output current high alarm.
|
||||||
|
|
||||||
|
curr13_label "iout1.0"
|
||||||
|
curr13_input VCORE phase 0 output current.
|
||||||
|
|
||||||
|
curr14_label "iout1.1"
|
||||||
|
curr14_input VCORE phase 1 output current.
|
||||||
|
|
||||||
|
curr15_label "iout1.2"
|
||||||
|
curr15_input VCORE phase 2 output current.
|
||||||
|
|
||||||
|
curr16_label "iout1.3"
|
||||||
|
curr16_input VCORE phase 3 output current.
|
||||||
|
|
||||||
|
curr17_label "iout1.4"
|
||||||
|
curr17_input VCORE phase 4 output current.
|
||||||
|
|
||||||
|
curr18_label "iout1.5"
|
||||||
|
curr18_input VCORE phase 5 output current.
|
||||||
|
|
||||||
|
curr19_label "iout1.6"
|
||||||
|
curr19_input VCORE phase 6 output current.
|
||||||
|
|
||||||
|
curr20_label "iout1.7"
|
||||||
|
curr20_input VCORE phase 7 output current.
|
||||||
|
|
||||||
|
curr21_label "iout3"
|
||||||
|
curr21_input VSA output current.
|
||||||
|
curr21_highest Historical maximum VSA output current.
|
||||||
|
curr21_reset_history Write any value to reset curr21_highest.
|
||||||
|
curr21_crit Critical output current.
|
||||||
|
curr21_crit_alarm Output current critical alarm.
|
||||||
|
curr21_max Maximum output current.
|
||||||
|
curr21_max_alarm Output current high alarm.
|
||||||
|
|
||||||
|
power1_label "pin1"
|
||||||
|
power1_input Input power, derived from duty cycle and output current.
|
||||||
|
power1_alarm Input power alarm.
|
||||||
|
|
||||||
|
power2_label "pin2"
|
||||||
|
power2_input Input power, derived from input current sensor.
|
||||||
|
|
||||||
|
power3_label "pout"
|
||||||
|
power3_input Output power.
|
||||||
|
|
||||||
|
temp1_input VCORE temperature.
|
||||||
|
temp1_crit Critical high temperature.
|
||||||
|
temp1_crit_alarm Chip temperature critical high alarm.
|
||||||
|
temp1_max Maximum temperature.
|
||||||
|
temp1_max_alarm Chip temperature high alarm.
|
||||||
|
|
||||||
|
temp2_input TSENSE_0 temperature
|
||||||
|
temp3_input TSENSE_1 temperature
|
||||||
|
temp4_input TSENSE_2 temperature
|
||||||
|
temp5_input TSENSE_3 temperature
|
||||||
|
|
||||||
|
temp6_input VSA temperature.
|
||||||
|
temp6_crit Critical high temperature.
|
||||||
|
temp6_crit_alarm Chip temperature critical high alarm.
|
||||||
|
temp6_max Maximum temperature.
|
||||||
|
temp6_max_alarm Chip temperature high alarm.
|
||||||
|
======================= =======================================================
|
18
MAINTAINERS
18
MAINTAINERS
@ -842,6 +842,13 @@ S: Supported
|
|||||||
T: git git://people.freedesktop.org/~agd5f/linux
|
T: git git://people.freedesktop.org/~agd5f/linux
|
||||||
F: drivers/gpu/drm/amd/display/
|
F: drivers/gpu/drm/amd/display/
|
||||||
|
|
||||||
|
AMD ENERGY DRIVER
|
||||||
|
M: Naveen Krishna Chatradhi <nchatrad@amd.com>
|
||||||
|
L: linux-hwmon@vger.kernel.org
|
||||||
|
S: Maintained
|
||||||
|
F: Documentation/hwmon/amd_energy.rst
|
||||||
|
F: drivers/hwmon/amd_energy.c
|
||||||
|
|
||||||
AMD FAM15H PROCESSOR POWER MONITORING DRIVER
|
AMD FAM15H PROCESSOR POWER MONITORING DRIVER
|
||||||
M: Huang Rui <ray.huang@amd.com>
|
M: Huang Rui <ray.huang@amd.com>
|
||||||
L: linux-hwmon@vger.kernel.org
|
L: linux-hwmon@vger.kernel.org
|
||||||
@ -7028,6 +7035,17 @@ F: kernel/futex.c
|
|||||||
F: tools/perf/bench/futex*
|
F: tools/perf/bench/futex*
|
||||||
F: tools/testing/selftests/futex/
|
F: tools/testing/selftests/futex/
|
||||||
|
|
||||||
|
GATEWORKS SYSTEM CONTROLLER (GSC) DRIVER
|
||||||
|
M: Tim Harvey <tharvey@gateworks.com>
|
||||||
|
M: Robert Jones <rjones@gateworks.com>
|
||||||
|
S: Maintained
|
||||||
|
F: Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml
|
||||||
|
F: drivers/mfd/gateworks-gsc.c
|
||||||
|
F: include/linux/mfd/gsc.h
|
||||||
|
F: Documentation/hwmon/gsc-hwmon.rst
|
||||||
|
F: drivers/hwmon/gsc-hwmon.c
|
||||||
|
F: include/linux/platform_data/gsc_hwmon.h
|
||||||
|
|
||||||
GASKET DRIVER FRAMEWORK
|
GASKET DRIVER FRAMEWORK
|
||||||
M: Rob Springer <rspringer@google.com>
|
M: Rob Springer <rspringer@google.com>
|
||||||
M: Todd Poynor <toddpoynor@google.com>
|
M: Todd Poynor <toddpoynor@google.com>
|
||||||
|
@ -324,6 +324,16 @@ config SENSORS_FAM15H_POWER
|
|||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called fam15h_power.
|
will be called fam15h_power.
|
||||||
|
|
||||||
|
config SENSORS_AMD_ENERGY
|
||||||
|
tristate "AMD RAPL MSR based Energy driver"
|
||||||
|
depends on X86
|
||||||
|
help
|
||||||
|
If you say yes here you get support for core and package energy
|
||||||
|
sensors, based on RAPL MSR for AMD family 17h and above CPUs.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module
|
||||||
|
will be called as amd_energy.
|
||||||
|
|
||||||
config SENSORS_APPLESMC
|
config SENSORS_APPLESMC
|
||||||
tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
|
tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
|
||||||
depends on INPUT && X86
|
depends on INPUT && X86
|
||||||
@ -404,6 +414,31 @@ config SENSORS_ATXP1
|
|||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called atxp1.
|
will be called atxp1.
|
||||||
|
|
||||||
|
config SENSORS_BT1_PVT
|
||||||
|
tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
|
||||||
|
depends on MIPS_BAIKAL_T1 || COMPILE_TEST
|
||||||
|
help
|
||||||
|
If you say yes here you get support for Baikal-T1 PVT sensor
|
||||||
|
embedded into the SoC.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will be
|
||||||
|
called bt1-pvt.
|
||||||
|
|
||||||
|
config SENSORS_BT1_PVT_ALARMS
|
||||||
|
bool "Enable Baikal-T1 PVT sensor alarms"
|
||||||
|
depends on SENSORS_BT1_PVT
|
||||||
|
help
|
||||||
|
Baikal-T1 PVT IP-block provides threshold registers for each
|
||||||
|
supported sensor. But the corresponding interrupts might be
|
||||||
|
generated by the thresholds comparator only in synchronization with
|
||||||
|
a data conversion. Additionally there is only one sensor data can
|
||||||
|
be converted at a time. All of these makes the interface impossible
|
||||||
|
to be used for the hwmon alarms implementation without periodic
|
||||||
|
switch between the PVT sensors. By default the data conversion is
|
||||||
|
performed on demand from the user-space. If this config is enabled
|
||||||
|
the data conversion will be periodically performed and the data will be
|
||||||
|
saved in the internal driver cache.
|
||||||
|
|
||||||
config SENSORS_DRIVETEMP
|
config SENSORS_DRIVETEMP
|
||||||
tristate "Hard disk drives with temperature sensors"
|
tristate "Hard disk drives with temperature sensors"
|
||||||
depends on SCSI && ATA
|
depends on SCSI && ATA
|
||||||
@ -523,6 +558,15 @@ config SENSORS_F75375S
|
|||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called f75375s.
|
will be called f75375s.
|
||||||
|
|
||||||
|
config SENSORS_GSC
|
||||||
|
tristate "Gateworks System Controller ADC"
|
||||||
|
depends on MFD_GATEWORKS_GSC
|
||||||
|
help
|
||||||
|
Support for the Gateworks System Controller A/D converters.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here:
|
||||||
|
the module will be called gsc-hwmon.
|
||||||
|
|
||||||
config SENSORS_MC13783_ADC
|
config SENSORS_MC13783_ADC
|
||||||
tristate "Freescale MC13783/MC13892 ADC"
|
tristate "Freescale MC13783/MC13892 ADC"
|
||||||
depends on MFD_MC13XXX
|
depends on MFD_MC13XXX
|
||||||
@ -1198,10 +1242,11 @@ config SENSORS_LM90
|
|||||||
help
|
help
|
||||||
If you say yes here you get support for National Semiconductor LM90,
|
If you say yes here you get support for National Semiconductor LM90,
|
||||||
LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A,
|
LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A,
|
||||||
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659,
|
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6654, MAX6657, MAX6658,
|
||||||
MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008,
|
MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696,
|
||||||
Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and
|
ON Semiconductor NCT1008, Winbond/Nuvoton W83L771W/G/AWG/ASG,
|
||||||
Texas Instruments TMP451 sensor chips.
|
Philips SA56004, GMT G781, and Texas Instruments TMP451
|
||||||
|
sensor chips.
|
||||||
|
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called lm90.
|
will be called lm90.
|
||||||
@ -1340,10 +1385,12 @@ config SENSORS_NCT7802
|
|||||||
|
|
||||||
config SENSORS_NCT7904
|
config SENSORS_NCT7904
|
||||||
tristate "Nuvoton NCT7904"
|
tristate "Nuvoton NCT7904"
|
||||||
depends on I2C
|
depends on I2C && WATCHDOG
|
||||||
|
select WATCHDOG_CORE
|
||||||
help
|
help
|
||||||
If you say yes here you get support for the Nuvoton NCT7904
|
If you say yes here you get support for the Nuvoton NCT7904
|
||||||
hardware monitoring chip, including manual fan speed control.
|
hardware monitoring chip, including manual fan speed control
|
||||||
|
and support for the integrated watchdog.
|
||||||
|
|
||||||
This driver can also be built as a module. If so, the module
|
This driver can also be built as a module. If so, the module
|
||||||
will be called nct7904.
|
will be called nct7904.
|
||||||
|
@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
|
|||||||
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
|
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
|
||||||
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
|
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
|
||||||
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
|
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
|
||||||
|
obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
|
||||||
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
||||||
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
|
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
|
||||||
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
|
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
|
||||||
@ -53,6 +54,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
|
|||||||
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
|
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
|
||||||
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
|
||||||
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
|
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
|
||||||
|
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
|
||||||
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
|
||||||
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
|
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
|
||||||
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
|
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
|
||||||
@ -74,6 +76,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o
|
|||||||
obj-$(CONFIG_SENSORS_G762) += g762.o
|
obj-$(CONFIG_SENSORS_G762) += g762.o
|
||||||
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
|
||||||
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
|
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
|
||||||
|
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
|
||||||
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
|
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
|
||||||
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
|
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
|
||||||
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
|
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
|
||||||
|
@ -716,7 +716,6 @@ static struct i2c_driver adt7411_driver = {
|
|||||||
|
|
||||||
module_i2c_driver(adt7411_driver);
|
module_i2c_driver(adt7411_driver);
|
||||||
|
|
||||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and "
|
MODULE_AUTHOR("Sascha Hauer, Wolfram Sang <kernel@pengutronix.de>");
|
||||||
"Wolfram Sang <w.sang@pengutronix.de>");
|
|
||||||
MODULE_DESCRIPTION("ADT7411 driver");
|
MODULE_DESCRIPTION("ADT7411 driver");
|
||||||
MODULE_LICENSE("GPL v2");
|
MODULE_LICENSE("GPL v2");
|
||||||
|
408
drivers/hwmon/amd_energy.c
Normal file
408
drivers/hwmon/amd_energy.c
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Advanced Micro Devices, Inc.
|
||||||
|
*/
|
||||||
|
#include <asm/cpu_device_id.h>
|
||||||
|
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include <linux/cpu.h>
|
||||||
|
#include <linux/cpumask.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/kthread.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/processor.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/sched.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/topology.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#define DRVNAME "amd_energy"
|
||||||
|
|
||||||
|
#define ENERGY_PWR_UNIT_MSR 0xC0010299
|
||||||
|
#define ENERGY_CORE_MSR 0xC001029A
|
||||||
|
#define ENERGY_PKG_MSR 0xC001029B
|
||||||
|
|
||||||
|
#define AMD_ENERGY_UNIT_MASK 0x01F00
|
||||||
|
#define AMD_ENERGY_MASK 0xFFFFFFFF
|
||||||
|
|
||||||
|
struct sensor_accumulator {
|
||||||
|
u64 energy_ctr;
|
||||||
|
u64 prev_value;
|
||||||
|
char label[10];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct amd_energy_data {
|
||||||
|
struct hwmon_channel_info energy_info;
|
||||||
|
const struct hwmon_channel_info *info[2];
|
||||||
|
struct hwmon_chip_info chip;
|
||||||
|
struct task_struct *wrap_accumulate;
|
||||||
|
/* Lock around the accumulator */
|
||||||
|
struct mutex lock;
|
||||||
|
/* An accumulator for each core and socket */
|
||||||
|
struct sensor_accumulator *accums;
|
||||||
|
/* Energy Status Units */
|
||||||
|
u64 energy_units;
|
||||||
|
int nr_cpus;
|
||||||
|
int nr_socks;
|
||||||
|
int core_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int amd_energy_read_labels(struct device *dev,
|
||||||
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel,
|
||||||
|
const char **str)
|
||||||
|
{
|
||||||
|
struct amd_energy_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
*str = data->accums[channel].label;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_energy_units(struct amd_energy_data *data)
|
||||||
|
{
|
||||||
|
u64 rapl_units;
|
||||||
|
|
||||||
|
rdmsrl_safe(ENERGY_PWR_UNIT_MSR, &rapl_units);
|
||||||
|
data->energy_units = (rapl_units & AMD_ENERGY_UNIT_MASK) >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void accumulate_socket_delta(struct amd_energy_data *data,
|
||||||
|
int sock, int cpu)
|
||||||
|
{
|
||||||
|
struct sensor_accumulator *s_accum;
|
||||||
|
u64 input;
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
rdmsrl_safe_on_cpu(cpu, ENERGY_PKG_MSR, &input);
|
||||||
|
input &= AMD_ENERGY_MASK;
|
||||||
|
|
||||||
|
s_accum = &data->accums[data->nr_cpus + sock];
|
||||||
|
if (input >= s_accum->prev_value)
|
||||||
|
s_accum->energy_ctr +=
|
||||||
|
input - s_accum->prev_value;
|
||||||
|
else
|
||||||
|
s_accum->energy_ctr += UINT_MAX -
|
||||||
|
s_accum->prev_value + input;
|
||||||
|
|
||||||
|
s_accum->prev_value = input;
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void accumulate_core_delta(struct amd_energy_data *data)
|
||||||
|
{
|
||||||
|
struct sensor_accumulator *c_accum;
|
||||||
|
u64 input;
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
if (data->core_id >= data->nr_cpus)
|
||||||
|
data->core_id = 0;
|
||||||
|
|
||||||
|
cpu = data->core_id;
|
||||||
|
|
||||||
|
if (!cpu_online(cpu))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
rdmsrl_safe_on_cpu(cpu, ENERGY_CORE_MSR, &input);
|
||||||
|
input &= AMD_ENERGY_MASK;
|
||||||
|
|
||||||
|
c_accum = &data->accums[cpu];
|
||||||
|
|
||||||
|
if (input >= c_accum->prev_value)
|
||||||
|
c_accum->energy_ctr +=
|
||||||
|
input - c_accum->prev_value;
|
||||||
|
else
|
||||||
|
c_accum->energy_ctr += UINT_MAX -
|
||||||
|
c_accum->prev_value + input;
|
||||||
|
|
||||||
|
c_accum->prev_value = input;
|
||||||
|
|
||||||
|
out:
|
||||||
|
data->core_id++;
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void read_accumulate(struct amd_energy_data *data)
|
||||||
|
{
|
||||||
|
int sock;
|
||||||
|
|
||||||
|
for (sock = 0; sock < data->nr_socks; sock++) {
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
cpu = cpumask_first_and(cpu_online_mask,
|
||||||
|
cpumask_of_node(sock));
|
||||||
|
|
||||||
|
accumulate_socket_delta(data, sock, cpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulate_core_delta(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void amd_add_delta(struct amd_energy_data *data, int ch,
|
||||||
|
int cpu, long *val, bool is_core)
|
||||||
|
{
|
||||||
|
struct sensor_accumulator *s_accum, *c_accum;
|
||||||
|
u64 input;
|
||||||
|
|
||||||
|
mutex_lock(&data->lock);
|
||||||
|
if (!is_core) {
|
||||||
|
rdmsrl_safe_on_cpu(cpu, ENERGY_PKG_MSR, &input);
|
||||||
|
input &= AMD_ENERGY_MASK;
|
||||||
|
|
||||||
|
s_accum = &data->accums[ch];
|
||||||
|
if (input >= s_accum->prev_value)
|
||||||
|
input += s_accum->energy_ctr -
|
||||||
|
s_accum->prev_value;
|
||||||
|
else
|
||||||
|
input += UINT_MAX - s_accum->prev_value +
|
||||||
|
s_accum->energy_ctr;
|
||||||
|
} else {
|
||||||
|
rdmsrl_safe_on_cpu(cpu, ENERGY_CORE_MSR, &input);
|
||||||
|
input &= AMD_ENERGY_MASK;
|
||||||
|
|
||||||
|
c_accum = &data->accums[ch];
|
||||||
|
if (input >= c_accum->prev_value)
|
||||||
|
input += c_accum->energy_ctr -
|
||||||
|
c_accum->prev_value;
|
||||||
|
else
|
||||||
|
input += UINT_MAX - c_accum->prev_value +
|
||||||
|
c_accum->energy_ctr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Energy consumed = (1/(2^ESU) * RAW * 1000000UL) μJoules */
|
||||||
|
*val = div64_ul(input * 1000000UL, BIT(data->energy_units));
|
||||||
|
|
||||||
|
mutex_unlock(&data->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int amd_energy_read(struct device *dev,
|
||||||
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, long *val)
|
||||||
|
{
|
||||||
|
struct amd_energy_data *data = dev_get_drvdata(dev);
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
if (channel >= data->nr_cpus) {
|
||||||
|
cpu = cpumask_first_and(cpu_online_mask,
|
||||||
|
cpumask_of_node
|
||||||
|
(channel - data->nr_cpus));
|
||||||
|
amd_add_delta(data, channel, cpu, val, false);
|
||||||
|
} else {
|
||||||
|
cpu = channel;
|
||||||
|
if (!cpu_online(cpu))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
amd_add_delta(data, channel, cpu, val, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static umode_t amd_energy_is_visible(const void *_data,
|
||||||
|
enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel)
|
||||||
|
{
|
||||||
|
return 0444;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int energy_accumulator(void *p)
|
||||||
|
{
|
||||||
|
struct amd_energy_data *data = (struct amd_energy_data *)p;
|
||||||
|
|
||||||
|
while (!kthread_should_stop()) {
|
||||||
|
/*
|
||||||
|
* Ignoring the conditions such as
|
||||||
|
* cpu being offline or rdmsr failure
|
||||||
|
*/
|
||||||
|
read_accumulate(data);
|
||||||
|
|
||||||
|
set_current_state(TASK_INTERRUPTIBLE);
|
||||||
|
if (kthread_should_stop())
|
||||||
|
break;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On a 240W system, with default resolution the
|
||||||
|
* Socket Energy status register may wrap around in
|
||||||
|
* 2^32*15.3 e-6/240 = 273.8041 secs (~4.5 mins)
|
||||||
|
*
|
||||||
|
* let us accumulate for every 100secs
|
||||||
|
*/
|
||||||
|
schedule_timeout(msecs_to_jiffies(100000));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hwmon_ops amd_energy_ops = {
|
||||||
|
.is_visible = amd_energy_is_visible,
|
||||||
|
.read = amd_energy_read,
|
||||||
|
.read_string = amd_energy_read_labels,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int amd_create_sensor(struct device *dev,
|
||||||
|
struct amd_energy_data *data,
|
||||||
|
u8 type, u32 config)
|
||||||
|
{
|
||||||
|
struct hwmon_channel_info *info = &data->energy_info;
|
||||||
|
struct sensor_accumulator *accums;
|
||||||
|
int i, num_siblings, cpus, sockets;
|
||||||
|
u32 *s_config;
|
||||||
|
|
||||||
|
/* Identify the number of siblings per core */
|
||||||
|
num_siblings = ((cpuid_ebx(0x8000001e) >> 8) & 0xff) + 1;
|
||||||
|
|
||||||
|
sockets = num_possible_nodes();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Energy counter register is accessed at core level.
|
||||||
|
* Hence, filterout the siblings.
|
||||||
|
*/
|
||||||
|
cpus = num_present_cpus() / num_siblings;
|
||||||
|
|
||||||
|
s_config = devm_kcalloc(dev, cpus + sockets,
|
||||||
|
sizeof(u32), GFP_KERNEL);
|
||||||
|
if (!s_config)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
accums = devm_kcalloc(dev, cpus + sockets,
|
||||||
|
sizeof(struct sensor_accumulator),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!accums)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
info->type = type;
|
||||||
|
info->config = s_config;
|
||||||
|
|
||||||
|
data->nr_cpus = cpus;
|
||||||
|
data->nr_socks = sockets;
|
||||||
|
data->accums = accums;
|
||||||
|
|
||||||
|
for (i = 0; i < cpus + sockets; i++) {
|
||||||
|
s_config[i] = config;
|
||||||
|
if (i < cpus)
|
||||||
|
scnprintf(accums[i].label, 10,
|
||||||
|
"Ecore%03u", i);
|
||||||
|
else
|
||||||
|
scnprintf(accums[i].label, 10,
|
||||||
|
"Esocket%u", (i - cpus));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int amd_energy_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
struct amd_energy_data *data;
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev,
|
||||||
|
sizeof(struct amd_energy_data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data->chip.ops = &amd_energy_ops;
|
||||||
|
data->chip.info = data->info;
|
||||||
|
|
||||||
|
dev_set_drvdata(dev, data);
|
||||||
|
/* Populate per-core energy reporting */
|
||||||
|
data->info[0] = &data->energy_info;
|
||||||
|
amd_create_sensor(dev, data, hwmon_energy,
|
||||||
|
HWMON_E_INPUT | HWMON_E_LABEL);
|
||||||
|
|
||||||
|
mutex_init(&data->lock);
|
||||||
|
get_energy_units(data);
|
||||||
|
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_info(dev, DRVNAME,
|
||||||
|
data,
|
||||||
|
&data->chip,
|
||||||
|
NULL);
|
||||||
|
if (IS_ERR(hwmon_dev))
|
||||||
|
return PTR_ERR(hwmon_dev);
|
||||||
|
|
||||||
|
data->wrap_accumulate = kthread_run(energy_accumulator, data,
|
||||||
|
"%s", dev_name(hwmon_dev));
|
||||||
|
if (IS_ERR(data->wrap_accumulate))
|
||||||
|
return PTR_ERR(data->wrap_accumulate);
|
||||||
|
|
||||||
|
return PTR_ERR_OR_ZERO(data->wrap_accumulate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int amd_energy_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct amd_energy_data *data = dev_get_drvdata(&pdev->dev);
|
||||||
|
|
||||||
|
if (data && data->wrap_accumulate)
|
||||||
|
kthread_stop(data->wrap_accumulate);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct platform_device_id amd_energy_ids[] = {
|
||||||
|
{ .name = DRVNAME, },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(platform, amd_energy_ids);
|
||||||
|
|
||||||
|
static struct platform_driver amd_energy_driver = {
|
||||||
|
.probe = amd_energy_probe,
|
||||||
|
.remove = amd_energy_remove,
|
||||||
|
.id_table = amd_energy_ids,
|
||||||
|
.driver = {
|
||||||
|
.name = DRVNAME,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_device *amd_energy_platdev;
|
||||||
|
|
||||||
|
static const struct x86_cpu_id cpu_ids[] __initconst = {
|
||||||
|
X86_MATCH_VENDOR_FAM(AMD, 0x17, NULL),
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(x86cpu, cpu_ids);
|
||||||
|
|
||||||
|
static int __init amd_energy_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!x86_match_cpu(cpu_ids))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
ret = platform_driver_register(&amd_energy_driver);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
amd_energy_platdev = platform_device_alloc(DRVNAME, 0);
|
||||||
|
if (!amd_energy_platdev) {
|
||||||
|
platform_driver_unregister(&amd_energy_driver);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = platform_device_add(amd_energy_platdev);
|
||||||
|
if (ret) {
|
||||||
|
platform_device_put(amd_energy_platdev);
|
||||||
|
platform_driver_unregister(&amd_energy_driver);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit amd_energy_exit(void)
|
||||||
|
{
|
||||||
|
platform_device_unregister(amd_energy_platdev);
|
||||||
|
platform_driver_unregister(&amd_energy_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(amd_energy_init);
|
||||||
|
module_exit(amd_energy_exit);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("Driver for AMD Energy reporting from RAPL MSR via HWMON interface");
|
||||||
|
MODULE_AUTHOR("Naveen Krishna Chatradhi <nchatrad@amd.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
@ -156,14 +156,19 @@ static struct workqueue_struct *applesmc_led_wq;
|
|||||||
*/
|
*/
|
||||||
static int wait_read(void)
|
static int wait_read(void)
|
||||||
{
|
{
|
||||||
|
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
|
||||||
u8 status;
|
u8 status;
|
||||||
int us;
|
int us;
|
||||||
|
|
||||||
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
||||||
udelay(us);
|
usleep_range(us, us * 16);
|
||||||
status = inb(APPLESMC_CMD_PORT);
|
status = inb(APPLESMC_CMD_PORT);
|
||||||
/* read: wait for smc to settle */
|
/* read: wait for smc to settle */
|
||||||
if (status & 0x01)
|
if (status & 0x01)
|
||||||
return 0;
|
return 0;
|
||||||
|
/* timeout: give up */
|
||||||
|
if (time_after(jiffies, end))
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pr_warn("wait_read() fail: 0x%02x\n", status);
|
pr_warn("wait_read() fail: 0x%02x\n", status);
|
||||||
@ -178,10 +183,11 @@ static int send_byte(u8 cmd, u16 port)
|
|||||||
{
|
{
|
||||||
u8 status;
|
u8 status;
|
||||||
int us;
|
int us;
|
||||||
|
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
|
||||||
|
|
||||||
outb(cmd, port);
|
outb(cmd, port);
|
||||||
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
||||||
udelay(us);
|
usleep_range(us, us * 16);
|
||||||
status = inb(APPLESMC_CMD_PORT);
|
status = inb(APPLESMC_CMD_PORT);
|
||||||
/* write: wait for smc to settle */
|
/* write: wait for smc to settle */
|
||||||
if (status & 0x02)
|
if (status & 0x02)
|
||||||
@ -190,7 +196,7 @@ static int send_byte(u8 cmd, u16 port)
|
|||||||
if (status & 0x04)
|
if (status & 0x04)
|
||||||
return 0;
|
return 0;
|
||||||
/* timeout: give up */
|
/* timeout: give up */
|
||||||
if (us << 1 == APPLESMC_MAX_WAIT)
|
if (time_after(jiffies, end))
|
||||||
break;
|
break;
|
||||||
/* busy: long wait and resend */
|
/* busy: long wait and resend */
|
||||||
udelay(APPLESMC_RETRY_WAIT);
|
udelay(APPLESMC_RETRY_WAIT);
|
||||||
|
1146
drivers/hwmon/bt1-pvt.c
Normal file
1146
drivers/hwmon/bt1-pvt.c
Normal file
File diff suppressed because it is too large
Load Diff
244
drivers/hwmon/bt1-pvt.h
Normal file
244
drivers/hwmon/bt1-pvt.h
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
|
||||||
|
*
|
||||||
|
* Baikal-T1 Process, Voltage, Temperature sensor driver
|
||||||
|
*/
|
||||||
|
#ifndef __HWMON_BT1_PVT_H__
|
||||||
|
#define __HWMON_BT1_PVT_H__
|
||||||
|
|
||||||
|
#include <linux/completion.h>
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/seqlock.h>
|
||||||
|
|
||||||
|
/* Baikal-T1 PVT registers and their bitfields */
|
||||||
|
#define PVT_CTRL 0x00
|
||||||
|
#define PVT_CTRL_EN BIT(0)
|
||||||
|
#define PVT_CTRL_MODE_FLD 1
|
||||||
|
#define PVT_CTRL_MODE_MASK GENMASK(3, PVT_CTRL_MODE_FLD)
|
||||||
|
#define PVT_CTRL_MODE_TEMP 0x0
|
||||||
|
#define PVT_CTRL_MODE_VOLT 0x1
|
||||||
|
#define PVT_CTRL_MODE_LVT 0x2
|
||||||
|
#define PVT_CTRL_MODE_HVT 0x4
|
||||||
|
#define PVT_CTRL_MODE_SVT 0x6
|
||||||
|
#define PVT_CTRL_TRIM_FLD 4
|
||||||
|
#define PVT_CTRL_TRIM_MASK GENMASK(8, PVT_CTRL_TRIM_FLD)
|
||||||
|
#define PVT_DATA 0x04
|
||||||
|
#define PVT_DATA_VALID BIT(10)
|
||||||
|
#define PVT_DATA_DATA_FLD 0
|
||||||
|
#define PVT_DATA_DATA_MASK GENMASK(9, PVT_DATA_DATA_FLD)
|
||||||
|
#define PVT_TTHRES 0x08
|
||||||
|
#define PVT_VTHRES 0x0C
|
||||||
|
#define PVT_LTHRES 0x10
|
||||||
|
#define PVT_HTHRES 0x14
|
||||||
|
#define PVT_STHRES 0x18
|
||||||
|
#define PVT_THRES_LO_FLD 0
|
||||||
|
#define PVT_THRES_LO_MASK GENMASK(9, PVT_THRES_LO_FLD)
|
||||||
|
#define PVT_THRES_HI_FLD 10
|
||||||
|
#define PVT_THRES_HI_MASK GENMASK(19, PVT_THRES_HI_FLD)
|
||||||
|
#define PVT_TTIMEOUT 0x1C
|
||||||
|
#define PVT_INTR_STAT 0x20
|
||||||
|
#define PVT_INTR_MASK 0x24
|
||||||
|
#define PVT_RAW_INTR_STAT 0x28
|
||||||
|
#define PVT_INTR_DVALID BIT(0)
|
||||||
|
#define PVT_INTR_TTHRES_LO BIT(1)
|
||||||
|
#define PVT_INTR_TTHRES_HI BIT(2)
|
||||||
|
#define PVT_INTR_VTHRES_LO BIT(3)
|
||||||
|
#define PVT_INTR_VTHRES_HI BIT(4)
|
||||||
|
#define PVT_INTR_LTHRES_LO BIT(5)
|
||||||
|
#define PVT_INTR_LTHRES_HI BIT(6)
|
||||||
|
#define PVT_INTR_HTHRES_LO BIT(7)
|
||||||
|
#define PVT_INTR_HTHRES_HI BIT(8)
|
||||||
|
#define PVT_INTR_STHRES_LO BIT(9)
|
||||||
|
#define PVT_INTR_STHRES_HI BIT(10)
|
||||||
|
#define PVT_INTR_ALL GENMASK(10, 0)
|
||||||
|
#define PVT_CLR_INTR 0x2C
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PVT sensors-related limits and default values
|
||||||
|
* @PVT_TEMP_MIN: Minimal temperature in millidegrees of Celsius.
|
||||||
|
* @PVT_TEMP_MAX: Maximal temperature in millidegrees of Celsius.
|
||||||
|
* @PVT_TEMP_CHS: Number of temperature hwmon channels.
|
||||||
|
* @PVT_VOLT_MIN: Minimal voltage in mV.
|
||||||
|
* @PVT_VOLT_MAX: Maximal voltage in mV.
|
||||||
|
* @PVT_VOLT_CHS: Number of voltage hwmon channels.
|
||||||
|
* @PVT_DATA_MIN: Minimal PVT raw data value.
|
||||||
|
* @PVT_DATA_MAX: Maximal PVT raw data value.
|
||||||
|
* @PVT_TRIM_MIN: Minimal temperature sensor trim value.
|
||||||
|
* @PVT_TRIM_MAX: Maximal temperature sensor trim value.
|
||||||
|
* @PVT_TRIM_DEF: Default temperature sensor trim value (set a proper value
|
||||||
|
* when one is determined for Baikal-T1 SoC).
|
||||||
|
* @PVT_TRIM_TEMP: Maximum temperature encoded by the trim factor.
|
||||||
|
* @PVT_TRIM_STEP: Temperature stride corresponding to the trim value.
|
||||||
|
* @PVT_TOUT_MIN: Minimal timeout between samples in nanoseconds.
|
||||||
|
* @PVT_TOUT_DEF: Default data measurements timeout. In case if alarms are
|
||||||
|
* activated the PVT IRQ is enabled to be raised after each
|
||||||
|
* conversion in order to have the thresholds checked and the
|
||||||
|
* converted value cached. Too frequent conversions may cause
|
||||||
|
* the system CPU overload. Lets set the 50ms delay between
|
||||||
|
* them by default to prevent this.
|
||||||
|
*/
|
||||||
|
#define PVT_TEMP_MIN -48380L
|
||||||
|
#define PVT_TEMP_MAX 147438L
|
||||||
|
#define PVT_TEMP_CHS 1
|
||||||
|
#define PVT_VOLT_MIN 620L
|
||||||
|
#define PVT_VOLT_MAX 1168L
|
||||||
|
#define PVT_VOLT_CHS 4
|
||||||
|
#define PVT_DATA_MIN 0
|
||||||
|
#define PVT_DATA_MAX (PVT_DATA_DATA_MASK >> PVT_DATA_DATA_FLD)
|
||||||
|
#define PVT_TRIM_MIN 0
|
||||||
|
#define PVT_TRIM_MAX (PVT_CTRL_TRIM_MASK >> PVT_CTRL_TRIM_FLD)
|
||||||
|
#define PVT_TRIM_TEMP 7130
|
||||||
|
#define PVT_TRIM_STEP (PVT_TRIM_TEMP / PVT_TRIM_MAX)
|
||||||
|
#define PVT_TRIM_DEF 0
|
||||||
|
#define PVT_TOUT_MIN (NSEC_PER_SEC / 3000)
|
||||||
|
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
|
||||||
|
# define PVT_TOUT_DEF 60000
|
||||||
|
#else
|
||||||
|
# define PVT_TOUT_DEF 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* enum pvt_sensor_type - Baikal-T1 PVT sensor types (correspond to each PVT
|
||||||
|
* sampling mode)
|
||||||
|
* @PVT_SENSOR*: helpers to traverse the sensors in loops.
|
||||||
|
* @PVT_TEMP: PVT Temperature sensor.
|
||||||
|
* @PVT_VOLT: PVT Voltage sensor.
|
||||||
|
* @PVT_LVT: PVT Low-Voltage threshold sensor.
|
||||||
|
* @PVT_HVT: PVT High-Voltage threshold sensor.
|
||||||
|
* @PVT_SVT: PVT Standard-Voltage threshold sensor.
|
||||||
|
*/
|
||||||
|
enum pvt_sensor_type {
|
||||||
|
PVT_SENSOR_FIRST,
|
||||||
|
PVT_TEMP = PVT_SENSOR_FIRST,
|
||||||
|
PVT_VOLT,
|
||||||
|
PVT_LVT,
|
||||||
|
PVT_HVT,
|
||||||
|
PVT_SVT,
|
||||||
|
PVT_SENSOR_LAST = PVT_SVT,
|
||||||
|
PVT_SENSORS_NUM
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* enum pvt_clock_type - Baikal-T1 PVT clocks.
|
||||||
|
* @PVT_CLOCK_APB: APB clock.
|
||||||
|
* @PVT_CLOCK_REF: PVT reference clock.
|
||||||
|
*/
|
||||||
|
enum pvt_clock_type {
|
||||||
|
PVT_CLOCK_APB,
|
||||||
|
PVT_CLOCK_REF,
|
||||||
|
PVT_CLOCK_NUM
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct pvt_sensor_info - Baikal-T1 PVT sensor informational structure
|
||||||
|
* @channel: Sensor channel ID.
|
||||||
|
* @label: hwmon sensor label.
|
||||||
|
* @mode: PVT mode corresponding to the channel.
|
||||||
|
* @thres_base: upper and lower threshold values of the sensor.
|
||||||
|
* @thres_sts_lo: low threshold status bitfield.
|
||||||
|
* @thres_sts_hi: high threshold status bitfield.
|
||||||
|
* @type: Sensor type.
|
||||||
|
* @attr_min_alarm: Min alarm attribute ID.
|
||||||
|
* @attr_min_alarm: Max alarm attribute ID.
|
||||||
|
*/
|
||||||
|
struct pvt_sensor_info {
|
||||||
|
int channel;
|
||||||
|
const char *label;
|
||||||
|
u32 mode;
|
||||||
|
unsigned long thres_base;
|
||||||
|
u32 thres_sts_lo;
|
||||||
|
u32 thres_sts_hi;
|
||||||
|
enum hwmon_sensor_types type;
|
||||||
|
u32 attr_min_alarm;
|
||||||
|
u32 attr_max_alarm;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PVT_SENSOR_INFO(_ch, _label, _type, _mode, _thres) \
|
||||||
|
{ \
|
||||||
|
.channel = _ch, \
|
||||||
|
.label = _label, \
|
||||||
|
.mode = PVT_CTRL_MODE_ ##_mode, \
|
||||||
|
.thres_base = PVT_ ##_thres, \
|
||||||
|
.thres_sts_lo = PVT_INTR_ ##_thres## _LO, \
|
||||||
|
.thres_sts_hi = PVT_INTR_ ##_thres## _HI, \
|
||||||
|
.type = _type, \
|
||||||
|
.attr_min_alarm = _type## _min, \
|
||||||
|
.attr_max_alarm = _type## _max, \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct pvt_cache - PVT sensors data cache
|
||||||
|
* @data: data cache in raw format.
|
||||||
|
* @thres_sts_lo: low threshold status saved on the previous data conversion.
|
||||||
|
* @thres_sts_hi: high threshold status saved on the previous data conversion.
|
||||||
|
* @data_seqlock: cached data seq-lock.
|
||||||
|
* @conversion: data conversion completion.
|
||||||
|
*/
|
||||||
|
struct pvt_cache {
|
||||||
|
u32 data;
|
||||||
|
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
|
||||||
|
seqlock_t data_seqlock;
|
||||||
|
u32 thres_sts_lo;
|
||||||
|
u32 thres_sts_hi;
|
||||||
|
#else
|
||||||
|
struct completion conversion;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct pvt_hwmon - Baikal-T1 PVT private data
|
||||||
|
* @dev: device structure of the PVT platform device.
|
||||||
|
* @hwmon: hwmon device structure.
|
||||||
|
* @regs: pointer to the Baikal-T1 PVT registers region.
|
||||||
|
* @irq: PVT events IRQ number.
|
||||||
|
* @clks: Array of the PVT clocks descriptor (APB/ref clocks).
|
||||||
|
* @ref_clk: Pointer to the reference clocks descriptor.
|
||||||
|
* @iface_mtx: Generic interface mutex (used to lock the alarm registers
|
||||||
|
* when the alarms enabled, or the data conversion interface
|
||||||
|
* if alarms are disabled).
|
||||||
|
* @sensor: current PVT sensor the data conversion is being performed for.
|
||||||
|
* @cache: data cache descriptor.
|
||||||
|
*/
|
||||||
|
struct pvt_hwmon {
|
||||||
|
struct device *dev;
|
||||||
|
struct device *hwmon;
|
||||||
|
|
||||||
|
void __iomem *regs;
|
||||||
|
int irq;
|
||||||
|
|
||||||
|
struct clk_bulk_data clks[PVT_CLOCK_NUM];
|
||||||
|
|
||||||
|
struct mutex iface_mtx;
|
||||||
|
enum pvt_sensor_type sensor;
|
||||||
|
struct pvt_cache cache[PVT_SENSORS_NUM];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct pvt_poly_term - a term descriptor of the PVT data translation
|
||||||
|
* polynomial
|
||||||
|
* @deg: degree of the term.
|
||||||
|
* @coef: multiplication factor of the term.
|
||||||
|
* @divider: distributed divider per each degree.
|
||||||
|
* @divider_leftover: divider leftover, which couldn't be redistributed.
|
||||||
|
*/
|
||||||
|
struct pvt_poly_term {
|
||||||
|
unsigned int deg;
|
||||||
|
long coef;
|
||||||
|
long divider;
|
||||||
|
long divider_leftover;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct pvt_poly - PVT data translation polynomial descriptor
|
||||||
|
* @total_divider: total data divider.
|
||||||
|
* @terms: polynomial terms up to a free one.
|
||||||
|
*/
|
||||||
|
struct pvt_poly {
|
||||||
|
long total_divider;
|
||||||
|
struct pvt_poly_term terms[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __HWMON_BT1_PVT_H__ */
|
@ -1072,13 +1072,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||||||
DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
|
DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.ident = "Dell XPS421",
|
|
||||||
.matches = {
|
|
||||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
||||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.ident = "Dell Studio",
|
.ident = "Dell Studio",
|
||||||
.matches = {
|
.matches = {
|
||||||
@ -1087,14 +1080,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||||||
},
|
},
|
||||||
.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
|
.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
.ident = "Dell XPS 13",
|
|
||||||
.matches = {
|
|
||||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
||||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
|
|
||||||
},
|
|
||||||
.driver_data = (void *)&i8k_config_data[DELL_XPS],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
.ident = "Dell XPS M140",
|
.ident = "Dell XPS M140",
|
||||||
.matches = {
|
.matches = {
|
||||||
@ -1104,17 +1089,10 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||||||
.driver_data = (void *)&i8k_config_data[DELL_XPS],
|
.driver_data = (void *)&i8k_config_data[DELL_XPS],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.ident = "Dell XPS 15 9560",
|
.ident = "Dell XPS",
|
||||||
.matches = {
|
.matches = {
|
||||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9560"),
|
DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.ident = "Dell XPS 15 9570",
|
|
||||||
.matches = {
|
|
||||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
|
||||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9570"),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ }
|
{ }
|
||||||
|
390
drivers/hwmon/gsc-hwmon.c
Normal file
390
drivers/hwmon/gsc-hwmon.c
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Driver for Gateworks System Controller Hardware Monitor module
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Gateworks Corporation
|
||||||
|
*/
|
||||||
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/hwmon-sysfs.h>
|
||||||
|
#include <linux/mfd/gsc.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
#include <linux/platform_data/gsc_hwmon.h>
|
||||||
|
|
||||||
|
#define GSC_HWMON_MAX_TEMP_CH 16
|
||||||
|
#define GSC_HWMON_MAX_IN_CH 16
|
||||||
|
|
||||||
|
#define GSC_HWMON_RESOLUTION 12
|
||||||
|
#define GSC_HWMON_VREF 2500
|
||||||
|
|
||||||
|
struct gsc_hwmon_data {
|
||||||
|
struct gsc_dev *gsc;
|
||||||
|
struct gsc_hwmon_platform_data *pdata;
|
||||||
|
struct regmap *regmap;
|
||||||
|
const struct gsc_hwmon_channel *temp_ch[GSC_HWMON_MAX_TEMP_CH];
|
||||||
|
const struct gsc_hwmon_channel *in_ch[GSC_HWMON_MAX_IN_CH];
|
||||||
|
u32 temp_config[GSC_HWMON_MAX_TEMP_CH + 1];
|
||||||
|
u32 in_config[GSC_HWMON_MAX_IN_CH + 1];
|
||||||
|
struct hwmon_channel_info temp_info;
|
||||||
|
struct hwmon_channel_info in_info;
|
||||||
|
const struct hwmon_channel_info *info[3];
|
||||||
|
struct hwmon_chip_info chip;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct regmap_bus gsc_hwmon_regmap_bus = {
|
||||||
|
.reg_read = gsc_read,
|
||||||
|
.reg_write = gsc_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_config gsc_hwmon_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.cache_type = REGCACHE_NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static ssize_t pwm_auto_point_temp_show(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
u8 reg = hwmon->pdata->fan_base + (2 * attr->index);
|
||||||
|
u8 regs[2];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_bulk_read(hwmon->regmap, reg, regs, 2);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = regs[0] | regs[1] << 8;
|
||||||
|
return sprintf(buf, "%d\n", ret * 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t pwm_auto_point_temp_store(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
u8 reg = hwmon->pdata->fan_base + (2 * attr->index);
|
||||||
|
u8 regs[2];
|
||||||
|
long temp;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (kstrtol(buf, 10, &temp))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
temp = clamp_val(temp, 0, 10000);
|
||||||
|
temp = DIV_ROUND_CLOSEST(temp, 10);
|
||||||
|
|
||||||
|
regs[0] = temp & 0xff;
|
||||||
|
regs[1] = (temp >> 8) & 0xff;
|
||||||
|
err = regmap_bulk_write(hwmon->regmap, reg, regs, 2);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t pwm_auto_point_pwm_show(struct device *dev,
|
||||||
|
struct device_attribute *devattr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||||
|
|
||||||
|
return sprintf(buf, "%d\n", 255 * (50 + (attr->index * 10)) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point1_pwm, pwm_auto_point_pwm, 0);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point1_temp, pwm_auto_point_temp, 0);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point2_pwm, pwm_auto_point_pwm, 1);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point2_temp, pwm_auto_point_temp, 1);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point3_pwm, pwm_auto_point_pwm, 2);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point3_temp, pwm_auto_point_temp, 2);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point4_pwm, pwm_auto_point_pwm, 3);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point4_temp, pwm_auto_point_temp, 3);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point5_pwm, pwm_auto_point_pwm, 4);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point5_temp, pwm_auto_point_temp, 4);
|
||||||
|
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(pwm1_auto_point6_pwm, pwm_auto_point_pwm, 5);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(pwm1_auto_point6_temp, pwm_auto_point_temp, 5);
|
||||||
|
|
||||||
|
static struct attribute *gsc_hwmon_attributes[] = {
|
||||||
|
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group gsc_hwmon_group = {
|
||||||
|
.attrs = gsc_hwmon_attributes,
|
||||||
|
};
|
||||||
|
__ATTRIBUTE_GROUPS(gsc_hwmon);
|
||||||
|
|
||||||
|
static int
|
||||||
|
gsc_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
|
||||||
|
int channel, long *val)
|
||||||
|
{
|
||||||
|
struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
|
||||||
|
const struct gsc_hwmon_channel *ch;
|
||||||
|
int sz, ret;
|
||||||
|
long tmp;
|
||||||
|
u8 buf[3];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_in:
|
||||||
|
ch = hwmon->in_ch[channel];
|
||||||
|
break;
|
||||||
|
case hwmon_temp:
|
||||||
|
ch = hwmon->temp_ch[channel];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
sz = (ch->mode == mode_voltage) ? 3 : 2;
|
||||||
|
ret = regmap_bulk_read(hwmon->regmap, ch->reg, buf, sz);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
tmp = 0;
|
||||||
|
while (sz-- > 0)
|
||||||
|
tmp |= (buf[sz] << (8 * sz));
|
||||||
|
|
||||||
|
switch (ch->mode) {
|
||||||
|
case mode_temperature:
|
||||||
|
if (tmp > 0x8000)
|
||||||
|
tmp -= 0xffff;
|
||||||
|
break;
|
||||||
|
case mode_voltage_raw:
|
||||||
|
tmp = clamp_val(tmp, 0, BIT(GSC_HWMON_RESOLUTION));
|
||||||
|
/* scale based on ref voltage and ADC resolution */
|
||||||
|
tmp *= GSC_HWMON_VREF;
|
||||||
|
tmp >>= GSC_HWMON_RESOLUTION;
|
||||||
|
/* scale based on optional voltage divider */
|
||||||
|
if (ch->vdiv[0] && ch->vdiv[1]) {
|
||||||
|
tmp *= (ch->vdiv[0] + ch->vdiv[1]);
|
||||||
|
tmp /= ch->vdiv[1];
|
||||||
|
}
|
||||||
|
/* adjust by uV offset */
|
||||||
|
tmp += ch->mvoffset;
|
||||||
|
break;
|
||||||
|
case mode_voltage:
|
||||||
|
/* no adjustment needed */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*val = tmp;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gsc_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel, const char **buf)
|
||||||
|
{
|
||||||
|
struct gsc_hwmon_data *hwmon = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case hwmon_in:
|
||||||
|
*buf = hwmon->in_ch[channel]->name;
|
||||||
|
break;
|
||||||
|
case hwmon_temp:
|
||||||
|
*buf = hwmon->temp_ch[channel]->name;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -ENOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static umode_t
|
||||||
|
gsc_hwmon_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
|
||||||
|
int ch)
|
||||||
|
{
|
||||||
|
return 0444;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hwmon_ops gsc_hwmon_ops = {
|
||||||
|
.is_visible = gsc_hwmon_is_visible,
|
||||||
|
.read = gsc_hwmon_read,
|
||||||
|
.read_string = gsc_hwmon_read_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct gsc_hwmon_platform_data *
|
||||||
|
gsc_hwmon_get_devtree_pdata(struct device *dev)
|
||||||
|
{
|
||||||
|
struct gsc_hwmon_platform_data *pdata;
|
||||||
|
struct gsc_hwmon_channel *ch;
|
||||||
|
struct fwnode_handle *child;
|
||||||
|
struct device_node *fan;
|
||||||
|
int nchannels;
|
||||||
|
|
||||||
|
nchannels = device_get_child_node_count(dev);
|
||||||
|
if (nchannels == 0)
|
||||||
|
return ERR_PTR(-ENODEV);
|
||||||
|
|
||||||
|
pdata = devm_kzalloc(dev,
|
||||||
|
sizeof(*pdata) + nchannels * sizeof(*ch),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!pdata)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
ch = (struct gsc_hwmon_channel *)(pdata + 1);
|
||||||
|
pdata->channels = ch;
|
||||||
|
pdata->nchannels = nchannels;
|
||||||
|
|
||||||
|
/* fan controller base address */
|
||||||
|
fan = of_find_compatible_node(dev->parent->of_node, NULL, "gw,gsc-fan");
|
||||||
|
if (fan && of_property_read_u32(fan, "reg", &pdata->fan_base)) {
|
||||||
|
dev_err(dev, "fan node without base\n");
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* allocate structures for channels and count instances of each type */
|
||||||
|
device_for_each_child_node(dev, child) {
|
||||||
|
if (fwnode_property_read_string(child, "label", &ch->name)) {
|
||||||
|
dev_err(dev, "channel without label\n");
|
||||||
|
fwnode_handle_put(child);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
if (fwnode_property_read_u32(child, "reg", &ch->reg)) {
|
||||||
|
dev_err(dev, "channel without reg\n");
|
||||||
|
fwnode_handle_put(child);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
if (fwnode_property_read_u32(child, "gw,mode", &ch->mode)) {
|
||||||
|
dev_err(dev, "channel without mode\n");
|
||||||
|
fwnode_handle_put(child);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
if (ch->mode > mode_max) {
|
||||||
|
dev_err(dev, "invalid channel mode\n");
|
||||||
|
fwnode_handle_put(child);
|
||||||
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fwnode_property_read_u32(child,
|
||||||
|
"gw,voltage-offset-microvolt",
|
||||||
|
&ch->mvoffset))
|
||||||
|
ch->mvoffset /= 1000;
|
||||||
|
fwnode_property_read_u32_array(child,
|
||||||
|
"gw,voltage-divider-ohms",
|
||||||
|
ch->vdiv, ARRAY_SIZE(ch->vdiv));
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gsc_hwmon_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct gsc_dev *gsc = dev_get_drvdata(pdev->dev.parent);
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device *hwmon_dev;
|
||||||
|
struct gsc_hwmon_platform_data *pdata = dev_get_platdata(dev);
|
||||||
|
struct gsc_hwmon_data *hwmon;
|
||||||
|
const struct attribute_group **groups;
|
||||||
|
int i, i_in, i_temp;
|
||||||
|
|
||||||
|
if (!pdata) {
|
||||||
|
pdata = gsc_hwmon_get_devtree_pdata(dev);
|
||||||
|
if (IS_ERR(pdata))
|
||||||
|
return PTR_ERR(pdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
|
||||||
|
if (!hwmon)
|
||||||
|
return -ENOMEM;
|
||||||
|
hwmon->gsc = gsc;
|
||||||
|
hwmon->pdata = pdata;
|
||||||
|
|
||||||
|
hwmon->regmap = devm_regmap_init(dev, &gsc_hwmon_regmap_bus,
|
||||||
|
gsc->i2c_hwmon,
|
||||||
|
&gsc_hwmon_regmap_config);
|
||||||
|
if (IS_ERR(hwmon->regmap))
|
||||||
|
return PTR_ERR(hwmon->regmap);
|
||||||
|
|
||||||
|
for (i = 0, i_in = 0, i_temp = 0; i < hwmon->pdata->nchannels; i++) {
|
||||||
|
const struct gsc_hwmon_channel *ch = &pdata->channels[i];
|
||||||
|
|
||||||
|
switch (ch->mode) {
|
||||||
|
case mode_temperature:
|
||||||
|
if (i_temp == GSC_HWMON_MAX_TEMP_CH) {
|
||||||
|
dev_err(gsc->dev, "too many temp channels\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
hwmon->temp_ch[i_temp] = ch;
|
||||||
|
hwmon->temp_config[i_temp] = HWMON_T_INPUT |
|
||||||
|
HWMON_T_LABEL;
|
||||||
|
i_temp++;
|
||||||
|
break;
|
||||||
|
case mode_voltage:
|
||||||
|
case mode_voltage_raw:
|
||||||
|
if (i_in == GSC_HWMON_MAX_IN_CH) {
|
||||||
|
dev_err(gsc->dev, "too many input channels\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
hwmon->in_ch[i_in] = ch;
|
||||||
|
hwmon->in_config[i_in] =
|
||||||
|
HWMON_I_INPUT | HWMON_I_LABEL;
|
||||||
|
i_in++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dev_err(gsc->dev, "invalid mode: %d\n", ch->mode);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* setup config structures */
|
||||||
|
hwmon->chip.ops = &gsc_hwmon_ops;
|
||||||
|
hwmon->chip.info = hwmon->info;
|
||||||
|
hwmon->info[0] = &hwmon->temp_info;
|
||||||
|
hwmon->info[1] = &hwmon->in_info;
|
||||||
|
hwmon->temp_info.type = hwmon_temp;
|
||||||
|
hwmon->temp_info.config = hwmon->temp_config;
|
||||||
|
hwmon->in_info.type = hwmon_in;
|
||||||
|
hwmon->in_info.config = hwmon->in_config;
|
||||||
|
|
||||||
|
groups = pdata->fan_base ? gsc_hwmon_groups : NULL;
|
||||||
|
hwmon_dev = devm_hwmon_device_register_with_info(dev,
|
||||||
|
KBUILD_MODNAME, hwmon,
|
||||||
|
&hwmon->chip, groups);
|
||||||
|
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id gsc_hwmon_of_match[] = {
|
||||||
|
{ .compatible = "gw,gsc-adc", },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct platform_driver gsc_hwmon_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "gsc-hwmon",
|
||||||
|
.of_match_table = gsc_hwmon_of_match,
|
||||||
|
},
|
||||||
|
.probe = gsc_hwmon_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(gsc_hwmon_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
|
||||||
|
MODULE_DESCRIPTION("GSC hardware monitor driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@ -15,6 +15,7 @@
|
|||||||
#include <linux/gfp.h>
|
#include <linux/gfp.h>
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
#include <linux/idr.h>
|
#include <linux/idr.h>
|
||||||
|
#include <linux/list.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/pci.h>
|
#include <linux/pci.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
@ -31,7 +32,7 @@ struct hwmon_device {
|
|||||||
const char *name;
|
const char *name;
|
||||||
struct device dev;
|
struct device dev;
|
||||||
const struct hwmon_chip_info *chip;
|
const struct hwmon_chip_info *chip;
|
||||||
|
struct list_head tzdata;
|
||||||
struct attribute_group group;
|
struct attribute_group group;
|
||||||
const struct attribute_group **groups;
|
const struct attribute_group **groups;
|
||||||
};
|
};
|
||||||
@ -55,12 +56,12 @@ struct hwmon_device_attribute {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Thermal zone information
|
* Thermal zone information
|
||||||
* In addition to the reference to the hwmon device,
|
|
||||||
* also provides the sensor index.
|
|
||||||
*/
|
*/
|
||||||
struct hwmon_thermal_data {
|
struct hwmon_thermal_data {
|
||||||
|
struct list_head node; /* hwmon tzdata list entry */
|
||||||
struct device *dev; /* Reference to hwmon device */
|
struct device *dev; /* Reference to hwmon device */
|
||||||
int index; /* sensor index */
|
int index; /* sensor index */
|
||||||
|
struct thermal_zone_device *tzd;/* thermal zone device */
|
||||||
};
|
};
|
||||||
|
|
||||||
static ssize_t
|
static ssize_t
|
||||||
@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
|
|||||||
.get_temp = hwmon_thermal_get_temp,
|
.get_temp = hwmon_thermal_get_temp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void hwmon_thermal_remove_sensor(void *data)
|
||||||
|
{
|
||||||
|
list_del(data);
|
||||||
|
}
|
||||||
|
|
||||||
static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
||||||
{
|
{
|
||||||
|
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||||
struct hwmon_thermal_data *tdata;
|
struct hwmon_thermal_data *tdata;
|
||||||
struct thermal_zone_device *tzd;
|
struct thermal_zone_device *tzd;
|
||||||
|
int err;
|
||||||
|
|
||||||
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
|
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
|
||||||
if (!tdata)
|
if (!tdata)
|
||||||
@ -177,13 +185,68 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
|||||||
if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
|
if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
|
||||||
return PTR_ERR(tzd);
|
return PTR_ERR(tzd);
|
||||||
|
|
||||||
|
err = devm_add_action(dev, hwmon_thermal_remove_sensor, &tdata->node);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
tdata->tzd = tzd;
|
||||||
|
list_add(&tdata->node, &hwdev->tzdata);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int hwmon_thermal_register_sensors(struct device *dev)
|
||||||
|
{
|
||||||
|
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||||
|
const struct hwmon_chip_info *chip = hwdev->chip;
|
||||||
|
const struct hwmon_channel_info **info = chip->info;
|
||||||
|
void *drvdata = dev_get_drvdata(dev);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 1; info[i]; i++) {
|
||||||
|
int j;
|
||||||
|
|
||||||
|
if (info[i]->type != hwmon_temp)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (j = 0; info[i]->config[j]; j++) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!(info[i]->config[j] & HWMON_T_INPUT) ||
|
||||||
|
!chip->ops->is_visible(drvdata, hwmon_temp,
|
||||||
|
hwmon_temp_input, j))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
err = hwmon_thermal_add_sensor(dev, j);
|
||||||
|
if (err)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hwmon_thermal_notify(struct device *dev, int index)
|
||||||
|
{
|
||||||
|
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||||
|
struct hwmon_thermal_data *tzdata;
|
||||||
|
|
||||||
|
list_for_each_entry(tzdata, &hwdev->tzdata, node) {
|
||||||
|
if (tzdata->index == index) {
|
||||||
|
thermal_zone_device_update(tzdata->tzd,
|
||||||
|
THERMAL_EVENT_UNSPECIFIED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
static int hwmon_thermal_register_sensors(struct device *dev)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hwmon_thermal_notify(struct device *dev, int index) { }
|
||||||
|
|
||||||
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
|
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
|
||||||
|
|
||||||
static int hwmon_attr_base(enum hwmon_sensor_types type)
|
static int hwmon_attr_base(enum hwmon_sensor_types type)
|
||||||
@ -511,6 +574,35 @@ static const int __templates_size[] = {
|
|||||||
[hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates),
|
[hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel)
|
||||||
|
{
|
||||||
|
char sattr[MAX_SYSFS_ATTR_NAME_LENGTH];
|
||||||
|
const char * const *templates;
|
||||||
|
const char *template;
|
||||||
|
int base;
|
||||||
|
|
||||||
|
if (type >= ARRAY_SIZE(__templates))
|
||||||
|
return -EINVAL;
|
||||||
|
if (attr >= __templates_size[type])
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
templates = __templates[type];
|
||||||
|
template = templates[attr];
|
||||||
|
|
||||||
|
base = hwmon_attr_base(type);
|
||||||
|
|
||||||
|
scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel);
|
||||||
|
sysfs_notify(&dev->kobj, NULL, sattr);
|
||||||
|
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
|
||||||
|
|
||||||
|
if (type == hwmon_temp)
|
||||||
|
hwmon_thermal_notify(dev, channel);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(hwmon_notify_event);
|
||||||
|
|
||||||
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
|
||||||
{
|
{
|
||||||
int i, n;
|
int i, n;
|
||||||
@ -596,7 +688,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||||||
{
|
{
|
||||||
struct hwmon_device *hwdev;
|
struct hwmon_device *hwdev;
|
||||||
struct device *hdev;
|
struct device *hdev;
|
||||||
int i, j, err, id;
|
int i, err, id;
|
||||||
|
|
||||||
/* Complain about invalid characters in hwmon name attribute */
|
/* Complain about invalid characters in hwmon name attribute */
|
||||||
if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
|
if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
|
||||||
@ -661,33 +753,19 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||||||
if (err)
|
if (err)
|
||||||
goto free_hwmon;
|
goto free_hwmon;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&hwdev->tzdata);
|
||||||
|
|
||||||
if (dev && dev->of_node && chip && chip->ops->read &&
|
if (dev && dev->of_node && chip && chip->ops->read &&
|
||||||
chip->info[0]->type == hwmon_chip &&
|
chip->info[0]->type == hwmon_chip &&
|
||||||
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
|
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
|
||||||
const struct hwmon_channel_info **info = chip->info;
|
err = hwmon_thermal_register_sensors(hdev);
|
||||||
|
if (err) {
|
||||||
for (i = 1; info[i]; i++) {
|
device_unregister(hdev);
|
||||||
if (info[i]->type != hwmon_temp)
|
/*
|
||||||
continue;
|
* Don't worry about hwdev; hwmon_dev_release(), called
|
||||||
|
* from device_unregister(), will free it.
|
||||||
for (j = 0; info[i]->config[j]; j++) {
|
*/
|
||||||
if (!chip->ops->is_visible(drvdata, hwmon_temp,
|
goto ida_remove;
|
||||||
hwmon_temp_input, j))
|
|
||||||
continue;
|
|
||||||
if (info[i]->config[j] & HWMON_T_INPUT) {
|
|
||||||
err = hwmon_thermal_add_sensor(hdev, j);
|
|
||||||
if (err) {
|
|
||||||
device_unregister(hdev);
|
|
||||||
/*
|
|
||||||
* Don't worry about hwdev;
|
|
||||||
* hwmon_dev_release(), called
|
|
||||||
* from device_unregister(),
|
|
||||||
* will free it.
|
|
||||||
*/
|
|
||||||
goto ida_remove;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +74,17 @@
|
|||||||
#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9)
|
#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9)
|
||||||
#define INA226_SHIFT_AVG(val) ((val) << 9)
|
#define INA226_SHIFT_AVG(val) ((val) << 9)
|
||||||
|
|
||||||
|
/* bit number of alert functions in Mask/Enable Register */
|
||||||
|
#define INA226_SHUNT_OVER_VOLTAGE_BIT 15
|
||||||
|
#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14
|
||||||
|
#define INA226_BUS_OVER_VOLTAGE_BIT 13
|
||||||
|
#define INA226_BUS_UNDER_VOLTAGE_BIT 12
|
||||||
|
#define INA226_POWER_OVER_LIMIT_BIT 11
|
||||||
|
|
||||||
|
/* bit mask for alert config bits of Mask/Enable Register */
|
||||||
|
#define INA226_ALERT_CONFIG_MASK 0xFC00
|
||||||
|
#define INA226_ALERT_FUNCTION_FLAG BIT(4)
|
||||||
|
|
||||||
/* common attrs, ina226 attrs and NULL */
|
/* common attrs, ina226 attrs and NULL */
|
||||||
#define INA2XX_MAX_ATTRIBUTE_GROUPS 3
|
#define INA2XX_MAX_ATTRIBUTE_GROUPS 3
|
||||||
|
|
||||||
@ -303,6 +314,145 @@ static ssize_t ina2xx_value_show(struct device *dev,
|
|||||||
ina2xx_get_value(data, attr->index, regval));
|
ina2xx_get_value(data, attr->index, regval));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval)
|
||||||
|
{
|
||||||
|
int reg;
|
||||||
|
|
||||||
|
switch (bit) {
|
||||||
|
case INA226_SHUNT_OVER_VOLTAGE_BIT:
|
||||||
|
case INA226_SHUNT_UNDER_VOLTAGE_BIT:
|
||||||
|
reg = INA2XX_SHUNT_VOLTAGE;
|
||||||
|
break;
|
||||||
|
case INA226_BUS_OVER_VOLTAGE_BIT:
|
||||||
|
case INA226_BUS_UNDER_VOLTAGE_BIT:
|
||||||
|
reg = INA2XX_BUS_VOLTAGE;
|
||||||
|
break;
|
||||||
|
case INA226_POWER_OVER_LIMIT_BIT:
|
||||||
|
reg = INA2XX_POWER;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* programmer goofed */
|
||||||
|
WARN_ON_ONCE(1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ina2xx_get_value(data, reg, regval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Turns alert limit values into register values.
|
||||||
|
* Opposite of the formula in ina2xx_get_value().
|
||||||
|
*/
|
||||||
|
static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val)
|
||||||
|
{
|
||||||
|
switch (bit) {
|
||||||
|
case INA226_SHUNT_OVER_VOLTAGE_BIT:
|
||||||
|
case INA226_SHUNT_UNDER_VOLTAGE_BIT:
|
||||||
|
val *= data->config->shunt_div;
|
||||||
|
return clamp_val(val, SHRT_MIN, SHRT_MAX);
|
||||||
|
case INA226_BUS_OVER_VOLTAGE_BIT:
|
||||||
|
case INA226_BUS_UNDER_VOLTAGE_BIT:
|
||||||
|
val = (val * 1000) << data->config->bus_voltage_shift;
|
||||||
|
val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
|
||||||
|
return clamp_val(val, 0, SHRT_MAX);
|
||||||
|
case INA226_POWER_OVER_LIMIT_BIT:
|
||||||
|
val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
|
||||||
|
return clamp_val(val, 0, USHRT_MAX);
|
||||||
|
default:
|
||||||
|
/* programmer goofed */
|
||||||
|
WARN_ON_ONCE(1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina226_alert_show(struct device *dev,
|
||||||
|
struct device_attribute *da, char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
|
struct ina2xx_data *data = dev_get_drvdata(dev);
|
||||||
|
int regval;
|
||||||
|
int val = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&data->config_lock);
|
||||||
|
ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val);
|
||||||
|
if (ret)
|
||||||
|
goto abort;
|
||||||
|
|
||||||
|
if (regval & BIT(attr->index)) {
|
||||||
|
ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, ®val);
|
||||||
|
if (ret)
|
||||||
|
goto abort;
|
||||||
|
val = ina226_reg_to_alert(data, attr->index, regval);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", val);
|
||||||
|
abort:
|
||||||
|
mutex_unlock(&data->config_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina226_alert_store(struct device *dev,
|
||||||
|
struct device_attribute *da,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
|
struct ina2xx_data *data = dev_get_drvdata(dev);
|
||||||
|
unsigned long val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = kstrtoul(buf, 10, &val);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear all alerts first to avoid accidentally triggering ALERT pin
|
||||||
|
* due to register write sequence. Then, only enable the alert
|
||||||
|
* if the value is non-zero.
|
||||||
|
*/
|
||||||
|
mutex_lock(&data->config_lock);
|
||||||
|
ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
|
||||||
|
INA226_ALERT_CONFIG_MASK, 0);
|
||||||
|
if (ret < 0)
|
||||||
|
goto abort;
|
||||||
|
|
||||||
|
ret = regmap_write(data->regmap, INA226_ALERT_LIMIT,
|
||||||
|
ina226_alert_to_reg(data, attr->index, val));
|
||||||
|
if (ret < 0)
|
||||||
|
goto abort;
|
||||||
|
|
||||||
|
if (val != 0) {
|
||||||
|
ret = regmap_update_bits(data->regmap, INA226_MASK_ENABLE,
|
||||||
|
INA226_ALERT_CONFIG_MASK,
|
||||||
|
BIT(attr->index));
|
||||||
|
if (ret < 0)
|
||||||
|
goto abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = count;
|
||||||
|
abort:
|
||||||
|
mutex_unlock(&data->config_lock);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t ina226_alarm_show(struct device *dev,
|
||||||
|
struct device_attribute *da, char *buf)
|
||||||
|
{
|
||||||
|
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
|
||||||
|
struct ina2xx_data *data = dev_get_drvdata(dev);
|
||||||
|
int regval;
|
||||||
|
int alarm = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_read(data->regmap, INA226_MASK_ENABLE, ®val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
alarm = (regval & BIT(attr->index)) &&
|
||||||
|
(regval & INA226_ALERT_FUNCTION_FLAG);
|
||||||
|
return snprintf(buf, PAGE_SIZE, "%d\n", alarm);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* In order to keep calibration register value fixed, the product
|
* In order to keep calibration register value fixed, the product
|
||||||
* of current_lsb and shunt_resistor should also be fixed and equal
|
* of current_lsb and shunt_resistor should also be fixed and equal
|
||||||
@ -392,15 +542,38 @@ static ssize_t ina226_interval_show(struct device *dev,
|
|||||||
|
|
||||||
/* shunt voltage */
|
/* shunt voltage */
|
||||||
static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE);
|
static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE);
|
||||||
|
/* shunt voltage over/under voltage alert setting and alarm */
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert,
|
||||||
|
INA226_SHUNT_OVER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert,
|
||||||
|
INA226_SHUNT_UNDER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm,
|
||||||
|
INA226_SHUNT_OVER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm,
|
||||||
|
INA226_SHUNT_UNDER_VOLTAGE_BIT);
|
||||||
|
|
||||||
/* bus voltage */
|
/* bus voltage */
|
||||||
static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE);
|
static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE);
|
||||||
|
/* bus voltage over/under voltage alert setting and alarm */
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert,
|
||||||
|
INA226_BUS_OVER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert,
|
||||||
|
INA226_BUS_UNDER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm,
|
||||||
|
INA226_BUS_OVER_VOLTAGE_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm,
|
||||||
|
INA226_BUS_UNDER_VOLTAGE_BIT);
|
||||||
|
|
||||||
/* calculated current */
|
/* calculated current */
|
||||||
static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);
|
static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);
|
||||||
|
|
||||||
/* calculated power */
|
/* calculated power */
|
||||||
static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER);
|
static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER);
|
||||||
|
/* over-limit power alert setting and alarm */
|
||||||
|
static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert,
|
||||||
|
INA226_POWER_OVER_LIMIT_BIT);
|
||||||
|
static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm,
|
||||||
|
INA226_POWER_OVER_LIMIT_BIT);
|
||||||
|
|
||||||
/* shunt resistance */
|
/* shunt resistance */
|
||||||
static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION);
|
static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION);
|
||||||
@ -423,6 +596,16 @@ static const struct attribute_group ina2xx_group = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct attribute *ina226_attrs[] = {
|
static struct attribute *ina226_attrs[] = {
|
||||||
|
&sensor_dev_attr_in0_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in0_lcrit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in0_crit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in1_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in1_lcrit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in1_crit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_power1_crit.dev_attr.attr,
|
||||||
|
&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
|
||||||
&sensor_dev_attr_update_interval.dev_attr.attr,
|
&sensor_dev_attr_update_interval.dev_attr.attr,
|
||||||
NULL,
|
NULL,
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
#include <linux/spi/spi.h>
|
#include <linux/spi/spi.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/of_device.h>
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
|
||||||
#define DRVNAME "lm70"
|
#define DRVNAME "lm70"
|
||||||
|
|
||||||
@ -148,18 +148,50 @@ static const struct of_device_id lm70_of_ids[] = {
|
|||||||
MODULE_DEVICE_TABLE(of, lm70_of_ids);
|
MODULE_DEVICE_TABLE(of, lm70_of_ids);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
static const struct acpi_device_id lm70_acpi_ids[] = {
|
||||||
|
{
|
||||||
|
.id = "LM000070",
|
||||||
|
.driver_data = LM70_CHIP_LM70,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = "TMP00121",
|
||||||
|
.driver_data = LM70_CHIP_TMP121,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = "LM000071",
|
||||||
|
.driver_data = LM70_CHIP_LM71,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.id = "LM000074",
|
||||||
|
.driver_data = LM70_CHIP_LM74,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(acpi, lm70_acpi_ids);
|
||||||
|
#endif
|
||||||
|
|
||||||
static int lm70_probe(struct spi_device *spi)
|
static int lm70_probe(struct spi_device *spi)
|
||||||
{
|
{
|
||||||
const struct of_device_id *match;
|
const struct of_device_id *of_match;
|
||||||
struct device *hwmon_dev;
|
struct device *hwmon_dev;
|
||||||
struct lm70 *p_lm70;
|
struct lm70 *p_lm70;
|
||||||
int chip;
|
int chip;
|
||||||
|
|
||||||
match = of_match_device(lm70_of_ids, &spi->dev);
|
of_match = of_match_device(lm70_of_ids, &spi->dev);
|
||||||
if (match)
|
if (of_match)
|
||||||
chip = (int)(uintptr_t)match->data;
|
chip = (int)(uintptr_t)of_match->data;
|
||||||
else
|
else {
|
||||||
chip = spi_get_device_id(spi)->driver_data;
|
#ifdef CONFIG_ACPI
|
||||||
|
const struct acpi_device_id *acpi_match;
|
||||||
|
|
||||||
|
acpi_match = acpi_match_device(lm70_acpi_ids, &spi->dev);
|
||||||
|
if (acpi_match)
|
||||||
|
chip = (int)(uintptr_t)acpi_match->driver_data;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
chip = spi_get_device_id(spi)->driver_data;
|
||||||
|
}
|
||||||
|
|
||||||
/* signaling is SPI_MODE_0 */
|
/* signaling is SPI_MODE_0 */
|
||||||
if (spi->mode & (SPI_CPOL | SPI_CPHA))
|
if (spi->mode & (SPI_CPOL | SPI_CPHA))
|
||||||
@ -195,6 +227,7 @@ static struct spi_driver lm70_driver = {
|
|||||||
.driver = {
|
.driver = {
|
||||||
.name = "lm70",
|
.name = "lm70",
|
||||||
.of_match_table = of_match_ptr(lm70_of_ids),
|
.of_match_table = of_match_ptr(lm70_of_ids),
|
||||||
|
.acpi_match_table = ACPI_PTR(lm70_acpi_ids),
|
||||||
},
|
},
|
||||||
.id_table = lm70_ids,
|
.id_table = lm70_ids,
|
||||||
.probe = lm70_probe,
|
.probe = lm70_probe,
|
||||||
|
@ -797,8 +797,10 @@ static int lm75_detect(struct i2c_client *new_client,
|
|||||||
|
|
||||||
/* First check for LM75A */
|
/* First check for LM75A */
|
||||||
if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) {
|
if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) {
|
||||||
/* LM75A returns 0xff on unused registers so
|
/*
|
||||||
just to be sure we check for that too. */
|
* LM75A returns 0xff on unused registers so
|
||||||
|
* just to be sure we check for that too.
|
||||||
|
*/
|
||||||
if (i2c_smbus_read_byte_data(new_client, 4) != 0xff
|
if (i2c_smbus_read_byte_data(new_client, 4) != 0xff
|
||||||
|| i2c_smbus_read_byte_data(new_client, 5) != 0xff
|
|| i2c_smbus_read_byte_data(new_client, 5) != 0xff
|
||||||
|| i2c_smbus_read_byte_data(new_client, 6) != 0xff)
|
|| i2c_smbus_read_byte_data(new_client, 6) != 0xff)
|
||||||
@ -849,6 +851,7 @@ static int lm75_suspend(struct device *dev)
|
|||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
|
||||||
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
@ -863,6 +866,7 @@ static int lm75_resume(struct device *dev)
|
|||||||
{
|
{
|
||||||
int status;
|
int status;
|
||||||
struct i2c_client *client = to_i2c_client(dev);
|
struct i2c_client *client = to_i2c_client(dev);
|
||||||
|
|
||||||
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
/*
|
/*
|
||||||
lm75.h - Part of lm_sensors, Linux kernel modules for hardware
|
* lm75.h - Part of lm_sensors, Linux kernel modules for hardware monitoring
|
||||||
monitoring
|
* Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
||||||
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
*/
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file contains common code for encoding/decoding LM75 type
|
* This file contains common code for encoding/decoding LM75 type
|
||||||
temperature readings, which are emulated by many of the chips
|
* temperature readings, which are emulated by many of the chips
|
||||||
we support. As the user is unlikely to load more than one driver
|
* we support. As the user is unlikely to load more than one driver
|
||||||
which contains this code, we don't worry about the wasted space.
|
* which contains this code, we don't worry about the wasted space.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
@ -20,18 +18,23 @@
|
|||||||
#define LM75_TEMP_MAX 125000
|
#define LM75_TEMP_MAX 125000
|
||||||
#define LM75_SHUTDOWN 0x01
|
#define LM75_SHUTDOWN 0x01
|
||||||
|
|
||||||
/* TEMP: 0.001C/bit (-55C to +125C)
|
/*
|
||||||
REG: (0.5C/bit, two's complement) << 7 */
|
* TEMP: 0.001C/bit (-55C to +125C)
|
||||||
|
* REG: (0.5C/bit, two's complement) << 7
|
||||||
|
*/
|
||||||
static inline u16 LM75_TEMP_TO_REG(long temp)
|
static inline u16 LM75_TEMP_TO_REG(long temp)
|
||||||
{
|
{
|
||||||
int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||||
|
|
||||||
ntemp += (ntemp < 0 ? -250 : 250);
|
ntemp += (ntemp < 0 ? -250 : 250);
|
||||||
return (u16)((ntemp / 500) << 7);
|
return (u16)((ntemp / 500) << 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int LM75_TEMP_FROM_REG(u16 reg)
|
static inline int LM75_TEMP_FROM_REG(u16 reg)
|
||||||
{
|
{
|
||||||
/* use integer division instead of equivalent right shift to
|
/*
|
||||||
guarantee arithmetic shift and preserve the sign */
|
* use integer division instead of equivalent right shift to
|
||||||
|
* guarantee arithmetic shift and preserve the sign
|
||||||
|
*/
|
||||||
return ((s16)reg / 128) * 500;
|
return ((s16)reg / 128) * 500;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,14 @@
|
|||||||
* explicitly as max6659, or if its address is not 0x4c.
|
* explicitly as max6659, or if its address is not 0x4c.
|
||||||
* These chips lack the remote temperature offset feature.
|
* These chips lack the remote temperature offset feature.
|
||||||
*
|
*
|
||||||
|
* This driver also supports the MAX6654 chip made by Maxim. This chip can
|
||||||
|
* be at 9 different addresses, similar to MAX6680/MAX6681. The MAX6654 is
|
||||||
|
* otherwise similar to MAX6657/MAX6658/MAX6659. Extended range is available
|
||||||
|
* by setting the configuration register accordingly, and is done during
|
||||||
|
* initialization. Extended precision is only available at conversion rates
|
||||||
|
* of 1 Hz and slower. Note that extended precision is not enabled by
|
||||||
|
* default, as this driver initializes all chips to 2 Hz by design.
|
||||||
|
*
|
||||||
* This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and
|
* This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and
|
||||||
* MAX6692 chips made by Maxim. These are again similar to the LM86,
|
* MAX6692 chips made by Maxim. These are again similar to the LM86,
|
||||||
* but they use unsigned temperature values and can report temperatures
|
* but they use unsigned temperature values and can report temperatures
|
||||||
@ -94,8 +102,8 @@
|
|||||||
* have address 0x4d.
|
* have address 0x4d.
|
||||||
* MAX6647 has address 0x4e.
|
* MAX6647 has address 0x4e.
|
||||||
* MAX6659 can have address 0x4c, 0x4d or 0x4e.
|
* MAX6659 can have address 0x4c, 0x4d or 0x4e.
|
||||||
* MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b,
|
* MAX6654, MAX6680, and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29,
|
||||||
* 0x4c, 0x4d or 0x4e.
|
* 0x2a, 0x2b, 0x4c, 0x4d or 0x4e.
|
||||||
* SA56004 can have address 0x48 through 0x4F.
|
* SA56004 can have address 0x48 through 0x4F.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -104,7 +112,7 @@ static const unsigned short normal_i2c[] = {
|
|||||||
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||||
|
|
||||||
enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
||||||
max6646, w83l771, max6696, sa56004, g781, tmp451 };
|
max6646, w83l771, max6696, sa56004, g781, tmp451, max6654 };
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The LM90 registers
|
* The LM90 registers
|
||||||
@ -145,7 +153,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
|||||||
#define LM90_REG_R_TCRIT_HYST 0x21
|
#define LM90_REG_R_TCRIT_HYST 0x21
|
||||||
#define LM90_REG_W_TCRIT_HYST 0x21
|
#define LM90_REG_W_TCRIT_HYST 0x21
|
||||||
|
|
||||||
/* MAX6646/6647/6649/6657/6658/6659/6695/6696 registers */
|
/* MAX6646/6647/6649/6654/6657/6658/6659/6695/6696 registers */
|
||||||
|
|
||||||
#define MAX6657_REG_R_LOCAL_TEMPL 0x11
|
#define MAX6657_REG_R_LOCAL_TEMPL 0x11
|
||||||
#define MAX6696_REG_R_STATUS2 0x12
|
#define MAX6696_REG_R_STATUS2 0x12
|
||||||
@ -209,6 +217,7 @@ static const struct i2c_device_id lm90_id[] = {
|
|||||||
{ "max6646", max6646 },
|
{ "max6646", max6646 },
|
||||||
{ "max6647", max6646 },
|
{ "max6647", max6646 },
|
||||||
{ "max6649", max6646 },
|
{ "max6649", max6646 },
|
||||||
|
{ "max6654", max6654 },
|
||||||
{ "max6657", max6657 },
|
{ "max6657", max6657 },
|
||||||
{ "max6658", max6657 },
|
{ "max6658", max6657 },
|
||||||
{ "max6659", max6659 },
|
{ "max6659", max6659 },
|
||||||
@ -269,6 +278,10 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = {
|
|||||||
.compatible = "dallas,max6649",
|
.compatible = "dallas,max6649",
|
||||||
.data = (void *)max6646
|
.data = (void *)max6646
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.compatible = "dallas,max6654",
|
||||||
|
.data = (void *)max6654
|
||||||
|
},
|
||||||
{
|
{
|
||||||
.compatible = "dallas,max6657",
|
.compatible = "dallas,max6657",
|
||||||
.data = (void *)max6657
|
.data = (void *)max6657
|
||||||
@ -367,6 +380,11 @@ static const struct lm90_params lm90_params[] = {
|
|||||||
.max_convrate = 6,
|
.max_convrate = 6,
|
||||||
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||||
},
|
},
|
||||||
|
[max6654] = {
|
||||||
|
.alert_alarms = 0x7c,
|
||||||
|
.max_convrate = 7,
|
||||||
|
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||||
|
},
|
||||||
[max6657] = {
|
[max6657] = {
|
||||||
.flags = LM90_PAUSE_FOR_CONFIG,
|
.flags = LM90_PAUSE_FOR_CONFIG,
|
||||||
.alert_alarms = 0x7c,
|
.alert_alarms = 0x7c,
|
||||||
@ -1557,6 +1575,16 @@ static int lm90_detect(struct i2c_client *client,
|
|||||||
&& (config1 & 0x3f) == 0x00
|
&& (config1 & 0x3f) == 0x00
|
||||||
&& convrate <= 0x07) {
|
&& convrate <= 0x07) {
|
||||||
name = "max6646";
|
name = "max6646";
|
||||||
|
} else
|
||||||
|
/*
|
||||||
|
* The chip_id of the MAX6654 holds the revision of the chip.
|
||||||
|
* The lowest 3 bits of the config1 register are unused and
|
||||||
|
* should return zero when read.
|
||||||
|
*/
|
||||||
|
if (chip_id == 0x08
|
||||||
|
&& (config1 & 0x07) == 0x00
|
||||||
|
&& convrate <= 0x07) {
|
||||||
|
name = "max6654";
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
if (address == 0x4C
|
if (address == 0x4C
|
||||||
@ -1660,6 +1688,15 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
|||||||
if (data->kind == max6680)
|
if (data->kind == max6680)
|
||||||
config |= 0x18;
|
config |= 0x18;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Put MAX6654 into extended range (0x20, extend minimum range from
|
||||||
|
* 0 degrees to -64 degrees). Note that extended resolution is not
|
||||||
|
* possible on the MAX6654 unless conversion rate is set to 1 Hz or
|
||||||
|
* slower, which is intentionally not done by default.
|
||||||
|
*/
|
||||||
|
if (data->kind == max6654)
|
||||||
|
config |= 0x20;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Select external channel 0 for max6695/96
|
* Select external channel 0 for max6695/96
|
||||||
*/
|
*/
|
||||||
|
@ -2047,7 +2047,7 @@ store_temp_beep(struct device *dev, struct device_attribute *attr,
|
|||||||
static umode_t nct6775_in_is_visible(struct kobject *kobj,
|
static umode_t nct6775_in_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
int in = index / 5; /* voltage index */
|
int in = index / 5; /* voltage index */
|
||||||
|
|
||||||
@ -2253,7 +2253,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr,
|
|||||||
static umode_t nct6775_fan_is_visible(struct kobject *kobj,
|
static umode_t nct6775_fan_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
int fan = index / 6; /* fan index */
|
int fan = index / 6; /* fan index */
|
||||||
int nr = index % 6; /* attribute index */
|
int nr = index % 6; /* attribute index */
|
||||||
@ -2440,7 +2440,7 @@ store_temp_type(struct device *dev, struct device_attribute *attr,
|
|||||||
static umode_t nct6775_temp_is_visible(struct kobject *kobj,
|
static umode_t nct6775_temp_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
int temp = index / 10; /* temp index */
|
int temp = index / 10; /* temp index */
|
||||||
int nr = index % 10; /* attribute index */
|
int nr = index % 10; /* attribute index */
|
||||||
@ -3257,7 +3257,7 @@ store_auto_temp(struct device *dev, struct device_attribute *attr,
|
|||||||
static umode_t nct6775_pwm_is_visible(struct kobject *kobj,
|
static umode_t nct6775_pwm_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
int pwm = index / 36; /* pwm index */
|
int pwm = index / 36; /* pwm index */
|
||||||
int nr = index % 36; /* attribute index */
|
int nr = index % 36; /* attribute index */
|
||||||
@ -3459,7 +3459,7 @@ static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep,
|
|||||||
static umode_t nct6775_other_is_visible(struct kobject *kobj,
|
static umode_t nct6775_other_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct6775_data *data = dev_get_drvdata(dev);
|
struct nct6775_data *data = dev_get_drvdata(dev);
|
||||||
|
|
||||||
if (index == 0 && !data->have_vid)
|
if (index == 0 && !data->have_vid)
|
||||||
|
@ -679,7 +679,7 @@ static struct attribute *nct7802_temp_attrs[] = {
|
|||||||
static umode_t nct7802_temp_is_visible(struct kobject *kobj,
|
static umode_t nct7802_temp_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct7802_data *data = dev_get_drvdata(dev);
|
struct nct7802_data *data = dev_get_drvdata(dev);
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
int err;
|
int err;
|
||||||
@ -778,7 +778,7 @@ static struct attribute *nct7802_in_attrs[] = {
|
|||||||
static umode_t nct7802_in_is_visible(struct kobject *kobj,
|
static umode_t nct7802_in_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct7802_data *data = dev_get_drvdata(dev);
|
struct nct7802_data *data = dev_get_drvdata(dev);
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
int err;
|
int err;
|
||||||
@ -853,7 +853,7 @@ static struct attribute *nct7802_fan_attrs[] = {
|
|||||||
static umode_t nct7802_fan_is_visible(struct kobject *kobj,
|
static umode_t nct7802_fan_is_visible(struct kobject *kobj,
|
||||||
struct attribute *attr, int index)
|
struct attribute *attr, int index)
|
||||||
{
|
{
|
||||||
struct device *dev = container_of(kobj, struct device, kobj);
|
struct device *dev = kobj_to_dev(kobj);
|
||||||
struct nct7802_data *data = dev_get_drvdata(dev);
|
struct nct7802_data *data = dev_get_drvdata(dev);
|
||||||
int fan = index / 4; /* 4 attributes per fan */
|
int fan = index / 4; /* 4 attributes per fan */
|
||||||
unsigned int reg;
|
unsigned int reg;
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
* Copyright (c) 2019 Advantech
|
* Copyright (c) 2019 Advantech
|
||||||
* Author: Amy.Shih <amy.shih@advantech.com.tw>
|
* Author: Amy.Shih <amy.shih@advantech.com.tw>
|
||||||
*
|
*
|
||||||
|
* Copyright (c) 2020 Advantech
|
||||||
|
* Author: Yuechao Zhao <yuechao.zhao@advantech.com.cn>
|
||||||
|
*
|
||||||
* Supports the following chips:
|
* Supports the following chips:
|
||||||
*
|
*
|
||||||
* Chip #vin #fan #pwm #temp #dts chip ID
|
* Chip #vin #fan #pwm #temp #dts chip ID
|
||||||
@ -20,6 +23,7 @@
|
|||||||
#include <linux/i2c.h>
|
#include <linux/i2c.h>
|
||||||
#include <linux/mutex.h>
|
#include <linux/mutex.h>
|
||||||
#include <linux/hwmon.h>
|
#include <linux/hwmon.h>
|
||||||
|
#include <linux/watchdog.h>
|
||||||
|
|
||||||
#define VENDOR_ID_REG 0x7A /* Any bank */
|
#define VENDOR_ID_REG 0x7A /* Any bank */
|
||||||
#define NUVOTON_ID 0x50
|
#define NUVOTON_ID 0x50
|
||||||
@ -88,18 +92,42 @@
|
|||||||
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
|
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
|
||||||
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
|
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
|
||||||
|
|
||||||
|
#define WDT_LOCK_REG 0xE0 /* W/O Lock Watchdog Register */
|
||||||
|
#define WDT_EN_REG 0xE1 /* R/O Watchdog Enable Register */
|
||||||
|
#define WDT_STS_REG 0xE2 /* R/O Watchdog Status Register */
|
||||||
|
#define WDT_TIMER_REG 0xE3 /* R/W Watchdog Timer Register */
|
||||||
|
#define WDT_SOFT_EN 0x55 /* Enable soft watchdog timer */
|
||||||
|
#define WDT_SOFT_DIS 0xAA /* Disable soft watchdog timer */
|
||||||
|
|
||||||
#define VOLT_MONITOR_MODE 0x0
|
#define VOLT_MONITOR_MODE 0x0
|
||||||
#define THERMAL_DIODE_MODE 0x1
|
#define THERMAL_DIODE_MODE 0x1
|
||||||
#define THERMISTOR_MODE 0x3
|
#define THERMISTOR_MODE 0x3
|
||||||
|
|
||||||
#define ENABLE_TSI BIT(1)
|
#define ENABLE_TSI BIT(1)
|
||||||
|
|
||||||
|
#define WATCHDOG_TIMEOUT 1 /* 1 minute default timeout */
|
||||||
|
|
||||||
|
/*The timeout range is 1-255 minutes*/
|
||||||
|
#define MIN_TIMEOUT (1 * 60)
|
||||||
|
#define MAX_TIMEOUT (255 * 60)
|
||||||
|
|
||||||
|
static int timeout;
|
||||||
|
module_param(timeout, int, 0);
|
||||||
|
MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1 <= timeout <= 255, default="
|
||||||
|
__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
|
||||||
|
|
||||||
|
static bool nowayout = WATCHDOG_NOWAYOUT;
|
||||||
|
module_param(nowayout, bool, 0);
|
||||||
|
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
|
||||||
|
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
||||||
|
|
||||||
static const unsigned short normal_i2c[] = {
|
static const unsigned short normal_i2c[] = {
|
||||||
0x2d, 0x2e, I2C_CLIENT_END
|
0x2d, 0x2e, I2C_CLIENT_END
|
||||||
};
|
};
|
||||||
|
|
||||||
struct nct7904_data {
|
struct nct7904_data {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
|
struct watchdog_device wdt;
|
||||||
struct mutex bank_lock;
|
struct mutex bank_lock;
|
||||||
int bank_sel;
|
int bank_sel;
|
||||||
u32 fanin_mask;
|
u32 fanin_mask;
|
||||||
@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_info = {
|
|||||||
.info = nct7904_info,
|
.info = nct7904_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Watchdog Function
|
||||||
|
*/
|
||||||
|
static int nct7904_wdt_start(struct watchdog_device *wdt)
|
||||||
|
{
|
||||||
|
struct nct7904_data *data = watchdog_get_drvdata(wdt);
|
||||||
|
|
||||||
|
/* Enable soft watchdog timer */
|
||||||
|
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nct7904_wdt_stop(struct watchdog_device *wdt)
|
||||||
|
{
|
||||||
|
struct nct7904_data *data = watchdog_get_drvdata(wdt);
|
||||||
|
|
||||||
|
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nct7904_wdt_set_timeout(struct watchdog_device *wdt,
|
||||||
|
unsigned int timeout)
|
||||||
|
{
|
||||||
|
struct nct7904_data *data = watchdog_get_drvdata(wdt);
|
||||||
|
/*
|
||||||
|
* The NCT7904 is very special in watchdog function.
|
||||||
|
* Its minimum unit is minutes. And wdt->timeout needs
|
||||||
|
* to match the actual timeout selected. So, this needs
|
||||||
|
* to be: wdt->timeout = timeout / 60 * 60.
|
||||||
|
* For example, if the user configures a timeout of
|
||||||
|
* 119 seconds, the actual timeout will be 60 seconds.
|
||||||
|
* So, wdt->timeout must then be set to 60 seconds.
|
||||||
|
*/
|
||||||
|
wdt->timeout = timeout / 60 * 60;
|
||||||
|
|
||||||
|
return nct7904_write_reg(data, BANK_0, WDT_TIMER_REG,
|
||||||
|
wdt->timeout / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nct7904_wdt_ping(struct watchdog_device *wdt)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Note:
|
||||||
|
* NCT7904 does not support refreshing WDT_TIMER_REG register when
|
||||||
|
* the watchdog is active. Please disable watchdog before feeding
|
||||||
|
* the watchdog and enable it again.
|
||||||
|
*/
|
||||||
|
struct nct7904_data *data = watchdog_get_drvdata(wdt);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Disable soft watchdog timer */
|
||||||
|
ret = nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* feed watchdog */
|
||||||
|
ret = nct7904_write_reg(data, BANK_0, WDT_TIMER_REG, wdt->timeout / 60);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Enable soft watchdog timer */
|
||||||
|
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int nct7904_wdt_get_timeleft(struct watchdog_device *wdt)
|
||||||
|
{
|
||||||
|
struct nct7904_data *data = watchdog_get_drvdata(wdt);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = nct7904_read_reg(data, BANK_0, WDT_TIMER_REG);
|
||||||
|
if (ret < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return ret * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct watchdog_info nct7904_wdt_info = {
|
||||||
|
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
|
||||||
|
WDIOF_MAGICCLOSE,
|
||||||
|
.identity = "nct7904 watchdog",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct watchdog_ops nct7904_wdt_ops = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.start = nct7904_wdt_start,
|
||||||
|
.stop = nct7904_wdt_stop,
|
||||||
|
.ping = nct7904_wdt_ping,
|
||||||
|
.set_timeout = nct7904_wdt_set_timeout,
|
||||||
|
.get_timeleft = nct7904_wdt_get_timeleft,
|
||||||
|
};
|
||||||
|
|
||||||
static int nct7904_probe(struct i2c_client *client,
|
static int nct7904_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client,
|
|||||||
hwmon_dev =
|
hwmon_dev =
|
||||||
devm_hwmon_device_register_with_info(dev, client->name, data,
|
devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||||
&nct7904_chip_info, NULL);
|
&nct7904_chip_info, NULL);
|
||||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
ret = PTR_ERR_OR_ZERO(hwmon_dev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Watchdog initialization */
|
||||||
|
data->wdt.ops = &nct7904_wdt_ops;
|
||||||
|
data->wdt.info = &nct7904_wdt_info;
|
||||||
|
|
||||||
|
data->wdt.timeout = WATCHDOG_TIMEOUT * 60; /* Set default timeout */
|
||||||
|
data->wdt.min_timeout = MIN_TIMEOUT;
|
||||||
|
data->wdt.max_timeout = MAX_TIMEOUT;
|
||||||
|
data->wdt.parent = &client->dev;
|
||||||
|
|
||||||
|
watchdog_init_timeout(&data->wdt, timeout * 60, &client->dev);
|
||||||
|
watchdog_set_nowayout(&data->wdt, nowayout);
|
||||||
|
watchdog_set_drvdata(&data->wdt, data);
|
||||||
|
|
||||||
|
watchdog_stop_on_unregister(&data->wdt);
|
||||||
|
|
||||||
|
return devm_watchdog_register_device(dev, &data->wdt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct i2c_device_id nct7904_id[] = {
|
static const struct i2c_device_id nct7904_id[] = {
|
||||||
|
@ -146,6 +146,15 @@ config SENSORS_MAX16064
|
|||||||
This driver can also be built as a module. If so, the module will
|
This driver can also be built as a module. If so, the module will
|
||||||
be called max16064.
|
be called max16064.
|
||||||
|
|
||||||
|
config SENSORS_MAX16601
|
||||||
|
tristate "Maxim MAX16601"
|
||||||
|
help
|
||||||
|
If you say yes here you get hardware monitoring support for Maxim
|
||||||
|
MAX16601.
|
||||||
|
|
||||||
|
This driver can also be built as a module. If so, the module will
|
||||||
|
be called max16601.
|
||||||
|
|
||||||
config SENSORS_MAX20730
|
config SENSORS_MAX20730
|
||||||
tristate "Maxim MAX20730, MAX20734, MAX20743"
|
tristate "Maxim MAX20730, MAX20734, MAX20743"
|
||||||
help
|
help
|
||||||
|
@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
|||||||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
||||||
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
|
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
|
||||||
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
|
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
|
||||||
|
obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
|
||||||
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
|
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
|
||||||
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
|
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
|
||||||
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
||||||
|
314
drivers/hwmon/pmbus/max16601.c
Normal file
314
drivers/hwmon/pmbus/max16601.c
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Hardware monitoring driver for Maxim MAX16601
|
||||||
|
*
|
||||||
|
* Implementation notes:
|
||||||
|
*
|
||||||
|
* Ths chip supports two rails, VCORE and VSA. Telemetry information for the
|
||||||
|
* two rails is reported in two subsequent I2C addresses. The driver
|
||||||
|
* instantiates a dummy I2C client at the second I2C address to report
|
||||||
|
* information for the VSA rail in a single instance of the driver.
|
||||||
|
* Telemetry for the VSA rail is reported to the PMBus core in PMBus page 2.
|
||||||
|
*
|
||||||
|
* The chip reports input current using two separate methods. The input current
|
||||||
|
* reported with the standard READ_IIN command is derived from the output
|
||||||
|
* current. The first method is reported to the PMBus core with PMBus page 0,
|
||||||
|
* the second method is reported with PMBus page 1.
|
||||||
|
*
|
||||||
|
* The chip supports reading per-phase temperatures and per-phase input/output
|
||||||
|
* currents for VCORE. Telemetry is reported in vendor specific registers.
|
||||||
|
* The driver translates the vendor specific register values to PMBus standard
|
||||||
|
* register values and reports per-phase information in PMBus page 0.
|
||||||
|
*
|
||||||
|
* Copyright 2019, 2020 Google LLC.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
|
||||||
|
#include "pmbus.h"
|
||||||
|
|
||||||
|
#define REG_SETPT_DVID 0xd1
|
||||||
|
#define DAC_10MV_MODE BIT(4)
|
||||||
|
#define REG_IOUT_AVG_PK 0xee
|
||||||
|
#define REG_IIN_SENSOR 0xf1
|
||||||
|
#define REG_TOTAL_INPUT_POWER 0xf2
|
||||||
|
#define REG_PHASE_ID 0xf3
|
||||||
|
#define CORE_RAIL_INDICATOR BIT(7)
|
||||||
|
#define REG_PHASE_REPORTING 0xf4
|
||||||
|
|
||||||
|
struct max16601_data {
|
||||||
|
struct pmbus_driver_info info;
|
||||||
|
struct i2c_client *vsa;
|
||||||
|
int iout_avg_pkg;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_max16601_data(x) container_of(x, struct max16601_data, info)
|
||||||
|
|
||||||
|
static int max16601_read_byte(struct i2c_client *client, int page, int reg)
|
||||||
|
{
|
||||||
|
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||||
|
struct max16601_data *data = to_max16601_data(info);
|
||||||
|
|
||||||
|
if (page > 0) {
|
||||||
|
if (page == 2) /* VSA */
|
||||||
|
return i2c_smbus_read_byte_data(data->vsa, reg);
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max16601_read_word(struct i2c_client *client, int page, int phase,
|
||||||
|
int reg)
|
||||||
|
{
|
||||||
|
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||||
|
struct max16601_data *data = to_max16601_data(info);
|
||||||
|
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
switch (page) {
|
||||||
|
case 0: /* VCORE */
|
||||||
|
if (phase == 0xff)
|
||||||
|
return -ENODATA;
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_READ_IIN:
|
||||||
|
case PMBUS_READ_IOUT:
|
||||||
|
case PMBUS_READ_TEMPERATURE_1:
|
||||||
|
ret = i2c_smbus_write_byte_data(client, REG_PHASE_ID,
|
||||||
|
phase);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
ret = i2c_smbus_read_block_data(client,
|
||||||
|
REG_PHASE_REPORTING,
|
||||||
|
buf);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (ret < 6)
|
||||||
|
return -EIO;
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_READ_TEMPERATURE_1:
|
||||||
|
return buf[1] << 8 | buf[0];
|
||||||
|
case PMBUS_READ_IOUT:
|
||||||
|
return buf[3] << 8 | buf[2];
|
||||||
|
case PMBUS_READ_IIN:
|
||||||
|
return buf[5] << 8 | buf[4];
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
case 1: /* VCORE, read IIN/PIN from sensor element */
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_READ_IIN:
|
||||||
|
return i2c_smbus_read_word_data(client, REG_IIN_SENSOR);
|
||||||
|
case PMBUS_READ_PIN:
|
||||||
|
return i2c_smbus_read_word_data(client,
|
||||||
|
REG_TOTAL_INPUT_POWER);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
case 2: /* VSA */
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_VIRT_READ_IOUT_MAX:
|
||||||
|
ret = i2c_smbus_read_word_data(data->vsa,
|
||||||
|
REG_IOUT_AVG_PK);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (sign_extend32(ret, 10) >
|
||||||
|
sign_extend32(data->iout_avg_pkg, 10))
|
||||||
|
data->iout_avg_pkg = ret;
|
||||||
|
return data->iout_avg_pkg;
|
||||||
|
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||||
|
return 0;
|
||||||
|
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||||
|
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||||
|
case PMBUS_OT_FAULT_LIMIT:
|
||||||
|
case PMBUS_OT_WARN_LIMIT:
|
||||||
|
case PMBUS_READ_IIN:
|
||||||
|
case PMBUS_READ_IOUT:
|
||||||
|
case PMBUS_READ_TEMPERATURE_1:
|
||||||
|
case PMBUS_STATUS_WORD:
|
||||||
|
return i2c_smbus_read_word_data(data->vsa, reg);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max16601_write_byte(struct i2c_client *client, int page, u8 reg)
|
||||||
|
{
|
||||||
|
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||||
|
struct max16601_data *data = to_max16601_data(info);
|
||||||
|
|
||||||
|
if (page == 2) {
|
||||||
|
if (reg == PMBUS_CLEAR_FAULTS)
|
||||||
|
return i2c_smbus_write_byte(data->vsa, reg);
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max16601_write_word(struct i2c_client *client, int page, int reg,
|
||||||
|
u16 value)
|
||||||
|
{
|
||||||
|
const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
|
||||||
|
struct max16601_data *data = to_max16601_data(info);
|
||||||
|
|
||||||
|
switch (page) {
|
||||||
|
case 0: /* VCORE */
|
||||||
|
return -ENODATA;
|
||||||
|
case 1: /* VCORE IIN/PIN from sensor element */
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
case 2: /* VSA */
|
||||||
|
switch (reg) {
|
||||||
|
case PMBUS_VIRT_RESET_IOUT_HISTORY:
|
||||||
|
data->iout_avg_pkg = 0xfc00;
|
||||||
|
return 0;
|
||||||
|
case PMBUS_IOUT_OC_FAULT_LIMIT:
|
||||||
|
case PMBUS_IOUT_OC_WARN_LIMIT:
|
||||||
|
case PMBUS_OT_FAULT_LIMIT:
|
||||||
|
case PMBUS_OT_WARN_LIMIT:
|
||||||
|
return i2c_smbus_write_word_data(data->vsa, reg, value);
|
||||||
|
default:
|
||||||
|
return -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max16601_identify(struct i2c_client *client,
|
||||||
|
struct pmbus_driver_info *info)
|
||||||
|
{
|
||||||
|
int reg;
|
||||||
|
|
||||||
|
reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID);
|
||||||
|
if (reg < 0)
|
||||||
|
return reg;
|
||||||
|
if (reg & DAC_10MV_MODE)
|
||||||
|
info->vrm_version[0] = vr13;
|
||||||
|
else
|
||||||
|
info->vrm_version[0] = vr12;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct pmbus_driver_info max16601_info = {
|
||||||
|
.pages = 3,
|
||||||
|
.format[PSC_VOLTAGE_IN] = linear,
|
||||||
|
.format[PSC_VOLTAGE_OUT] = vid,
|
||||||
|
.format[PSC_CURRENT_IN] = linear,
|
||||||
|
.format[PSC_CURRENT_OUT] = linear,
|
||||||
|
.format[PSC_TEMPERATURE] = linear,
|
||||||
|
.format[PSC_POWER] = linear,
|
||||||
|
.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_PIN |
|
||||||
|
PMBUS_HAVE_STATUS_INPUT |
|
||||||
|
PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
|
||||||
|
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||||
|
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP |
|
||||||
|
PMBUS_HAVE_POUT | PMBUS_PAGE_VIRTUAL | PMBUS_PHASE_VIRTUAL,
|
||||||
|
.func[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_PAGE_VIRTUAL,
|
||||||
|
.func[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT |
|
||||||
|
PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT |
|
||||||
|
PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | PMBUS_PAGE_VIRTUAL,
|
||||||
|
.phases[0] = 8,
|
||||||
|
.pfunc[0] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
|
||||||
|
.pfunc[1] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
|
||||||
|
.pfunc[2] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
|
||||||
|
.pfunc[3] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
|
||||||
|
.pfunc[4] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
|
||||||
|
.pfunc[5] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
|
||||||
|
.pfunc[6] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP,
|
||||||
|
.pfunc[7] = PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT,
|
||||||
|
.identify = max16601_identify,
|
||||||
|
.read_byte_data = max16601_read_byte,
|
||||||
|
.read_word_data = max16601_read_word,
|
||||||
|
.write_byte = max16601_write_byte,
|
||||||
|
.write_word_data = max16601_write_word,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void max16601_remove(void *_data)
|
||||||
|
{
|
||||||
|
struct max16601_data *data = _data;
|
||||||
|
|
||||||
|
i2c_unregister_device(data->vsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int max16601_probe(struct i2c_client *client,
|
||||||
|
const struct i2c_device_id *id)
|
||||||
|
{
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
|
||||||
|
struct max16601_data *data;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!i2c_check_functionality(client->adapter,
|
||||||
|
I2C_FUNC_SMBUS_READ_BYTE_DATA |
|
||||||
|
I2C_FUNC_SMBUS_READ_BLOCK_DATA))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
|
||||||
|
if (ret < 0)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" */
|
||||||
|
if (ret < 11 || strncmp(buf, "MAX16601", 8)) {
|
||||||
|
buf[ret] = '\0';
|
||||||
|
dev_err(dev, "Unsupported chip '%s'\n", buf);
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
if (!(ret & CORE_RAIL_INDICATOR)) {
|
||||||
|
dev_err(dev,
|
||||||
|
"Driver must be instantiated on CORE rail I2C address\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
data->iout_avg_pkg = 0xfc00;
|
||||||
|
data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1);
|
||||||
|
if (IS_ERR(data->vsa)) {
|
||||||
|
dev_err(dev, "Failed to register VSA client\n");
|
||||||
|
return PTR_ERR(data->vsa);
|
||||||
|
}
|
||||||
|
ret = devm_add_action_or_reset(dev, max16601_remove, data);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
data->info = max16601_info;
|
||||||
|
|
||||||
|
return pmbus_do_probe(client, id, &data->info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct i2c_device_id max16601_id[] = {
|
||||||
|
{"max16601", 0},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(i2c, max16601_id);
|
||||||
|
|
||||||
|
static struct i2c_driver max16601_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "max16601",
|
||||||
|
},
|
||||||
|
.probe = max16601_probe,
|
||||||
|
.remove = pmbus_do_remove,
|
||||||
|
.id_table = max16601_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(max16601_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>");
|
||||||
|
MODULE_DESCRIPTION("PMBus driver for Maxim MAX16601");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@ -109,8 +109,8 @@ struct pmbus_data {
|
|||||||
bool has_status_word; /* device uses STATUS_WORD register */
|
bool has_status_word; /* device uses STATUS_WORD register */
|
||||||
int (*read_status)(struct i2c_client *client, int page);
|
int (*read_status)(struct i2c_client *client, int page);
|
||||||
|
|
||||||
u8 currpage;
|
s16 currpage; /* current page, -1 for unknown/unset */
|
||||||
u8 currphase; /* current phase, 0xff for all */
|
s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct pmbus_debugfs_entry {
|
struct pmbus_debugfs_entry {
|
||||||
@ -2529,8 +2529,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
|||||||
if (pdata)
|
if (pdata)
|
||||||
data->flags = pdata->flags;
|
data->flags = pdata->flags;
|
||||||
data->info = info;
|
data->info = info;
|
||||||
data->currpage = 0xff;
|
data->currpage = -1;
|
||||||
data->currphase = 0xfe;
|
data->currphase = -1;
|
||||||
|
|
||||||
ret = pmbus_init_common(client, data, info);
|
ret = pmbus_init_common(client, data, info);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS
|
|||||||
Select this option to enable support for Samsung Exynos Low Power
|
Select this option to enable support for Samsung Exynos Low Power
|
||||||
Audio Subsystem.
|
Audio Subsystem.
|
||||||
|
|
||||||
|
config MFD_GATEWORKS_GSC
|
||||||
|
tristate "Gateworks System Controller"
|
||||||
|
depends on (I2C && OF)
|
||||||
|
select MFD_CORE
|
||||||
|
select REGMAP_I2C
|
||||||
|
select REGMAP_IRQ
|
||||||
|
help
|
||||||
|
Enable support for the Gateworks System Controller (GSC) found
|
||||||
|
on Gateworks Single Board Computers supporting system functions
|
||||||
|
such as push-button monitor, multiple ADC's for voltage and
|
||||||
|
temperature monitoring, fan controller and watchdog monitor.
|
||||||
|
This driver provides common support for accessing the device.
|
||||||
|
Additional drivers must be enabled in order to use the
|
||||||
|
functionality of the device.
|
||||||
|
|
||||||
config MFD_MC13XXX
|
config MFD_MC13XXX
|
||||||
tristate
|
tristate
|
||||||
depends on (SPI_MASTER || I2C)
|
depends on (SPI_MASTER || I2C)
|
||||||
|
@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
|
|||||||
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
||||||
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
|
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
|
||||||
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
|
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
|
||||||
|
obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o
|
||||||
|
|
||||||
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
|
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
|
||||||
obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o
|
obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o
|
||||||
|
277
drivers/mfd/gateworks-gsc.c
Normal file
277
drivers/mfd/gateworks-gsc.c
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* The Gateworks System Controller (GSC) is a multi-function
|
||||||
|
* device designed for use in Gateworks Single Board Computers.
|
||||||
|
* The control interface is I2C, with an interrupt. The device supports
|
||||||
|
* system functions such as push-button monitoring, multiple ADC's for
|
||||||
|
* voltage and temperature monitoring, fan controller and watchdog monitor.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Gateworks Corporation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/mfd/gsc.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/of_platform.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
#include <asm/unaligned.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The GSC suffers from an errata where occasionally during
|
||||||
|
* ADC cycles the chip can NAK I2C transactions. To ensure we have reliable
|
||||||
|
* register access we place retries around register access.
|
||||||
|
*/
|
||||||
|
#define I2C_RETRIES 3
|
||||||
|
|
||||||
|
int gsc_write(void *context, unsigned int reg, unsigned int val)
|
||||||
|
{
|
||||||
|
struct i2c_client *client = context;
|
||||||
|
int retry, ret;
|
||||||
|
|
||||||
|
for (retry = 0; retry < I2C_RETRIES; retry++) {
|
||||||
|
ret = i2c_smbus_write_byte_data(client, reg, val);
|
||||||
|
/*
|
||||||
|
* -EAGAIN returned when the i2c host controller is busy
|
||||||
|
* -EIO returned when i2c device is busy
|
||||||
|
*/
|
||||||
|
if (ret != -EAGAIN && ret != -EIO)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(gsc_write);
|
||||||
|
|
||||||
|
int gsc_read(void *context, unsigned int reg, unsigned int *val)
|
||||||
|
{
|
||||||
|
struct i2c_client *client = context;
|
||||||
|
int retry, ret;
|
||||||
|
|
||||||
|
for (retry = 0; retry < I2C_RETRIES; retry++) {
|
||||||
|
ret = i2c_smbus_read_byte_data(client, reg);
|
||||||
|
/*
|
||||||
|
* -EAGAIN returned when the i2c host controller is busy
|
||||||
|
* -EIO returned when i2c device is busy
|
||||||
|
*/
|
||||||
|
if (ret != -EAGAIN && ret != -EIO)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*val = ret & 0xff;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(gsc_read);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* gsc_powerdown - API to use GSC to power down board for a specific time
|
||||||
|
*
|
||||||
|
* secs - number of seconds to remain powered off
|
||||||
|
*/
|
||||||
|
static int gsc_powerdown(struct gsc_dev *gsc, unsigned long secs)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
unsigned char regs[4];
|
||||||
|
|
||||||
|
dev_info(&gsc->i2c->dev, "GSC powerdown for %ld seconds\n",
|
||||||
|
secs);
|
||||||
|
|
||||||
|
put_unaligned_le32(secs, regs);
|
||||||
|
ret = regmap_bulk_write(gsc->regmap, GSC_TIME_ADD, regs, 4);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1,
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ADD),
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ADD));
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = regmap_update_bits(gsc->regmap, GSC_CTRL_1,
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ACTIVATE) |
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ENABLE),
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ACTIVATE) |
|
||||||
|
BIT(GSC_CTRL_1_SLEEP_ENABLE));
|
||||||
|
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gsc_show(struct device *dev, struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct gsc_dev *gsc = dev_get_drvdata(dev);
|
||||||
|
const char *name = attr->attr.name;
|
||||||
|
int rz = 0;
|
||||||
|
|
||||||
|
if (strcasecmp(name, "fw_version") == 0)
|
||||||
|
rz = sprintf(buf, "%d\n", gsc->fwver);
|
||||||
|
else if (strcasecmp(name, "fw_crc") == 0)
|
||||||
|
rz = sprintf(buf, "0x%04x\n", gsc->fwcrc);
|
||||||
|
else
|
||||||
|
dev_err(dev, "invalid command: '%s'\n", name);
|
||||||
|
|
||||||
|
return rz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t gsc_store(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct gsc_dev *gsc = dev_get_drvdata(dev);
|
||||||
|
const char *name = attr->attr.name;
|
||||||
|
long value;
|
||||||
|
|
||||||
|
if (strcasecmp(name, "powerdown") == 0) {
|
||||||
|
if (kstrtol(buf, 0, &value) == 0)
|
||||||
|
gsc_powerdown(gsc, value);
|
||||||
|
} else {
|
||||||
|
dev_err(dev, "invalid command: '%s\n", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct device_attribute attr_fwver =
|
||||||
|
__ATTR(fw_version, 0440, gsc_show, NULL);
|
||||||
|
static struct device_attribute attr_fwcrc =
|
||||||
|
__ATTR(fw_crc, 0440, gsc_show, NULL);
|
||||||
|
static struct device_attribute attr_pwrdown =
|
||||||
|
__ATTR(powerdown, 0220, NULL, gsc_store);
|
||||||
|
|
||||||
|
static struct attribute *gsc_attrs[] = {
|
||||||
|
&attr_fwver.attr,
|
||||||
|
&attr_fwcrc.attr,
|
||||||
|
&attr_pwrdown.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute_group attr_group = {
|
||||||
|
.attrs = gsc_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct of_device_id gsc_of_match[] = {
|
||||||
|
{ .compatible = "gw,gsc", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, gsc_of_match);
|
||||||
|
|
||||||
|
static struct regmap_bus gsc_regmap_bus = {
|
||||||
|
.reg_read = gsc_read,
|
||||||
|
.reg_write = gsc_write,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_config gsc_regmap_config = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.cache_type = REGCACHE_NONE,
|
||||||
|
.max_register = GSC_WP,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_irq gsc_irqs[] = {
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_PB, 0, BIT(GSC_IRQ_PB)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_KEY_ERASED, 0, BIT(GSC_IRQ_KEY_ERASED)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_EEPROM_WP, 0, BIT(GSC_IRQ_EEPROM_WP)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_RESV, 0, BIT(GSC_IRQ_RESV)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_GPIO, 0, BIT(GSC_IRQ_GPIO)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_TAMPER, 0, BIT(GSC_IRQ_TAMPER)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_WDT_TIMEOUT, 0, BIT(GSC_IRQ_WDT_TIMEOUT)),
|
||||||
|
REGMAP_IRQ_REG(GSC_IRQ_SWITCH_HOLD, 0, BIT(GSC_IRQ_SWITCH_HOLD)),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct regmap_irq_chip gsc_irq_chip = {
|
||||||
|
.name = "gateworks-gsc",
|
||||||
|
.irqs = gsc_irqs,
|
||||||
|
.num_irqs = ARRAY_SIZE(gsc_irqs),
|
||||||
|
.num_regs = 1,
|
||||||
|
.status_base = GSC_IRQ_STATUS,
|
||||||
|
.mask_base = GSC_IRQ_ENABLE,
|
||||||
|
.mask_invert = true,
|
||||||
|
.ack_base = GSC_IRQ_STATUS,
|
||||||
|
.ack_invert = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int gsc_probe(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct device *dev = &client->dev;
|
||||||
|
struct gsc_dev *gsc;
|
||||||
|
struct regmap_irq_chip_data *irq_data;
|
||||||
|
int ret;
|
||||||
|
unsigned int reg;
|
||||||
|
|
||||||
|
gsc = devm_kzalloc(dev, sizeof(*gsc), GFP_KERNEL);
|
||||||
|
if (!gsc)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
gsc->dev = &client->dev;
|
||||||
|
gsc->i2c = client;
|
||||||
|
i2c_set_clientdata(client, gsc);
|
||||||
|
|
||||||
|
gsc->regmap = devm_regmap_init(dev, &gsc_regmap_bus, client,
|
||||||
|
&gsc_regmap_config);
|
||||||
|
if (IS_ERR(gsc->regmap))
|
||||||
|
return PTR_ERR(gsc->regmap);
|
||||||
|
|
||||||
|
if (regmap_read(gsc->regmap, GSC_FW_VER, ®))
|
||||||
|
return -EIO;
|
||||||
|
gsc->fwver = reg;
|
||||||
|
|
||||||
|
regmap_read(gsc->regmap, GSC_FW_CRC, ®);
|
||||||
|
gsc->fwcrc = reg;
|
||||||
|
regmap_read(gsc->regmap, GSC_FW_CRC + 1, ®);
|
||||||
|
gsc->fwcrc |= reg << 8;
|
||||||
|
|
||||||
|
gsc->i2c_hwmon = devm_i2c_new_dummy_device(dev, client->adapter,
|
||||||
|
GSC_HWMON);
|
||||||
|
if (IS_ERR(gsc->i2c_hwmon)) {
|
||||||
|
dev_err(dev, "Failed to allocate I2C device for HWMON\n");
|
||||||
|
return PTR_ERR(gsc->i2c_hwmon);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = devm_regmap_add_irq_chip(dev, gsc->regmap, client->irq,
|
||||||
|
IRQF_ONESHOT | IRQF_SHARED |
|
||||||
|
IRQF_TRIGGER_FALLING, 0,
|
||||||
|
&gsc_irq_chip, &irq_data);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
dev_info(dev, "Gateworks System Controller v%d: fw 0x%04x\n",
|
||||||
|
gsc->fwver, gsc->fwcrc);
|
||||||
|
|
||||||
|
ret = sysfs_create_group(&dev->kobj, &attr_group);
|
||||||
|
if (ret)
|
||||||
|
dev_err(dev, "failed to create sysfs attrs\n");
|
||||||
|
|
||||||
|
ret = devm_of_platform_populate(dev);
|
||||||
|
if (ret) {
|
||||||
|
sysfs_remove_group(&dev->kobj, &attr_group);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int gsc_remove(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
sysfs_remove_group(&client->dev.kobj, &attr_group);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct i2c_driver gsc_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "gateworks-gsc",
|
||||||
|
.of_match_table = gsc_of_match,
|
||||||
|
},
|
||||||
|
.probe_new = gsc_probe,
|
||||||
|
.remove = gsc_remove,
|
||||||
|
};
|
||||||
|
module_i2c_driver(gsc_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Tim Harvey <tharvey@gateworks.com>");
|
||||||
|
MODULE_DESCRIPTION("I2C Core interface for GSC");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
@ -436,6 +436,9 @@ devm_hwmon_device_register_with_info(struct device *dev,
|
|||||||
void hwmon_device_unregister(struct device *dev);
|
void hwmon_device_unregister(struct device *dev);
|
||||||
void devm_hwmon_device_unregister(struct device *dev);
|
void devm_hwmon_device_unregister(struct device *dev);
|
||||||
|
|
||||||
|
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
|
||||||
|
u32 attr, int channel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hwmon_is_bad_char - Is the char invalid in a hwmon name
|
* hwmon_is_bad_char - Is the char invalid in a hwmon name
|
||||||
* @ch: the char to be considered
|
* @ch: the char to be considered
|
||||||
|
76
include/linux/mfd/gsc.h
Normal file
76
include/linux/mfd/gsc.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 Gateworks Corporation
|
||||||
|
*/
|
||||||
|
#ifndef __LINUX_MFD_GSC_H_
|
||||||
|
#define __LINUX_MFD_GSC_H_
|
||||||
|
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
/* Device Addresses */
|
||||||
|
#define GSC_MISC 0x20
|
||||||
|
#define GSC_UPDATE 0x21
|
||||||
|
#define GSC_GPIO 0x23
|
||||||
|
#define GSC_HWMON 0x29
|
||||||
|
#define GSC_EEPROM0 0x50
|
||||||
|
#define GSC_EEPROM1 0x51
|
||||||
|
#define GSC_EEPROM2 0x52
|
||||||
|
#define GSC_EEPROM3 0x53
|
||||||
|
#define GSC_RTC 0x68
|
||||||
|
|
||||||
|
/* Register offsets */
|
||||||
|
enum {
|
||||||
|
GSC_CTRL_0 = 0x00,
|
||||||
|
GSC_CTRL_1 = 0x01,
|
||||||
|
GSC_TIME = 0x02,
|
||||||
|
GSC_TIME_ADD = 0x06,
|
||||||
|
GSC_IRQ_STATUS = 0x0A,
|
||||||
|
GSC_IRQ_ENABLE = 0x0B,
|
||||||
|
GSC_FW_CRC = 0x0C,
|
||||||
|
GSC_FW_VER = 0x0E,
|
||||||
|
GSC_WP = 0x0F,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Bit definitions */
|
||||||
|
#define GSC_CTRL_0_PB_HARD_RESET 0
|
||||||
|
#define GSC_CTRL_0_PB_CLEAR_SECURE_KEY 1
|
||||||
|
#define GSC_CTRL_0_PB_SOFT_POWER_DOWN 2
|
||||||
|
#define GSC_CTRL_0_PB_BOOT_ALTERNATE 3
|
||||||
|
#define GSC_CTRL_0_PERFORM_CRC 4
|
||||||
|
#define GSC_CTRL_0_TAMPER_DETECT 5
|
||||||
|
#define GSC_CTRL_0_SWITCH_HOLD 6
|
||||||
|
|
||||||
|
#define GSC_CTRL_1_SLEEP_ENABLE 0
|
||||||
|
#define GSC_CTRL_1_SLEEP_ACTIVATE 1
|
||||||
|
#define GSC_CTRL_1_SLEEP_ADD 2
|
||||||
|
#define GSC_CTRL_1_SLEEP_NOWAKEPB 3
|
||||||
|
#define GSC_CTRL_1_WDT_TIME 4
|
||||||
|
#define GSC_CTRL_1_WDT_ENABLE 5
|
||||||
|
#define GSC_CTRL_1_SWITCH_BOOT_ENABLE 6
|
||||||
|
#define GSC_CTRL_1_SWITCH_BOOT_CLEAR 7
|
||||||
|
|
||||||
|
#define GSC_IRQ_PB 0
|
||||||
|
#define GSC_IRQ_KEY_ERASED 1
|
||||||
|
#define GSC_IRQ_EEPROM_WP 2
|
||||||
|
#define GSC_IRQ_RESV 3
|
||||||
|
#define GSC_IRQ_GPIO 4
|
||||||
|
#define GSC_IRQ_TAMPER 5
|
||||||
|
#define GSC_IRQ_WDT_TIMEOUT 6
|
||||||
|
#define GSC_IRQ_SWITCH_HOLD 7
|
||||||
|
|
||||||
|
int gsc_read(void *context, unsigned int reg, unsigned int *val);
|
||||||
|
int gsc_write(void *context, unsigned int reg, unsigned int val);
|
||||||
|
|
||||||
|
struct gsc_dev {
|
||||||
|
struct device *dev;
|
||||||
|
|
||||||
|
struct i2c_client *i2c; /* 0x20: interrupt controller, WDT */
|
||||||
|
struct i2c_client *i2c_hwmon; /* 0x29: hwmon, fan controller */
|
||||||
|
|
||||||
|
struct regmap *regmap;
|
||||||
|
|
||||||
|
unsigned int fwver;
|
||||||
|
unsigned short fwcrc;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __LINUX_MFD_GSC_H_ */
|
44
include/linux/platform_data/gsc_hwmon.h
Normal file
44
include/linux/platform_data/gsc_hwmon.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
#ifndef _GSC_HWMON_H
|
||||||
|
#define _GSC_HWMON_H
|
||||||
|
|
||||||
|
enum gsc_hwmon_mode {
|
||||||
|
mode_temperature,
|
||||||
|
mode_voltage,
|
||||||
|
mode_voltage_raw,
|
||||||
|
mode_max,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct gsc_hwmon_channel - configuration parameters
|
||||||
|
* @reg: I2C register offset
|
||||||
|
* @mode: channel mode
|
||||||
|
* @name: channel name
|
||||||
|
* @mvoffset: voltage offset
|
||||||
|
* @vdiv: voltage divider array (2 resistor values in milli-ohms)
|
||||||
|
*/
|
||||||
|
struct gsc_hwmon_channel {
|
||||||
|
unsigned int reg;
|
||||||
|
unsigned int mode;
|
||||||
|
const char *name;
|
||||||
|
unsigned int mvoffset;
|
||||||
|
unsigned int vdiv[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct gsc_hwmon_platform_data - platform data for gsc_hwmon driver
|
||||||
|
* @channels: pointer to array of gsc_hwmon_channel structures
|
||||||
|
* describing channels
|
||||||
|
* @nchannels: number of elements in @channels array
|
||||||
|
* @vreference: voltage reference (mV)
|
||||||
|
* @resolution: ADC bit resolution
|
||||||
|
* @fan_base: register base for FAN controller
|
||||||
|
*/
|
||||||
|
struct gsc_hwmon_platform_data {
|
||||||
|
const struct gsc_hwmon_channel *channels;
|
||||||
|
int nchannels;
|
||||||
|
unsigned int resolution;
|
||||||
|
unsigned int vreference;
|
||||||
|
unsigned int fan_base;
|
||||||
|
};
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user