mirror of
https://github.com/tbsdtv/linux_media.git
synced 2025-07-23 20:51:03 +02:00
Merge tag 'hwmon-for-v5.12' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging
Pull hwmon updates from Guenter Roeck: "New drivers: - Texas Instruments TPS23861 driver - AHT10 Temperature and Humidity Sensor Driver Support for new chips/variants to existing drivers: - Add AMD family 19h model 30h x86 match to amd_energy driver - Add Zen3 Ryzen Desktop CPUs support to k10temp driver - Add support for MAX16508 to max16601 driver - Support revision "B" of max31785 - Add support for ASRock boards to nct6683 driver Driver removals: - Decomission abx500 driver Various other minor fixes and improvements" * tag 'hwmon-for-v5.12' of git://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging: (30 commits) MAINTAINERS: Add entry for Texas Instruments TPS23861 PoE PSE hwmon: add Texas Instruments TPS23861 driver dt-bindings: hwmon: Add TI TPS23861 bindings hwmon: (da9052) Switch to using the new API kobj_to_dev() hwmon: (amd_energy) Add AMD family 19h model 30h x86 match hwmon: (pmbus/max31785) Support revision "B" hwmon: (pmbus/lm25066) Remove unnecessary pmbus_clear_cache function call hwmon: (pmbus) Clear sensor data after chip write hwmon: (pmbus/max16601) Add support for MAX16508 hwmon: (pmbus/max16601) Determine and use number of populated phases hwmon: (pmbus) Simplify the calculation of variables hwmon: (aht10) Unlock on error in aht10_read_values() hwmon: (pwm-fan) stop using legacy PWM functions and some cleanups hwmon: Add AHT10 Temperature and Humidity Sensor Driver hwmon: (applesmc) Assign boolean values to a bool variable hwmon: (nct6683) Support ASRock boards hwmon: (aspeed-pwm-tacho) Switch to using the new API kobj_to_dev() hwmon: (max6650) Switch to using the new API kobj_to_dev() hwmon: (pwm-fan) Support multiple fan tachometers hwmon: (pwm-fan) Store tach data separately ...
This commit is contained in:
51
Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
Normal file
51
Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
Normal file
@@ -0,0 +1,51 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
|
||||
$id: http://devicetree.org/schemas/hwmon/ti,tps23861.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: TI TPS23861 PoE PSE
|
||||
|
||||
maintainers:
|
||||
- Robert Marko <robert.marko@sartura.hr>
|
||||
|
||||
description: |
|
||||
The TPS23861 is a IEEE 802.3at Quad Port Power-over-Ethernet PSE Controller.
|
||||
|
||||
Datasheets:
|
||||
https://www.ti.com/lit/gpn/tps23861
|
||||
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- ti,tps23861
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
shunt-resistor-micro-ohms:
|
||||
description: The value of curent sense resistor in microohms.
|
||||
default: 255000
|
||||
minimum: 250000
|
||||
maximum: 255000
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
i2c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
tps23861@30 {
|
||||
compatible = "ti,tps23861";
|
||||
reg = <0x30>;
|
||||
shunt-resistor-micro-ohms = <255000>;
|
||||
};
|
||||
};
|
@@ -1,26 +0,0 @@
|
||||
Kernel driver ab8500
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* ST-Ericsson AB8500
|
||||
|
||||
Prefix: 'ab8500'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: http://www.stericsson.com/developers/documentation.jsp
|
||||
|
||||
Authors:
|
||||
- Martin Persson <martin.persson@stericsson.com>
|
||||
- Hongbo Zhang <hongbo.zhang@linaro.org>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
See also Documentation/hwmon/abx500.rst. This is the ST-Ericsson AB8500 specific
|
||||
driver.
|
||||
|
||||
Currently only the AB8500 internal sensor and one external sensor for battery
|
||||
temperature are monitored. Other GPADC channels can also be monitored if needed
|
||||
in future.
|
@@ -1,32 +0,0 @@
|
||||
Kernel driver abx500
|
||||
====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* ST-Ericsson ABx500 series
|
||||
|
||||
Prefix: 'abx500'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: http://www.stericsson.com/developers/documentation.jsp
|
||||
|
||||
Authors:
|
||||
Martin Persson <martin.persson@stericsson.com>
|
||||
Hongbo Zhang <hongbo.zhang@linaro.org>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Every ST-Ericsson Ux500 SOC consists of both ABx500 and DBx500 physically,
|
||||
this is kernel hwmon driver for ABx500.
|
||||
|
||||
There are some GPADCs inside ABx500 which are designed for connecting to
|
||||
thermal sensors, and there is also a thermal sensor inside ABx500 too, which
|
||||
raises interrupt when critical temperature reached.
|
||||
|
||||
This abx500 is a common layer which can monitor all of the sensors, every
|
||||
specific abx500 chip has its special configurations in its own file, e.g. some
|
||||
sensors can be configured invisible if they are not available on that chip, and
|
||||
the corresponding gpadc_addr should be set to 0, thus this sensor won't be
|
||||
polled.
|
46
Documentation/hwmon/aht10.rst
Normal file
46
Documentation/hwmon/aht10.rst
Normal file
@@ -0,0 +1,46 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
Kernel driver aht10
|
||||
=====================
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Aosong AHT10
|
||||
|
||||
Prefix: 'aht10'
|
||||
|
||||
Addresses scanned: None
|
||||
|
||||
Datasheet:
|
||||
|
||||
Chinese: http://www.aosong.com/userfiles/files/media/AHT10%E4%BA%A7%E5%93%81%E6%89%8B%E5%86%8C%20A3%2020201210.pdf
|
||||
English: https://server4.eca.ir/eshop/AHT10/Aosong_AHT10_en_draft_0c.pdf
|
||||
|
||||
Author: Johannes Cornelis Draaijer <jcdra1@gmail.com>
|
||||
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The AHT10 is a Temperature and Humidity sensor
|
||||
|
||||
The address of this i2c device may only be 0x38
|
||||
|
||||
Usage Notes
|
||||
-----------
|
||||
|
||||
This driver does not probe for AHT10 devices, as there is no reliable
|
||||
way to determine if an i2c chip is or isn't an AHT10. The device has
|
||||
to be instantiated explicitly with the address 0x38. See
|
||||
Documentation/i2c/instantiating-devices.rst for details.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
=============== ============================================
|
||||
temp1_input Measured temperature in millidegrees Celcius
|
||||
humidity1_input Measured humidity in %H
|
||||
update_interval The minimum interval for polling the sensor,
|
||||
in milliseconds. Writable. Must be at
|
||||
least 2000.
|
||||
=============== ============================================
|
@@ -74,7 +74,7 @@ bus supply voltage.
|
||||
|
||||
The shunt value in micro-ohms can be set via platform data or device tree at
|
||||
compile-time or via the shunt_resistor attribute in sysfs at run-time. Please
|
||||
refer to the Documentation/devicetree/bindings/hwmon/ina2xx.txt for bindings
|
||||
refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings
|
||||
if the device tree is used.
|
||||
|
||||
Additionally ina226 supports update_interval attribute as described in
|
||||
|
@@ -18,10 +18,8 @@ Hardware Monitoring Kernel Drivers
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
ab8500
|
||||
abituguru
|
||||
abituguru3
|
||||
abx500
|
||||
acpi_power_meter
|
||||
ad7314
|
||||
adc128d818
|
||||
@@ -39,6 +37,7 @@ Hardware Monitoring Kernel Drivers
|
||||
adt7462
|
||||
adt7470
|
||||
adt7475
|
||||
aht10
|
||||
amc6821
|
||||
amd_energy
|
||||
asb100
|
||||
@@ -178,6 +177,7 @@ Hardware Monitoring Kernel Drivers
|
||||
tmp401
|
||||
tmp421
|
||||
tmp513
|
||||
tps23861
|
||||
tps40422
|
||||
tps53679
|
||||
twl4030-madc-hwmon
|
||||
|
@@ -5,6 +5,14 @@ Kernel driver max16601
|
||||
|
||||
Supported chips:
|
||||
|
||||
* Maxim MAX16508
|
||||
|
||||
Prefix: 'max16508'
|
||||
|
||||
Addresses scanned: -
|
||||
|
||||
Datasheet: Not published
|
||||
|
||||
* Maxim MAX16601
|
||||
|
||||
Prefix: 'max16601'
|
||||
@@ -19,8 +27,8 @@ Author: Guenter Roeck <linux@roeck-us.net>
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports the MAX16601 VR13.HC Dual-Output Voltage Regulator
|
||||
Chipset.
|
||||
This driver supports the MAX16508 VR13 Dual-Output Voltage Regulator
|
||||
as well as the MAX16601 VR13.HC Dual-Output Voltage Regulator chipsets.
|
||||
|
||||
The driver is a client driver to the core PMBus driver.
|
||||
Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
|
||||
@@ -55,83 +63,44 @@ 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_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.
|
||||
curr[P+2]_label "iin1.P"
|
||||
curr[P+2]_input VCORE phase P input current.
|
||||
|
||||
curr3_label "iin1.1"
|
||||
curr3_input VCORE phase 1 input current.
|
||||
curr[N+2]_label "iin2"
|
||||
curr[N+2]_input VCORE input current, derived from sensor
|
||||
element.
|
||||
'N' is the number of enabled/populated phases.
|
||||
|
||||
curr4_label "iin1.2"
|
||||
curr4_input VCORE phase 2 input current.
|
||||
curr[N+3]_label "iin3"
|
||||
curr[N+3]_input VSA input current.
|
||||
|
||||
curr5_label "iin1.3"
|
||||
curr5_input VCORE phase 3 input current.
|
||||
curr[N+4]_label "iout1"
|
||||
curr[N+4]_input VCORE output current.
|
||||
curr[N+4]_crit Critical output current.
|
||||
curr[N+4]_crit_alarm Output current critical alarm.
|
||||
curr[N+4]_max Maximum output current.
|
||||
curr[N+4]_max_alarm Output current high alarm.
|
||||
|
||||
curr6_label "iin1.4"
|
||||
curr6_input VCORE phase 4 input current.
|
||||
curr[N+P+5]_label "iout1.P"
|
||||
curr[N+P+5]_input VCORE phase P output 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.
|
||||
curr[2*N+5]_label "iout3"
|
||||
curr[2*N+5]_input VSA output current.
|
||||
curr[2*N+5]_highest Historical maximum VSA output current.
|
||||
curr[2*N+5]_reset_history Write any value to reset curr21_highest.
|
||||
curr[2*N+5]_crit Critical output current.
|
||||
curr[2*N+5]_crit_alarm Output current critical alarm.
|
||||
curr[2*N+5]_max Maximum output current.
|
||||
curr[2*N+5]_max_alarm Output current high alarm.
|
||||
|
||||
power1_label "pin1"
|
||||
power1_input Input power, derived from duty cycle and output current.
|
||||
power1_input Input power, derived from duty cycle and output
|
||||
current.
|
||||
power1_alarm Input power alarm.
|
||||
|
||||
power2_label "pin2"
|
||||
|
@@ -61,5 +61,6 @@ Board Firmware version
|
||||
Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13
|
||||
Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13
|
||||
Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13
|
||||
ASRock X570 NCT6683D EC firmware version 1.0 build 06/28/19
|
||||
MSI B550 NCT6687D EC firmware version 1.0 build 05/07/20
|
||||
=============== ===============================================
|
||||
|
41
Documentation/hwmon/tps23861.rst
Normal file
41
Documentation/hwmon/tps23861.rst
Normal file
@@ -0,0 +1,41 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
Kernel driver tps23861
|
||||
======================
|
||||
|
||||
Supported chips:
|
||||
* Texas Instruments TPS23861
|
||||
|
||||
Prefix: 'tps23861'
|
||||
|
||||
Datasheet: https://www.ti.com/lit/gpn/tps23861
|
||||
|
||||
Author: Robert Marko <robert.marko@sartura.hr>
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This driver supports hardware monitoring for Texas Instruments TPS23861 PoE PSE.
|
||||
|
||||
TPS23861 is a quad port IEEE802.3at PSE controller with optional I2C control
|
||||
and monitoring capabilities.
|
||||
|
||||
TPS23861 offers three modes of operation: Auto, Semi-Auto and Manual.
|
||||
|
||||
This driver only supports the Auto mode of operation providing monitoring
|
||||
as well as enabling/disabling the four ports.
|
||||
|
||||
Sysfs entries
|
||||
-------------
|
||||
|
||||
======================= =====================================================================
|
||||
in[0-3]_input Voltage on ports [1-4]
|
||||
in[0-3]_label "Port[1-4]"
|
||||
in4_input IC input voltage
|
||||
in4_label "Input"
|
||||
temp1_input IC die temperature
|
||||
temp1_label "Die"
|
||||
curr[1-4]_input Current on ports [1-4]
|
||||
in[1-4]_label "Port[1-4]"
|
||||
in[0-3]_enable Enable/disable ports [1-4]
|
||||
======================= =====================================================================
|
@@ -17620,6 +17620,15 @@ F: include/dt-bindings/soc/ti,sci_pm_domain.h
|
||||
F: include/linux/soc/ti/ti_sci_inta_msi.h
|
||||
F: include/linux/soc/ti/ti_sci_protocol.h
|
||||
|
||||
TEXAS INSTRUMENTS TPS23861 PoE PSE DRIVER
|
||||
M: Robert Marko <robert.marko@sartura.hr>
|
||||
M: Luka Perkov <luka.perkov@sartura.hr>
|
||||
L: linux-hwmon@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/hwmon/ti,tps23861.yaml
|
||||
F: Documentation/hwmon/tps23861.rst
|
||||
F: drivers/hwmon/tps23861.c
|
||||
|
||||
THANKO'S RAREMONO AM/FM/SW RADIO RECEIVER USB DRIVER
|
||||
M: Hans Verkuil <hverkuil@xs4all.nl>
|
||||
L: linux-media@vger.kernel.org
|
||||
|
@@ -38,19 +38,6 @@ config HWMON_DEBUG_CHIP
|
||||
|
||||
comment "Native drivers"
|
||||
|
||||
config SENSORS_AB8500
|
||||
tristate "AB8500 thermal monitoring"
|
||||
depends on AB8500_GPADC && AB8500_BM && (IIO = y)
|
||||
default n
|
||||
help
|
||||
If you say yes here you get support for the thermal sensor part
|
||||
of the AB8500 chip. The driver includes thermal management for
|
||||
AB8500 die and two GPADC channels. The GPADC channel are preferably
|
||||
used to access sensors outside the AB8500 chip.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called abx500-temp.
|
||||
|
||||
config SENSORS_ABITUGURU
|
||||
tristate "Abit uGuru (rev 1 & 2)"
|
||||
depends on X86 && DMI
|
||||
@@ -257,6 +244,16 @@ config SENSORS_ADT7475
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called adt7475.
|
||||
|
||||
config SENSORS_AHT10
|
||||
tristate "Aosong AHT10"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here, you get support for the Aosong AHT10
|
||||
temperature and humidity sensors
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called aht10.
|
||||
|
||||
config SENSORS_AS370
|
||||
tristate "Synaptics AS370 SoC hardware monitoring driver"
|
||||
help
|
||||
@@ -1136,6 +1133,17 @@ config SENSORS_TC654
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tc654.
|
||||
|
||||
config SENSORS_TPS23861
|
||||
tristate "Texas Instruments TPS23861 PoE PSE"
|
||||
depends on I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for Texas Instruments
|
||||
TPS23861 802.3at PoE PSE chips.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called tps23861.
|
||||
|
||||
config SENSORS_MENF21BMC_HWMON
|
||||
tristate "MEN 14F021P00 BMC Hardware Monitoring"
|
||||
depends on MFD_MENF21BMC
|
||||
|
@@ -21,7 +21,6 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o
|
||||
obj-$(CONFIG_SENSORS_W83781D) += w83781d.o
|
||||
obj-$(CONFIG_SENSORS_W83791D) += w83791d.o
|
||||
|
||||
obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o
|
||||
obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o
|
||||
obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o
|
||||
obj-$(CONFIG_SENSORS_AD7314) += ad7314.o
|
||||
@@ -45,6 +44,7 @@ obj-$(CONFIG_SENSORS_ADT7411) += adt7411.o
|
||||
obj-$(CONFIG_SENSORS_ADT7462) += adt7462.o
|
||||
obj-$(CONFIG_SENSORS_ADT7470) += adt7470.o
|
||||
obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o
|
||||
obj-$(CONFIG_SENSORS_AHT10) += aht10.o
|
||||
obj-$(CONFIG_SENSORS_AMD_ENERGY) += amd_energy.o
|
||||
obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o
|
||||
obj-$(CONFIG_SENSORS_ARM_SCMI) += scmi-hwmon.o
|
||||
@@ -144,6 +144,7 @@ obj-$(CONFIG_SENSORS_MAX31790) += max31790.o
|
||||
obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
|
||||
obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o
|
||||
obj-$(CONFIG_SENSORS_TC654) += tc654.o
|
||||
obj-$(CONFIG_SENSORS_TPS23861) += tps23861.o
|
||||
obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
|
||||
obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
|
||||
obj-$(CONFIG_SENSORS_MR75203) += mr75203.o
|
||||
|
@@ -1,224 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson 2010 - 2013
|
||||
* Author: Martin Persson <martin.persson@stericsson.com>
|
||||
* Hongbo Zhang <hongbo.zhang@linaro.org>
|
||||
*
|
||||
* When the AB8500 thermal warning temperature is reached (threshold cannot
|
||||
* be changed by SW), an interrupt is set, and if no further action is taken
|
||||
* within a certain time frame, kernel_power_off will be called.
|
||||
*
|
||||
* When AB8500 thermal shutdown temperature is reached a hardware shutdown of
|
||||
* the AB8500 will occur.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/mfd/abx500.h>
|
||||
#include <linux/mfd/abx500/ab8500-bm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/power/ab8500.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include "abx500.h"
|
||||
|
||||
#define DEFAULT_POWER_OFF_DELAY (HZ * 10)
|
||||
#define THERMAL_VCC 1800
|
||||
#define PULL_UP_RESISTOR 47000
|
||||
|
||||
#define AB8500_SENSOR_AUX1 0
|
||||
#define AB8500_SENSOR_AUX2 1
|
||||
#define AB8500_SENSOR_BTEMP_BALL 2
|
||||
#define AB8500_SENSOR_BAT_CTRL 3
|
||||
#define NUM_MONITORED_SENSORS 4
|
||||
|
||||
struct ab8500_gpadc_cfg {
|
||||
const struct abx500_res_to_temp *temp_tbl;
|
||||
int tbl_sz;
|
||||
int vcc;
|
||||
int r_up;
|
||||
};
|
||||
|
||||
struct ab8500_temp {
|
||||
struct iio_channel *aux1;
|
||||
struct iio_channel *aux2;
|
||||
struct ab8500_btemp *btemp;
|
||||
struct delayed_work power_off_work;
|
||||
struct ab8500_gpadc_cfg cfg;
|
||||
struct abx500_temp *abx500_data;
|
||||
};
|
||||
|
||||
/*
|
||||
* The hardware connection is like this:
|
||||
* VCC----[ R_up ]-----[ NTC ]----GND
|
||||
* where R_up is pull-up resistance, and GPADC measures voltage on NTC.
|
||||
* and res_to_temp table is strictly sorted by falling resistance values.
|
||||
*/
|
||||
static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg,
|
||||
int v_ntc, int *temp)
|
||||
{
|
||||
int r_ntc, i = 0, tbl_sz = cfg->tbl_sz;
|
||||
const struct abx500_res_to_temp *tbl = cfg->temp_tbl;
|
||||
|
||||
if (cfg->vcc < 0 || v_ntc >= cfg->vcc)
|
||||
return -EINVAL;
|
||||
|
||||
r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc);
|
||||
if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist)
|
||||
return -EINVAL;
|
||||
|
||||
while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) &&
|
||||
i < tbl_sz - 2)
|
||||
i++;
|
||||
|
||||
/* return milli-Celsius */
|
||||
*temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 *
|
||||
(r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp)
|
||||
{
|
||||
int voltage, ret;
|
||||
struct ab8500_temp *ab8500_data = data->plat_data;
|
||||
|
||||
if (sensor == AB8500_SENSOR_BTEMP_BALL) {
|
||||
*temp = ab8500_btemp_get_temp(ab8500_data->btemp);
|
||||
} else if (sensor == AB8500_SENSOR_BAT_CTRL) {
|
||||
*temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp);
|
||||
} else if (sensor == AB8500_SENSOR_AUX1) {
|
||||
ret = iio_read_channel_processed(ab8500_data->aux1, &voltage);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else if (sensor == AB8500_SENSOR_AUX2) {
|
||||
ret = iio_read_channel_processed(ab8500_data->aux2, &voltage);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ab8500_thermal_power_off(struct work_struct *work)
|
||||
{
|
||||
struct ab8500_temp *ab8500_data = container_of(work,
|
||||
struct ab8500_temp, power_off_work.work);
|
||||
struct abx500_temp *abx500_data = ab8500_data->abx500_data;
|
||||
|
||||
dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n");
|
||||
|
||||
kernel_power_off();
|
||||
}
|
||||
|
||||
static ssize_t ab8500_show_name(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "ab8500\n");
|
||||
}
|
||||
|
||||
static ssize_t ab8500_show_label(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
char *label;
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
int index = attr->index;
|
||||
|
||||
switch (index) {
|
||||
case 1:
|
||||
label = "ext_adc1";
|
||||
break;
|
||||
case 2:
|
||||
label = "ext_adc2";
|
||||
break;
|
||||
case 3:
|
||||
label = "bat_temp";
|
||||
break;
|
||||
case 4:
|
||||
label = "bat_ctrl";
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return sprintf(buf, "%s\n", label);
|
||||
}
|
||||
|
||||
static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data)
|
||||
{
|
||||
struct ab8500_temp *ab8500_data = data->plat_data;
|
||||
|
||||
dev_warn(&data->pdev->dev, "Power off in %d s\n",
|
||||
DEFAULT_POWER_OFF_DELAY / HZ);
|
||||
|
||||
schedule_delayed_work(&ab8500_data->power_off_work,
|
||||
DEFAULT_POWER_OFF_DELAY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int abx500_hwmon_init(struct abx500_temp *data)
|
||||
{
|
||||
struct ab8500_temp *ab8500_data;
|
||||
|
||||
ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data),
|
||||
GFP_KERNEL);
|
||||
if (!ab8500_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ab8500_data->btemp = ab8500_btemp_get();
|
||||
if (IS_ERR(ab8500_data->btemp))
|
||||
return PTR_ERR(ab8500_data->btemp);
|
||||
|
||||
INIT_DELAYED_WORK(&ab8500_data->power_off_work,
|
||||
ab8500_thermal_power_off);
|
||||
|
||||
ab8500_data->cfg.vcc = THERMAL_VCC;
|
||||
ab8500_data->cfg.r_up = PULL_UP_RESISTOR;
|
||||
ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor;
|
||||
ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size;
|
||||
|
||||
data->plat_data = ab8500_data;
|
||||
ab8500_data->aux1 = devm_iio_channel_get(&data->pdev->dev, "aux1");
|
||||
if (IS_ERR(ab8500_data->aux1)) {
|
||||
if (PTR_ERR(ab8500_data->aux1) == -ENODEV)
|
||||
return -EPROBE_DEFER;
|
||||
dev_err(&data->pdev->dev, "failed to get AUX1 ADC channel\n");
|
||||
return PTR_ERR(ab8500_data->aux1);
|
||||
}
|
||||
ab8500_data->aux2 = devm_iio_channel_get(&data->pdev->dev, "aux2");
|
||||
if (IS_ERR(ab8500_data->aux2)) {
|
||||
if (PTR_ERR(ab8500_data->aux2) == -ENODEV)
|
||||
return -EPROBE_DEFER;
|
||||
dev_err(&data->pdev->dev, "failed to get AUX2 ADC channel\n");
|
||||
return PTR_ERR(ab8500_data->aux2);
|
||||
}
|
||||
|
||||
data->gpadc_addr[0] = AB8500_SENSOR_AUX1;
|
||||
data->gpadc_addr[1] = AB8500_SENSOR_AUX2;
|
||||
data->gpadc_addr[2] = AB8500_SENSOR_BTEMP_BALL;
|
||||
data->gpadc_addr[3] = AB8500_SENSOR_BAT_CTRL;
|
||||
data->monitored_sensors = NUM_MONITORED_SENSORS;
|
||||
|
||||
data->ops.read_sensor = ab8500_read_sensor;
|
||||
data->ops.irq_handler = ab8500_temp_irq_handler;
|
||||
data->ops.show_name = ab8500_show_name;
|
||||
data->ops.show_label = ab8500_show_label;
|
||||
data->ops.is_visible = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(abx500_hwmon_init);
|
||||
|
||||
MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@linaro.org>");
|
||||
MODULE_DESCRIPTION("AB8500 temperature driver");
|
||||
MODULE_LICENSE("GPL");
|
@@ -1,487 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson 2010 - 2013
|
||||
* Author: Martin Persson <martin.persson@stericsson.com>
|
||||
* Hongbo Zhang <hongbo.zhang@linaro.org>
|
||||
*
|
||||
* ABX500 does not provide auto ADC, so to monitor the required temperatures,
|
||||
* a periodic work is used. It is more important to not wake up the CPU than
|
||||
* to perform this job, hence the use of a deferred delay.
|
||||
*
|
||||
* A deferred delay for thermal monitor is considered safe because:
|
||||
* If the chip gets too hot during a sleep state it's most likely due to
|
||||
* external factors, such as the surrounding temperature. I.e. no SW decisions
|
||||
* will make any difference.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "abx500.h"
|
||||
|
||||
#define DEFAULT_MONITOR_DELAY HZ
|
||||
#define DEFAULT_MAX_TEMP 130
|
||||
|
||||
static inline void schedule_monitor(struct abx500_temp *data)
|
||||
{
|
||||
data->work_active = true;
|
||||
schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY);
|
||||
}
|
||||
|
||||
static void threshold_updated(struct abx500_temp *data)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < data->monitored_sensors; i++)
|
||||
if (data->max[i] != 0 || data->min[i] != 0) {
|
||||
schedule_monitor(data);
|
||||
return;
|
||||
}
|
||||
|
||||
dev_dbg(&data->pdev->dev, "No active thresholds.\n");
|
||||
cancel_delayed_work_sync(&data->work);
|
||||
data->work_active = false;
|
||||
}
|
||||
|
||||
static void gpadc_monitor(struct work_struct *work)
|
||||
{
|
||||
int temp, i, ret;
|
||||
char alarm_node[30];
|
||||
bool updated_min_alarm, updated_max_alarm;
|
||||
struct abx500_temp *data;
|
||||
|
||||
data = container_of(work, struct abx500_temp, work.work);
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
for (i = 0; i < data->monitored_sensors; i++) {
|
||||
/* Thresholds are considered inactive if set to 0 */
|
||||
if (data->max[i] == 0 && data->min[i] == 0)
|
||||
continue;
|
||||
|
||||
if (data->max[i] < data->min[i])
|
||||
continue;
|
||||
|
||||
ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp);
|
||||
if (ret < 0) {
|
||||
dev_err(&data->pdev->dev, "GPADC read failed\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
updated_min_alarm = false;
|
||||
updated_max_alarm = false;
|
||||
|
||||
if (data->min[i] != 0) {
|
||||
if (temp < data->min[i]) {
|
||||
if (data->min_alarm[i] == false) {
|
||||
data->min_alarm[i] = true;
|
||||
updated_min_alarm = true;
|
||||
}
|
||||
} else {
|
||||
if (data->min_alarm[i] == true) {
|
||||
data->min_alarm[i] = false;
|
||||
updated_min_alarm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data->max[i] != 0) {
|
||||
if (temp > data->max[i]) {
|
||||
if (data->max_alarm[i] == false) {
|
||||
data->max_alarm[i] = true;
|
||||
updated_max_alarm = true;
|
||||
}
|
||||
} else if (temp < data->max[i] - data->max_hyst[i]) {
|
||||
if (data->max_alarm[i] == true) {
|
||||
data->max_alarm[i] = false;
|
||||
updated_max_alarm = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updated_min_alarm) {
|
||||
ret = sprintf(alarm_node, "temp%d_min_alarm", i + 1);
|
||||
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
|
||||
}
|
||||
if (updated_max_alarm) {
|
||||
ret = sprintf(alarm_node, "temp%d_max_alarm", i + 1);
|
||||
sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
|
||||
}
|
||||
}
|
||||
|
||||
schedule_monitor(data);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
/* HWMON sysfs interfaces */
|
||||
static ssize_t name_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
/* Show chip name */
|
||||
return data->ops.show_name(dev, devattr, buf);
|
||||
}
|
||||
|
||||
static ssize_t label_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
/* Show each sensor label */
|
||||
return data->ops.show_label(dev, devattr, buf);
|
||||
}
|
||||
|
||||
static ssize_t input_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
int ret, temp;
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
u8 gpadc_addr = data->gpadc_addr[attr->index];
|
||||
|
||||
ret = data->ops.read_sensor(data, gpadc_addr, &temp);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sprintf(buf, "%d\n", temp);
|
||||
}
|
||||
|
||||
/* Set functions (RW nodes) */
|
||||
static ssize_t min_store(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long val;
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
int res = kstrtol(buf, 10, &val);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
val = clamp_val(val, 0, DEFAULT_MAX_TEMP);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->min[attr->index] = val;
|
||||
threshold_updated(data);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t max_store(struct device *dev, struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long val;
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
int res = kstrtol(buf, 10, &val);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
val = clamp_val(val, 0, DEFAULT_MAX_TEMP);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->max[attr->index] = val;
|
||||
threshold_updated(data);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t max_hyst_store(struct device *dev,
|
||||
struct device_attribute *devattr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
unsigned long val;
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
int res = kstrtoul(buf, 10, &val);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
val = clamp_val(val, 0, DEFAULT_MAX_TEMP);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->max_hyst[attr->index] = val;
|
||||
threshold_updated(data);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Show functions (RO nodes) */
|
||||
static ssize_t min_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%lu\n", data->min[attr->index]);
|
||||
}
|
||||
|
||||
static ssize_t max_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%lu\n", data->max[attr->index]);
|
||||
}
|
||||
|
||||
static ssize_t max_hyst_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%lu\n", data->max_hyst[attr->index]);
|
||||
}
|
||||
|
||||
static ssize_t min_alarm_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%d\n", data->min_alarm[attr->index]);
|
||||
}
|
||||
|
||||
static ssize_t max_alarm_show(struct device *dev,
|
||||
struct device_attribute *devattr, char *buf)
|
||||
{
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
|
||||
|
||||
return sprintf(buf, "%d\n", data->max_alarm[attr->index]);
|
||||
}
|
||||
|
||||
static umode_t abx500_attrs_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int n)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct abx500_temp *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->ops.is_visible)
|
||||
return data->ops.is_visible(attr, n);
|
||||
|
||||
return attr->mode;
|
||||
}
|
||||
|
||||
/* Chip name, required by hwmon */
|
||||
static SENSOR_DEVICE_ATTR_RO(name, name, 0);
|
||||
|
||||
/* GPADC - SENSOR1 */
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_label, label, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_input, input, 0);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp1_min, min, 0);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp1_max, max, 0);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp1_max_hyst, max_hyst, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_min_alarm, min_alarm, 0);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp1_max_alarm, max_alarm, 0);
|
||||
|
||||
/* GPADC - SENSOR2 */
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_label, label, 1);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_input, input, 1);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp2_min, min, 1);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp2_max, max, 1);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp2_max_hyst, max_hyst, 1);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_min_alarm, min_alarm, 1);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp2_max_alarm, max_alarm, 1);
|
||||
|
||||
/* GPADC - SENSOR3 */
|
||||
static SENSOR_DEVICE_ATTR_RO(temp3_label, label, 2);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp3_input, input, 2);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp3_min, min, 2);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp3_max, max, 2);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp3_max_hyst, max_hyst, 2);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp3_min_alarm, min_alarm, 2);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp3_max_alarm, max_alarm, 2);
|
||||
|
||||
/* GPADC - SENSOR4 */
|
||||
static SENSOR_DEVICE_ATTR_RO(temp4_label, label, 3);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp4_input, input, 3);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp4_min, min, 3);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp4_max, max, 3);
|
||||
static SENSOR_DEVICE_ATTR_RW(temp4_max_hyst, max_hyst, 3);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp4_min_alarm, min_alarm, 3);
|
||||
static SENSOR_DEVICE_ATTR_RO(temp4_max_alarm, max_alarm, 3);
|
||||
|
||||
static struct attribute *abx500_temp_attributes[] = {
|
||||
&sensor_dev_attr_name.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_min.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_min_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_max_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_temp2_label.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_min.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_min_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_max_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_temp3_label.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_min.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_min_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp3_max_alarm.dev_attr.attr,
|
||||
|
||||
&sensor_dev_attr_temp4_label.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_min.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_max.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_max_hyst.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_min_alarm.dev_attr.attr,
|
||||
&sensor_dev_attr_temp4_max_alarm.dev_attr.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group abx500_temp_group = {
|
||||
.attrs = abx500_temp_attributes,
|
||||
.is_visible = abx500_attrs_visible,
|
||||
};
|
||||
|
||||
static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data)
|
||||
{
|
||||
struct platform_device *pdev = irq_data;
|
||||
struct abx500_temp *data = platform_get_drvdata(pdev);
|
||||
|
||||
data->ops.irq_handler(irq, data);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int setup_irqs(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM");
|
||||
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "Get irq by name failed\n");
|
||||
return irq;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
abx500_temp_irq_handler, 0, "abx500-temp", pdev);
|
||||
if (ret < 0)
|
||||
dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int abx500_temp_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct abx500_temp *data;
|
||||
int err;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->pdev = pdev;
|
||||
mutex_init(&data->lock);
|
||||
|
||||
/* Chip specific initialization */
|
||||
err = abx500_hwmon_init(data);
|
||||
if (err < 0 || !data->ops.read_sensor || !data->ops.show_name ||
|
||||
!data->ops.show_label)
|
||||
return err;
|
||||
|
||||
INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor);
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->hwmon_dev = hwmon_device_register(&pdev->dev);
|
||||
if (IS_ERR(data->hwmon_dev)) {
|
||||
err = PTR_ERR(data->hwmon_dev);
|
||||
dev_err(&pdev->dev, "Class registration failed (%d)\n", err);
|
||||
goto exit_sysfs_group;
|
||||
}
|
||||
|
||||
if (data->ops.irq_handler) {
|
||||
err = setup_irqs(pdev);
|
||||
if (err < 0)
|
||||
goto exit_hwmon_reg;
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit_hwmon_reg:
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
exit_sysfs_group:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int abx500_temp_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct abx500_temp *data = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_delayed_work_sync(&data->work);
|
||||
hwmon_device_unregister(data->hwmon_dev);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int abx500_temp_suspend(struct platform_device *pdev,
|
||||
pm_message_t state)
|
||||
{
|
||||
struct abx500_temp *data = platform_get_drvdata(pdev);
|
||||
|
||||
if (data->work_active)
|
||||
cancel_delayed_work_sync(&data->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int abx500_temp_resume(struct platform_device *pdev)
|
||||
{
|
||||
struct abx500_temp *data = platform_get_drvdata(pdev);
|
||||
|
||||
if (data->work_active)
|
||||
schedule_monitor(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id abx500_temp_match[] = {
|
||||
{ .compatible = "stericsson,abx500-temp" },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, abx500_temp_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver abx500_temp_driver = {
|
||||
.driver = {
|
||||
.name = "abx500-temp",
|
||||
.of_match_table = of_match_ptr(abx500_temp_match),
|
||||
},
|
||||
.suspend = abx500_temp_suspend,
|
||||
.resume = abx500_temp_resume,
|
||||
.probe = abx500_temp_probe,
|
||||
.remove = abx500_temp_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(abx500_temp_driver);
|
||||
|
||||
MODULE_AUTHOR("Martin Persson <martin.persson@stericsson.com>");
|
||||
MODULE_DESCRIPTION("ABX500 temperature driver");
|
||||
MODULE_LICENSE("GPL");
|
@@ -1,69 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) ST-Ericsson 2010 - 2013
|
||||
* Author: Martin Persson <martin.persson@stericsson.com>
|
||||
* Hongbo Zhang <hongbo.zhang@linaro.com>
|
||||
*/
|
||||
|
||||
#ifndef _ABX500_H
|
||||
#define _ABX500_H
|
||||
|
||||
#define NUM_SENSORS 5
|
||||
|
||||
struct abx500_temp;
|
||||
|
||||
/*
|
||||
* struct abx500_temp_ops - abx500 chip specific ops
|
||||
* @read_sensor: reads gpadc output
|
||||
* @irq_handler: irq handler
|
||||
* @show_name: hwmon device name
|
||||
* @show_label: hwmon attribute label
|
||||
* @is_visible: is attribute visible
|
||||
*/
|
||||
struct abx500_temp_ops {
|
||||
int (*read_sensor)(struct abx500_temp *, u8, int *);
|
||||
int (*irq_handler)(int, struct abx500_temp *);
|
||||
ssize_t (*show_name)(struct device *,
|
||||
struct device_attribute *, char *);
|
||||
ssize_t (*show_label) (struct device *,
|
||||
struct device_attribute *, char *);
|
||||
int (*is_visible)(struct attribute *, int);
|
||||
};
|
||||
|
||||
/*
|
||||
* struct abx500_temp - representation of temp mon device
|
||||
* @pdev: platform device
|
||||
* @hwmon_dev: hwmon device
|
||||
* @ops: abx500 chip specific ops
|
||||
* @gpadc_addr: gpadc channel address
|
||||
* @min: sensor temperature min value
|
||||
* @max: sensor temperature max value
|
||||
* @max_hyst: sensor temperature hysteresis value for max limit
|
||||
* @min_alarm: sensor temperature min alarm
|
||||
* @max_alarm: sensor temperature max alarm
|
||||
* @work: delayed work scheduled to monitor temperature periodically
|
||||
* @work_active: True if work is active
|
||||
* @lock: mutex
|
||||
* @monitored_sensors: number of monitored sensors
|
||||
* @plat_data: private usage, usually points to platform specific data
|
||||
*/
|
||||
struct abx500_temp {
|
||||
struct platform_device *pdev;
|
||||
struct device *hwmon_dev;
|
||||
struct abx500_temp_ops ops;
|
||||
u8 gpadc_addr[NUM_SENSORS];
|
||||
unsigned long min[NUM_SENSORS];
|
||||
unsigned long max[NUM_SENSORS];
|
||||
unsigned long max_hyst[NUM_SENSORS];
|
||||
bool min_alarm[NUM_SENSORS];
|
||||
bool max_alarm[NUM_SENSORS];
|
||||
struct delayed_work work;
|
||||
bool work_active;
|
||||
struct mutex lock;
|
||||
int monitored_sensors;
|
||||
void *plat_data;
|
||||
};
|
||||
|
||||
int abx500_hwmon_init(struct abx500_temp *data);
|
||||
|
||||
#endif /* _ABX500_H */
|
348
drivers/hwmon/aht10.c
Normal file
348
drivers/hwmon/aht10.c
Normal file
@@ -0,0 +1,348 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* aht10.c - Linux hwmon driver for AHT10 Temperature and Humidity sensor
|
||||
* Copyright (C) 2020 Johannes Cornelis Draaijer
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define AHT10_MEAS_SIZE 6
|
||||
|
||||
/*
|
||||
* Poll intervals (in milliseconds)
|
||||
*/
|
||||
#define AHT10_DEFAULT_MIN_POLL_INTERVAL 2000
|
||||
#define AHT10_MIN_POLL_INTERVAL 2000
|
||||
|
||||
/*
|
||||
* I2C command delays (in microseconds)
|
||||
*/
|
||||
#define AHT10_MEAS_DELAY 80000
|
||||
#define AHT10_CMD_DELAY 350000
|
||||
#define AHT10_DELAY_EXTRA 100000
|
||||
|
||||
/*
|
||||
* Command bytes
|
||||
*/
|
||||
#define AHT10_CMD_INIT 0b11100001
|
||||
#define AHT10_CMD_MEAS 0b10101100
|
||||
#define AHT10_CMD_RST 0b10111010
|
||||
|
||||
/*
|
||||
* Flags in the answer byte/command
|
||||
*/
|
||||
#define AHT10_CAL_ENABLED BIT(3)
|
||||
#define AHT10_BUSY BIT(7)
|
||||
#define AHT10_MODE_NOR (BIT(5) | BIT(6))
|
||||
#define AHT10_MODE_CYC BIT(5)
|
||||
#define AHT10_MODE_CMD BIT(6)
|
||||
|
||||
#define AHT10_MAX_POLL_INTERVAL_LEN 30
|
||||
|
||||
/**
|
||||
* struct aht10_data - All the data required to operate an AHT10 chip
|
||||
* @client: the i2c client associated with the AHT10
|
||||
* @lock: a mutex that is used to prevent parallel access to the
|
||||
* i2c client
|
||||
* @min_poll_interval: the minimum poll interval
|
||||
* While the poll rate limit is not 100% necessary,
|
||||
* the datasheet recommends that a measurement
|
||||
* is not performed too often to prevent
|
||||
* the chip from warming up due to the heat it generates.
|
||||
* If it's unwanted, it can be ignored setting it to
|
||||
* it to 0. Default value is 2000 ms
|
||||
* @previous_poll_time: the previous time that the AHT10
|
||||
* was polled
|
||||
* @temperature: the latest temperature value received from
|
||||
* the AHT10
|
||||
* @humidity: the latest humidity value received from the
|
||||
* AHT10
|
||||
*/
|
||||
|
||||
struct aht10_data {
|
||||
struct i2c_client *client;
|
||||
/*
|
||||
* Prevent simultaneous access to the i2c
|
||||
* client and previous_poll_time
|
||||
*/
|
||||
struct mutex lock;
|
||||
ktime_t min_poll_interval;
|
||||
ktime_t previous_poll_time;
|
||||
int temperature;
|
||||
int humidity;
|
||||
};
|
||||
|
||||
/**
|
||||
* aht10_init() - Initialize an AHT10 chip
|
||||
* @client: the i2c client associated with the AHT10
|
||||
* @data: the data associated with this AHT10 chip
|
||||
* Return: 0 if succesfull, 1 if not
|
||||
*/
|
||||
static int aht10_init(struct aht10_data *data)
|
||||
{
|
||||
const u8 cmd_init[] = {AHT10_CMD_INIT, AHT10_CAL_ENABLED | AHT10_MODE_CYC,
|
||||
0x00};
|
||||
int res;
|
||||
u8 status;
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
res = i2c_master_send(client, cmd_init, 3);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
usleep_range(AHT10_CMD_DELAY, AHT10_CMD_DELAY +
|
||||
AHT10_DELAY_EXTRA);
|
||||
|
||||
res = i2c_master_recv(client, &status, 1);
|
||||
if (res != 1)
|
||||
return -ENODATA;
|
||||
|
||||
if (status & AHT10_BUSY)
|
||||
return -EBUSY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_polltime_expired() - check if the minimum poll interval has
|
||||
* expired
|
||||
* @data: the data containing the time to compare
|
||||
* Return: 1 if the minimum poll interval has expired, 0 if not
|
||||
*/
|
||||
static int aht10_polltime_expired(struct aht10_data *data)
|
||||
{
|
||||
ktime_t current_time = ktime_get_boottime();
|
||||
ktime_t difference = ktime_sub(current_time, data->previous_poll_time);
|
||||
|
||||
return ktime_after(difference, data->min_poll_interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_read_values() - read and parse the raw data from the AHT10
|
||||
* @aht10_data: the struct aht10_data to use for the lock
|
||||
* Return: 0 if succesfull, 1 if not
|
||||
*/
|
||||
static int aht10_read_values(struct aht10_data *data)
|
||||
{
|
||||
const u8 cmd_meas[] = {AHT10_CMD_MEAS, 0x33, 0x00};
|
||||
u32 temp, hum;
|
||||
int res;
|
||||
u8 raw_data[AHT10_MEAS_SIZE];
|
||||
struct i2c_client *client = data->client;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
if (aht10_polltime_expired(data)) {
|
||||
res = i2c_master_send(client, cmd_meas, sizeof(cmd_meas));
|
||||
if (res < 0) {
|
||||
mutex_unlock(&data->lock);
|
||||
return res;
|
||||
}
|
||||
|
||||
usleep_range(AHT10_MEAS_DELAY,
|
||||
AHT10_MEAS_DELAY + AHT10_DELAY_EXTRA);
|
||||
|
||||
res = i2c_master_recv(client, raw_data, AHT10_MEAS_SIZE);
|
||||
if (res != AHT10_MEAS_SIZE) {
|
||||
mutex_unlock(&data->lock);
|
||||
if (res >= 0)
|
||||
return -ENODATA;
|
||||
else
|
||||
return res;
|
||||
}
|
||||
|
||||
hum = ((u32)raw_data[1] << 12u) |
|
||||
((u32)raw_data[2] << 4u) |
|
||||
((raw_data[3] & 0xF0u) >> 4u);
|
||||
|
||||
temp = ((u32)(raw_data[3] & 0x0Fu) << 16u) |
|
||||
((u32)raw_data[4] << 8u) |
|
||||
raw_data[5];
|
||||
|
||||
temp = ((temp * 625) >> 15u) * 10;
|
||||
hum = ((hum * 625) >> 16u) * 10;
|
||||
|
||||
data->temperature = (int)temp - 50000;
|
||||
data->humidity = hum;
|
||||
data->previous_poll_time = ktime_get_boottime();
|
||||
}
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_interval_write() - store the given minimum poll interval.
|
||||
* Return: 0 on success, -EINVAL if a value lower than the
|
||||
* AHT10_MIN_POLL_INTERVAL is given
|
||||
*/
|
||||
static ssize_t aht10_interval_write(struct aht10_data *data,
|
||||
long val)
|
||||
{
|
||||
data->min_poll_interval = ms_to_ktime(clamp_val(val, 2000, LONG_MAX));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_interval_read() - read the minimum poll interval
|
||||
* in milliseconds
|
||||
*/
|
||||
static ssize_t aht10_interval_read(struct aht10_data *data,
|
||||
long *val)
|
||||
{
|
||||
*val = ktime_to_ms(data->min_poll_interval);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_temperature1_read() - read the temperature in millidegrees
|
||||
*/
|
||||
static int aht10_temperature1_read(struct aht10_data *data, long *val)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = aht10_read_values(data);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
*val = data->temperature;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aht10_humidity1_read() - read the relative humidity in millipercent
|
||||
*/
|
||||
static int aht10_humidity1_read(struct aht10_data *data, long *val)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = aht10_read_values(data);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
*val = data->humidity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static umode_t aht10_hwmon_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
case hwmon_humidity:
|
||||
return 0444;
|
||||
case hwmon_chip:
|
||||
return 0644;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int aht10_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct aht10_data *data = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
return aht10_temperature1_read(data, val);
|
||||
case hwmon_humidity:
|
||||
return aht10_humidity1_read(data, val);
|
||||
case hwmon_chip:
|
||||
return aht10_interval_read(data, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int aht10_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct aht10_data *data = dev_get_drvdata(dev);
|
||||
|
||||
switch (type) {
|
||||
case hwmon_chip:
|
||||
return aht10_interval_write(data, val);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *aht10_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
|
||||
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
|
||||
HWMON_CHANNEL_INFO(humidity, HWMON_H_INPUT),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct hwmon_ops aht10_hwmon_ops = {
|
||||
.is_visible = aht10_hwmon_visible,
|
||||
.read = aht10_hwmon_read,
|
||||
.write = aht10_hwmon_write,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info aht10_chip_info = {
|
||||
.ops = &aht10_hwmon_ops,
|
||||
.info = aht10_info,
|
||||
};
|
||||
|
||||
static int aht10_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *aht10_id)
|
||||
{
|
||||
struct device *device = &client->dev;
|
||||
struct device *hwmon_dev;
|
||||
struct aht10_data *data;
|
||||
int res;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
|
||||
return -ENOENT;
|
||||
|
||||
data = devm_kzalloc(device, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->min_poll_interval = ms_to_ktime(AHT10_DEFAULT_MIN_POLL_INTERVAL);
|
||||
data->client = client;
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
res = aht10_init(data);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
res = aht10_read_values(data);
|
||||
if (res < 0)
|
||||
return res;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(device,
|
||||
client->name,
|
||||
data,
|
||||
&aht10_chip_info,
|
||||
NULL);
|
||||
|
||||
return PTR_ERR_OR_ZERO(hwmon_dev);
|
||||
}
|
||||
|
||||
static const struct i2c_device_id aht10_id[] = {
|
||||
{ "aht10", 0 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, aht10_id);
|
||||
|
||||
static struct i2c_driver aht10_driver = {
|
||||
.driver = {
|
||||
.name = "aht10",
|
||||
},
|
||||
.probe = aht10_probe,
|
||||
.id_table = aht10_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(aht10_driver);
|
||||
|
||||
MODULE_AUTHOR("Johannes Cornelis Draaijer <jcdra1@gmail.com>");
|
||||
MODULE_DESCRIPTION("AHT10 Temperature and Humidity sensor driver");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_LICENSE("GPL v2");
|
@@ -333,6 +333,7 @@ static struct platform_device *amd_energy_platdev;
|
||||
static const struct x86_cpu_id cpu_ids[] __initconst = {
|
||||
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x17, 0x31, NULL),
|
||||
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x01, NULL),
|
||||
X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x19, 0x30, NULL),
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(x86cpu, cpu_ids);
|
||||
|
@@ -565,7 +565,7 @@ static int applesmc_init_index(struct applesmc_registers *s)
|
||||
static int applesmc_init_smcreg_try(void)
|
||||
{
|
||||
struct applesmc_registers *s = &smcreg;
|
||||
bool left_light_sensor = 0, right_light_sensor = 0;
|
||||
bool left_light_sensor = false, right_light_sensor = false;
|
||||
unsigned int count;
|
||||
u8 tmp[1];
|
||||
int ret;
|
||||
|
@@ -620,7 +620,7 @@ static ssize_t rpm_show(struct device *dev, struct device_attribute *attr,
|
||||
static umode_t pwm_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int index)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (!priv->pwm_present[index])
|
||||
@@ -631,7 +631,7 @@ static umode_t pwm_is_visible(struct kobject *kobj,
|
||||
static umode_t fan_dev_is_visible(struct kobject *kobj,
|
||||
struct attribute *a, int index)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct aspeed_pwm_tacho_data *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (!priv->fan_tach_present[index])
|
||||
|
@@ -299,7 +299,7 @@ static ssize_t label_show(struct device *dev,
|
||||
static umode_t da9052_channel_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int index)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct da9052_hwmon *hwmon = dev_get_drvdata(dev);
|
||||
struct device_attribute *dattr = container_of(attr,
|
||||
struct device_attribute, attr);
|
||||
|
@@ -1159,6 +1159,13 @@ static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Dell XPS 15 L502X",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
|
||||
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
|
||||
},
|
||||
},
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@@ -299,7 +299,7 @@ static DEVICE_ATTR(fan1_target, 0644, fan1_input_show, set_rpm);
|
||||
static umode_t gpio_fan_is_visible(struct kobject *kobj,
|
||||
struct attribute *attr, int index)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct gpio_fan_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (index == 0 && !data->alarm_gpio)
|
||||
|
@@ -448,7 +448,8 @@ static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
||||
data->is_zen = true;
|
||||
|
||||
switch (boot_cpu_data.x86_model) {
|
||||
case 0x0 ... 0x1: /* Zen3 */
|
||||
case 0x0 ... 0x1: /* Zen3 SP3/TR */
|
||||
case 0x21: /* Zen3 Ryzen Desktop */
|
||||
k10temp_get_ccd_support(pdev, data, 8);
|
||||
break;
|
||||
}
|
||||
|
@@ -321,7 +321,7 @@ static SENSOR_DEVICE_ATTR_RO(gpio2_alarm, alarm, MAX6650_ALRM_GPIO2);
|
||||
static umode_t max6650_attrs_visible(struct kobject *kobj, struct attribute *a,
|
||||
int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct max6650_data *data = dev_get_drvdata(dev);
|
||||
struct device_attribute *devattr;
|
||||
|
||||
|
@@ -169,6 +169,7 @@ superio_exit(int ioreg)
|
||||
#define NCT6683_CUSTOMER_ID_INTEL 0x805
|
||||
#define NCT6683_CUSTOMER_ID_MITAC 0xa0e
|
||||
#define NCT6683_CUSTOMER_ID_MSI 0x201
|
||||
#define NCT6683_CUSTOMER_ID_ASROCK 0xe2c
|
||||
|
||||
#define NCT6683_REG_BUILD_YEAR 0x604
|
||||
#define NCT6683_REG_BUILD_MONTH 0x605
|
||||
@@ -1225,6 +1226,8 @@ static int nct6683_probe(struct platform_device *pdev)
|
||||
break;
|
||||
case NCT6683_CUSTOMER_ID_MSI:
|
||||
break;
|
||||
case NCT6683_CUSTOMER_ID_ASROCK:
|
||||
break;
|
||||
default:
|
||||
if (!force)
|
||||
return -ENODEV;
|
||||
|
@@ -1700,8 +1700,8 @@ static int __init pc87360_device_add(unsigned short address)
|
||||
continue;
|
||||
res[res_count].start = extra_isa[i];
|
||||
res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1;
|
||||
res[res_count].name = "pc87360",
|
||||
res[res_count].flags = IORESOURCE_IO,
|
||||
res[res_count].name = "pc87360";
|
||||
res[res_count].flags = IORESOURCE_IO;
|
||||
|
||||
err = acpi_check_resource_conflict(&res[res_count]);
|
||||
if (err)
|
||||
|
@@ -158,10 +158,10 @@ config SENSORS_MAX16064
|
||||
be called max16064.
|
||||
|
||||
config SENSORS_MAX16601
|
||||
tristate "Maxim MAX16601"
|
||||
tristate "Maxim MAX16508, MAX16601"
|
||||
help
|
||||
If you say yes here you get hardware monitoring support for Maxim
|
||||
MAX16601.
|
||||
MAX16508 and MAX16601.
|
||||
|
||||
This driver can also be built as a module. If so, the module will
|
||||
be called max16601.
|
||||
|
@@ -472,7 +472,7 @@ static struct pmbus_driver_info ibm_cffps_info[] = {
|
||||
};
|
||||
|
||||
static struct pmbus_platform_data ibm_cffps_pdata = {
|
||||
.flags = PMBUS_SKIP_STATUS_CHECK,
|
||||
.flags = PMBUS_SKIP_STATUS_CHECK | PMBUS_NO_CAPABILITY,
|
||||
};
|
||||
|
||||
static int ibm_cffps_probe(struct i2c_client *client)
|
||||
|
@@ -371,21 +371,18 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
case PMBUS_VIN_OV_WARN_LIMIT:
|
||||
word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit);
|
||||
ret = pmbus_write_word_data(client, 0, reg, word);
|
||||
pmbus_clear_cache(client);
|
||||
break;
|
||||
case PMBUS_IIN_OC_WARN_LIMIT:
|
||||
word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit);
|
||||
ret = pmbus_write_word_data(client, 0,
|
||||
LM25066_MFR_IIN_OC_WARN_LIMIT,
|
||||
word);
|
||||
pmbus_clear_cache(client);
|
||||
break;
|
||||
case PMBUS_PIN_OP_WARN_LIMIT:
|
||||
word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit);
|
||||
ret = pmbus_write_word_data(client, 0,
|
||||
LM25066_MFR_PIN_OP_WARN_LIMIT,
|
||||
word);
|
||||
pmbus_clear_cache(client);
|
||||
break;
|
||||
case PMBUS_VIRT_VMON_UV_WARN_LIMIT:
|
||||
/* Adjust from VIN coefficients (for LM25056) */
|
||||
@@ -393,7 +390,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit);
|
||||
ret = pmbus_write_word_data(client, 0,
|
||||
LM25056_VAUX_UV_WARN_LIMIT, word);
|
||||
pmbus_clear_cache(client);
|
||||
break;
|
||||
case PMBUS_VIRT_VMON_OV_WARN_LIMIT:
|
||||
/* Adjust from VIN coefficients (for LM25056) */
|
||||
@@ -401,7 +397,6 @@ static int lm25066_write_word_data(struct i2c_client *client, int page, int reg,
|
||||
word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit);
|
||||
ret = pmbus_write_word_data(client, 0,
|
||||
LM25056_VAUX_OV_WARN_LIMIT, word);
|
||||
pmbus_clear_cache(client);
|
||||
break;
|
||||
case PMBUS_VIRT_RESET_PIN_HISTORY:
|
||||
ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK);
|
||||
|
@@ -1,11 +1,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Hardware monitoring driver for Maxim MAX16601
|
||||
* Hardware monitoring driver for Maxim MAX16508 and 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
|
||||
* This chip series 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.
|
||||
@@ -31,6 +31,9 @@
|
||||
|
||||
#include "pmbus.h"
|
||||
|
||||
enum chips { max16508, max16601 };
|
||||
|
||||
#define REG_DEFAULT_NUM_POP 0xc4
|
||||
#define REG_SETPT_DVID 0xd1
|
||||
#define DAC_10MV_MODE BIT(4)
|
||||
#define REG_IOUT_AVG_PK 0xee
|
||||
@@ -40,7 +43,10 @@
|
||||
#define CORE_RAIL_INDICATOR BIT(7)
|
||||
#define REG_PHASE_REPORTING 0xf4
|
||||
|
||||
#define MAX16601_NUM_PHASES 8
|
||||
|
||||
struct max16601_data {
|
||||
enum chips id;
|
||||
struct pmbus_driver_info info;
|
||||
struct i2c_client *vsa;
|
||||
int iout_avg_pkg;
|
||||
@@ -185,6 +191,7 @@ static int max16601_write_word(struct i2c_client *client, int page, int reg,
|
||||
static int max16601_identify(struct i2c_client *client,
|
||||
struct pmbus_driver_info *info)
|
||||
{
|
||||
struct max16601_data *data = to_max16601_data(info);
|
||||
int reg;
|
||||
|
||||
reg = i2c_smbus_read_byte_data(client, REG_SETPT_DVID);
|
||||
@@ -195,6 +202,21 @@ static int max16601_identify(struct i2c_client *client,
|
||||
else
|
||||
info->vrm_version[0] = vr12;
|
||||
|
||||
if (data->id != max16601)
|
||||
return 0;
|
||||
|
||||
reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP);
|
||||
if (reg < 0)
|
||||
return reg;
|
||||
|
||||
/*
|
||||
* If REG_DEFAULT_NUM_POP returns 0, we don't know how many phases
|
||||
* are populated. Stick with the default in that case.
|
||||
*/
|
||||
reg &= 0x0f;
|
||||
if (reg && reg <= MAX16601_NUM_PHASES)
|
||||
info->phases[0] = reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -216,7 +238,7 @@ static struct pmbus_driver_info max16601_info = {
|
||||
.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,
|
||||
.phases[0] = MAX16601_NUM_PHASES,
|
||||
.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,
|
||||
@@ -239,28 +261,61 @@ static void max16601_remove(void *_data)
|
||||
i2c_unregister_device(data->vsa);
|
||||
}
|
||||
|
||||
static int max16601_probe(struct i2c_client *client)
|
||||
static const struct i2c_device_id max16601_id[] = {
|
||||
{"max16508", max16508},
|
||||
{"max16601", max16601},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max16601_id);
|
||||
|
||||
static int max16601_get_id(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
|
||||
struct max16601_data *data;
|
||||
enum chips id;
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
|
||||
if (ret < 0 || ret < 11)
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
* PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx"
|
||||
* or "MAX16500y.xx".
|
||||
*/
|
||||
if (!strncmp(buf, "MAX16500", 8)) {
|
||||
id = max16508;
|
||||
} else if (!strncmp(buf, "MAX16601", 8)) {
|
||||
id = max16601;
|
||||
} else {
|
||||
buf[ret] = '\0';
|
||||
dev_err(dev, "Unsupported chip '%s'\n", buf);
|
||||
return -ENODEV;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
static int max16601_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
const struct i2c_device_id *id;
|
||||
struct max16601_data *data;
|
||||
int ret, chip_id;
|
||||
|
||||
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;
|
||||
chip_id = max16601_get_id(client);
|
||||
if (chip_id < 0)
|
||||
return chip_id;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
id = i2c_match_id(max16601_id, client);
|
||||
if (chip_id != id->driver_data)
|
||||
dev_warn(&client->dev,
|
||||
"Device mismatch: Configured %s (%d), detected %d\n",
|
||||
id->name, (int) id->driver_data, chip_id);
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, REG_PHASE_ID);
|
||||
if (ret < 0)
|
||||
@@ -275,6 +330,7 @@ static int max16601_probe(struct i2c_client *client)
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->id = chip_id;
|
||||
data->iout_avg_pkg = 0xfc00;
|
||||
data->vsa = i2c_new_dummy_device(client->adapter, client->addr + 1);
|
||||
if (IS_ERR(data->vsa)) {
|
||||
@@ -290,13 +346,6 @@ static int max16601_probe(struct i2c_client *client)
|
||||
return pmbus_do_probe(client, &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",
|
||||
|
@@ -17,6 +17,7 @@ enum max31785_regs {
|
||||
|
||||
#define MAX31785 0x3030
|
||||
#define MAX31785A 0x3040
|
||||
#define MAX31785B 0x3061
|
||||
|
||||
#define MFR_FAN_CONFIG_DUAL_TACH BIT(12)
|
||||
|
||||
@@ -329,7 +330,7 @@ static int max31785_probe(struct i2c_client *client)
|
||||
struct device *dev = &client->dev;
|
||||
struct pmbus_driver_info *info;
|
||||
bool dual_tach = false;
|
||||
s64 ret;
|
||||
int ret;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA |
|
||||
@@ -350,12 +351,14 @@ static int max31785_probe(struct i2c_client *client)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret == MAX31785A) {
|
||||
if (ret == MAX31785A || ret == MAX31785B) {
|
||||
dual_tach = true;
|
||||
} else if (ret == MAX31785) {
|
||||
if (!strcmp("max31785a", client->name))
|
||||
dev_warn(dev, "Expected max3175a, found max31785: cannot provide secondary tachometer readings\n");
|
||||
if (!strcmp("max31785a", client->name) ||
|
||||
!strcmp("max31785b", client->name))
|
||||
dev_warn(dev, "Expected max31785a/b, found max31785: cannot provide secondary tachometer readings\n");
|
||||
} else {
|
||||
dev_err(dev, "Unrecognized MAX31785 revision: %x\n", ret);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
@@ -371,6 +374,7 @@ static int max31785_probe(struct i2c_client *client)
|
||||
static const struct i2c_device_id max31785_id[] = {
|
||||
{ "max31785", 0 },
|
||||
{ "max31785a", 0 },
|
||||
{ "max31785b", 0 },
|
||||
{ },
|
||||
};
|
||||
|
||||
@@ -379,6 +383,7 @@ MODULE_DEVICE_TABLE(i2c, max31785_id);
|
||||
static const struct of_device_id max31785_of_match[] = {
|
||||
{ .compatible = "maxim,max31785" },
|
||||
{ .compatible = "maxim,max31785a" },
|
||||
{ .compatible = "maxim,max31785b" },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
@@ -974,7 +974,7 @@ static ssize_t pmbus_set_sensor(struct device *dev,
|
||||
if (ret < 0)
|
||||
rv = ret;
|
||||
else
|
||||
sensor->data = regval;
|
||||
sensor->data = -ENODATA;
|
||||
mutex_unlock(&data->update_lock);
|
||||
return rv;
|
||||
}
|
||||
@@ -1262,7 +1262,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client,
|
||||
* which global bit is set) for this page is accessible.
|
||||
*/
|
||||
if (!ret && attr->gbit &&
|
||||
(!upper || (upper && data->has_status_word)) &&
|
||||
(!upper || data->has_status_word) &&
|
||||
pmbus_check_status_register(client, page)) {
|
||||
ret = pmbus_add_boolean(data, name, "alarm", index,
|
||||
NULL, NULL,
|
||||
@@ -2204,9 +2204,11 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data,
|
||||
}
|
||||
|
||||
/* Enable PEC if the controller supports it */
|
||||
if (!(data->flags & PMBUS_NO_CAPABILITY)) {
|
||||
ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY);
|
||||
if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK))
|
||||
client->flags |= I2C_CLIENT_PEC;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if the chip is write protected. If it is, we can not clear
|
||||
|
@@ -21,15 +21,21 @@
|
||||
|
||||
#define MAX_PWM 255
|
||||
|
||||
struct pwm_fan_ctx {
|
||||
struct mutex lock;
|
||||
struct pwm_device *pwm;
|
||||
struct regulator *reg_en;
|
||||
|
||||
struct pwm_fan_tach {
|
||||
int irq;
|
||||
atomic_t pulses;
|
||||
unsigned int rpm;
|
||||
u8 pulses_per_revolution;
|
||||
};
|
||||
|
||||
struct pwm_fan_ctx {
|
||||
struct mutex lock;
|
||||
struct pwm_device *pwm;
|
||||
struct pwm_state pwm_state;
|
||||
struct regulator *reg_en;
|
||||
|
||||
int tach_count;
|
||||
struct pwm_fan_tach *tachs;
|
||||
ktime_t sample_start;
|
||||
struct timer_list rpm_timer;
|
||||
|
||||
@@ -40,6 +46,7 @@ struct pwm_fan_ctx {
|
||||
struct thermal_cooling_device *cdev;
|
||||
|
||||
struct hwmon_chip_info info;
|
||||
struct hwmon_channel_info fan_channel;
|
||||
};
|
||||
|
||||
static const u32 pwm_fan_channel_config_pwm[] = {
|
||||
@@ -52,22 +59,12 @@ static const struct hwmon_channel_info pwm_fan_channel_pwm = {
|
||||
.config = pwm_fan_channel_config_pwm,
|
||||
};
|
||||
|
||||
static const u32 pwm_fan_channel_config_fan[] = {
|
||||
HWMON_F_INPUT,
|
||||
0
|
||||
};
|
||||
|
||||
static const struct hwmon_channel_info pwm_fan_channel_fan = {
|
||||
.type = hwmon_fan,
|
||||
.config = pwm_fan_channel_config_fan,
|
||||
};
|
||||
|
||||
/* This handler assumes self resetting edge triggered interrupt. */
|
||||
static irqreturn_t pulse_handler(int irq, void *dev_id)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_id;
|
||||
struct pwm_fan_tach *tach = dev_id;
|
||||
|
||||
atomic_inc(&ctx->pulses);
|
||||
atomic_inc(&tach->pulses);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@@ -76,13 +73,18 @@ static void sample_timer(struct timer_list *t)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer);
|
||||
unsigned int delta = ktime_ms_delta(ktime_get(), ctx->sample_start);
|
||||
int pulses;
|
||||
int i;
|
||||
|
||||
if (delta) {
|
||||
pulses = atomic_read(&ctx->pulses);
|
||||
atomic_sub(pulses, &ctx->pulses);
|
||||
ctx->rpm = (unsigned int)(pulses * 1000 * 60) /
|
||||
(ctx->pulses_per_revolution * delta);
|
||||
for (i = 0; i < ctx->tach_count; i++) {
|
||||
struct pwm_fan_tach *tach = &ctx->tachs[i];
|
||||
int pulses;
|
||||
|
||||
pulses = atomic_read(&tach->pulses);
|
||||
atomic_sub(pulses, &tach->pulses);
|
||||
tach->rpm = (unsigned int)(pulses * 1000 * 60) /
|
||||
(tach->pulses_per_revolution * delta);
|
||||
}
|
||||
|
||||
ctx->sample_start = ktime_get();
|
||||
}
|
||||
@@ -94,18 +96,17 @@ static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
|
||||
{
|
||||
unsigned long period;
|
||||
int ret = 0;
|
||||
struct pwm_state state = { };
|
||||
struct pwm_state *state = &ctx->pwm_state;
|
||||
|
||||
mutex_lock(&ctx->lock);
|
||||
if (ctx->pwm_value == pwm)
|
||||
goto exit_set_pwm_err;
|
||||
|
||||
pwm_init_state(ctx->pwm, &state);
|
||||
period = ctx->pwm->args.period;
|
||||
state.duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
|
||||
state.enabled = pwm ? true : false;
|
||||
period = state->period;
|
||||
state->duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM);
|
||||
state->enabled = pwm ? true : false;
|
||||
|
||||
ret = pwm_apply_state(ctx->pwm, &state);
|
||||
ret = pwm_apply_state(ctx->pwm, state);
|
||||
if (!ret)
|
||||
ctx->pwm_value = pwm;
|
||||
exit_set_pwm_err:
|
||||
@@ -152,7 +153,7 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
return 0;
|
||||
|
||||
case hwmon_fan:
|
||||
*val = ctx->rpm;
|
||||
*val = ctx->tachs[channel].rpm;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
@@ -287,7 +288,9 @@ static void pwm_fan_regulator_disable(void *data)
|
||||
static void pwm_fan_pwm_disable(void *__ctx)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = __ctx;
|
||||
pwm_disable(ctx->pwm);
|
||||
|
||||
ctx->pwm_state.enabled = false;
|
||||
pwm_apply_state(ctx->pwm, &ctx->pwm_state);
|
||||
del_timer_sync(&ctx->rpm_timer);
|
||||
}
|
||||
|
||||
@@ -298,9 +301,10 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
struct pwm_fan_ctx *ctx;
|
||||
struct device *hwmon;
|
||||
int ret;
|
||||
struct pwm_state state = { };
|
||||
int tach_count;
|
||||
const struct hwmon_channel_info **channels;
|
||||
u32 *fan_channel_config;
|
||||
int channel_count = 1; /* We always have a PWM channel. */
|
||||
int i;
|
||||
|
||||
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
@@ -334,22 +338,20 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
|
||||
ctx->pwm_value = MAX_PWM;
|
||||
|
||||
pwm_init_state(ctx->pwm, &state);
|
||||
pwm_init_state(ctx->pwm, &ctx->pwm_state);
|
||||
|
||||
/*
|
||||
* __set_pwm assumes that MAX_PWM * (period - 1) fits into an unsigned
|
||||
* long. Check this here to prevent the fan running at a too low
|
||||
* frequency.
|
||||
*/
|
||||
if (state.period > ULONG_MAX / MAX_PWM + 1) {
|
||||
if (ctx->pwm_state.period > ULONG_MAX / MAX_PWM + 1) {
|
||||
dev_err(dev, "Configured period too big\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set duty cycle to maximum allowed and enable PWM output */
|
||||
state.duty_cycle = ctx->pwm->args.period - 1;
|
||||
state.enabled = true;
|
||||
|
||||
ret = pwm_apply_state(ctx->pwm, &state);
|
||||
ret = __set_pwm(ctx, MAX_PWM);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to configure PWM: %d\n", ret);
|
||||
return ret;
|
||||
@@ -359,27 +361,46 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tach_count = platform_irq_count(pdev);
|
||||
if (tach_count < 0)
|
||||
return dev_err_probe(dev, tach_count,
|
||||
ctx->tach_count = platform_irq_count(pdev);
|
||||
if (ctx->tach_count < 0)
|
||||
return dev_err_probe(dev, ctx->tach_count,
|
||||
"Could not get number of fan tachometer inputs\n");
|
||||
dev_dbg(dev, "%d fan tachometer inputs\n", ctx->tach_count);
|
||||
|
||||
channels = devm_kcalloc(dev, tach_count + 2,
|
||||
if (ctx->tach_count) {
|
||||
channel_count++; /* We also have a FAN channel. */
|
||||
|
||||
ctx->tachs = devm_kcalloc(dev, ctx->tach_count,
|
||||
sizeof(struct pwm_fan_tach),
|
||||
GFP_KERNEL);
|
||||
if (!ctx->tachs)
|
||||
return -ENOMEM;
|
||||
|
||||
ctx->fan_channel.type = hwmon_fan;
|
||||
fan_channel_config = devm_kcalloc(dev, ctx->tach_count + 1,
|
||||
sizeof(u32), GFP_KERNEL);
|
||||
if (!fan_channel_config)
|
||||
return -ENOMEM;
|
||||
ctx->fan_channel.config = fan_channel_config;
|
||||
}
|
||||
|
||||
channels = devm_kcalloc(dev, channel_count + 1,
|
||||
sizeof(struct hwmon_channel_info *), GFP_KERNEL);
|
||||
if (!channels)
|
||||
return -ENOMEM;
|
||||
|
||||
channels[0] = &pwm_fan_channel_pwm;
|
||||
|
||||
if (tach_count > 0) {
|
||||
for (i = 0; i < ctx->tach_count; i++) {
|
||||
struct pwm_fan_tach *tach = &ctx->tachs[i];
|
||||
u32 ppr = 2;
|
||||
|
||||
ctx->irq = platform_get_irq(pdev, 0);
|
||||
if (ctx->irq == -EPROBE_DEFER)
|
||||
return ctx->irq;
|
||||
if (ctx->irq > 0) {
|
||||
ret = devm_request_irq(dev, ctx->irq, pulse_handler, 0,
|
||||
pdev->name, ctx);
|
||||
tach->irq = platform_get_irq(pdev, i);
|
||||
if (tach->irq == -EPROBE_DEFER)
|
||||
return tach->irq;
|
||||
if (tach->irq > 0) {
|
||||
ret = devm_request_irq(dev, tach->irq, pulse_handler, 0,
|
||||
pdev->name, tach);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to request interrupt: %d\n",
|
||||
@@ -388,22 +409,27 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
}
|
||||
}
|
||||
|
||||
of_property_read_u32(dev->of_node,
|
||||
of_property_read_u32_index(dev->of_node,
|
||||
"pulses-per-revolution",
|
||||
i,
|
||||
&ppr);
|
||||
ctx->pulses_per_revolution = ppr;
|
||||
if (!ctx->pulses_per_revolution) {
|
||||
tach->pulses_per_revolution = ppr;
|
||||
if (!tach->pulses_per_revolution) {
|
||||
dev_err(dev, "pulses-per-revolution can't be zero.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(dev, "tach: irq=%d, pulses_per_revolution=%d\n",
|
||||
ctx->irq, ctx->pulses_per_revolution);
|
||||
fan_channel_config[i] = HWMON_F_INPUT;
|
||||
|
||||
dev_dbg(dev, "tach%d: irq=%d, pulses_per_revolution=%d\n",
|
||||
i, tach->irq, tach->pulses_per_revolution);
|
||||
}
|
||||
|
||||
if (ctx->tach_count > 0) {
|
||||
ctx->sample_start = ktime_get();
|
||||
mod_timer(&ctx->rpm_timer, jiffies + HZ);
|
||||
|
||||
channels[1] = &pwm_fan_channel_fan;
|
||||
channels[1] = &ctx->fan_channel;
|
||||
}
|
||||
|
||||
ctx->info.ops = &pwm_fan_hwmon_ops;
|
||||
@@ -441,17 +467,17 @@ static int pwm_fan_probe(struct platform_device *pdev)
|
||||
static int pwm_fan_disable(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
struct pwm_args args;
|
||||
int ret;
|
||||
|
||||
pwm_get_args(ctx->pwm, &args);
|
||||
|
||||
if (ctx->pwm_value) {
|
||||
ret = pwm_config(ctx->pwm, 0, args.period);
|
||||
/* keep ctx->pwm_state unmodified for pwm_fan_resume() */
|
||||
struct pwm_state state = ctx->pwm_state;
|
||||
|
||||
state.duty_cycle = 0;
|
||||
state.enabled = false;
|
||||
ret = pwm_apply_state(ctx->pwm, &state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pwm_disable(ctx->pwm);
|
||||
}
|
||||
|
||||
if (ctx->reg_en) {
|
||||
@@ -479,8 +505,6 @@ static int pwm_fan_suspend(struct device *dev)
|
||||
static int pwm_fan_resume(struct device *dev)
|
||||
{
|
||||
struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
|
||||
struct pwm_args pargs;
|
||||
unsigned long duty;
|
||||
int ret;
|
||||
|
||||
if (ctx->reg_en) {
|
||||
@@ -494,12 +518,7 @@ static int pwm_fan_resume(struct device *dev)
|
||||
if (ctx->pwm_value == 0)
|
||||
return 0;
|
||||
|
||||
pwm_get_args(ctx->pwm, &pargs);
|
||||
duty = DIV_ROUND_UP_ULL(ctx->pwm_value * (pargs.period - 1), MAX_PWM);
|
||||
ret = pwm_config(ctx->pwm, duty, pargs.period);
|
||||
if (ret)
|
||||
return ret;
|
||||
return pwm_enable(ctx->pwm);
|
||||
return pwm_apply_state(ctx->pwm, &ctx->pwm_state);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@@ -682,7 +682,7 @@ static int __init smsc47m1_handle_resources(unsigned short address,
|
||||
/* Request the resources */
|
||||
if (!devm_request_region(dev, start, len, DRVNAME)) {
|
||||
dev_err(dev,
|
||||
"Region 0x%hx-0x%hx already in use!\n",
|
||||
"Region 0x%x-0x%x already in use!\n",
|
||||
start, start + len);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
601
drivers/hwmon/tps23861.c
Normal file
601
drivers/hwmon/tps23861.c
Normal file
@@ -0,0 +1,601 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2020 Sartura Ltd.
|
||||
*
|
||||
* Driver for the TI TPS23861 PoE PSE.
|
||||
*
|
||||
* Author: Robert Marko <robert.marko@sartura.hr>
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define TEMPERATURE 0x2c
|
||||
#define INPUT_VOLTAGE_LSB 0x2e
|
||||
#define INPUT_VOLTAGE_MSB 0x2f
|
||||
#define PORT_1_CURRENT_LSB 0x30
|
||||
#define PORT_1_CURRENT_MSB 0x31
|
||||
#define PORT_1_VOLTAGE_LSB 0x32
|
||||
#define PORT_1_VOLTAGE_MSB 0x33
|
||||
#define PORT_2_CURRENT_LSB 0x34
|
||||
#define PORT_2_CURRENT_MSB 0x35
|
||||
#define PORT_2_VOLTAGE_LSB 0x36
|
||||
#define PORT_2_VOLTAGE_MSB 0x37
|
||||
#define PORT_3_CURRENT_LSB 0x38
|
||||
#define PORT_3_CURRENT_MSB 0x39
|
||||
#define PORT_3_VOLTAGE_LSB 0x3a
|
||||
#define PORT_3_VOLTAGE_MSB 0x3b
|
||||
#define PORT_4_CURRENT_LSB 0x3c
|
||||
#define PORT_4_CURRENT_MSB 0x3d
|
||||
#define PORT_4_VOLTAGE_LSB 0x3e
|
||||
#define PORT_4_VOLTAGE_MSB 0x3f
|
||||
#define PORT_N_CURRENT_LSB_OFFSET 0x04
|
||||
#define PORT_N_VOLTAGE_LSB_OFFSET 0x04
|
||||
#define VOLTAGE_CURRENT_MASK GENMASK(13, 0)
|
||||
#define PORT_1_RESISTANCE_LSB 0x60
|
||||
#define PORT_1_RESISTANCE_MSB 0x61
|
||||
#define PORT_2_RESISTANCE_LSB 0x62
|
||||
#define PORT_2_RESISTANCE_MSB 0x63
|
||||
#define PORT_3_RESISTANCE_LSB 0x64
|
||||
#define PORT_3_RESISTANCE_MSB 0x65
|
||||
#define PORT_4_RESISTANCE_LSB 0x66
|
||||
#define PORT_4_RESISTANCE_MSB 0x67
|
||||
#define PORT_N_RESISTANCE_LSB_OFFSET 0x02
|
||||
#define PORT_RESISTANCE_MASK GENMASK(13, 0)
|
||||
#define PORT_RESISTANCE_RSN_MASK GENMASK(15, 14)
|
||||
#define PORT_RESISTANCE_RSN_OTHER 0
|
||||
#define PORT_RESISTANCE_RSN_LOW 1
|
||||
#define PORT_RESISTANCE_RSN_OPEN 2
|
||||
#define PORT_RESISTANCE_RSN_SHORT 3
|
||||
#define PORT_1_STATUS 0x0c
|
||||
#define PORT_2_STATUS 0x0d
|
||||
#define PORT_3_STATUS 0x0e
|
||||
#define PORT_4_STATUS 0x0f
|
||||
#define PORT_STATUS_CLASS_MASK GENMASK(7, 4)
|
||||
#define PORT_STATUS_DETECT_MASK GENMASK(3, 0)
|
||||
#define PORT_CLASS_UNKNOWN 0
|
||||
#define PORT_CLASS_1 1
|
||||
#define PORT_CLASS_2 2
|
||||
#define PORT_CLASS_3 3
|
||||
#define PORT_CLASS_4 4
|
||||
#define PORT_CLASS_RESERVED 5
|
||||
#define PORT_CLASS_0 6
|
||||
#define PORT_CLASS_OVERCURRENT 7
|
||||
#define PORT_CLASS_MISMATCH 8
|
||||
#define PORT_DETECT_UNKNOWN 0
|
||||
#define PORT_DETECT_SHORT 1
|
||||
#define PORT_DETECT_RESERVED 2
|
||||
#define PORT_DETECT_RESISTANCE_LOW 3
|
||||
#define PORT_DETECT_RESISTANCE_OK 4
|
||||
#define PORT_DETECT_RESISTANCE_HIGH 5
|
||||
#define PORT_DETECT_OPEN_CIRCUIT 6
|
||||
#define PORT_DETECT_RESERVED_2 7
|
||||
#define PORT_DETECT_MOSFET_FAULT 8
|
||||
#define PORT_DETECT_LEGACY 9
|
||||
/* Measurment beyond clamp voltage */
|
||||
#define PORT_DETECT_CAPACITANCE_INVALID_BEYOND 10
|
||||
/* Insufficient voltage delta */
|
||||
#define PORT_DETECT_CAPACITANCE_INVALID_DELTA 11
|
||||
#define PORT_DETECT_CAPACITANCE_OUT_OF_RANGE 12
|
||||
#define POE_PLUS 0x40
|
||||
#define OPERATING_MODE 0x12
|
||||
#define OPERATING_MODE_OFF 0
|
||||
#define OPERATING_MODE_MANUAL 1
|
||||
#define OPERATING_MODE_SEMI 2
|
||||
#define OPERATING_MODE_AUTO 3
|
||||
#define OPERATING_MODE_PORT_1_MASK GENMASK(1, 0)
|
||||
#define OPERATING_MODE_PORT_2_MASK GENMASK(3, 2)
|
||||
#define OPERATING_MODE_PORT_3_MASK GENMASK(5, 4)
|
||||
#define OPERATING_MODE_PORT_4_MASK GENMASK(7, 6)
|
||||
|
||||
#define DETECT_CLASS_RESTART 0x18
|
||||
#define POWER_ENABLE 0x19
|
||||
#define TPS23861_NUM_PORTS 4
|
||||
|
||||
#define TEMPERATURE_LSB 652 /* 0.652 degrees Celsius */
|
||||
#define VOLTAGE_LSB 3662 /* 3.662 mV */
|
||||
#define SHUNT_RESISTOR_DEFAULT 255000 /* 255 mOhm */
|
||||
#define CURRENT_LSB_255 62260 /* 62.260 uA */
|
||||
#define CURRENT_LSB_250 61039 /* 61.039 uA */
|
||||
#define RESISTANCE_LSB 110966 /* 11.0966 Ohm*/
|
||||
#define RESISTANCE_LSB_LOW 157216 /* 15.7216 Ohm*/
|
||||
|
||||
struct tps23861_data {
|
||||
struct regmap *regmap;
|
||||
u32 shunt_resistor;
|
||||
struct i2c_client *client;
|
||||
struct dentry *debugfs_dir;
|
||||
};
|
||||
|
||||
static struct regmap_config tps23861_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int tps23861_read_temp(struct tps23861_data *data, long *val)
|
||||
{
|
||||
unsigned int regval;
|
||||
int err;
|
||||
|
||||
err = regmap_read(data->regmap, TEMPERATURE, ®val);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
*val = (regval * TEMPERATURE_LSB) - 20000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23861_read_voltage(struct tps23861_data *data, int channel,
|
||||
long *val)
|
||||
{
|
||||
unsigned int regval;
|
||||
int err;
|
||||
|
||||
if (channel < TPS23861_NUM_PORTS) {
|
||||
err = regmap_bulk_read(data->regmap,
|
||||
PORT_1_VOLTAGE_LSB + channel * PORT_N_VOLTAGE_LSB_OFFSET,
|
||||
®val, 2);
|
||||
} else {
|
||||
err = regmap_bulk_read(data->regmap,
|
||||
INPUT_VOLTAGE_LSB,
|
||||
®val, 2);
|
||||
}
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * VOLTAGE_LSB) / 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23861_read_current(struct tps23861_data *data, int channel,
|
||||
long *val)
|
||||
{
|
||||
unsigned int current_lsb;
|
||||
unsigned int regval;
|
||||
int err;
|
||||
|
||||
if (data->shunt_resistor == SHUNT_RESISTOR_DEFAULT)
|
||||
current_lsb = CURRENT_LSB_255;
|
||||
else
|
||||
current_lsb = CURRENT_LSB_250;
|
||||
|
||||
err = regmap_bulk_read(data->regmap,
|
||||
PORT_1_CURRENT_LSB + channel * PORT_N_CURRENT_LSB_OFFSET,
|
||||
®val, 2);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
*val = (FIELD_GET(VOLTAGE_CURRENT_MASK, regval) * current_lsb) / 1000000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23861_port_disable(struct tps23861_data *data, int channel)
|
||||
{
|
||||
unsigned int regval = 0;
|
||||
int err;
|
||||
|
||||
regval |= BIT(channel + 4);
|
||||
err = regmap_write(data->regmap, POWER_ENABLE, regval);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tps23861_port_enable(struct tps23861_data *data, int channel)
|
||||
{
|
||||
unsigned int regval = 0;
|
||||
int err;
|
||||
|
||||
regval |= BIT(channel);
|
||||
regval |= BIT(channel + 4);
|
||||
err = regmap_write(data->regmap, DETECT_CLASS_RESTART, regval);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static umode_t tps23861_is_visible(const void *data, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
case hwmon_temp_label:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
case hwmon_in_label:
|
||||
return 0444;
|
||||
case hwmon_in_enable:
|
||||
return 0200;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
case hwmon_curr_label:
|
||||
return 0444;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int tps23861_write(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long val)
|
||||
{
|
||||
struct tps23861_data *data = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_enable:
|
||||
if (val == 0)
|
||||
err = tps23861_port_disable(data, channel);
|
||||
else if (val == 1)
|
||||
err = tps23861_port_enable(data, channel);
|
||||
else
|
||||
err = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int tps23861_read(struct device *dev, enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, long *val)
|
||||
{
|
||||
struct tps23861_data *data = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
switch (type) {
|
||||
case hwmon_temp:
|
||||
switch (attr) {
|
||||
case hwmon_temp_input:
|
||||
err = tps23861_read_temp(data, val);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case hwmon_in:
|
||||
switch (attr) {
|
||||
case hwmon_in_input:
|
||||
err = tps23861_read_voltage(data, channel, val);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
case hwmon_curr:
|
||||
switch (attr) {
|
||||
case hwmon_curr_input:
|
||||
err = tps23861_read_current(data, channel, val);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const char * const tps23861_port_label[] = {
|
||||
"Port1",
|
||||
"Port2",
|
||||
"Port3",
|
||||
"Port4",
|
||||
"Input",
|
||||
};
|
||||
|
||||
static int tps23861_read_string(struct device *dev,
|
||||
enum hwmon_sensor_types type,
|
||||
u32 attr, int channel, const char **str)
|
||||
{
|
||||
switch (type) {
|
||||
case hwmon_in:
|
||||
case hwmon_curr:
|
||||
*str = tps23861_port_label[channel];
|
||||
break;
|
||||
case hwmon_temp:
|
||||
*str = "Die";
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hwmon_channel_info *tps23861_info[] = {
|
||||
HWMON_CHANNEL_INFO(chip,
|
||||
HWMON_C_REGISTER_TZ),
|
||||
HWMON_CHANNEL_INFO(temp,
|
||||
HWMON_T_INPUT | HWMON_T_LABEL),
|
||||
HWMON_CHANNEL_INFO(in,
|
||||
HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_ENABLE | HWMON_I_LABEL,
|
||||
HWMON_I_INPUT | HWMON_I_LABEL),
|
||||
HWMON_CHANNEL_INFO(curr,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL,
|
||||
HWMON_C_INPUT | HWMON_C_LABEL),
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct hwmon_ops tps23861_hwmon_ops = {
|
||||
.is_visible = tps23861_is_visible,
|
||||
.write = tps23861_write,
|
||||
.read = tps23861_read,
|
||||
.read_string = tps23861_read_string,
|
||||
};
|
||||
|
||||
static const struct hwmon_chip_info tps23861_chip_info = {
|
||||
.ops = &tps23861_hwmon_ops,
|
||||
.info = tps23861_info,
|
||||
};
|
||||
|
||||
static char *tps23861_port_operating_mode(struct tps23861_data *data, int port)
|
||||
{
|
||||
unsigned int regval;
|
||||
int mode;
|
||||
|
||||
regmap_read(data->regmap, OPERATING_MODE, ®val);
|
||||
|
||||
switch (port) {
|
||||
case 1:
|
||||
mode = FIELD_GET(OPERATING_MODE_PORT_1_MASK, regval);
|
||||
break;
|
||||
case 2:
|
||||
mode = FIELD_GET(OPERATING_MODE_PORT_2_MASK, regval);
|
||||
break;
|
||||
case 3:
|
||||
mode = FIELD_GET(OPERATING_MODE_PORT_3_MASK, regval);
|
||||
break;
|
||||
case 4:
|
||||
mode = FIELD_GET(OPERATING_MODE_PORT_4_MASK, regval);
|
||||
break;
|
||||
default:
|
||||
mode = -EINVAL;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case OPERATING_MODE_OFF:
|
||||
return "Off";
|
||||
case OPERATING_MODE_MANUAL:
|
||||
return "Manual";
|
||||
case OPERATING_MODE_SEMI:
|
||||
return "Semi-Auto";
|
||||
case OPERATING_MODE_AUTO:
|
||||
return "Auto";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static char *tps23861_port_detect_status(struct tps23861_data *data, int port)
|
||||
{
|
||||
unsigned int regval;
|
||||
|
||||
regmap_read(data->regmap,
|
||||
PORT_1_STATUS + (port - 1),
|
||||
®val);
|
||||
|
||||
switch (FIELD_GET(PORT_STATUS_DETECT_MASK, regval)) {
|
||||
case PORT_DETECT_UNKNOWN:
|
||||
return "Unknown device";
|
||||
case PORT_DETECT_SHORT:
|
||||
return "Short circuit";
|
||||
case PORT_DETECT_RESISTANCE_LOW:
|
||||
return "Too low resistance";
|
||||
case PORT_DETECT_RESISTANCE_OK:
|
||||
return "Valid resistance";
|
||||
case PORT_DETECT_RESISTANCE_HIGH:
|
||||
return "Too high resistance";
|
||||
case PORT_DETECT_OPEN_CIRCUIT:
|
||||
return "Open circuit";
|
||||
case PORT_DETECT_MOSFET_FAULT:
|
||||
return "MOSFET fault";
|
||||
case PORT_DETECT_LEGACY:
|
||||
return "Legacy device";
|
||||
case PORT_DETECT_CAPACITANCE_INVALID_BEYOND:
|
||||
return "Invalid capacitance, beyond clamp voltage";
|
||||
case PORT_DETECT_CAPACITANCE_INVALID_DELTA:
|
||||
return "Invalid capacitance, insufficient voltage delta";
|
||||
case PORT_DETECT_CAPACITANCE_OUT_OF_RANGE:
|
||||
return "Valid capacitance, outside of legacy range";
|
||||
case PORT_DETECT_RESERVED:
|
||||
case PORT_DETECT_RESERVED_2:
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static char *tps23861_port_class_status(struct tps23861_data *data, int port)
|
||||
{
|
||||
unsigned int regval;
|
||||
|
||||
regmap_read(data->regmap,
|
||||
PORT_1_STATUS + (port - 1),
|
||||
®val);
|
||||
|
||||
switch (FIELD_GET(PORT_STATUS_CLASS_MASK, regval)) {
|
||||
case PORT_CLASS_UNKNOWN:
|
||||
return "Unknown";
|
||||
case PORT_CLASS_RESERVED:
|
||||
case PORT_CLASS_0:
|
||||
return "0";
|
||||
case PORT_CLASS_1:
|
||||
return "1";
|
||||
case PORT_CLASS_2:
|
||||
return "2";
|
||||
case PORT_CLASS_3:
|
||||
return "3";
|
||||
case PORT_CLASS_4:
|
||||
return "4";
|
||||
case PORT_CLASS_OVERCURRENT:
|
||||
return "Overcurrent";
|
||||
case PORT_CLASS_MISMATCH:
|
||||
return "Mismatch";
|
||||
default:
|
||||
return "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static char *tps23861_port_poe_plus_status(struct tps23861_data *data, int port)
|
||||
{
|
||||
unsigned int regval;
|
||||
|
||||
regmap_read(data->regmap, POE_PLUS, ®val);
|
||||
|
||||
if (BIT(port + 3) & regval)
|
||||
return "Yes";
|
||||
else
|
||||
return "No";
|
||||
}
|
||||
|
||||
static int tps23861_port_resistance(struct tps23861_data *data, int port)
|
||||
{
|
||||
u16 regval;
|
||||
|
||||
regmap_bulk_read(data->regmap,
|
||||
PORT_1_RESISTANCE_LSB + PORT_N_RESISTANCE_LSB_OFFSET * (port - 1),
|
||||
®val,
|
||||
2);
|
||||
|
||||
switch (FIELD_GET(PORT_RESISTANCE_RSN_MASK, regval)) {
|
||||
case PORT_RESISTANCE_RSN_OTHER:
|
||||
return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB) / 10000;
|
||||
case PORT_RESISTANCE_RSN_LOW:
|
||||
return (FIELD_GET(PORT_RESISTANCE_MASK, regval) * RESISTANCE_LSB_LOW) / 10000;
|
||||
case PORT_RESISTANCE_RSN_SHORT:
|
||||
case PORT_RESISTANCE_RSN_OPEN:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int tps23861_port_status_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct tps23861_data *priv = s->private;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < TPS23861_NUM_PORTS + 1; i++) {
|
||||
seq_printf(s, "Port: \t\t%d\n", i);
|
||||
seq_printf(s, "Operating mode: %s\n", tps23861_port_operating_mode(priv, i));
|
||||
seq_printf(s, "Detected: \t%s\n", tps23861_port_detect_status(priv, i));
|
||||
seq_printf(s, "Class: \t\t%s\n", tps23861_port_class_status(priv, i));
|
||||
seq_printf(s, "PoE Plus: \t%s\n", tps23861_port_poe_plus_status(priv, i));
|
||||
seq_printf(s, "Resistance: \t%d\n", tps23861_port_resistance(priv, i));
|
||||
seq_putc(s, '\n');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(tps23861_port_status);
|
||||
|
||||
static void tps23861_init_debugfs(struct tps23861_data *data)
|
||||
{
|
||||
data->debugfs_dir = debugfs_create_dir(data->client->name, NULL);
|
||||
|
||||
debugfs_create_file("port_status",
|
||||
0400,
|
||||
data->debugfs_dir,
|
||||
data,
|
||||
&tps23861_port_status_fops);
|
||||
}
|
||||
|
||||
static int tps23861_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct tps23861_data *data;
|
||||
struct device *hwmon_dev;
|
||||
u32 shunt_resistor;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
data->regmap = devm_regmap_init_i2c(client, &tps23861_regmap_config);
|
||||
if (IS_ERR(data->regmap)) {
|
||||
dev_err(dev, "failed to allocate register map\n");
|
||||
return PTR_ERR(data->regmap);
|
||||
}
|
||||
|
||||
if (!of_property_read_u32(dev->of_node, "shunt-resistor-micro-ohms", &shunt_resistor))
|
||||
data->shunt_resistor = shunt_resistor;
|
||||
else
|
||||
data->shunt_resistor = SHUNT_RESISTOR_DEFAULT;
|
||||
|
||||
hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
|
||||
data, &tps23861_chip_info,
|
||||
NULL);
|
||||
if (IS_ERR(hwmon_dev))
|
||||
return PTR_ERR(hwmon_dev);
|
||||
|
||||
tps23861_init_debugfs(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps23861_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tps23861_data *data = i2c_get_clientdata(client);
|
||||
|
||||
debugfs_remove_recursive(data->debugfs_dir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id __maybe_unused tps23861_of_match[] = {
|
||||
{ .compatible = "ti,tps23861", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tps23861_of_match);
|
||||
|
||||
static struct i2c_driver tps23861_driver = {
|
||||
.probe_new = tps23861_probe,
|
||||
.remove = tps23861_remove,
|
||||
.driver = {
|
||||
.name = "tps23861",
|
||||
.of_match_table = of_match_ptr(tps23861_of_match),
|
||||
},
|
||||
};
|
||||
module_i2c_driver(tps23861_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Robert Marko <robert.marko@sartura.hr>");
|
||||
MODULE_DESCRIPTION("TI TPS23861 PoE PSE");
|
@@ -1110,7 +1110,7 @@ clear_caseopen(struct device *dev, struct w83627ehf_data *data, int channel,
|
||||
static umode_t w83627ehf_attrs_visible(struct kobject *kobj,
|
||||
struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, struct device, kobj);
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct w83627ehf_data *data = dev_get_drvdata(dev);
|
||||
struct device_attribute *devattr;
|
||||
struct sensor_device_attribute *sda;
|
||||
|
@@ -34,6 +34,15 @@
|
||||
*/
|
||||
#define PMBUS_WRITE_PROTECTED BIT(1)
|
||||
|
||||
/*
|
||||
* PMBUS_NO_CAPABILITY
|
||||
*
|
||||
* Some PMBus chips don't respond with valid data when reading the CAPABILITY
|
||||
* register. For such chips, this flag should be set so that the PMBus core
|
||||
* driver doesn't use CAPABILITY to determine it's behavior.
|
||||
*/
|
||||
#define PMBUS_NO_CAPABILITY BIT(2)
|
||||
|
||||
struct pmbus_platform_data {
|
||||
u32 flags; /* Device specific flags */
|
||||
|
||||
|
Reference in New Issue
Block a user