Commit 129b9a5c authored by Linus Torvalds's avatar Linus Torvalds

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
  ...
parents b6f91ab6 87976ce2
# 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";
};
...
# 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>;
};
};
};
.. 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
=============== ======== ======================================
.. 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.
=============================== ======= =======================================
.. 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 ...@@ -99,6 +99,25 @@ Sysfs entries for ina226, ina230 and ina231 only
------------------------------------------------ ------------------------------------------------
======================= ==================================================== ======================= ====================================================
in0_lcrit Critical low shunt voltage
in0_crit Critical high shunt voltage
in0_lcrit_alarm Shunt voltage critical low alarm
in0_crit_alarm Shunt voltage critical high alarm
in1_lcrit Critical low bus voltage
in1_crit Critical high bus voltage
in1_lcrit_alarm Bus voltage critical low alarm
in1_crit_alarm Bus voltage critical high alarm
power1_crit Critical high power
power1_crit_alarm Power critical high alarm
update_interval data conversion time; affects number of samples used update_interval data conversion time; affects number of samples used
to average results for shunt and bus voltages. to average results for shunt and bus voltages.
======================= ==================================================== ======================= ====================================================
.. note::
- Configure `shunt_resistor` before configure `power1_crit`, because power
value is calculated based on `shunt_resistor` set.
- Because of the underlying register implementation, only one `*crit` setting
and its `alarm` can be active. Writing to one `*crit` setting clears other
`*crit` settings and alarms. Writing 0 to any `*crit` setting clears all
`*crit` settings and alarms.
...@@ -39,10 +39,12 @@ Hardware Monitoring Kernel Drivers ...@@ -39,10 +39,12 @@ Hardware Monitoring Kernel Drivers
adt7470 adt7470
adt7475 adt7475
amc6821 amc6821
amd_energy
asb100 asb100
asc7621 asc7621
aspeed-pwm-tacho aspeed-pwm-tacho
bel-pfe bel-pfe
bt1-pvt
coretemp coretemp
da9052 da9052
da9055 da9055
...@@ -60,6 +62,7 @@ Hardware Monitoring Kernel Drivers ...@@ -60,6 +62,7 @@ Hardware Monitoring Kernel Drivers
ftsteutates ftsteutates
g760a g760a
g762 g762
gsc-hwmon
gl518sm gl518sm
hih6130 hih6130
ibmaem ibmaem
...@@ -106,6 +109,7 @@ Hardware Monitoring Kernel Drivers ...@@ -106,6 +109,7 @@ Hardware Monitoring Kernel Drivers
max16064 max16064
max16065 max16065
max1619 max1619
max16601
max1668 max1668
max197 max197
max20730 max20730
......
...@@ -123,6 +123,18 @@ Supported chips: ...@@ -123,6 +123,18 @@ Supported chips:
http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3497 http://www.maxim-ic.com/quick_view2.cfm/qv_pk/3497
* Maxim MAX6654
Prefix: 'max6654'
Addresses scanned: I2C 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b,
0x4c, 0x4d and 0x4e
Datasheet: Publicly available at the Maxim website
https://www.maximintegrated.com/en/products/sensors/MAX6654.html
* Maxim MAX6657 * Maxim MAX6657
Prefix: 'max6657' Prefix: 'max6657'
...@@ -301,6 +313,13 @@ ADT7461, ADT7461A, NCT1008: ...@@ -301,6 +313,13 @@ ADT7461, ADT7461A, NCT1008:
* Extended temperature range (breaks compatibility) * Extended temperature range (breaks compatibility)
* Lower resolution for remote temperature * Lower resolution for remote temperature
MAX6654:
* Better local resolution
* Selectable address
* Remote sensor type selection
* Extended temperature range
* Extended resolution only available when conversion rate <= 1 Hz
MAX6657 and MAX6658: MAX6657 and MAX6658:
* Better local resolution * Better local resolution
* Remote sensor type selection * Remote sensor type selection
...@@ -336,8 +355,8 @@ SA56004X: ...@@ -336,8 +355,8 @@ SA56004X:
All temperature values are given in degrees Celsius. Resolution All temperature values are given in degrees Celsius. Resolution
is 1.0 degree for the local temperature, 0.125 degree for the remote is 1.0 degree for the local temperature, 0.125 degree for the remote
temperature, except for the MAX6657, MAX6658 and MAX6659 which have a temperature, except for the MAX6654, MAX6657, MAX6658 and MAX6659 which have
resolution of 0.125 degree for both temperatures. a resolution of 0.125 degree for both temperatures.
Each sensor has its own high and low limits, plus a critical limit. Each sensor has its own high and low limits, plus a critical limit.
Additionally, there is a relative hysteresis value common to both critical Additionally, there is a relative hysteresis value common to both critical
......
.. 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.
======================= =======================================================
...@@ -842,6 +842,13 @@ S: Supported ...@@ -842,6 +842,13 @@ S: Supported
T: git git://people.freedesktop.org/~agd5f/linux T: git git://people.freedesktop.org/~agd5f/linux
F: drivers/gpu/drm/amd/display/ F: drivers/gpu/drm/amd/display/
AMD ENERGY DRIVER
M: Naveen Krishna Chatradhi <nchatrad@amd.com>
L: linux-hwmon@vger.kernel.org
S: Maintained
F: Documentation/hwmon/amd_energy.rst
F: drivers/hwmon/amd_energy.c
AMD FAM15H PROCESSOR POWER MONITORING DRIVER AMD FAM15H PROCESSOR POWER MONITORING DRIVER
M: Huang Rui <ray.huang@amd.com> M: Huang Rui <ray.huang@amd.com>
L: linux-hwmon@vger.kernel.org L: linux-hwmon@vger.kernel.org
...@@ -7028,6 +7035,17 @@ F: kernel/futex.c ...@@ -7028,6 +7035,17 @@ F: kernel/futex.c
F: tools/perf/bench/futex* F: tools/perf/bench/futex*
F: tools/testing/selftests/futex/ F: tools/testing/selftests/futex/
GATEWORKS SYSTEM CONTROLLER (GSC) DRIVER
M: Tim Harvey <tharvey@gateworks.com>
M: Robert Jones <rjones@gateworks.com>
S: Maintained
F: Documentation/devicetree/bindings/mfd/gateworks-gsc.yaml
F: drivers/mfd/gateworks-gsc.c
F: include/linux/mfd/gsc.h
F: Documentation/hwmon/gsc-hwmon.rst
F: drivers/hwmon/gsc-hwmon.c
F: include/linux/platform_data/gsc_hwmon.h
GASKET DRIVER FRAMEWORK GASKET DRIVER FRAMEWORK
M: Rob Springer <rspringer@google.com> M: Rob Springer <rspringer@google.com>
M: Todd Poynor <toddpoynor@google.com> M: Todd Poynor <toddpoynor@google.com>
......
...@@ -324,6 +324,16 @@ config SENSORS_FAM15H_POWER ...@@ -324,6 +324,16 @@ config SENSORS_FAM15H_POWER
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called fam15h_power. will be called fam15h_power.
config SENSORS_AMD_ENERGY
tristate "AMD RAPL MSR based Energy driver"
depends on X86
help
If you say yes here you get support for core and package energy
sensors, based on RAPL MSR for AMD family 17h and above CPUs.
This driver can also be built as a module. If so, the module
will be called as amd_energy.
config SENSORS_APPLESMC config SENSORS_APPLESMC
tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)"
depends on INPUT && X86 depends on INPUT && X86
...@@ -404,6 +414,31 @@ config SENSORS_ATXP1 ...@@ -404,6 +414,31 @@ config SENSORS_ATXP1
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called atxp1. will be called atxp1.
config SENSORS_BT1_PVT
tristate "Baikal-T1 Process, Voltage, Temperature sensor driver"
depends on MIPS_BAIKAL_T1 || COMPILE_TEST
help
If you say yes here you get support for Baikal-T1 PVT sensor
embedded into the SoC.
This driver can also be built as a module. If so, the module will be
called bt1-pvt.
config SENSORS_BT1_PVT_ALARMS
bool "Enable Baikal-T1 PVT sensor alarms"
depends on SENSORS_BT1_PVT
help
Baikal-T1 PVT IP-block provides threshold registers for each
supported sensor. But the corresponding interrupts might be
generated by the thresholds comparator only in synchronization with
a data conversion. Additionally there is only one sensor data can
be converted at a time. All of these makes the interface impossible
to be used for the hwmon alarms implementation without periodic
switch between the PVT sensors. By default the data conversion is
performed on demand from the user-space. If this config is enabled
the data conversion will be periodically performed and the data will be
saved in the internal driver cache.
config SENSORS_DRIVETEMP config SENSORS_DRIVETEMP
tristate "Hard disk drives with temperature sensors" tristate "Hard disk drives with temperature sensors"
depends on SCSI && ATA depends on SCSI && ATA
...@@ -523,6 +558,15 @@ config SENSORS_F75375S ...@@ -523,6 +558,15 @@ config SENSORS_F75375S
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called f75375s. will be called f75375s.
config SENSORS_GSC
tristate "Gateworks System Controller ADC"
depends on MFD_GATEWORKS_GSC
help
Support for the Gateworks System Controller A/D converters.
To compile this driver as a module, choose M here:
the module will be called gsc-hwmon.
config SENSORS_MC13783_ADC config SENSORS_MC13783_ADC
tristate "Freescale MC13783/MC13892 ADC" tristate "Freescale MC13783/MC13892 ADC"
depends on MFD_MC13XXX depends on MFD_MC13XXX
...@@ -1198,10 +1242,11 @@ config SENSORS_LM90 ...@@ -1198,10 +1242,11 @@ config SENSORS_LM90
help help
If you say yes here you get support for National Semiconductor LM90, If you say yes here you get support for National Semiconductor LM90,
LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A, LM86, LM89 and LM99, Analog Devices ADM1032, ADT7461, and ADT7461A,
Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6657, MAX6658, MAX6659, Maxim MAX6646, MAX6647, MAX6648, MAX6649, MAX6654, MAX6657, MAX6658,
MAX6680, MAX6681, MAX6692, MAX6695, MAX6696, ON Semiconductor NCT1008, MAX6659, MAX6680, MAX6681, MAX6692, MAX6695, MAX6696,
Winbond/Nuvoton W83L771W/G/AWG/ASG, Philips SA56004, GMT G781, and ON Semiconductor NCT1008, Winbond/Nuvoton W83L771W/G/AWG/ASG,
Texas Instruments TMP451 sensor chips. Philips SA56004, GMT G781, and Texas Instruments TMP451
sensor chips.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called lm90. will be called lm90.
...@@ -1340,10 +1385,12 @@ config SENSORS_NCT7802 ...@@ -1340,10 +1385,12 @@ config SENSORS_NCT7802
config SENSORS_NCT7904 config SENSORS_NCT7904
tristate "Nuvoton NCT7904" tristate "Nuvoton NCT7904"
depends on I2C depends on I2C && WATCHDOG
select WATCHDOG_CORE
help help
If you say yes here you get support for the Nuvoton NCT7904 If you say yes here you get support for the Nuvoton NCT7904
hardware monitoring chip, including manual fan speed control. hardware monitoring chip, including manual fan speed control
and support for the integrated watchdog.
This driver can also be built as a module. If so, the module This driver can also be built as a module. If so, the module
will be called nct7904. will be called nct7904.
......
...@@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o ...@@ -45,6 +45,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o obj-$(CONFIG_SENSORS_ARM_SCPI) += scpi-hwmon.o
...@@ -53,6 +54,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o ...@@ -53,6 +54,7 @@ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o
obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_BT1_PVT) += bt1-pvt.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
...@@ -74,6 +76,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o ...@@ -74,6 +76,7 @@ obj-$(CONFIG_SENSORS_G760A) += g760a.o
obj-$(CONFIG_SENSORS_G762) += g762.o obj-$(CONFIG_SENSORS_G762) += g762.o
obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o
obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o
obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o
obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o
obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o
obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o
......
...@@ -716,7 +716,6 @@ static struct i2c_driver adt7411_driver = { ...@@ -716,7 +716,6 @@ static struct i2c_driver adt7411_driver = {
module_i2c_driver(adt7411_driver); module_i2c_driver(adt7411_driver);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de> and " MODULE_AUTHOR("Sascha Hauer, Wolfram Sang <kernel@pengutronix.de>");
"Wolfram Sang <w.sang@pengutronix.de>");
MODULE_DESCRIPTION("ADT7411 driver"); MODULE_DESCRIPTION("ADT7411 driver");
MODULE_LICENSE("GPL v2"); MODULE_LICENSE("GPL v2");
// 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; ...@@ -156,14 +156,19 @@ static struct workqueue_struct *applesmc_led_wq;
*/ */
static int wait_read(void) static int wait_read(void)
{ {
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
u8 status; u8 status;
int us; int us;
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
udelay(us); usleep_range(us, us * 16);
status = inb(APPLESMC_CMD_PORT); status = inb(APPLESMC_CMD_PORT);
/* read: wait for smc to settle */ /* read: wait for smc to settle */
if (status & 0x01) if (status & 0x01)
return 0; return 0;
/* timeout: give up */
if (time_after(jiffies, end))
break;
} }
pr_warn("wait_read() fail: 0x%02x\n", status); pr_warn("wait_read() fail: 0x%02x\n", status);
...@@ -178,10 +183,11 @@ static int send_byte(u8 cmd, u16 port) ...@@ -178,10 +183,11 @@ static int send_byte(u8 cmd, u16 port)
{ {
u8 status; u8 status;
int us; int us;
unsigned long end = jiffies + (APPLESMC_MAX_WAIT * HZ) / USEC_PER_SEC;
outb(cmd, port); outb(cmd, port);
for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) {
udelay(us); usleep_range(us, us * 16);
status = inb(APPLESMC_CMD_PORT); status = inb(APPLESMC_CMD_PORT);
/* write: wait for smc to settle */ /* write: wait for smc to settle */
if (status & 0x02) if (status & 0x02)
...@@ -190,7 +196,7 @@ static int send_byte(u8 cmd, u16 port) ...@@ -190,7 +196,7 @@ static int send_byte(u8 cmd, u16 port)
if (status & 0x04) if (status & 0x04)
return 0; return 0;
/* timeout: give up */ /* timeout: give up */
if (us << 1 == APPLESMC_MAX_WAIT) if (time_after(jiffies, end))
break; break;
/* busy: long wait and resend */ /* busy: long wait and resend */
udelay(APPLESMC_RETRY_WAIT); udelay(APPLESMC_RETRY_WAIT);
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
*
* Authors:
* Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>
* Serge Semin <Sergey.Semin@baikalelectronics.ru>
*
* Baikal-T1 Process, Voltage, Temperature sensor driver
*/
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/device.h>
#include <linux/hwmon-sysfs.h>
#include <linux/hwmon.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/seqlock.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include "bt1-pvt.h"
/*
* For the sake of the code simplification we created the sensors info table
* with the sensor names, activation modes, threshold registers base address
* and the thresholds bit fields.
*/
static const struct pvt_sensor_info pvt_info[] = {
PVT_SENSOR_INFO(0, "CPU Core Temperature", hwmon_temp, TEMP, TTHRES),
PVT_SENSOR_INFO(0, "CPU Core Voltage", hwmon_in, VOLT, VTHRES),
PVT_SENSOR_INFO(1, "CPU Core Low-Vt", hwmon_in, LVT, LTHRES),
PVT_SENSOR_INFO(2, "CPU Core High-Vt", hwmon_in, HVT, HTHRES),
PVT_SENSOR_INFO(3, "CPU Core Standard-Vt", hwmon_in, SVT, STHRES),
};
/*
* The original translation formulae of the temperature (in degrees of Celsius)
* to PVT data and vice-versa are following:
* N = 1.8322e-8*(T^4) + 2.343e-5*(T^3) + 8.7018e-3*(T^2) + 3.9269*(T^1) +
* 1.7204e2,
* T = -1.6743e-11*(N^4) + 8.1542e-8*(N^3) + -1.8201e-4*(N^2) +
* 3.1020e-1*(N^1) - 4.838e1,
* where T = [-48.380, 147.438]C and N = [0, 1023].
* They must be accordingly altered to be suitable for the integer arithmetics.
* The technique is called 'factor redistribution', which just makes sure the
* multiplications and divisions are made so to have a result of the operations
* within the integer numbers limit. In addition we need to translate the
* formulae to accept millidegrees of Celsius. Here what they look like after
* the alterations:
* N = (18322e-20*(T^4) + 2343e-13*(T^3) + 87018e-9*(T^2) + 39269e-3*T +
* 17204e2) / 1e4,
* T = -16743e-12*(D^4) + 81542e-9*(D^3) - 182010e-6*(D^2) + 310200e-3*D -
* 48380,
* where T = [-48380, 147438] mC and N = [0, 1023].
*/
static const struct pvt_poly poly_temp_to_N = {
.total_divider = 10000,
.terms = {
{4, 18322, 10000, 10000},
{3, 2343, 10000, 10},
{2, 87018, 10000, 10},
{1, 39269, 1000, 1},
{0, 1720400, 1, 1}
}
};
static const struct pvt_poly poly_N_to_temp = {
.total_divider = 1,
.terms = {
{4, -16743, 1000, 1},
{3, 81542, 1000, 1},
{2, -182010, 1000, 1},
{1, 310200, 1000, 1},
{0, -48380, 1, 1}
}
};
/*
* Similar alterations are performed for the voltage conversion equations.
* The original formulae are:
* N = 1.8658e3*V - 1.1572e3,
* V = (N + 1.1572e3) / 1.8658e3,
* where V = [0.620, 1.168] V and N = [0, 1023].
* After the optimization they looks as follows:
* N = (18658e-3*V - 11572) / 10,
* V = N * 10^5 / 18658 + 11572 * 10^4 / 18658.
*/
static const struct pvt_poly poly_volt_to_N = {
.total_divider = 10,
.terms = {
{1, 18658, 1000, 1},
{0, -11572, 1, 1}
}
};
static const struct pvt_poly poly_N_to_volt = {
.total_divider = 10,
.terms = {
{1, 100000, 18658, 1},
{0, 115720000, 1, 18658}
}
};
/*
* Here is the polynomial calculation function, which performs the
* redistributed terms calculations. It's pretty straightforward. We walk
* over each degree term up to the free one, and perform the redistributed
* multiplication of the term coefficient, its divider (as for the rationale
* fraction representation), data power and the rational fraction divider
* leftover. Then all of this is collected in a total sum variable, which
* value is normalized by the total divider before being returned.
*/
static long pvt_calc_poly(const struct pvt_poly *poly, long data)
{
const struct pvt_poly_term *term = poly->terms;
long tmp, ret = 0;
int deg;
do {
tmp = term->coef;
for (deg = 0; deg < term->deg; ++deg)
tmp = mult_frac(tmp, data, term->divider);
ret += tmp / term->divider_leftover;
} while ((term++)->deg);
return ret / poly->total_divider;
}
static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data)
{
u32 old;
old = readl_relaxed(reg);
writel((old & ~mask) | (data & mask), reg);
return old & mask;
}
/*
* Baikal-T1 PVT mode can be updated only when the controller is disabled.
* So first we disable it, then set the new mode together with the controller
* getting back enabled. The same concerns the temperature trim and
* measurements timeout. If it is necessary the interface mutex is supposed
* to be locked at the time the operations are performed.
*/
static inline void pvt_set_mode(struct pvt_hwmon *pvt, u32 mode)
{
u32 old;
mode = FIELD_PREP(PVT_CTRL_MODE_MASK, mode);
old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_MODE_MASK | PVT_CTRL_EN,
mode | old);
}
static inline u32 pvt_calc_trim(long temp)
{
temp = clamp_val(temp, 0, PVT_TRIM_TEMP);
return DIV_ROUND_UP(temp, PVT_TRIM_STEP);
}
static inline void pvt_set_trim(struct pvt_hwmon *pvt, u32 trim)
{
u32 old;
trim = FIELD_PREP(PVT_CTRL_TRIM_MASK, trim);
old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_TRIM_MASK | PVT_CTRL_EN,
trim | old);
}
static inline void pvt_set_tout(struct pvt_hwmon *pvt, u32 tout)
{
u32 old;
old = pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
writel(tout, pvt->regs + PVT_TTIMEOUT);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, old);
}
/*
* This 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
* described further in this comment. 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.
*
* Baikal-T1 PVT embedded controller is based on the Analog Bits PVT sensor,
* but is equipped with a dedicated control wrapper. It exposes the PVT
* sub-block registers space via the APB3 bus. In addition the wrapper provides
* a common interrupt vector of the sensors conversion completion events and
* threshold value alarms. Alas the wrapper interface hasn't been fully thought
* through. There is only one sensor can be activated at a time, for which the
* thresholds comparator is enabled right after the data conversion is
* completed. Due to this if alarms need to be implemented for all available
* sensors we can't just set the thresholds and enable the interrupts. We need
* to enable the sensors one after another and let the controller to detect
* the alarms by itself at each conversion. This also makes pointless to handle
* the alarms interrupts, since in occasion they happen synchronously with
* data conversion completion. The best driver design would be to have the
* completion interrupts enabled only and keep the converted value in the
* driver data cache. This solution is implemented if hwmon alarms are enabled
* in this driver. In case if the alarms are disabled, the conversion is
* performed on demand at the time a sensors input file is read.
*/
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
#define pvt_hard_isr NULL
static irqreturn_t pvt_soft_isr(int irq, void *data)
{
const struct pvt_sensor_info *info;
struct pvt_hwmon *pvt = data;
struct pvt_cache *cache;
u32 val, thres_sts, old;
/*
* DVALID bit will be cleared by reading the data. We need to save the
* status before the next conversion happens. Threshold events will be
* handled a bit later.
*/
thres_sts = readl(pvt->regs + PVT_RAW_INTR_STAT);
/*
* Then lets recharge the PVT interface with the next sampling mode.
* Lock the interface mutex to serialize trim, timeouts and alarm
* thresholds settings.
*/
cache = &pvt->cache[pvt->sensor];
info = &pvt_info[pvt->sensor];
pvt->sensor = (pvt->sensor == PVT_SENSOR_LAST) ?
PVT_SENSOR_FIRST : (pvt->sensor + 1);
/*
* For some reason we have to mask the interrupt before changing the
* mode, otherwise sometimes the temperature mode doesn't get
* activated even though the actual mode in the ctrl register
* corresponds to one. Then we read the data. By doing so we also
* recharge the data conversion. After this the mode corresponding
* to the next sensor in the row is set. Finally we enable the
* interrupts back.
*/
mutex_lock(&pvt->iface_mtx);
old = pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
val = readl(pvt->regs + PVT_DATA);
pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, old);
mutex_unlock(&pvt->iface_mtx);
/*
* We can now update the data cache with data just retrieved from the
* sensor. Lock write-seqlock to make sure the reader has a coherent
* data.
*/
write_seqlock(&cache->data_seqlock);
cache->data = FIELD_GET(PVT_DATA_DATA_MASK, val);
write_sequnlock(&cache->data_seqlock);
/*
* While PVT core is doing the next mode data conversion, we'll check
* whether the alarms were triggered for the current sensor. Note that
* according to the documentation only one threshold IRQ status can be
* set at a time, that's why if-else statement is utilized.
*/
if ((thres_sts & info->thres_sts_lo) ^ cache->thres_sts_lo) {
WRITE_ONCE(cache->thres_sts_lo, thres_sts & info->thres_sts_lo);
hwmon_notify_event(pvt->hwmon, info->type, info->attr_min_alarm,
info->channel);
} else if ((thres_sts & info->thres_sts_hi) ^ cache->thres_sts_hi) {
WRITE_ONCE(cache->thres_sts_hi, thres_sts & info->thres_sts_hi);
hwmon_notify_event(pvt->hwmon, info->type, info->attr_max_alarm,
info->channel);
}
return IRQ_HANDLED;
}
inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type)
{
return 0644;
}
inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type)
{
return 0444;
}
static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
long *val)
{
struct pvt_cache *cache = &pvt->cache[type];
unsigned int seq;
u32 data;
do {
seq = read_seqbegin(&cache->data_seqlock);
data = cache->data;
} while (read_seqretry(&cache->data_seqlock, seq));
if (type == PVT_TEMP)
*val = pvt_calc_poly(&poly_N_to_temp, data);
else
*val = pvt_calc_poly(&poly_N_to_volt, data);
return 0;
}
static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long *val)
{
u32 data;
/* No need in serialization, since it is just read from MMIO. */
data = readl(pvt->regs + pvt_info[type].thres_base);
if (is_low)
data = FIELD_GET(PVT_THRES_LO_MASK, data);
else
data = FIELD_GET(PVT_THRES_HI_MASK, data);
if (type == PVT_TEMP)
*val = pvt_calc_poly(&poly_N_to_temp, data);
else
*val = pvt_calc_poly(&poly_N_to_volt, data);
return 0;
}
static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long val)
{
u32 data, limit, mask;
int ret;
if (type == PVT_TEMP) {
val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX);
data = pvt_calc_poly(&poly_temp_to_N, val);
} else {
val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX);
data = pvt_calc_poly(&poly_volt_to_N, val);
}
/* Serialize limit update, since a part of the register is changed. */
ret = mutex_lock_interruptible(&pvt->iface_mtx);
if (ret)
return ret;
/* Make sure the upper and lower ranges don't intersect. */
limit = readl(pvt->regs + pvt_info[type].thres_base);
if (is_low) {
limit = FIELD_GET(PVT_THRES_HI_MASK, limit);
data = clamp_val(data, PVT_DATA_MIN, limit);
data = FIELD_PREP(PVT_THRES_LO_MASK, data);
mask = PVT_THRES_LO_MASK;
} else {
limit = FIELD_GET(PVT_THRES_LO_MASK, limit);
data = clamp_val(data, limit, PVT_DATA_MAX);
data = FIELD_PREP(PVT_THRES_HI_MASK, data);
mask = PVT_THRES_HI_MASK;
}
pvt_update(pvt->regs + pvt_info[type].thres_base, mask, data);
mutex_unlock(&pvt->iface_mtx);
return 0;
}
static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long *val)
{
if (is_low)
*val = !!READ_ONCE(pvt->cache[type].thres_sts_lo);
else
*val = !!READ_ONCE(pvt->cache[type].thres_sts_hi);
return 0;
}
static const struct hwmon_channel_info *pvt_channel_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL |
HWMON_T_MIN | HWMON_T_MIN_ALARM |
HWMON_T_MAX | HWMON_T_MAX_ALARM |
HWMON_T_OFFSET),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL |
HWMON_I_MIN | HWMON_I_MIN_ALARM |
HWMON_I_MAX | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LABEL |
HWMON_I_MIN | HWMON_I_MIN_ALARM |
HWMON_I_MAX | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LABEL |
HWMON_I_MIN | HWMON_I_MIN_ALARM |
HWMON_I_MAX | HWMON_I_MAX_ALARM,
HWMON_I_INPUT | HWMON_I_LABEL |
HWMON_I_MIN | HWMON_I_MIN_ALARM |
HWMON_I_MAX | HWMON_I_MAX_ALARM),
NULL
};
#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */
static irqreturn_t pvt_hard_isr(int irq, void *data)
{
struct pvt_hwmon *pvt = data;
struct pvt_cache *cache;
u32 val;
/*
* Mask the DVALID interrupt so after exiting from the handler a
* repeated conversion wouldn't happen.
*/
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
/*
* Nothing special for alarm-less driver. Just read the data, update
* the cache and notify a waiter of this event.
*/
val = readl(pvt->regs + PVT_DATA);
if (!(val & PVT_DATA_VALID)) {
dev_err(pvt->dev, "Got IRQ when data isn't valid\n");
return IRQ_HANDLED;
}
cache = &pvt->cache[pvt->sensor];
WRITE_ONCE(cache->data, FIELD_GET(PVT_DATA_DATA_MASK, val));
complete(&cache->conversion);
return IRQ_HANDLED;
}
#define pvt_soft_isr NULL
inline umode_t pvt_limit_is_visible(enum pvt_sensor_type type)
{
return 0;
}
inline umode_t pvt_alarm_is_visible(enum pvt_sensor_type type)
{
return 0;
}
static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
long *val)
{
struct pvt_cache *cache = &pvt->cache[type];
u32 data;
int ret;
/*
* Lock PVT conversion interface until data cache is updated. The
* data read procedure is following: set the requested PVT sensor
* mode, enable IRQ and conversion, wait until conversion is finished,
* then disable conversion and IRQ, and read the cached data.
*/
ret = mutex_lock_interruptible(&pvt->iface_mtx);
if (ret)
return ret;
pvt->sensor = type;
pvt_set_mode(pvt, pvt_info[type].mode);
/*
* Unmask the DVALID interrupt and enable the sensors conversions.
* Do the reverse procedure when conversion is done.
*/
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
wait_for_completion(&cache->conversion);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
data = READ_ONCE(cache->data);
mutex_unlock(&pvt->iface_mtx);
if (type == PVT_TEMP)
*val = pvt_calc_poly(&poly_N_to_temp, data);
else
*val = pvt_calc_poly(&poly_N_to_volt, data);
return 0;
}
static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long *val)
{
return -EOPNOTSUPP;
}
static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long val)
{
return -EOPNOTSUPP;
}
static int pvt_read_alarm(struct pvt_hwmon *pvt, enum pvt_sensor_type type,
bool is_low, long *val)
{
return -EOPNOTSUPP;
}
static const struct hwmon_channel_info *pvt_channel_info[] = {
HWMON_CHANNEL_INFO(chip,
HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_TYPE | HWMON_T_LABEL |
HWMON_T_OFFSET),
HWMON_CHANNEL_INFO(in,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL,
HWMON_I_INPUT | HWMON_I_LABEL),
NULL
};
#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */
static inline bool pvt_hwmon_channel_is_valid(enum hwmon_sensor_types type,
int ch)
{
switch (type) {
case hwmon_temp:
if (ch < 0 || ch >= PVT_TEMP_CHS)
return false;
break;
case hwmon_in:
if (ch < 0 || ch >= PVT_VOLT_CHS)
return false;
break;
default:
break;
}
/* The rest of the types are independent from the channel number. */
return true;
}
static umode_t pvt_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int ch)
{
if (!pvt_hwmon_channel_is_valid(type, ch))
return 0;
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
return 0644;
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
case hwmon_temp_type:
case hwmon_temp_label:
return 0444;
case hwmon_temp_min:
case hwmon_temp_max:
return pvt_limit_is_visible(ch);
case hwmon_temp_min_alarm:
case hwmon_temp_max_alarm:
return pvt_alarm_is_visible(ch);
case hwmon_temp_offset:
return 0644;
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_input:
case hwmon_in_label:
return 0444;
case hwmon_in_min:
case hwmon_in_max:
return pvt_limit_is_visible(PVT_VOLT + ch);
case hwmon_in_min_alarm:
case hwmon_in_max_alarm:
return pvt_alarm_is_visible(PVT_VOLT + ch);
}
break;
default:
break;
}
return 0;
}
static int pvt_read_trim(struct pvt_hwmon *pvt, long *val)
{
u32 data;
data = readl(pvt->regs + PVT_CTRL);
*val = FIELD_GET(PVT_CTRL_TRIM_MASK, data) * PVT_TRIM_STEP;
return 0;
}
static int pvt_write_trim(struct pvt_hwmon *pvt, long val)
{
u32 trim;
int ret;
/*
* Serialize trim update, since a part of the register is changed and
* the controller is supposed to be disabled during this operation.
*/
ret = mutex_lock_interruptible(&pvt->iface_mtx);
if (ret)
return ret;
trim = pvt_calc_trim(val);
pvt_set_trim(pvt, trim);
mutex_unlock(&pvt->iface_mtx);
return 0;
}
static int pvt_read_timeout(struct pvt_hwmon *pvt, long *val)
{
unsigned long rate;
ktime_t kt;
u32 data;
rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
if (!rate)
return -ENODEV;
/*
* Don't bother with mutex here, since we just read data from MMIO.
* We also have to scale the ticks timeout up to compensate the
* ms-ns-data translations.
*/
data = readl(pvt->regs + PVT_TTIMEOUT) + 1;
/*
* Calculate ref-clock based delay (Ttotal) between two consecutive
* data samples of the same sensor. So we first must calculate the
* delay introduced by the internal ref-clock timer (Tref * Fclk).
* Then add the constant timeout cuased by each conversion latency
* (Tmin). The basic formulae for each conversion is following:
* Ttotal = Tref * Fclk + Tmin
* Note if alarms are enabled the sensors are polled one after
* another, so in order to have the delay being applicable for each
* sensor the requested value must be equally redistirbuted.
*/
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
kt = ktime_set(PVT_SENSORS_NUM * (u64)data, 0);
kt = ktime_divns(kt, rate);
kt = ktime_add_ns(kt, PVT_SENSORS_NUM * PVT_TOUT_MIN);
#else
kt = ktime_set(data, 0);
kt = ktime_divns(kt, rate);
kt = ktime_add_ns(kt, PVT_TOUT_MIN);
#endif
/* Return the result in msec as hwmon sysfs interface requires. */
*val = ktime_to_ms(kt);
return 0;
}
static int pvt_write_timeout(struct pvt_hwmon *pvt, long val)
{
unsigned long rate;
ktime_t kt;
u32 data;
int ret;
rate = clk_get_rate(pvt->clks[PVT_CLOCK_REF].clk);
if (!rate)
return -ENODEV;
/*
* If alarms are enabled, the requested timeout must be divided
* between all available sensors to have the requested delay
* applicable to each individual sensor.
*/
kt = ms_to_ktime(val);
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
kt = ktime_divns(kt, PVT_SENSORS_NUM);
#endif
/*
* Subtract a constant lag, which always persists due to the limited
* PVT sampling rate. Make sure the timeout is not negative.
*/
kt = ktime_sub_ns(kt, PVT_TOUT_MIN);
if (ktime_to_ns(kt) < 0)
kt = ktime_set(0, 0);
/*
* Finally recalculate the timeout in terms of the reference clock
* period.
*/
data = ktime_divns(kt * rate, NSEC_PER_SEC);
/*
* Update the measurements delay, but lock the interface first, since
* we have to disable PVT in order to have the new delay actually
* updated.
*/
ret = mutex_lock_interruptible(&pvt->iface_mtx);
if (ret)
return ret;
pvt_set_tout(pvt, data);
mutex_unlock(&pvt->iface_mtx);
return 0;
}
static int pvt_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int ch, long *val)
{
struct pvt_hwmon *pvt = dev_get_drvdata(dev);
if (!pvt_hwmon_channel_is_valid(type, ch))
return -EINVAL;
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
return pvt_read_timeout(pvt, val);
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_input:
return pvt_read_data(pvt, ch, val);
case hwmon_temp_type:
*val = 1;
return 0;
case hwmon_temp_min:
return pvt_read_limit(pvt, ch, true, val);
case hwmon_temp_max:
return pvt_read_limit(pvt, ch, false, val);
case hwmon_temp_min_alarm:
return pvt_read_alarm(pvt, ch, true, val);
case hwmon_temp_max_alarm:
return pvt_read_alarm(pvt, ch, false, val);
case hwmon_temp_offset:
return pvt_read_trim(pvt, val);
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_input:
return pvt_read_data(pvt, PVT_VOLT + ch, val);
case hwmon_in_min:
return pvt_read_limit(pvt, PVT_VOLT + ch, true, val);
case hwmon_in_max:
return pvt_read_limit(pvt, PVT_VOLT + ch, false, val);
case hwmon_in_min_alarm:
return pvt_read_alarm(pvt, PVT_VOLT + ch, true, val);
case hwmon_in_max_alarm:
return pvt_read_alarm(pvt, PVT_VOLT + ch, false, val);
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static int pvt_hwmon_read_string(struct device *dev,
enum hwmon_sensor_types type,
u32 attr, int ch, const char **str)
{
if (!pvt_hwmon_channel_is_valid(type, ch))
return -EINVAL;
switch (type) {
case hwmon_temp:
switch (attr) {
case hwmon_temp_label:
*str = pvt_info[ch].label;
return 0;
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_label:
*str = pvt_info[PVT_VOLT + ch].label;
return 0;
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static int pvt_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int ch, long val)
{
struct pvt_hwmon *pvt = dev_get_drvdata(dev);
if (!pvt_hwmon_channel_is_valid(type, ch))
return -EINVAL;
switch (type) {
case hwmon_chip:
switch (attr) {
case hwmon_chip_update_interval:
return pvt_write_timeout(pvt, val);
}
break;
case hwmon_temp:
switch (attr) {
case hwmon_temp_min:
return pvt_write_limit(pvt, ch, true, val);
case hwmon_temp_max:
return pvt_write_limit(pvt, ch, false, val);
case hwmon_temp_offset:
return pvt_write_trim(pvt, val);
}
break;
case hwmon_in:
switch (attr) {
case hwmon_in_min:
return pvt_write_limit(pvt, PVT_VOLT + ch, true, val);
case hwmon_in_max:
return pvt_write_limit(pvt, PVT_VOLT + ch, false, val);
}
break;
default:
break;
}
return -EOPNOTSUPP;
}
static const struct hwmon_ops pvt_hwmon_ops = {
.is_visible = pvt_hwmon_is_visible,
.read = pvt_hwmon_read,
.read_string = pvt_hwmon_read_string,
.write = pvt_hwmon_write
};
static const struct hwmon_chip_info pvt_hwmon_info = {
.ops = &pvt_hwmon_ops,
.info = pvt_channel_info
};
static void pvt_clear_data(void *data)
{
struct pvt_hwmon *pvt = data;
#if !defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
int idx;
for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
complete_all(&pvt->cache[idx].conversion);
#endif
mutex_destroy(&pvt->iface_mtx);
}
static struct pvt_hwmon *pvt_create_data(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pvt_hwmon *pvt;
int ret, idx;
pvt = devm_kzalloc(dev, sizeof(*pvt), GFP_KERNEL);
if (!pvt)
return ERR_PTR(-ENOMEM);
ret = devm_add_action(dev, pvt_clear_data, pvt);
if (ret) {
dev_err(dev, "Can't add PVT data clear action\n");
return ERR_PTR(ret);
}
pvt->dev = dev;
pvt->sensor = PVT_SENSOR_FIRST;
mutex_init(&pvt->iface_mtx);
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
seqlock_init(&pvt->cache[idx].data_seqlock);
#else
for (idx = 0; idx < PVT_SENSORS_NUM; ++idx)
init_completion(&pvt->cache[idx].conversion);
#endif
return pvt;
}
static int pvt_request_regs(struct pvt_hwmon *pvt)
{
struct platform_device *pdev = to_platform_device(pvt->dev);
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(pvt->dev, "Couldn't find PVT memresource\n");
return -EINVAL;
}
pvt->regs = devm_ioremap_resource(pvt->dev, res);
if (IS_ERR(pvt->regs)) {
dev_err(pvt->dev, "Couldn't map PVT registers\n");
return PTR_ERR(pvt->regs);
}
return 0;
}
static void pvt_disable_clks(void *data)
{
struct pvt_hwmon *pvt = data;
clk_bulk_disable_unprepare(PVT_CLOCK_NUM, pvt->clks);
}
static int pvt_request_clks(struct pvt_hwmon *pvt)
{
int ret;
pvt->clks[PVT_CLOCK_APB].id = "pclk";
pvt->clks[PVT_CLOCK_REF].id = "ref";
ret = devm_clk_bulk_get(pvt->dev, PVT_CLOCK_NUM, pvt->clks);
if (ret) {
dev_err(pvt->dev, "Couldn't get PVT clocks descriptors\n");
return ret;
}
ret = clk_bulk_prepare_enable(PVT_CLOCK_NUM, pvt->clks);
if (ret) {
dev_err(pvt->dev, "Couldn't enable the PVT clocks\n");
return ret;
}
ret = devm_add_action_or_reset(pvt->dev, pvt_disable_clks, pvt);
if (ret) {
dev_err(pvt->dev, "Can't add PVT clocks disable action\n");
return ret;
}
return 0;
}
static void pvt_init_iface(struct pvt_hwmon *pvt)
{
u32 trim, temp;
/*
* Make sure all interrupts and controller are disabled so not to
* accidentally have ISR executed before the driver data is fully
* initialized. Clear the IRQ status as well.
*/
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_ALL, PVT_INTR_ALL);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
readl(pvt->regs + PVT_CLR_INTR);
readl(pvt->regs + PVT_DATA);
/* Setup default sensor mode, timeout and temperature trim. */
pvt_set_mode(pvt, pvt_info[pvt->sensor].mode);
pvt_set_tout(pvt, PVT_TOUT_DEF);
trim = PVT_TRIM_DEF;
if (!of_property_read_u32(pvt->dev->of_node,
"baikal,pvt-temp-offset-millicelsius", &temp))
trim = pvt_calc_trim(temp);
pvt_set_trim(pvt, trim);
}
static int pvt_request_irq(struct pvt_hwmon *pvt)
{
struct platform_device *pdev = to_platform_device(pvt->dev);
int ret;
pvt->irq = platform_get_irq(pdev, 0);
if (pvt->irq < 0)
return pvt->irq;
ret = devm_request_threaded_irq(pvt->dev, pvt->irq,
pvt_hard_isr, pvt_soft_isr,
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
IRQF_SHARED | IRQF_TRIGGER_HIGH |
IRQF_ONESHOT,
#else
IRQF_SHARED | IRQF_TRIGGER_HIGH,
#endif
"pvt", pvt);
if (ret) {
dev_err(pvt->dev, "Couldn't request PVT IRQ\n");
return ret;
}
return 0;
}
static int pvt_create_hwmon(struct pvt_hwmon *pvt)
{
pvt->hwmon = devm_hwmon_device_register_with_info(pvt->dev, "pvt", pvt,
&pvt_hwmon_info, NULL);
if (IS_ERR(pvt->hwmon)) {
dev_err(pvt->dev, "Couldn't create hwmon device\n");
return PTR_ERR(pvt->hwmon);
}
return 0;
}
#if defined(CONFIG_SENSORS_BT1_PVT_ALARMS)
static void pvt_disable_iface(void *data)
{
struct pvt_hwmon *pvt = data;
mutex_lock(&pvt->iface_mtx);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, 0);
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID,
PVT_INTR_DVALID);
mutex_unlock(&pvt->iface_mtx);
}
static int pvt_enable_iface(struct pvt_hwmon *pvt)
{
int ret;
ret = devm_add_action(pvt->dev, pvt_disable_iface, pvt);
if (ret) {
dev_err(pvt->dev, "Can't add PVT disable interface action\n");
return ret;
}
/*
* Enable sensors data conversion and IRQ. We need to lock the
* interface mutex since hwmon has just been created and the
* corresponding sysfs files are accessible from user-space,
* which theoretically may cause races.
*/
mutex_lock(&pvt->iface_mtx);
pvt_update(pvt->regs + PVT_INTR_MASK, PVT_INTR_DVALID, 0);
pvt_update(pvt->regs + PVT_CTRL, PVT_CTRL_EN, PVT_CTRL_EN);
mutex_unlock(&pvt->iface_mtx);
return 0;
}
#else /* !CONFIG_SENSORS_BT1_PVT_ALARMS */
static int pvt_enable_iface(struct pvt_hwmon *pvt)
{
return 0;
}
#endif /* !CONFIG_SENSORS_BT1_PVT_ALARMS */
static int pvt_probe(struct platform_device *pdev)
{
struct pvt_hwmon *pvt;
int ret;
pvt = pvt_create_data(pdev);
if (IS_ERR(pvt))
return PTR_ERR(pvt);
ret = pvt_request_regs(pvt);
if (ret)
return ret;
ret = pvt_request_clks(pvt);
if (ret)
return ret;
pvt_init_iface(pvt);
ret = pvt_request_irq(pvt);
if (ret)
return ret;
ret = pvt_create_hwmon(pvt);
if (ret)
return ret;
ret = pvt_enable_iface(pvt);
if (ret)
return ret;
return 0;
}
static const struct of_device_id pvt_of_match[] = {
{ .compatible = "baikal,bt1-pvt" },
{ }
};
MODULE_DEVICE_TABLE(of, pvt_of_match);
static struct platform_driver pvt_driver = {
.probe = pvt_probe,
.driver = {
.name = "bt1-pvt",
.of_match_table = pvt_of_match
}
};
module_platform_driver(pvt_driver);
MODULE_AUTHOR("Maxim Kaurkin <maxim.kaurkin@baikalelectronics.ru>");
MODULE_DESCRIPTION("Baikal-T1 PVT driver");
MODULE_LICENSE("GPL v2");
/* 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 = { ...@@ -1072,13 +1072,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
}, },
}, },
{
.ident = "Dell XPS421",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
},
},
{ {
.ident = "Dell Studio", .ident = "Dell Studio",
.matches = { .matches = {
...@@ -1087,14 +1080,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { ...@@ -1087,14 +1080,6 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
}, },
.driver_data = (void *)&i8k_config_data[DELL_STUDIO], .driver_data = (void *)&i8k_config_data[DELL_STUDIO],
}, },
{
.ident = "Dell XPS 13",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
},
.driver_data = (void *)&i8k_config_data[DELL_XPS],
},
{ {
.ident = "Dell XPS M140", .ident = "Dell XPS M140",
.matches = { .matches = {
...@@ -1104,17 +1089,10 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { ...@@ -1104,17 +1089,10 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = {
.driver_data = (void *)&i8k_config_data[DELL_XPS], .driver_data = (void *)&i8k_config_data[DELL_XPS],
}, },
{ {
.ident = "Dell XPS 15 9560", .ident = "Dell XPS",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9560"),
},
},
{
.ident = "Dell XPS 15 9570",
.matches = { .matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
DMI_MATCH(DMI_PRODUCT_NAME, "XPS 15 9570"), DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
}, },
}, },
{ } { }
......
// 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 @@ ...@@ -15,6 +15,7 @@
#include <linux/gfp.h> #include <linux/gfp.h>
#include <linux/hwmon.h> #include <linux/hwmon.h>
#include <linux/idr.h> #include <linux/idr.h>
#include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/slab.h> #include <linux/slab.h>
...@@ -31,7 +32,7 @@ struct hwmon_device { ...@@ -31,7 +32,7 @@ struct hwmon_device {
const char *name; const char *name;
struct device dev; struct device dev;
const struct hwmon_chip_info *chip; const struct hwmon_chip_info *chip;
struct list_head tzdata;
struct attribute_group group; struct attribute_group group;
const struct attribute_group **groups; const struct attribute_group **groups;
}; };
...@@ -55,12 +56,12 @@ struct hwmon_device_attribute { ...@@ -55,12 +56,12 @@ struct hwmon_device_attribute {
/* /*
* Thermal zone information * Thermal zone information
* In addition to the reference to the hwmon device,
* also provides the sensor index.
*/ */
struct hwmon_thermal_data { struct hwmon_thermal_data {
struct list_head node; /* hwmon tzdata list entry */
struct device *dev; /* Reference to hwmon device */ struct device *dev; /* Reference to hwmon device */
int index; /* sensor index */ int index; /* sensor index */
struct thermal_zone_device *tzd;/* thermal zone device */
}; };
static ssize_t static ssize_t
...@@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = { ...@@ -156,10 +157,17 @@ static const struct thermal_zone_of_device_ops hwmon_thermal_ops = {
.get_temp = hwmon_thermal_get_temp, .get_temp = hwmon_thermal_get_temp,
}; };
static void hwmon_thermal_remove_sensor(void *data)
{
list_del(data);
}
static int hwmon_thermal_add_sensor(struct device *dev, int index) static int hwmon_thermal_add_sensor(struct device *dev, int index)
{ {
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tdata; struct hwmon_thermal_data *tdata;
struct thermal_zone_device *tzd; struct thermal_zone_device *tzd;
int err;
tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL);
if (!tdata) if (!tdata)
...@@ -177,13 +185,68 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index) ...@@ -177,13 +185,68 @@ static int hwmon_thermal_add_sensor(struct device *dev, int index)
if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV))
return PTR_ERR(tzd); return PTR_ERR(tzd);
err = devm_add_action(dev, hwmon_thermal_remove_sensor, &tdata->node);
if (err)
return err;
tdata->tzd = tzd;
list_add(&tdata->node, &hwdev->tzdata);
return 0; return 0;
} }
static int hwmon_thermal_register_sensors(struct device *dev)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
const struct hwmon_chip_info *chip = hwdev->chip;
const struct hwmon_channel_info **info = chip->info;
void *drvdata = dev_get_drvdata(dev);
int i;
for (i = 1; info[i]; i++) {
int j;
if (info[i]->type != hwmon_temp)
continue;
for (j = 0; info[i]->config[j]; j++) {
int err;
if (!(info[i]->config[j] & HWMON_T_INPUT) ||
!chip->ops->is_visible(drvdata, hwmon_temp,
hwmon_temp_input, j))
continue;
err = hwmon_thermal_add_sensor(dev, j);
if (err)
return err;
}
}
return 0;
}
static void hwmon_thermal_notify(struct device *dev, int index)
{
struct hwmon_device *hwdev = to_hwmon_device(dev);
struct hwmon_thermal_data *tzdata;
list_for_each_entry(tzdata, &hwdev->tzdata, node) {
if (tzdata->index == index) {
thermal_zone_device_update(tzdata->tzd,
THERMAL_EVENT_UNSPECIFIED);
}
}
}
#else #else
static int hwmon_thermal_add_sensor(struct device *dev, int index) static int hwmon_thermal_register_sensors(struct device *dev)
{ {
return 0; return 0;
} }
static void hwmon_thermal_notify(struct device *dev, int index) { }
#endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */ #endif /* IS_REACHABLE(CONFIG_THERMAL) && ... */
static int hwmon_attr_base(enum hwmon_sensor_types type) static int hwmon_attr_base(enum hwmon_sensor_types type)
...@@ -511,6 +574,35 @@ static const int __templates_size[] = { ...@@ -511,6 +574,35 @@ static const int __templates_size[] = {
[hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates), [hwmon_intrusion] = ARRAY_SIZE(hwmon_intrusion_attr_templates),
}; };
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel)
{
char sattr[MAX_SYSFS_ATTR_NAME_LENGTH];
const char * const *templates;
const char *template;
int base;
if (type >= ARRAY_SIZE(__templates))
return -EINVAL;
if (attr >= __templates_size[type])
return -EINVAL;
templates = __templates[type];
template = templates[attr];
base = hwmon_attr_base(type);
scnprintf(sattr, MAX_SYSFS_ATTR_NAME_LENGTH, template, base + channel);
sysfs_notify(&dev->kobj, NULL, sattr);
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
if (type == hwmon_temp)
hwmon_thermal_notify(dev, channel);
return 0;
}
EXPORT_SYMBOL_GPL(hwmon_notify_event);
static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info)
{ {
int i, n; int i, n;
...@@ -596,7 +688,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, ...@@ -596,7 +688,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
{ {
struct hwmon_device *hwdev; struct hwmon_device *hwdev;
struct device *hdev; struct device *hdev;
int i, j, err, id; int i, err, id;
/* Complain about invalid characters in hwmon name attribute */ /* Complain about invalid characters in hwmon name attribute */
if (name && (!strlen(name) || strpbrk(name, "-* \t\n"))) if (name && (!strlen(name) || strpbrk(name, "-* \t\n")))
...@@ -661,33 +753,19 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, ...@@ -661,33 +753,19 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata,
if (err) if (err)
goto free_hwmon; goto free_hwmon;
INIT_LIST_HEAD(&hwdev->tzdata);
if (dev && dev->of_node && chip && chip->ops->read && if (dev && dev->of_node && chip && chip->ops->read &&
chip->info[0]->type == hwmon_chip && chip->info[0]->type == hwmon_chip &&
(chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) { (chip->info[0]->config[0] & HWMON_C_REGISTER_TZ)) {
const struct hwmon_channel_info **info = chip->info; err = hwmon_thermal_register_sensors(hdev);
if (err) {
for (i = 1; info[i]; i++) { device_unregister(hdev);
if (info[i]->type != hwmon_temp) /*
continue; * Don't worry about hwdev; hwmon_dev_release(), called
* from device_unregister(), will free it.
for (j = 0; info[i]->config[j]; j++) { */
if (!chip->ops->is_visible(drvdata, hwmon_temp, goto ida_remove;
hwmon_temp_input, j))
continue;
if (info[i]->config[j] & HWMON_T_INPUT) {
err = hwmon_thermal_add_sensor(hdev, j);
if (err) {
device_unregister(hdev);
/*
* Don't worry about hwdev;
* hwmon_dev_release(), called
* from device_unregister(),
* will free it.
*/
goto ida_remove;
}
}
}
} }
} }
......
...@@ -74,6 +74,17 @@ ...@@ -74,6 +74,17 @@
#define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9) #define INA226_READ_AVG(reg) (((reg) & INA226_AVG_RD_MASK) >> 9)
#define INA226_SHIFT_AVG(val) ((val) << 9) #define INA226_SHIFT_AVG(val) ((val) << 9)
/* bit number of alert functions in Mask/Enable Register */
#define INA226_SHUNT_OVER_VOLTAGE_BIT 15
#define INA226_SHUNT_UNDER_VOLTAGE_BIT 14
#define INA226_BUS_OVER_VOLTAGE_BIT 13
#define INA226_BUS_UNDER_VOLTAGE_BIT 12
#define INA226_POWER_OVER_LIMIT_BIT 11
/* bit mask for alert config bits of Mask/Enable Register */
#define INA226_ALERT_CONFIG_MASK 0xFC00
#define INA226_ALERT_FUNCTION_FLAG BIT(4)
/* common attrs, ina226 attrs and NULL */ /* common attrs, ina226 attrs and NULL */
#define INA2XX_MAX_ATTRIBUTE_GROUPS 3 #define INA2XX_MAX_ATTRIBUTE_GROUPS 3
...@@ -303,6 +314,145 @@ static ssize_t ina2xx_value_show(struct device *dev, ...@@ -303,6 +314,145 @@ static ssize_t ina2xx_value_show(struct device *dev,
ina2xx_get_value(data, attr->index, regval)); ina2xx_get_value(data, attr->index, regval));
} }
static int ina226_reg_to_alert(struct ina2xx_data *data, u8 bit, u16 regval)
{
int reg;
switch (bit) {
case INA226_SHUNT_OVER_VOLTAGE_BIT:
case INA226_SHUNT_UNDER_VOLTAGE_BIT:
reg = INA2XX_SHUNT_VOLTAGE;
break;
case INA226_BUS_OVER_VOLTAGE_BIT:
case INA226_BUS_UNDER_VOLTAGE_BIT:
reg = INA2XX_BUS_VOLTAGE;
break;
case INA226_POWER_OVER_LIMIT_BIT:
reg = INA2XX_POWER;
break;
default:
/* programmer goofed */
WARN_ON_ONCE(1);
return 0;
}
return ina2xx_get_value(data, reg, regval);
}
/*
* Turns alert limit values into register values.
* Opposite of the formula in ina2xx_get_value().
*/
static s16 ina226_alert_to_reg(struct ina2xx_data *data, u8 bit, int val)
{
switch (bit) {
case INA226_SHUNT_OVER_VOLTAGE_BIT:
case INA226_SHUNT_UNDER_VOLTAGE_BIT:
val *= data->config->shunt_div;
return clamp_val(val, SHRT_MIN, SHRT_MAX);
case INA226_BUS_OVER_VOLTAGE_BIT:
case INA226_BUS_UNDER_VOLTAGE_BIT:
val = (val * 1000) << data->config->bus_voltage_shift;
val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
return clamp_val(val, 0, SHRT_MAX);
case INA226_POWER_OVER_LIMIT_BIT:
val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
return clamp_val(val, 0, USHRT_MAX);
default:
/* programmer goofed */
WARN_ON_ONCE(1);
return 0;
}
}
static ssize_t ina226_alert_show(struct device *dev,
struct device_attribute *da, char *buf)
{
struct sensor_device_attribute *attr = to_sensor_dev_attr(da);
struct ina2xx_data *data = dev_get_drvdata(dev);
int regval;
int val = 0;
int ret;
mutex_lock(&data->config_lock);
ret = regmap_read(data->regmap, INA226_MASK_ENABLE, &regval);
if (ret)
goto abort;
if (regval & BIT(attr->index)) {
ret = regmap_read(data->regmap, INA226_ALERT_LIMIT, &regval);
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, &regval);
if (ret)
return ret;
alarm = (regval & BIT(attr->index)) &&
(regval & INA226_ALERT_FUNCTION_FLAG);
return snprintf(buf, PAGE_SIZE, "%d\n", alarm);
}
/* /*
* In order to keep calibration register value fixed, the product * In order to keep calibration register value fixed, the product
* of current_lsb and shunt_resistor should also be fixed and equal * of current_lsb and shunt_resistor should also be fixed and equal
...@@ -392,15 +542,38 @@ static ssize_t ina226_interval_show(struct device *dev, ...@@ -392,15 +542,38 @@ static ssize_t ina226_interval_show(struct device *dev,
/* shunt voltage */ /* shunt voltage */
static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE); static SENSOR_DEVICE_ATTR_RO(in0_input, ina2xx_value, INA2XX_SHUNT_VOLTAGE);
/* shunt voltage over/under voltage alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(in0_crit, ina226_alert,
INA226_SHUNT_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RW(in0_lcrit, ina226_alert,
INA226_SHUNT_UNDER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in0_crit_alarm, ina226_alarm,
INA226_SHUNT_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in0_lcrit_alarm, ina226_alarm,
INA226_SHUNT_UNDER_VOLTAGE_BIT);
/* bus voltage */ /* bus voltage */
static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE); static SENSOR_DEVICE_ATTR_RO(in1_input, ina2xx_value, INA2XX_BUS_VOLTAGE);
/* bus voltage over/under voltage alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(in1_crit, ina226_alert,
INA226_BUS_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RW(in1_lcrit, ina226_alert,
INA226_BUS_UNDER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in1_crit_alarm, ina226_alarm,
INA226_BUS_OVER_VOLTAGE_BIT);
static SENSOR_DEVICE_ATTR_RO(in1_lcrit_alarm, ina226_alarm,
INA226_BUS_UNDER_VOLTAGE_BIT);
/* calculated current */ /* calculated current */
static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT); static SENSOR_DEVICE_ATTR_RO(curr1_input, ina2xx_value, INA2XX_CURRENT);
/* calculated power */ /* calculated power */
static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER); static SENSOR_DEVICE_ATTR_RO(power1_input, ina2xx_value, INA2XX_POWER);
/* over-limit power alert setting and alarm */
static SENSOR_DEVICE_ATTR_RW(power1_crit, ina226_alert,
INA226_POWER_OVER_LIMIT_BIT);
static SENSOR_DEVICE_ATTR_RO(power1_crit_alarm, ina226_alarm,
INA226_POWER_OVER_LIMIT_BIT);
/* shunt resistance */ /* shunt resistance */
static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION); static SENSOR_DEVICE_ATTR_RW(shunt_resistor, ina2xx_shunt, INA2XX_CALIBRATION);
...@@ -423,6 +596,16 @@ static const struct attribute_group ina2xx_group = { ...@@ -423,6 +596,16 @@ static const struct attribute_group ina2xx_group = {
}; };
static struct attribute *ina226_attrs[] = { static struct attribute *ina226_attrs[] = {
&sensor_dev_attr_in0_crit.dev_attr.attr,
&sensor_dev_attr_in0_lcrit.dev_attr.attr,
&sensor_dev_attr_in0_crit_alarm.dev_attr.attr,
&sensor_dev_attr_in0_lcrit_alarm.dev_attr.attr,
&sensor_dev_attr_in1_crit.dev_attr.attr,
&sensor_dev_attr_in1_lcrit.dev_attr.attr,
&sensor_dev_attr_in1_crit_alarm.dev_attr.attr,
&sensor_dev_attr_in1_lcrit_alarm.dev_attr.attr,
&sensor_dev_attr_power1_crit.dev_attr.attr,
&sensor_dev_attr_power1_crit_alarm.dev_attr.attr,
&sensor_dev_attr_update_interval.dev_attr.attr, &sensor_dev_attr_update_interval.dev_attr.attr,
NULL, NULL,
}; };
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#include <linux/spi/spi.h> #include <linux/spi/spi.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/of_device.h> #include <linux/of_device.h>
#include <linux/acpi.h>
#define DRVNAME "lm70" #define DRVNAME "lm70"
...@@ -148,18 +148,50 @@ static const struct of_device_id lm70_of_ids[] = { ...@@ -148,18 +148,50 @@ static const struct of_device_id lm70_of_ids[] = {
MODULE_DEVICE_TABLE(of, lm70_of_ids); MODULE_DEVICE_TABLE(of, lm70_of_ids);
#endif #endif
#ifdef CONFIG_ACPI
static const struct acpi_device_id lm70_acpi_ids[] = {
{
.id = "LM000070",
.driver_data = LM70_CHIP_LM70,
},
{
.id = "TMP00121",
.driver_data = LM70_CHIP_TMP121,
},
{
.id = "LM000071",
.driver_data = LM70_CHIP_LM71,
},
{
.id = "LM000074",
.driver_data = LM70_CHIP_LM74,
},
{},
};
MODULE_DEVICE_TABLE(acpi, lm70_acpi_ids);
#endif
static int lm70_probe(struct spi_device *spi) static int lm70_probe(struct spi_device *spi)
{ {
const struct of_device_id *match; const struct of_device_id *of_match;
struct device *hwmon_dev; struct device *hwmon_dev;
struct lm70 *p_lm70; struct lm70 *p_lm70;
int chip; int chip;
match = of_match_device(lm70_of_ids, &spi->dev); of_match = of_match_device(lm70_of_ids, &spi->dev);
if (match) if (of_match)
chip = (int)(uintptr_t)match->data; chip = (int)(uintptr_t)of_match->data;
else else {
chip = spi_get_device_id(spi)->driver_data; #ifdef CONFIG_ACPI
const struct acpi_device_id *acpi_match;
acpi_match = acpi_match_device(lm70_acpi_ids, &spi->dev);
if (acpi_match)
chip = (int)(uintptr_t)acpi_match->driver_data;
else
#endif
chip = spi_get_device_id(spi)->driver_data;
}
/* signaling is SPI_MODE_0 */ /* signaling is SPI_MODE_0 */
if (spi->mode & (SPI_CPOL | SPI_CPHA)) if (spi->mode & (SPI_CPOL | SPI_CPHA))
...@@ -195,6 +227,7 @@ static struct spi_driver lm70_driver = { ...@@ -195,6 +227,7 @@ static struct spi_driver lm70_driver = {
.driver = { .driver = {
.name = "lm70", .name = "lm70",
.of_match_table = of_match_ptr(lm70_of_ids), .of_match_table = of_match_ptr(lm70_of_ids),
.acpi_match_table = ACPI_PTR(lm70_acpi_ids),
}, },
.id_table = lm70_ids, .id_table = lm70_ids,
.probe = lm70_probe, .probe = lm70_probe,
......
...@@ -797,8 +797,10 @@ static int lm75_detect(struct i2c_client *new_client, ...@@ -797,8 +797,10 @@ static int lm75_detect(struct i2c_client *new_client,
/* First check for LM75A */ /* First check for LM75A */
if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) {
/* LM75A returns 0xff on unused registers so /*
just to be sure we check for that too. */ * LM75A returns 0xff on unused registers so
* just to be sure we check for that too.
*/
if (i2c_smbus_read_byte_data(new_client, 4) != 0xff if (i2c_smbus_read_byte_data(new_client, 4) != 0xff
|| i2c_smbus_read_byte_data(new_client, 5) != 0xff || i2c_smbus_read_byte_data(new_client, 5) != 0xff
|| i2c_smbus_read_byte_data(new_client, 6) != 0xff) || i2c_smbus_read_byte_data(new_client, 6) != 0xff)
...@@ -849,6 +851,7 @@ static int lm75_suspend(struct device *dev) ...@@ -849,6 +851,7 @@ static int lm75_suspend(struct device *dev)
{ {
int status; int status;
struct i2c_client *client = to_i2c_client(dev); struct i2c_client *client = to_i2c_client(dev);
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
if (status < 0) { if (status < 0) {
dev_dbg(&client->dev, "Can't read config? %d\n", status); dev_dbg(&client->dev, "Can't read config? %d\n", status);
...@@ -863,6 +866,7 @@ static int lm75_resume(struct device *dev) ...@@ -863,6 +866,7 @@ static int lm75_resume(struct device *dev)
{ {
int status; int status;
struct i2c_client *client = to_i2c_client(dev); struct i2c_client *client = to_i2c_client(dev);
status = i2c_smbus_read_byte_data(client, LM75_REG_CONF); status = i2c_smbus_read_byte_data(client, LM75_REG_CONF);
if (status < 0) { if (status < 0) {
dev_dbg(&client->dev, "Can't read config? %d\n", status); dev_dbg(&client->dev, "Can't read config? %d\n", status);
......
/* SPDX-License-Identifier: GPL-2.0-or-later */ /* SPDX-License-Identifier: GPL-2.0-or-later */
/* /*
lm75.h - Part of lm_sensors, Linux kernel modules for hardware * lm75.h - Part of lm_sensors, Linux kernel modules for hardware monitoring
monitoring * Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com> */
*/
/* /*
This file contains common code for encoding/decoding LM75 type * This file contains common code for encoding/decoding LM75 type
temperature readings, which are emulated by many of the chips * temperature readings, which are emulated by many of the chips
we support. As the user is unlikely to load more than one driver * we support. As the user is unlikely to load more than one driver
which contains this code, we don't worry about the wasted space. * which contains this code, we don't worry about the wasted space.
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -20,18 +18,23 @@ ...@@ -20,18 +18,23 @@
#define LM75_TEMP_MAX 125000 #define LM75_TEMP_MAX 125000
#define LM75_SHUTDOWN 0x01 #define LM75_SHUTDOWN 0x01
/* TEMP: 0.001C/bit (-55C to +125C) /*
REG: (0.5C/bit, two's complement) << 7 */ * TEMP: 0.001C/bit (-55C to +125C)
* REG: (0.5C/bit, two's complement) << 7
*/
static inline u16 LM75_TEMP_TO_REG(long temp) static inline u16 LM75_TEMP_TO_REG(long temp)
{ {
int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX); int ntemp = clamp_val(temp, LM75_TEMP_MIN, LM75_TEMP_MAX);
ntemp += (ntemp < 0 ? -250 : 250); ntemp += (ntemp < 0 ? -250 : 250);
return (u16)((ntemp / 500) << 7); return (u16)((ntemp / 500) << 7);
} }
static inline int LM75_TEMP_FROM_REG(u16 reg) static inline int LM75_TEMP_FROM_REG(u16 reg)
{ {
/* use integer division instead of equivalent right shift to /*
guarantee arithmetic shift and preserve the sign */ * use integer division instead of equivalent right shift to
* guarantee arithmetic shift and preserve the sign
*/
return ((s16)reg / 128) * 500; return ((s16)reg / 128) * 500;
} }
...@@ -35,6 +35,14 @@ ...@@ -35,6 +35,14 @@
* explicitly as max6659, or if its address is not 0x4c. * explicitly as max6659, or if its address is not 0x4c.
* These chips lack the remote temperature offset feature. * These chips lack the remote temperature offset feature.
* *
* This driver also supports the MAX6654 chip made by Maxim. This chip can
* be at 9 different addresses, similar to MAX6680/MAX6681. The MAX6654 is
* otherwise similar to MAX6657/MAX6658/MAX6659. Extended range is available
* by setting the configuration register accordingly, and is done during
* initialization. Extended precision is only available at conversion rates
* of 1 Hz and slower. Note that extended precision is not enabled by
* default, as this driver initializes all chips to 2 Hz by design.
*
* This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and * This driver also supports the MAX6646, MAX6647, MAX6648, MAX6649 and
* MAX6692 chips made by Maxim. These are again similar to the LM86, * MAX6692 chips made by Maxim. These are again similar to the LM86,
* but they use unsigned temperature values and can report temperatures * but they use unsigned temperature values and can report temperatures
...@@ -94,8 +102,8 @@ ...@@ -94,8 +102,8 @@
* have address 0x4d. * have address 0x4d.
* MAX6647 has address 0x4e. * MAX6647 has address 0x4e.
* MAX6659 can have address 0x4c, 0x4d or 0x4e. * MAX6659 can have address 0x4c, 0x4d or 0x4e.
* MAX6680 and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29, 0x2a, 0x2b, * MAX6654, MAX6680, and MAX6681 can have address 0x18, 0x19, 0x1a, 0x29,
* 0x4c, 0x4d or 0x4e. * 0x2a, 0x2b, 0x4c, 0x4d or 0x4e.
* SA56004 can have address 0x48 through 0x4F. * SA56004 can have address 0x48 through 0x4F.
*/ */
...@@ -104,7 +112,7 @@ static const unsigned short normal_i2c[] = { ...@@ -104,7 +112,7 @@ static const unsigned short normal_i2c[] = {
0x4d, 0x4e, 0x4f, I2C_CLIENT_END }; 0x4d, 0x4e, 0x4f, I2C_CLIENT_END };
enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
max6646, w83l771, max6696, sa56004, g781, tmp451 }; max6646, w83l771, max6696, sa56004, g781, tmp451, max6654 };
/* /*
* The LM90 registers * The LM90 registers
...@@ -145,7 +153,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680, ...@@ -145,7 +153,7 @@ enum chips { lm90, adm1032, lm99, lm86, max6657, max6659, adt7461, max6680,
#define LM90_REG_R_TCRIT_HYST 0x21 #define LM90_REG_R_TCRIT_HYST 0x21
#define LM90_REG_W_TCRIT_HYST 0x21 #define LM90_REG_W_TCRIT_HYST 0x21
/* MAX6646/6647/6649/6657/6658/6659/6695/6696 registers */ /* MAX6646/6647/6649/6654/6657/6658/6659/6695/6696 registers */
#define MAX6657_REG_R_LOCAL_TEMPL 0x11 #define MAX6657_REG_R_LOCAL_TEMPL 0x11
#define MAX6696_REG_R_STATUS2 0x12 #define MAX6696_REG_R_STATUS2 0x12
...@@ -209,6 +217,7 @@ static const struct i2c_device_id lm90_id[] = { ...@@ -209,6 +217,7 @@ static const struct i2c_device_id lm90_id[] = {
{ "max6646", max6646 }, { "max6646", max6646 },
{ "max6647", max6646 }, { "max6647", max6646 },
{ "max6649", max6646 }, { "max6649", max6646 },
{ "max6654", max6654 },
{ "max6657", max6657 }, { "max6657", max6657 },
{ "max6658", max6657 }, { "max6658", max6657 },
{ "max6659", max6659 }, { "max6659", max6659 },
...@@ -269,6 +278,10 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = { ...@@ -269,6 +278,10 @@ static const struct of_device_id __maybe_unused lm90_of_match[] = {
.compatible = "dallas,max6649", .compatible = "dallas,max6649",
.data = (void *)max6646 .data = (void *)max6646
}, },
{
.compatible = "dallas,max6654",
.data = (void *)max6654
},
{ {
.compatible = "dallas,max6657", .compatible = "dallas,max6657",
.data = (void *)max6657 .data = (void *)max6657
...@@ -367,6 +380,11 @@ static const struct lm90_params lm90_params[] = { ...@@ -367,6 +380,11 @@ static const struct lm90_params lm90_params[] = {
.max_convrate = 6, .max_convrate = 6,
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL, .reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
}, },
[max6654] = {
.alert_alarms = 0x7c,
.max_convrate = 7,
.reg_local_ext = MAX6657_REG_R_LOCAL_TEMPL,
},
[max6657] = { [max6657] = {
.flags = LM90_PAUSE_FOR_CONFIG, .flags = LM90_PAUSE_FOR_CONFIG,
.alert_alarms = 0x7c, .alert_alarms = 0x7c,
...@@ -1557,6 +1575,16 @@ static int lm90_detect(struct i2c_client *client, ...@@ -1557,6 +1575,16 @@ static int lm90_detect(struct i2c_client *client,
&& (config1 & 0x3f) == 0x00 && (config1 & 0x3f) == 0x00
&& convrate <= 0x07) { && convrate <= 0x07) {
name = "max6646"; name = "max6646";
} else
/*
* The chip_id of the MAX6654 holds the revision of the chip.
* The lowest 3 bits of the config1 register are unused and
* should return zero when read.
*/
if (chip_id == 0x08
&& (config1 & 0x07) == 0x00
&& convrate <= 0x07) {
name = "max6654";
} }
} else } else
if (address == 0x4C if (address == 0x4C
...@@ -1660,6 +1688,15 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) ...@@ -1660,6 +1688,15 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data)
if (data->kind == max6680) if (data->kind == max6680)
config |= 0x18; config |= 0x18;
/*
* Put MAX6654 into extended range (0x20, extend minimum range from
* 0 degrees to -64 degrees). Note that extended resolution is not
* possible on the MAX6654 unless conversion rate is set to 1 Hz or
* slower, which is intentionally not done by default.
*/
if (data->kind == max6654)
config |= 0x20;
/* /*
* Select external channel 0 for max6695/96 * Select external channel 0 for max6695/96
*/ */
......
...@@ -2047,7 +2047,7 @@ store_temp_beep(struct device *dev, struct device_attribute *attr, ...@@ -2047,7 +2047,7 @@ store_temp_beep(struct device *dev, struct device_attribute *attr,
static umode_t nct6775_in_is_visible(struct kobject *kobj, static umode_t nct6775_in_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_data *data = dev_get_drvdata(dev);
int in = index / 5; /* voltage index */ int in = index / 5; /* voltage index */
...@@ -2253,7 +2253,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, ...@@ -2253,7 +2253,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr,
static umode_t nct6775_fan_is_visible(struct kobject *kobj, static umode_t nct6775_fan_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_data *data = dev_get_drvdata(dev);
int fan = index / 6; /* fan index */ int fan = index / 6; /* fan index */
int nr = index % 6; /* attribute index */ int nr = index % 6; /* attribute index */
...@@ -2440,7 +2440,7 @@ store_temp_type(struct device *dev, struct device_attribute *attr, ...@@ -2440,7 +2440,7 @@ store_temp_type(struct device *dev, struct device_attribute *attr,
static umode_t nct6775_temp_is_visible(struct kobject *kobj, static umode_t nct6775_temp_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_data *data = dev_get_drvdata(dev);
int temp = index / 10; /* temp index */ int temp = index / 10; /* temp index */
int nr = index % 10; /* attribute index */ int nr = index % 10; /* attribute index */
...@@ -3257,7 +3257,7 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, ...@@ -3257,7 +3257,7 @@ store_auto_temp(struct device *dev, struct device_attribute *attr,
static umode_t nct6775_pwm_is_visible(struct kobject *kobj, static umode_t nct6775_pwm_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_data *data = dev_get_drvdata(dev);
int pwm = index / 36; /* pwm index */ int pwm = index / 36; /* pwm index */
int nr = index % 36; /* attribute index */ int nr = index % 36; /* attribute index */
...@@ -3459,7 +3459,7 @@ static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep, ...@@ -3459,7 +3459,7 @@ static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep,
static umode_t nct6775_other_is_visible(struct kobject *kobj, static umode_t nct6775_other_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct6775_data *data = dev_get_drvdata(dev); struct nct6775_data *data = dev_get_drvdata(dev);
if (index == 0 && !data->have_vid) if (index == 0 && !data->have_vid)
......
...@@ -679,7 +679,7 @@ static struct attribute *nct7802_temp_attrs[] = { ...@@ -679,7 +679,7 @@ static struct attribute *nct7802_temp_attrs[] = {
static umode_t nct7802_temp_is_visible(struct kobject *kobj, static umode_t nct7802_temp_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct7802_data *data = dev_get_drvdata(dev); struct nct7802_data *data = dev_get_drvdata(dev);
unsigned int reg; unsigned int reg;
int err; int err;
...@@ -778,7 +778,7 @@ static struct attribute *nct7802_in_attrs[] = { ...@@ -778,7 +778,7 @@ static struct attribute *nct7802_in_attrs[] = {
static umode_t nct7802_in_is_visible(struct kobject *kobj, static umode_t nct7802_in_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct7802_data *data = dev_get_drvdata(dev); struct nct7802_data *data = dev_get_drvdata(dev);
unsigned int reg; unsigned int reg;
int err; int err;
...@@ -853,7 +853,7 @@ static struct attribute *nct7802_fan_attrs[] = { ...@@ -853,7 +853,7 @@ static struct attribute *nct7802_fan_attrs[] = {
static umode_t nct7802_fan_is_visible(struct kobject *kobj, static umode_t nct7802_fan_is_visible(struct kobject *kobj,
struct attribute *attr, int index) struct attribute *attr, int index)
{ {
struct device *dev = container_of(kobj, struct device, kobj); struct device *dev = kobj_to_dev(kobj);
struct nct7802_data *data = dev_get_drvdata(dev); struct nct7802_data *data = dev_get_drvdata(dev);
int fan = index / 4; /* 4 attributes per fan */ int fan = index / 4; /* 4 attributes per fan */
unsigned int reg; unsigned int reg;
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
* Copyright (c) 2019 Advantech * Copyright (c) 2019 Advantech
* Author: Amy.Shih <amy.shih@advantech.com.tw> * Author: Amy.Shih <amy.shih@advantech.com.tw>
* *
* Copyright (c) 2020 Advantech
* Author: Yuechao Zhao <yuechao.zhao@advantech.com.cn>
*
* Supports the following chips: * Supports the following chips:
* *
* Chip #vin #fan #pwm #temp #dts chip ID * Chip #vin #fan #pwm #temp #dts chip ID
...@@ -20,6 +23,7 @@ ...@@ -20,6 +23,7 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/hwmon.h> #include <linux/hwmon.h>
#include <linux/watchdog.h>
#define VENDOR_ID_REG 0x7A /* Any bank */ #define VENDOR_ID_REG 0x7A /* Any bank */
#define NUVOTON_ID 0x50 #define NUVOTON_ID 0x50
...@@ -88,18 +92,42 @@ ...@@ -88,18 +92,42 @@
#define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */ #define FANCTL1_FMR_REG 0x00 /* Bank 3; 1 reg per channel */
#define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */ #define FANCTL1_OUT_REG 0x10 /* Bank 3; 1 reg per channel */
#define WDT_LOCK_REG 0xE0 /* W/O Lock Watchdog Register */
#define WDT_EN_REG 0xE1 /* R/O Watchdog Enable Register */
#define WDT_STS_REG 0xE2 /* R/O Watchdog Status Register */
#define WDT_TIMER_REG 0xE3 /* R/W Watchdog Timer Register */
#define WDT_SOFT_EN 0x55 /* Enable soft watchdog timer */
#define WDT_SOFT_DIS 0xAA /* Disable soft watchdog timer */
#define VOLT_MONITOR_MODE 0x0 #define VOLT_MONITOR_MODE 0x0
#define THERMAL_DIODE_MODE 0x1 #define THERMAL_DIODE_MODE 0x1
#define THERMISTOR_MODE 0x3 #define THERMISTOR_MODE 0x3
#define ENABLE_TSI BIT(1) #define ENABLE_TSI BIT(1)
#define WATCHDOG_TIMEOUT 1 /* 1 minute default timeout */
/*The timeout range is 1-255 minutes*/
#define MIN_TIMEOUT (1 * 60)
#define MAX_TIMEOUT (255 * 60)
static int timeout;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in minutes. 1 <= timeout <= 255, default="
__MODULE_STRING(WATCHDOG_TIMEOUT) ".");
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
static const unsigned short normal_i2c[] = { static const unsigned short normal_i2c[] = {
0x2d, 0x2e, I2C_CLIENT_END 0x2d, 0x2e, I2C_CLIENT_END
}; };
struct nct7904_data { struct nct7904_data {
struct i2c_client *client; struct i2c_client *client;
struct watchdog_device wdt;
struct mutex bank_lock; struct mutex bank_lock;
int bank_sel; int bank_sel;
u32 fanin_mask; u32 fanin_mask;
...@@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_info = { ...@@ -892,6 +920,95 @@ static const struct hwmon_chip_info nct7904_chip_info = {
.info = nct7904_info, .info = nct7904_info,
}; };
/*
* Watchdog Function
*/
static int nct7904_wdt_start(struct watchdog_device *wdt)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
/* Enable soft watchdog timer */
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
}
static int nct7904_wdt_stop(struct watchdog_device *wdt)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
}
static int nct7904_wdt_set_timeout(struct watchdog_device *wdt,
unsigned int timeout)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
/*
* The NCT7904 is very special in watchdog function.
* Its minimum unit is minutes. And wdt->timeout needs
* to match the actual timeout selected. So, this needs
* to be: wdt->timeout = timeout / 60 * 60.
* For example, if the user configures a timeout of
* 119 seconds, the actual timeout will be 60 seconds.
* So, wdt->timeout must then be set to 60 seconds.
*/
wdt->timeout = timeout / 60 * 60;
return nct7904_write_reg(data, BANK_0, WDT_TIMER_REG,
wdt->timeout / 60);
}
static int nct7904_wdt_ping(struct watchdog_device *wdt)
{
/*
* Note:
* NCT7904 does not support refreshing WDT_TIMER_REG register when
* the watchdog is active. Please disable watchdog before feeding
* the watchdog and enable it again.
*/
struct nct7904_data *data = watchdog_get_drvdata(wdt);
int ret;
/* Disable soft watchdog timer */
ret = nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_DIS);
if (ret < 0)
return ret;
/* feed watchdog */
ret = nct7904_write_reg(data, BANK_0, WDT_TIMER_REG, wdt->timeout / 60);
if (ret < 0)
return ret;
/* Enable soft watchdog timer */
return nct7904_write_reg(data, BANK_0, WDT_LOCK_REG, WDT_SOFT_EN);
}
static unsigned int nct7904_wdt_get_timeleft(struct watchdog_device *wdt)
{
struct nct7904_data *data = watchdog_get_drvdata(wdt);
int ret;
ret = nct7904_read_reg(data, BANK_0, WDT_TIMER_REG);
if (ret < 0)
return 0;
return ret * 60;
}
static const struct watchdog_info nct7904_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.identity = "nct7904 watchdog",
};
static const struct watchdog_ops nct7904_wdt_ops = {
.owner = THIS_MODULE,
.start = nct7904_wdt_start,
.stop = nct7904_wdt_stop,
.ping = nct7904_wdt_ping,
.set_timeout = nct7904_wdt_set_timeout,
.get_timeleft = nct7904_wdt_get_timeleft,
};
static int nct7904_probe(struct i2c_client *client, static int nct7904_probe(struct i2c_client *client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
...@@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client, ...@@ -1022,7 +1139,26 @@ static int nct7904_probe(struct i2c_client *client,
hwmon_dev = hwmon_dev =
devm_hwmon_device_register_with_info(dev, client->name, data, devm_hwmon_device_register_with_info(dev, client->name, data,
&nct7904_chip_info, NULL); &nct7904_chip_info, NULL);
return PTR_ERR_OR_ZERO(hwmon_dev); ret = PTR_ERR_OR_ZERO(hwmon_dev);
if (ret)
return ret;
/* Watchdog initialization */
data->wdt.ops = &nct7904_wdt_ops;
data->wdt.info = &nct7904_wdt_info;
data->wdt.timeout = WATCHDOG_TIMEOUT * 60; /* Set default timeout */
data->wdt.min_timeout = MIN_TIMEOUT;
data->wdt.max_timeout = MAX_TIMEOUT;
data->wdt.parent = &client->dev;
watchdog_init_timeout(&data->wdt, timeout * 60, &client->dev);
watchdog_set_nowayout(&data->wdt, nowayout);
watchdog_set_drvdata(&data->wdt, data);
watchdog_stop_on_unregister(&data->wdt);
return devm_watchdog_register_device(dev, &data->wdt);
} }
static const struct i2c_device_id nct7904_id[] = { static const struct i2c_device_id nct7904_id[] = {
......
...@@ -146,6 +146,15 @@ config SENSORS_MAX16064 ...@@ -146,6 +146,15 @@ config SENSORS_MAX16064
This driver can also be built as a module. If so, the module will This driver can also be built as a module. If so, the module will
be called max16064. be called max16064.
config SENSORS_MAX16601
tristate "Maxim MAX16601"
help
If you say yes here you get hardware monitoring support for Maxim
MAX16601.
This driver can also be built as a module. If so, the module will
be called max16601.
config SENSORS_MAX20730 config SENSORS_MAX20730
tristate "Maxim MAX20730, MAX20734, MAX20743" tristate "Maxim MAX20730, MAX20734, MAX20743"
help help
......
...@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o ...@@ -17,6 +17,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o
obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o
obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o
obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o
obj-$(CONFIG_SENSORS_MAX16601) += max16601.o
obj-$(CONFIG_SENSORS_MAX20730) += max20730.o obj-$(CONFIG_SENSORS_MAX20730) += max20730.o
obj-$(CONFIG_SENSORS_MAX20751) += max20751.o obj-$(CONFIG_SENSORS_MAX20751) += max20751.o
obj-$(CONFIG_SENSORS_MAX31785) += max31785.o obj-$(CONFIG_SENSORS_MAX31785) += max31785.o
......
// 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 { ...@@ -109,8 +109,8 @@ struct pmbus_data {
bool has_status_word; /* device uses STATUS_WORD register */ bool has_status_word; /* device uses STATUS_WORD register */
int (*read_status)(struct i2c_client *client, int page); int (*read_status)(struct i2c_client *client, int page);
u8 currpage; s16 currpage; /* current page, -1 for unknown/unset */
u8 currphase; /* current phase, 0xff for all */ s16 currphase; /* current phase, 0xff for all, -1 for unknown/unset */
}; };
struct pmbus_debugfs_entry { struct pmbus_debugfs_entry {
...@@ -2529,8 +2529,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, ...@@ -2529,8 +2529,8 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id,
if (pdata) if (pdata)
data->flags = pdata->flags; data->flags = pdata->flags;
data->info = info; data->info = info;
data->currpage = 0xff; data->currpage = -1;
data->currphase = 0xfe; data->currphase = -1;
ret = pmbus_init_common(client, data, info); ret = pmbus_init_common(client, data, info);
if (ret < 0) if (ret < 0)
......
...@@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS ...@@ -407,6 +407,21 @@ config MFD_EXYNOS_LPASS
Select this option to enable support for Samsung Exynos Low Power Select this option to enable support for Samsung Exynos Low Power
Audio Subsystem. Audio Subsystem.
config MFD_GATEWORKS_GSC
tristate "Gateworks System Controller"
depends on (I2C && OF)
select MFD_CORE
select REGMAP_I2C
select REGMAP_IRQ
help
Enable support for the Gateworks System Controller (GSC) found
on Gateworks Single Board Computers supporting system functions
such as push-button monitor, multiple ADC's for voltage and
temperature monitoring, fan controller and watchdog monitor.
This driver provides common support for accessing the device.
Additional drivers must be enabled in order to use the
functionality of the device.
config MFD_MC13XXX config MFD_MC13XXX
tristate tristate
depends on (SPI_MASTER || I2C) depends on (SPI_MASTER || I2C)
......
...@@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o ...@@ -15,6 +15,7 @@ obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o obj-$(CONFIG_MFD_EXYNOS_LPASS) += exynos-lpass.o
obj-$(CONFIG_MFD_GATEWORKS_GSC) += gateworks-gsc.o
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o
......
// 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, &reg))
return -EIO;
gsc->fwver = reg;
regmap_read(gsc->regmap, GSC_FW_CRC, &reg);
gsc->fwcrc = reg;
regmap_read(gsc->regmap, GSC_FW_CRC + 1, &reg);
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, ...@@ -436,6 +436,9 @@ devm_hwmon_device_register_with_info(struct device *dev,
void hwmon_device_unregister(struct device *dev); void hwmon_device_unregister(struct device *dev);
void devm_hwmon_device_unregister(struct device *dev); void devm_hwmon_device_unregister(struct device *dev);
int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel);
/** /**
* hwmon_is_bad_char - Is the char invalid in a hwmon name * hwmon_is_bad_char - Is the char invalid in a hwmon name
* @ch: the char to be considered * @ch: the char to be considered
......
/* 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_ */
/* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment