Skip to content
Snippets Groups Projects
Commit 909fd2b8 authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag 'mfd-next-6.14' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd

Pull mfd updates from Lee Jones:

 - Fix race in device_node_get_regmap() using more extensive locking

 - Remove unused platform driver support for syscon

 - Allow syscon nodes to be registered without a "syscon" compatible
   string

 - Make `platform_data` pointer const in struct mfd_cell

 - Revert support for multiple AXP PMICs to avoid regressions

 - Increase SoundWire attach timeout and use gpiod_set_raw() for GPIO
   operation

 - Store the result from fault_log() for use by other sub-components

 - Fix an invalid regmap-config max_register value

 - Add another Gemini Lake ISA bridge PCI device ID

 - Use devm_register_power_off_handler() to simplify code

 - Add support for QNAP microcontroller units, including LEDs, input,
   and hwmon

 - Use MFD_CELL macros and remove unused code

 - Add support for AAEON UP board FPGA

 - Remove unused includes

 - Fix various typos and compatibility issues in multiple bindings

 - Add new bindings for rk3562 QoS, LED1202, and qcom,tcsr-ipq5424

 - Convert several bindings to YAML schema

 - Update sprd,sc2731 bindings to reference sprd,sc2731-efuse bindings
   directly

 - Fix rohm,bd71815 bindings by correcting resistor values and typos

 - Documentation improvements:
    - Add documentation for LED1202 and qnap-mcu-hwmon
    - Adjust the file entry for the qnap-mcu header in MAINTAINERS

* tag 'mfd-next-6.14' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (35 commits)
  MAINTAINERS: Adjust the file entry for the qnap-mcu header
  dt-bindings: mfd: syscon: Fix ti,j784s4-acspcie-proxy-ctrl compatible
  dt-bindings: mfd: syscon: Fix al,alpine-sysfabric-service compatible
  Revert "mfd: axp20x: Allow multiple regulators"
  dt-bindings: mfd: syscon: Add rk3562 QoS register compatible
  mfd: syscon: Allow syscon nodes without a "syscon" compatible
  mfd: syscon: Remove the platform driver support
  mfd: syscon: Fix race in device_node_get_regmap()
  dt-bindings: mfd: atmel: Convert to YAML schema
  dt-bindings: mfd: atmel,at91sam9260: Convert to YAML schema
  dt-bindings: mfd: sprd,sc2731: Reference sprd,sc2731-efuse bindings
  mfd: tps65219: Remove unused macros & add regmap.h
  mfd: tps65219: Use MFD_CELL macros
  leds: Add LED1202 I2C driver
  dt-bindings: leds: Add LED1202 LED Controller
  Documentation:leds: Add leds-st1202.rst
  mfd: Add support for AAEON UP board FPGA
  mfd: da9052: Store result from fault_log
  mfd: intel_soc_pmic_chtdc_ti: Fix invalid regmap-config max_register value
  mfd: cs42l43: Use devres for remove as well
  ...
