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
|
@ -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";
|
||||
};
|
||||
...
|
|
@ -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>;
|
||||
};
|
||||
};
|
||||
};
|
|
@ -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
|
||||
=============== ======== ======================================
|
|
@ -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.
|
||||
=============================== ======= =======================================
|
|
@ -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
|
||||
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
|
||||
adt7475
|
||||
amc6821
|
||||
amd_energy
|
||||
asb100
|
||||
asc7621
|
||||
aspeed-pwm-tacho
|
||||
bel-pfe
|
||||
bt1-pvt
|
||||
coretemp
|
||||
da9052
|
||||
da9055
|
||||
|
@ -60,6 +62,7 @@ Hardware Monitoring Kernel Drivers
|
|||
ftsteutates
|
||||
g760a
|
||||
g762
|
||||
gsc-hwmon
|
||||
gl518sm
|
||||
hih6130
|
||||
ibmaem
|
||||
|
@ -106,6 +109,7 @@ Hardware Monitoring Kernel Drivers
|
|||
max16064
|
||||
max16065
|
||||
max1619
|
||||
max16601
|
||||
max1668
|
||||
max197
|
||||
max20730
|
||||
|
|
|
@ -123,6 +123,18 @@ Supported chips:
|
|||
|
||||
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
|
||||
|
||||
Prefix: 'max6657'
|
||||
|
@ -301,6 +313,13 @@ ADT7461, ADT7461A, NCT1008:
|
|||
* Extended temperature range (breaks compatibility)
|
||||
* 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:
|
||||
* Better local resolution
|
||||
* Remote sensor type selection
|
||||
|
@ -336,8 +355,8 @@ SA56004X:
|
|||
|
||||
All temperature values are given in degrees Celsius. Resolution
|
||||
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
|
||||
resolution of 0.125 degree for both temperatures.
|
||||
temperature, except for the MAX6654, MAX6657, MAX6658 and MAX6659 which have
|
||||
a resolution of 0.125 degree for both temperatures.
|
||||
|
||||
Each sensor has its own high and low limits, plus a critical limit.
|
||||
Additionally, there is a relative hysteresis value common to both critical
|
||||
|
|
|
@ -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
|
||||
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
|
||||
M: Huang Rui <ray.huang@amd.com>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
|
@ -7028,6 +7035,17 @@ F: kernel/futex.c
|
|||
F: tools/perf/bench/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
|
||||
M: Rob Springer <rspringer@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
|
||||
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
|
||||
tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
|
||||
depends on INPUT && X86
|
||||
|
@ -404,6 +414,31 @@ config SENSORS_ATXP1
|
|||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Hard disk drives with temperature sensors"
|
||||
depends on SCSI && ATA
|
||||
|
@ -523,6 +558,15 @@ config SENSORS_F75375S
|
|||
This driver can also be built as a module. If so, the module
|
||||
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
|
||||
tristate "Freescale MC13783/MC13892 ADC"
|
||||
depends on MFD_MC13XXX
|
||||
|
@ -1198,10 +1242,11 @@ config SENSORS_LM90
|
|||
help
|
||||
If you say yes here you get support for National Semiconductor LM90,
|
||||
LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A,
|
||||
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659,
|
||||
MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008,
|
||||
Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and
|
||||
Texas Instruments TMP451 sensor chips.
|
||||
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6654, MAX6657, MAX6658,
|
||||
MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696,
|
||||
ON Semiconductor NCT1008, Winbond/Nuvoton W83L771W/G/AWG/ASG,
|
||||
Philips SA56004, GMT G781, and Texas Instruments TMP451
|
||||
sensor chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called lm90.
|
||||
|
@ -1340,10 +1385,12 @@ config SENSORS_NCT7802
|
|||
|
||||
config SENSORS_NCT7904
|
||||
tristate "Nuvoton NCT7904"
|
||||
depends on I2C
|
||||
depends on I2C && WATCHDOG
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
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
|
||||
will be called nct7904.
|
||||
|
|
|
@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
|
|||
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
|
||||
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
|
||||
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
|
||||
obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
|
||||
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
||||
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-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_ATXP1) += atxp1.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_DA9052_ADC)+= da9052-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_GL518SM) += gl518sm.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_HIH6130) += hih6130.o
|
||||
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
|
||||
|
|
|
@ -716,7 +716,6 @@ static struct i2c_driver adt7411_driver = {
|
|||
|
||||
module_i2c_driver(adt7411_driver);
|
||||
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and "
|
||||
"Wolfram Sang <w.sang@pengutronix.de>");
|
||||
MODULE_AUTHOR("Sascha Hauer, Wolfram Sang <kernel@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("ADT7411 driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
|
||||
u8 status;
|
||||
int us;
|
||||
|
||||
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
||||
udelay(us);
|
||||
usleep_range(us, us * 16);
|
||||
status = inb(APPLESMC_CMD_PORT);
|
||||
/* read: wait for smc to settle */
|
||||
if (status & 0x01)
|
||||
return 0;
|
||||
/* timeout: give up */
|
||||
if (time_after(jiffies, end))
|
||||
break;
|
||||
}
|
||||
|
||||
pr_warn("wait_read() fail: 0x%02x\n", status);
|
||||
|
@ -178,10 +183,11 @@ static int send_byte(u8 cmd, u16 port)
|
|||
{
|
||||
u8 status;
|
||||
int us;
|
||||
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
|
||||
|
||||
outb(cmd, port);
|
||||
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
|
||||
udelay(us);
|
||||
usleep_range(us, us * 16);
|
||||
status = inb(APPLESMC_CMD_PORT);
|
||||
/* write: wait for smc to settle */
|
||||
if (status & 0x02)
|
||||
|
@ -190,7 +196,7 @@ static int send_byte(u8 cmd, u16 port)
|
|||
if (status & 0x04)
|
||||
return 0;
|
||||
/* timeout: give up */
|
||||
if (us << 1 == APPLESMC_MAX_WAIT)
|
||||
if (time_after(jiffies, end))
|
||||
break;
|
||||
/* busy: long wait and resend */
|
||||
udelay(APPLESMC_RETRY_WAIT);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell XPS421",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell Studio",
|
||||
.matches = {
|
||||
|
@ -1087,14 +1080,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||
},
|
||||
.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",
|
||||
.matches = {
|
||||
|
@ -1104,17 +1089,10 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
|
|||
.driver_data = (void *)&i8k_config_data[DELL_XPS],
|
||||
},
|
||||
{
|
||||
.ident = "Dell XPS 15 9560",
|
||||
.ident = "Dell XPS",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9560"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell XPS 15 9570",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9570"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
|
|
|
@ -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/hwmon.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -31,7 +32,7 @@ struct hwmon_device {
|
|||
const char *name;
|
||||
struct device dev;
|
||||
const struct hwmon_chip_info *chip;
|
||||
|
||||
struct list_head tzdata;
|
||||
struct attribute_group group;
|
||||
const struct attribute_group **groups;
|
||||
};
|
||||
|
@ -55,12 +56,12 @@ struct hwmon_device_attribute {
|
|||
|
||||
/*
|
||||
* Thermal zone information
|
||||
* In addition to the reference to the hwmon device,
|
||||
* also provides the sensor index.
|
||||
*/
|
||||
struct hwmon_thermal_data {
|
||||
struct list_head node; /* hwmon tzdata list entry */
|
||||
struct device *dev; /* Reference to hwmon device */
|
||||
int index; /* sensor index */
|
||||
struct thermal_zone_device *tzd;/* thermal zone device */
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
|
@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
|
|||
.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)
|
||||
{
|
||||
struct hwmon_device *hwdev = to_hwmon_device(dev);
|
||||
struct hwmon_thermal_data *tdata;
|
||||
struct thermal_zone_device *tzd;
|
||||
int err;
|
||||
|
||||
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
|
||||
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))
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
static int hwmon_thermal_add_sensor(struct device *dev, int index)
|
||||
static int hwmon_thermal_register_sensors(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hwmon_thermal_notify(struct device *dev, int index) { }
|
||||
|
||||
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
|
||||
|
||||
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),
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
int i, n;
|
||||
|
@ -596,7 +688,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
|
|||
{
|
||||
struct hwmon_device *hwdev;
|
||||
struct device *hdev;
|
||||
int i, j, err, id;
|
||||
int i, err, id;
|
||||
|
||||
/* Complain about invalid characters in hwmon name attribute */
|
||||
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)
|
||||
goto free_hwmon;
|
||||
|
||||
INIT_LIST_HEAD(&hwdev->tzdata);
|
||||
|
||||
if (dev && dev->of_node && chip && chip->ops->read &&
|
||||
chip->info[0]->type == hwmon_chip &&
|
||||
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
|
||||
const struct hwmon_channel_info **info = chip->info;
|
||||
|
||||
for (i = 1; info[i]; i++) {
|
||||
if (info[i]->type != hwmon_temp)
|
||||
continue;
|
||||
|
||||
for (j = 0; info[i]->config[j]; j++) {
|
||||
if (!chip->ops->is_visible(drvdata, hwmon_temp,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
err = hwmon_thermal_register_sensors(hdev);
|
||||
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_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 */
|
||||
#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));
|
||||
}
|
||||
|
||||
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
|
||||
* 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 */
|
||||
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 */
|
||||
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 */
|
||||
static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);
|
||||
|
||||
/* calculated 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 */
|
||||
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[] = {
|
||||
&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,
|
||||
NULL,
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
#include <linux/spi/spi.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of_device.h>
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
||||
#define DRVNAME "lm70"
|
||||
|
||||
|
@ -148,18 +148,50 @@ static const struct of_device_id lm70_of_ids[] = {
|
|||
MODULE_DEVICE_TABLE(of, lm70_of_ids);
|
||||
#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)
|
||||
{
|
||||
const struct of_device_id *match;
|
||||
const struct of_device_id *of_match;
|
||||
struct device *hwmon_dev;
|
||||
struct lm70 *p_lm70;
|
||||
int chip;
|
||||
|
||||
match = of_match_device(lm70_of_ids, &spi->dev);
|
||||
if (match)
|
||||
chip = (int)(uintptr_t)match->data;
|
||||
else
|
||||
chip = spi_get_device_id(spi)->driver_data;
|
||||
of_match = of_match_device(lm70_of_ids, &spi->dev);
|
||||
if (of_match)
|
||||
chip = (int)(uintptr_t)of_match->data;
|
||||
else {
|
||||
#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 */
|
||||
if (spi->mode & (SPI_CPOL | SPI_CPHA))
|
||||
|
@ -195,6 +227,7 @@ static struct spi_driver lm70_driver = {
|
|||
.driver = {
|
||||
.name = "lm70",
|
||||
.of_match_table = of_match_ptr(lm70_of_ids),
|
||||
.acpi_match_table = ACPI_PTR(lm70_acpi_ids),
|
||||
},
|
||||
.id_table = lm70_ids,
|
||||
.probe = lm70_probe,
|
||||
|
|
|
@ -797,8 +797,10 @@ static int lm75_detect(struct i2c_client *new_client,
|
|||
|
||||
/* First check for LM75A */
|
||||
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
|
||||
|| i2c_smbus_read_byte_data(new_client, 5) != 0xff
|
||||
|| i2c_smbus_read_byte_data(new_client, 6) != 0xff)
|
||||
|
@ -849,6 +851,7 @@ static int lm75_suspend(struct device *dev)
|
|||
{
|
||||
int status;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||
|
@ -863,6 +866,7 @@ static int lm75_resume(struct device *dev)
|
|||
{
|
||||
int status;
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
|
||||
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
|
||||
if (status < 0) {
|
||||
dev_dbg(&client->dev, "Can't read config? %d\n", status);
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
lm75.h - Part of lm_sensors, Linux kernel modules for hardware
|
||||
monitoring
|
||||
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
||||
|
||||
*/
|
||||
* lm75.h - Part of lm_sensors, Linux kernel modules for hardware monitoring
|
||||
* Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
||||
*/
|
||||
|
||||
/*
|
||||
This file contains common code for encoding/decoding LM75 type
|
||||
temperature readings, which are emulated by many of the chips
|
||||
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.
|
||||
*/
|
||||
* This file contains common code for encoding/decoding LM75 type
|
||||
* temperature readings, which are emulated by many of the chips
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
|
@ -20,18 +18,23 @@
|
|||
#define LM75_TEMP_MAX 125000
|
||||
#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)
|
||||
{
|
||||
int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
|
||||
|
||||
ntemp += (ntemp < 0 ? -250 : 250);
|
||||
return (u16)((ntemp / 500) << 7);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,14 @@
|
|||
* explicitly as max6659, or if its address is not 0x4c.
|
||||
* 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
|
||||
* MAX6692 chips made by Maxim. These are again similar to the LM86,
|
||||
* but they use unsigned temperature values and can report temperatures
|
||||
|
@ -94,8 +102,8 @@
|
|||
* have address 0x4d.
|
||||
* MAX6647 has address 0x4e.
|
||||
* MAX6659 can have address 0x4c, 0x4d or 0x4e.
|
||||
* MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b,
|
||||
* 0x4c, 0x4d or 0x4e.
|
||||
* MAX6654, MAX6680, and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29,
|
||||
* 0x2a, 0x2b, 0x4c, 0x4d or 0x4e.
|
||||
* SA56004 can have address 0x48 through 0x4F.
|
||||
*/
|
||||
|
||||
|
@ -104,7 +112,7 @@ static const unsigned short normal_i2c[] = {
|
|||
0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
|
||||
|
||||
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
|
||||
|
@ -145,7 +153,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
|
|||
#define LM90_REG_R_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 MAX6696_REG_R_STATUS2 0x12
|
||||
|
@ -209,6 +217,7 @@ static const struct i2c_device_id lm90_id[] = {
|
|||
{ "max6646", max6646 },
|
||||
{ "max6647", max6646 },
|
||||
{ "max6649", max6646 },
|
||||
{ "max6654", max6654 },
|
||||
{ "max6657", max6657 },
|
||||
{ "max6658", max6657 },
|
||||
{ "max6659", max6659 },
|
||||
|
@ -269,6 +278,10 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = {
|
|||
.compatible = "dallas,max6649",
|
||||
.data = (void *)max6646
|
||||
},
|
||||
{
|
||||
.compatible = "dallas,max6654",
|
||||
.data = (void *)max6654
|
||||
},
|
||||
{
|
||||
.compatible = "dallas,max6657",
|
||||
.data = (void *)max6657
|
||||
|
@ -367,6 +380,11 @@ static const struct lm90_params lm90_params[] = {
|
|||
.max_convrate = 6,
|
||||
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||
},
|
||||
[max6654] = {
|
||||
.alert_alarms = 0x7c,
|
||||
.max_convrate = 7,
|
||||
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
|
||||
},
|
||||
[max6657] = {
|
||||
.flags = LM90_PAUSE_FOR_CONFIG,
|
||||
.alert_alarms = 0x7c,
|
||||
|
@ -1557,6 +1575,16 @@ static int lm90_detect(struct i2c_client *client,
|
|||
&& (config1 & 0x3f) == 0x00
|
||||
&& convrate <= 0x07) {
|
||||
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
|
||||
if (address == 0x4C
|
||||
|
@ -1660,6 +1688,15 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
|
|||
if (data->kind == max6680)
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -2047,7 +2047,7 @@ store_temp_beep(struct device *dev, struct device_attribute *attr,
|
|||
static umode_t nct6775_in_is_visible(struct kobject *kobj,
|
||||
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);
|
||||
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,
|
||||
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);
|
||||
int fan = index / 6; /* fan 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,
|
||||
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);
|
||||
int temp = index / 10; /* temp 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,
|
||||
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);
|
||||
int pwm = index / 36; /* pwm 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,
|
||||
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);
|
||||
|
||||
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,
|
||||
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);
|
||||
unsigned int reg;
|
||||
int err;
|
||||
|
@ -778,7 +778,7 @@ static struct attribute *nct7802_in_attrs[] = {
|
|||
static umode_t nct7802_in_is_visible(struct kobject *kobj,
|
||||
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);
|
||||
unsigned int reg;
|
||||
int err;
|
||||
|
@ -853,7 +853,7 @@ static struct attribute *nct7802_fan_attrs[] = {
|
|||
static umode_t nct7802_fan_is_visible(struct kobject *kobj,
|
||||
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);
|
||||
int fan = index / 4; /* 4 attributes per fan */
|
||||
unsigned int reg;
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
* Copyright (c) 2019 Advantech
|
||||
* Author: Amy.Shih <amy.shih@advantech.com.tw>
|
||||
*
|
||||
* Copyright (c) 2020 Advantech
|
||||
* Author: Yuechao Zhao <yuechao.zhao@advantech.com.cn>
|
||||
*
|
||||
* Supports the following chips:
|
||||
*
|
||||
* Chip #vin #fan #pwm #temp #dts chip ID
|
||||
|
@ -20,6 +23,7 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/watchdog.h>
|
||||
|
||||
#define VENDOR_ID_REG 0x7A /* Any bank */
|
||||
#define NUVOTON_ID 0x50
|
||||
|
@ -88,18 +92,42 @@
|
|||
#define FANCTL1_FMR_REG 0x00 /* 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 THERMAL_DIODE_MODE 0x1
|
||||
#define THERMISTOR_MODE 0x3
|
||||
|
||||
#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[] = {
|
||||
0x2d, 0x2e, I2C_CLIENT_END
|
||||
};
|
||||
|
||||
struct nct7904_data {
|
||||
struct i2c_client *client;
|
||||
struct watchdog_device wdt;
|
||||
struct mutex bank_lock;
|
||||
int bank_sel;
|
||||
u32 fanin_mask;
|
||||
|
@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_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,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client,
|
|||
hwmon_dev =
|
||||
devm_hwmon_device_register_with_info(dev, client->name, data,
|
||||
&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[] = {
|
||||
|
|
|
@ -146,6 +146,15 @@ config SENSORS_MAX16064
|
|||
This driver can also be built as a module. If so, the module will
|
||||
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
|
||||
tristate "Maxim MAX20730, MAX20734, MAX20743"
|
||||
help
|
||||
|
|
|
@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
|
|||
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
|
||||
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
|
||||
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
|
||||
obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
|
||||
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
|
||||
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
|
||||
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
|
||||
|
|
|
@ -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 */
|
||||
int (*read_status)(struct i2c_client *client, int page);
|
||||
|
||||
u8 currpage;
|
||||
u8 currphase; /* current phase, 0xff for all */
|
||||
s16 currpage; /* current page, -1 for unknown/unset */
|
||||
s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */
|
||||
};
|
||||
|
||||
struct pmbus_debugfs_entry {
|
||||
|
@ -2529,8 +2529,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
|
|||
if (pdata)
|
||||
data->flags = pdata->flags;
|
||||
data->info = info;
|
||||
data->currpage = 0xff;
|
||||
data->currphase = 0xfe;
|
||||
data->currpage = -1;
|
||||
data->currphase = -1;
|
||||
|
||||
ret = pmbus_init_common(client, data, info);
|
||||
if (ret < 0)
|
||||
|
|
|
@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS
|
|||
Select this option to enable support for Samsung Exynos Low Power
|
||||
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
|
||||
tristate
|
||||
depends on (SPI_MASTER || I2C)
|
||||
|
|
|
@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
|
|||
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
|
||||
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.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_I2CPLD) += htc-i2cpld.o
|
||||
|
|
|
@ -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 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
|
||||
* @ch: the char to be considered
|
||||
|
|
|
@ -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_ */
|
|
@ -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