parents 38f5265e 2816b0c9
No related branches found
No related tags found
No related merge requests found
Showing
with 750 additions and 101 deletions
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/leds/st,led1202.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: ST LED1202 LED controllers
maintainers:
- Vicentiu Galanopulo <vicentiu.galanopulo@remote-tech.co.uk>
description: |
The LED1202 is a 12-channel low quiescent current LED controller
programmable via I2C; The output current can be adjusted separately
for each channel by 8-bit analog and 12-bit digital dimming control.
Datasheet available at
https://www.st.com/en/power-management/led1202.html
properties:
compatible:
const: st,led1202
reg:
maxItems: 1
"#address-cells":
const: 1
"#size-cells":
const: 0
patternProperties:
"^led@[0-9a-f]$":
type: object
$ref: common.yaml#
unevaluatedProperties: false
properties:
reg:
minimum: 0
maximum: 11
required:
- reg
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
additionalProperties: false
examples:
- |
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
led-controller@58 {
compatible = "st,led1202";
reg = <0x58>;
#address-cells = <1>;
#size-cells = <0>;
led@0 {
reg = <0x0>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>;
function-enumerator = <1>;
};
led@1 {
reg = <0x1>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>;
function-enumerator = <2>;
};
led@2 {
reg = <0x2>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_BLUE>;
function-enumerator = <3>;
};
led@3 {
reg = <0x3>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>;
function-enumerator = <4>;
};
led@4 {
reg = <0x4>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>;
function-enumerator = <5>;
};
led@5 {
reg = <0x5>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_BLUE>;
function-enumerator = <6>;
};
led@6 {
reg = <0x6>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_RED>;
function-enumerator = <7>;
};
led@7 {
reg = <0x7>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_GREEN>;
function-enumerator = <8>;
};
led@8 {
reg = <0x8>;
function = LED_FUNCTION_STATUS;
color = <LED_COLOR_ID_BLUE>;
function-enumerator = <9>;
};
};
};
...
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-gpbr.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Microchip AT91 General Purpose Backup Registers
maintainers:
- Nicolas Ferre <nicolas.ferre@microchip.com>
description:
The system controller embeds 256 bits of General Purpose Backup
registers organized as 8 32-bit registers.
properties:
compatible:
oneOf:
- items:
- enum:
- atmel,at91sam9260-gpbr
- const: syscon
- items:
- enum:
- microchip,sam9x60-gpbr
- microchip,sam9x7-gpbr
- const: atmel,at91sam9260-gpbr
- const: syscon
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
syscon@fffffd50 {
compatible = "atmel,at91sam9260-gpbr", "syscon";
reg = <0xfffffd50 0x10>;
};
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mfd/atmel,at91sam9260-matrix.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Microchip AT91 Bus Matrix
maintainers:
- Nicolas Ferre <nicolas.ferre@microchip.com>
description:
The Bus Matrix (MATRIX) implements a multi-layer AHB, based on the
AHB-Lite protocol, that enables parallel access paths between multiple
masters and slaves in a system, thus increasing the overall bandwidth.
properties:
compatible:
oneOf:
- items:
- enum:
- atmel,at91sam9260-matrix
- atmel,at91sam9261-matrix
- atmel,at91sam9263-matrix
- atmel,at91sam9rl-matrix
- atmel,at91sam9g45-matrix
- atmel,at91sam9n12-matrix
- atmel,at91sam9x5-matrix
- atmel,sama5d3-matrix
- const: syscon
- items:
- enum:
- microchip,sam9x60-matrix
- microchip,sam9x7-matrix
- const: atmel,at91sam9x5-matrix
- const: syscon
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
syscon@ffffec00 {
compatible = "atmel,sama5d3-matrix", "syscon";
reg = <0xffffec00 0x200>;
};
* Device tree bindings for Atmel GPBR (General Purpose Backup Registers)
The GPBR are a set of battery-backed registers.
Required properties:
- compatible: Should be one of the following:
"atmel,at91sam9260-gpbr", "syscon"
"microchip,sam9x60-gpbr", "syscon"
"microchip,sam9x7-gpbr", "microchip,sam9x60-gpbr", "syscon"
- reg: contains offset/length value of the GPBR memory
region.
Example:
gpbr: gpbr@fffffd50 {
compatible = "atmel,at91sam9260-gpbr", "syscon";
reg = <0xfffffd50 0x10>;
};
* Device tree bindings for Atmel Bus Matrix
The Bus Matrix registers are used to configure Atmel SoCs internal bus
behavior (master/slave priorities, undefined burst length type, ...)
Required properties:
- compatible: Should be one of the following
"atmel,at91sam9260-matrix", "syscon"
"atmel,at91sam9261-matrix", "syscon"
"atmel,at91sam9263-matrix", "syscon"
"atmel,at91sam9rl-matrix", "syscon"
"atmel,at91sam9g45-matrix", "syscon"
"atmel,at91sam9n12-matrix", "syscon"
"atmel,at91sam9x5-matrix", "syscon"
"atmel,sama5d3-matrix", "syscon"
"microchip,sam9x60-matrix", "syscon"
"microchip,sam9x7-matrix", "atmel,at91sam9x5-matrix", "syscon"
- reg: Contains offset/length value of the Bus Matrix
memory region.
Example:
matrix: matrix@ffffec00 {
compatible = "atmel,sama5d3-matrix", "syscon";
reg = <0xffffec00 0x200>;
};
......@@ -42,6 +42,7 @@ properties:
- qcom,tcsr-apq8064
- qcom,tcsr-apq8084
- qcom,tcsr-ipq5332
- qcom,tcsr-ipq5424
- qcom,tcsr-ipq6018
- qcom,tcsr-ipq8064
- qcom,tcsr-ipq8074
......
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/mfd/qnap,ts433-mcu.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: QNAP NAS on-board Microcontroller
maintainers:
- Heiko Stuebner <heiko@sntech.de>
description:
QNAP embeds a microcontroller on their NAS devices adding system feature
as PWM Fan control, additional LEDs, power button status and more.
properties:
compatible:
enum:
- qnap,ts433-mcu
patternProperties:
"^fan-[0-9]+$":
$ref: /schemas/hwmon/fan-common.yaml#
unevaluatedProperties: false
required:
- compatible
additionalProperties: false
examples:
- |
uart {
mcu {
compatible = "qnap,ts433-mcu";
fan-0 {
#cooling-cells = <2>;
cooling-levels = <0 64 89 128 166 204 221 238>;
};
};
};
......@@ -50,15 +50,15 @@ properties:
minimum: 0
maximum: 1
rohm,charger-sense-resistor-ohms:
minimum: 10000000
maximum: 50000000
rohm,charger-sense-resistor-micro-ohms:
minimum: 10000
maximum: 50000
description: |
BD71827 and BD71828 have SAR ADC for measuring charging currents.
External sense resistor (RSENSE in data sheet) should be used. If
something other but 30MOhm resistor is used the resistance value
should be given here in Ohms.
default: 30000000
BD71815 has SAR ADC for measuring charging currents. External sense
resistor (RSENSE in data sheet) should be used. If something other
but a 30 mOhm resistor is used the resistance value should be given
here in micro Ohms.
default: 30000
regulators:
$ref: /schemas/regulator/rohm,bd71815-regulator.yaml
......@@ -67,7 +67,7 @@ properties:
gpio-reserved-ranges:
description: |
Usage of BD71828 GPIO pins can be changed via OTP. This property can be
Usage of BD71815 GPIO pins can be changed via OTP. This property can be
used to mark the pins which should not be configured for GPIO. Please see
the ../gpio/gpio.txt for more information.
......@@ -113,7 +113,7 @@ examples:
gpio-controller;
#gpio-cells = <2>;
rohm,charger-sense-resistor-ohms = <10000000>;
rohm,charger-sense-resistor-micro-ohms = <10000>;
regulators {
buck1: buck1 {
......
......@@ -67,15 +67,7 @@ patternProperties:
"^efuse@[0-9a-f]+$":
type: object
additionalProperties: true
properties:
compatible:
enum:
- sprd,sc2720-efuse
- sprd,sc2721-efuse
- sprd,sc2723-efuse
- sprd,sc2730-efuse
- sprd,sc2731-efuse
$ref: /schemas/nvmem/sprd,sc2731-efuse.yaml#
"^fuel-gauge@[0-9a-f]+$":
type: object
......@@ -199,7 +191,7 @@ examples:
};
};
adc@480 {
pmic_adc: adc@480 {
compatible = "sprd,sc2731-adc";
reg = <0x480>;
interrupt-parent = <&sc2731_pmic>;
......
......@@ -27,7 +27,7 @@ select:
compatible:
contains:
enum:
- al,alpine-sysfabric-servic
- al,alpine-sysfabric-service
- allwinner,sun8i-a83t-system-controller
- allwinner,sun8i-h3-system-controller
- allwinner,sun8i-v3s-system-controller
......@@ -103,6 +103,7 @@ select:
- rockchip,rk3288-qos
- rockchip,rk3368-qos
- rockchip,rk3399-qos
- rockchip,rk3562-qos
- rockchip,rk3568-qos
- rockchip,rk3576-qos
- rockchip,rk3588-qos
......@@ -201,6 +202,7 @@ properties:
- rockchip,rk3288-qos
- rockchip,rk3368-qos
- rockchip,rk3399-qos
- rockchip,rk3562-qos
- rockchip,rk3568-qos
- rockchip,rk3576-qos
- rockchip,rk3588-qos
......@@ -213,6 +215,7 @@ properties:
- ti,am625-dss-oldi-io-ctrl
- ti,am62p-cpsw-mac-efuse
- ti,am654-dss-oldi-io-ctrl
- ti,j784s4-acspcie-proxy-ctrl
- ti,j784s4-pcie-ctrl
- ti,keystone-pllctrl
- const: syscon
......
......@@ -36,33 +36,4 @@ allOf:
- $ref: nvmem-deprecated-cells.yaml#
unevaluatedProperties: false
examples:
- |
pmic {
#address-cells = <1>;
#size-cells = <0>;
efuse@380 {
compatible = "sprd,sc2731-efuse";
reg = <0x380>;
hwlocks = <&hwlock 12>;
#address-cells = <1>;
#size-cells = <1>;
/* Data cells */
fgu_calib: calib@6 {
reg = <0x6 0x2>;
bits = <0 9>;
};
adc_big_scale: calib@24 {
reg = <0x24 0x2>;
};
adc_small_scale: calib@26 {
reg = <0x26 0x2>;
};
};
};
...
......@@ -201,6 +201,7 @@ Hardware Monitoring Kernel Drivers
pxe1610
pwm-fan
q54sj108a2
qnap-mcu-hwmon
raspberrypi-hwmon
sbrmi
sbtsi_temp
......
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver qnap-mcu-hwmon
============================
This driver enables the use of the hardware monitoring and fan control
of the MCU used on some QNAP network attached storage devices.
Author: Heiko Stuebner <heiko@sntech.de>
Description
-----------
The driver implements a simple interface for driving the fan controlled by
setting its PWM output value and exposes the fan rpm and case-temperature
to user space through hwmon's sysfs interface.
The fan rotation speed returned via the optional 'fan1_input' is calculated
inside the MCU device.
The driver provides the following sensor accesses in sysfs:
=============== ======= =======================================================
fan1_input ro fan tachometer speed in RPM
pwm1 rw relative speed (0-255), 255=max. speed.
temp1_input ro Measured temperature in millicelsius
=============== ======= =======================================================
......@@ -28,4 +28,5 @@ LEDs
leds-mlxcpld
leds-mt6370-rgb
leds-sc27xx
leds-st1202.rst
leds-qcom-lpg
.. SPDX-License-Identifier: GPL-2.0
============================================
Kernel driver for STMicroelectronics LED1202
============================================
/sys/class/leds/<led>/hw_pattern
--------------------------------
Specify a hardware pattern for the ST1202 LED. The LED controller
implements 12 low-side current generators with independent dimming
control. Internal volatile memory allows the user to store up to 8
different patterns. Each pattern is a particular output configuration
in terms of PWM duty-cycle and duration (ms).
To be compatible with the hardware pattern format, maximum 8 tuples of
brightness (PWM) and duration must be written to hw_pattern.
- Min pattern duration: 22 ms
- Max pattern duration: 5660 ms
The format of the hardware pattern values should be:
"brightness duration brightness duration ..."
/sys/class/leds/<led>/repeat
----------------------------
Specify a pattern repeat number, which is common for all channels.
Default is 1; negative numbers and 0 are invalid.
This file will always return the originally written repeat number.
When the 255 value is written to it, all patterns will repeat
indefinitely.
......@@ -19158,6 +19158,15 @@ L: linux-media@vger.kernel.org
S: Odd Fixes
F: drivers/media/tuners/qm1d1c0042*
 
QNAP MCU DRIVER
M: Heiko Stuebner <heiko@sntech.de>
S: Maintained
F: drivers/hwmon/qnap-mcu-hwmon.c
F: drivers/input/misc/qnap-mcu-input.c
F: drivers/leds/leds-qnap-mcu.c
F: drivers/mfd/qnap-mcu.c
F: include/linux/mfd/qnap-mcu.h
QNX4 FILESYSTEM
M: Anders Larsen <al@alarsen.net>
S: Maintained
......
......@@ -730,23 +730,30 @@ static int sensor_hub_probe(struct hid_device *hdev,
return ret;
}
static int sensor_hub_finalize_pending_fn(struct device *dev, void *data)
{
struct hid_sensor_hub_device *hsdev = dev->platform_data;
if (hsdev->pending.status)
complete(&hsdev->pending.ready);
return 0;
}
static void sensor_hub_remove(struct hid_device *hdev)
{
struct sensor_hub_data *data = hid_get_drvdata(hdev);
unsigned long flags;
int i;
hid_dbg(hdev, " hardware removed\n");
hid_hw_close(hdev);
hid_hw_stop(hdev);
spin_lock_irqsave(&data->lock, flags);
for (i = 0; i < data->hid_sensor_client_cnt; ++i) {
struct hid_sensor_hub_device *hsdev =
data->hid_sensor_hub_client_devs[i].platform_data;
if (hsdev->pending.status)
complete(&hsdev->pending.ready);
}
device_for_each_child(&hdev->dev, NULL,
sensor_hub_finalize_pending_fn);
spin_unlock_irqrestore(&data->lock, flags);
mfd_remove_devices(&hdev->dev);
mutex_destroy(&data->mutex);
}
......
......@@ -1822,6 +1822,18 @@ config SENSORS_PWM_FAN
This driver can also be built as a module. If so, the module
will be called pwm-fan.
config SENSORS_QNAP_MCU_HWMON
tristate "QNAP MCU hardware monitoring"
depends on MFD_QNAP_MCU
depends on THERMAL || THERMAL=n
help
Say yes here to enable support for fan and temperature sensor
connected to a QNAP MCU, as found in a number of QNAP network
attached storage devices.
This driver can also be built as a module. If so, the module
will be called qnap-mcu-hwmon.
config SENSORS_RASPBERRYPI_HWMON
tristate "Raspberry Pi voltage monitor"
depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
......
......@@ -189,6 +189,7 @@ obj-$(CONFIG_SENSORS_POWERZ) += powerz.o
obj-$(CONFIG_SENSORS_POWR1220) += powr1220.o
obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for hwmon elements found on QNAP-MCU devices
*
* Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
*/
#include <linux/fwnode.h>
#include <linux/hwmon.h>
#include <linux/mfd/qnap-mcu.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/thermal.h>
struct qnap_mcu_hwmon {
struct qnap_mcu *mcu;
struct device *dev;
unsigned int pwm_min;
unsigned int pwm_max;
struct fwnode_handle *fan_node;
unsigned int fan_state;
unsigned int fan_max_state;
unsigned int *fan_cooling_levels;
struct thermal_cooling_device *cdev;
struct hwmon_chip_info info;
};
static int qnap_mcu_hwmon_get_rpm(struct qnap_mcu_hwmon *hwm)
{
static const u8 cmd[] = { '@', 'F', 'A' };
u8 reply[6];
int ret;
/* poll the fan rpm */
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
if (ret)
return ret;
/* First 2 bytes must mirror the sent command */
if (memcmp(cmd, reply, 2))
return -EIO;
return reply[4] * 30;
}
static int qnap_mcu_hwmon_get_pwm(struct qnap_mcu_hwmon *hwm)
{
static const u8 cmd[] = { '@', 'F', 'Z', '0' }; /* 0 = fan-id? */
u8 reply[4];
int ret;
/* poll the fan pwm */
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
if (ret)
return ret;
/* First 3 bytes must mirror the sent command */
if (memcmp(cmd, reply, 3))
return -EIO;
return reply[3];
}
static int qnap_mcu_hwmon_set_pwm(struct qnap_mcu_hwmon *hwm, u8 pwm)
{
const u8 cmd[] = { '@', 'F', 'W', '0', pwm }; /* 0 = fan-id?, pwm 0-255 */
/* set the fan pwm */
return qnap_mcu_exec_with_ack(hwm->mcu, cmd, sizeof(cmd));
}
static int qnap_mcu_hwmon_get_temp(struct qnap_mcu_hwmon *hwm)
{
static const u8 cmd[] = { '@', 'T', '3' };
u8 reply[4];
int ret;
/* poll the fan rpm */
ret = qnap_mcu_exec(hwm->mcu, cmd, sizeof(cmd), reply, sizeof(reply));
if (ret)
return ret;
/* First bytes must mirror the sent command */
if (memcmp(cmd, reply, sizeof(cmd)))
return -EIO;
/*
* There is an unknown bit set in bit7.
* Bits [6:0] report the actual temperature as returned by the
* original qnap firmware-tools, so just drop bit7 for now.
*/
return (reply[3] & 0x7f) * 1000;
}
static int qnap_mcu_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long val)
{
struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
switch (attr) {
case hwmon_pwm_input:
if (val < 0 || val > 255)
return -EINVAL;
if (val != 0)
val = clamp_val(val, hwm->pwm_min, hwm->pwm_max);
return qnap_mcu_hwmon_set_pwm(hwm, val);
default:
return -EOPNOTSUPP;
}
return 0;
}
static int qnap_mcu_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
struct qnap_mcu_hwmon *hwm = dev_get_drvdata(dev);
int ret;
switch (type) {
case hwmon_pwm:
switch (attr) {
case hwmon_pwm_input:
ret = qnap_mcu_hwmon_get_pwm(hwm);
if (ret < 0)
return ret;
*val = ret;
return 0;
default:
return -EOPNOTSUPP;
}
case hwmon_fan:
ret = qnap_mcu_hwmon_get_rpm(hwm);
if (ret < 0)
return ret;
*val = ret;
return 0;
case hwmon_temp:
ret = qnap_mcu_hwmon_get_temp(hwm);
if (ret < 0)
return ret;
*val = ret;
return 0;
default:
return -EOPNOTSUPP;
}
}
static umode_t qnap_mcu_hwmon_is_visible(const void *data,
enum hwmon_sensor_types type,
u32 attr, int channel)
{
switch (type) {
case hwmon_temp:
return 0444;
case hwmon_pwm:
return 0644;
case hwmon_fan:
return 0444;
default:
return 0;
}
}
static const struct hwmon_ops qnap_mcu_hwmon_hwmon_ops = {
.is_visible = qnap_mcu_hwmon_is_visible,
.read = qnap_mcu_hwmon_read,
.write = qnap_mcu_hwmon_write,
};
/* thermal cooling device callbacks */
static int qnap_mcu_hwmon_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct qnap_mcu_hwmon *hwm = cdev->devdata;
if (!hwm)
return -EINVAL;
*state = hwm->fan_max_state;
return 0;
}
static int qnap_mcu_hwmon_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct qnap_mcu_hwmon *hwm = cdev->devdata;
if (!hwm)
return -EINVAL;
*state = hwm->fan_state;
return 0;
}
static int qnap_mcu_hwmon_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct qnap_mcu_hwmon *hwm = cdev->devdata;
int ret;
if (!hwm || state > hwm->fan_max_state)
return -EINVAL;
if (state == hwm->fan_state)
return 0;
ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->fan_cooling_levels[state]);
if (ret)
return ret;
hwm->fan_state = state;
return ret;
}
static const struct thermal_cooling_device_ops qnap_mcu_hwmon_cooling_ops = {
.get_max_state = qnap_mcu_hwmon_get_max_state,
.get_cur_state = qnap_mcu_hwmon_get_cur_state,
.set_cur_state = qnap_mcu_hwmon_set_cur_state,
};
static void devm_fan_node_release(void *data)
{
struct qnap_mcu_hwmon *hwm = data;
fwnode_handle_put(hwm->fan_node);
}
static int qnap_mcu_hwmon_get_cooling_data(struct device *dev, struct qnap_mcu_hwmon *hwm)
{
struct fwnode_handle *fwnode;
int num, i, ret;
fwnode = device_get_named_child_node(dev->parent, "fan-0");
if (!fwnode)
return 0;
/* if we found the fan-node, we're keeping it until device-unbind */
hwm->fan_node = fwnode;
ret = devm_add_action_or_reset(dev, devm_fan_node_release, hwm);
if (ret)
return ret;
num = fwnode_property_count_u32(fwnode, "cooling-levels");
if (num <= 0)
return dev_err_probe(dev, num ? : -EINVAL,
"Failed to count elements in 'cooling-levels'\n");
hwm->fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32),
GFP_KERNEL);
if (!hwm->fan_cooling_levels)
return -ENOMEM;
ret = fwnode_property_read_u32_array(fwnode, "cooling-levels",
hwm->fan_cooling_levels, num);
if (ret)
return dev_err_probe(dev, ret, "Failed to read 'cooling-levels'\n");
for (i = 0; i < num; i++) {
if (hwm->fan_cooling_levels[i] > hwm->pwm_max)
return dev_err_probe(dev, -EINVAL, "fan state[%d]:%d > %d\n", i,
hwm->fan_cooling_levels[i], hwm->pwm_max);
}
hwm->fan_max_state = num - 1;
return 0;
}
static const struct hwmon_channel_info * const qnap_mcu_hwmon_channels[] = {
HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT),
NULL
};
static int qnap_mcu_hwmon_probe(struct platform_device *pdev)
{
struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
struct qnap_mcu_hwmon *hwm;
struct thermal_cooling_device *cdev;
struct device *dev = &pdev->dev;
struct device *hwmon;
int ret;
hwm = devm_kzalloc(dev, sizeof(*hwm), GFP_KERNEL);
if (!hwm)
return -ENOMEM;
hwm->mcu = mcu;
hwm->dev = &pdev->dev;
hwm->pwm_min = variant->fan_pwm_min;
hwm->pwm_max = variant->fan_pwm_max;
platform_set_drvdata(pdev, hwm);
/*
* Set duty cycle to maximum allowed.
*/
ret = qnap_mcu_hwmon_set_pwm(hwm, hwm->pwm_max);
if (ret)
return ret;
hwm->info.ops = &qnap_mcu_hwmon_hwmon_ops;
hwm->info.info = qnap_mcu_hwmon_channels;
ret = qnap_mcu_hwmon_get_cooling_data(dev, hwm);
if (ret)
return ret;
hwm->fan_state = hwm->fan_max_state;
hwmon = devm_hwmon_device_register_with_info(dev, "qnapmcu",
hwm, &hwm->info, NULL);
if (IS_ERR(hwmon))
return dev_err_probe(dev, PTR_ERR(hwmon), "Failed to register hwmon device\n");
/*
* Only register cooling device when we found cooling-levels.
* qnap_mcu_hwmon_get_cooling_data() will fail when reading malformed
* levels and only succeed with either no or correct cooling levels.
*/
if (IS_ENABLED(CONFIG_THERMAL) && hwm->fan_cooling_levels) {
cdev = devm_thermal_of_cooling_device_register(dev,
to_of_node(hwm->fan_node), "qnap-mcu-hwmon",
hwm, &qnap_mcu_hwmon_cooling_ops);
if (IS_ERR(cdev))
return dev_err_probe(dev, PTR_ERR(cdev),
"Failed to register qnap-mcu-hwmon as cooling device\n");
hwm->cdev = cdev;
}
return 0;
}
static struct platform_driver qnap_mcu_hwmon_driver = {
.probe = qnap_mcu_hwmon_probe,
.driver = {
.name = "qnap-mcu-hwmon",
},
};
module_platform_driver(qnap_mcu_hwmon_driver);
MODULE_ALIAS("platform:qnap-mcu-hwmon");
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
MODULE_DESCRIPTION("QNAP MCU hwmon driver");
MODULE_LICENSE("GPL");
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment