* [PATCH v2 0/2] hwmon: add Axiado AX3000 and AX3005 PWM fan support
@ 2026-09-03 8:38 Petar Stepanovic
2026-09-03 8:38 ` [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller Petar Stepanovic
2026-09-03 8:38 ` [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver Petar Stepanovic
0 siblings, 2 replies; 5+ messages in thread
From: Petar Stepanovic @ 2026-09-03 8:38 UTC (permalink / raw)
To: Petar Stepanovic, Akhila Kavi, Prasad Bolisetty, Guenter Roeck,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Uwe Kleine-König, Tzu-Hao Wei, Karthikeyan Mitran,
Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-arm-kernel, linux-kernel,
linux-pwm, linux-doc, openbmc
This series adds support for the fan controller found on the Axiado
AX3000 and AX3005 SoCs.
The controller provides a hardware tachometer block for measuring the
rotational speed of one fan. Fan speed is controlled through a PWM
signal supplied by an external PWM controller.
The tachometer counts fan pulses in hardware and reports the accumulated
count through an MMIO register when the measurement interval expires.
The generic pwm-fan driver counts tachometer interrupts in software and
therefore cannot directly operate this hardware counter block.
The driver registers with the hwmon subsystem and provides the
following standard sysfs attributes:
- fan1_input for the measured fan speed in RPM
- pwm1 for fan-speed control in the range 0 to 255
The number of tachometer pulses generated per fan revolution can be
configured using the pulses-per-revolution devicetree property. If the
property is not specified, the driver uses two pulses per revolution.
The series includes:
- Devicetree binding documentation
- Axiado PWM fan hwmon driver
- hwmon driver documentation
- MAINTAINERS entry
The driver was tested on AX3000 and AX3005 platforms.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
Changes in v2:
- Represent the physical fan as a child node of the tachometer
controller and reference fan-common.yaml.
- Move pwms and pulses-per-revolution to the fan child node.
- Update the driver to obtain the PWM and pulses-per-revolution
property from the fan child node.
- Rename PWM_MAX to AX_TACH_PWM_VAL_MAX.
- Remove unnecessary locking when reading the cached PWM value.
- Remove the trailing comma from the OF match-table terminator.
- Fix minor formatting issues.
- Link to v1: https://lore.kernel.org/r/20260806-axiado-ax3000-pwm-fan-v1-0-f6db4cf2166b@axiado.com
To: Petar Stepanovic <pstepanovic@axiado.com>
To: Akhila Kavi <akavi@axiado.com>
To: Prasad Bolisetty <pbolisetty@axiado.com>
To: Guenter Roeck <linux@roeck-us.net>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Tzu-Hao Wei <twei@axiado.com>
To: Karthikeyan Mitran <kmitran@axiado.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Shuah Khan <skhan@linuxfoundation.org>
To: Uwe Kleine-König <ukleinek@kernel.org>
Cc: linux-hwmon@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: linux-pwm@vger.kernel.org
---
Petar Stepanovic (2):
dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller
hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver
.../bindings/hwmon/axiado,ax3000-pwm-fan.yaml | 72 ++++
Documentation/hwmon/axiado-pwm-fan.rst | 38 ++
Documentation/hwmon/index.rst | 1 +
MAINTAINERS | 10 +
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/axiado-pwm-fan.c | 412 +++++++++++++++++++++
7 files changed, 546 insertions(+)
---
base-commit: 28807a684f870463e059fbc8cb22f5b780b94fe4
change-id: 20260520-axiado-ax3000-pwm-fan-36a89de6db61
Best regards,
--
Petar Stepanovic <pstepanovic@axiado.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller
2026-09-03 8:38 [PATCH v2 0/2] hwmon: add Axiado AX3000 and AX3005 PWM fan support Petar Stepanovic
@ 2026-09-03 8:38 ` Petar Stepanovic
2026-09-03 8:43 ` sashiko-bot
2026-09-03 8:38 ` [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver Petar Stepanovic
1 sibling, 1 reply; 5+ messages in thread
From: Petar Stepanovic @ 2026-09-03 8:38 UTC (permalink / raw)
To: Petar Stepanovic, Akhila Kavi, Prasad Bolisetty, Guenter Roeck,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Uwe Kleine-König, Tzu-Hao Wei, Karthikeyan Mitran,
Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-arm-kernel, linux-kernel,
linux-pwm, linux-doc, openbmc
Add a Devicetree binding for the PWM fan controller found on the
Axiado AX3000 and AX3005 SoCs.
The controller measures fan speed using a hardware tachometer block.
Fan speed is controlled through a PWM signal supplied by an external
PWM controller.
Add a MAINTAINERS entry for the binding.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
.../bindings/hwmon/axiado,ax3000-pwm-fan.yaml | 72 ++++++++++++++++++++++
MAINTAINERS | 8 +++
2 files changed, 80 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml b/Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml
new file mode 100644
index 000000000000..76a3e12fc656
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/axiado,ax3000-pwm-fan.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Axiado AX3000 and AX3005 PWM Fan Controller
+
+maintainers:
+ - Petar Stepanovic <pstepanovic@axiado.com>
+ - Akhila Kavi <akavi@axiado.com>
+ - Prasad Bolisetty <pbolisetty@axiado.com>
+
+description:
+ The Axiado AX3000 and AX3005 fan controllers use a hardware tachometer
+ block to measure fan speed. Fan speed is controlled through a PWM signal
+ supplied by an external PWM controller.
+
+properties:
+ compatible:
+ oneOf:
+ - const: axiado,ax3000-pwm-fan
+ - items:
+ - const: axiado,ax3005-pwm-fan
+ - const: axiado,ax3000-pwm-fan
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ fan:
+ type: object
+ $ref: fan-common.yaml#
+ unevaluatedProperties: false
+ properties:
+ pulses-per-revolution:
+ minimum: 1
+ default: 2
+
+ required:
+ - pwms
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - fan
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ fan-controller@33001400 {
+ compatible = "axiado,ax3000-pwm-fan";
+ reg = <0x33001400 0x400>;
+ interrupts = <GIC_SPI 150 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clk>;
+
+ fan {
+ pwms = <&pwm0 0 400>;
+ pulses-per-revolution = <2>;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index a674e36529f7..f528b1a42e0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4457,6 +4457,14 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
F: drivers/pwm/pwm-axi-pwmgen.c
+AXIADO PWM FAN CONTROLLER DRIVER
+M: Petar Stepanovic <pstepanovic@axiado.com>
+M: Akhila Kavi <akavi@axiado.com>
+M: Prasad Bolisetty <pbolisetty@axiado.com>
+L: linux-hwmon@vger.kernel.org
+S: Supported
+F: Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml
+
AXIADO SPI DB DRIVER
M: Vladimir Moravcevic <vmoravcevic@axiado.com>
M: Tzu-Hao Wei <twei@axiado.com>
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver
2026-09-03 8:38 [PATCH v2 0/2] hwmon: add Axiado AX3000 and AX3005 PWM fan support Petar Stepanovic
2026-09-03 8:38 ` [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller Petar Stepanovic
@ 2026-09-03 8:38 ` Petar Stepanovic
2026-09-03 8:48 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Petar Stepanovic @ 2026-09-03 8:38 UTC (permalink / raw)
To: Petar Stepanovic, Akhila Kavi, Prasad Bolisetty, Guenter Roeck,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Harshit Shah,
Uwe Kleine-König, Tzu-Hao Wei, Karthikeyan Mitran,
Jonathan Corbet, Shuah Khan
Cc: linux-hwmon, devicetree, linux-arm-kernel, linux-kernel,
linux-pwm, linux-doc, openbmc
Add support for the PWM fan controller found on the Axiado AX3000 and
AX3005 SoCs.
The controller uses a hardware tachometer block and interrupt to measure
fan speed. Fan speed is controlled through a PWM signal supplied by an
external PWM controller.
Register the controller with the hwmon subsystem and expose the standard
fan1_input and pwm1 attributes. The physical fan is represented by a fan
child node containing the PWM specifier and the optional
pulses-per-revolution property. If pulses-per-revolution is not specified,
the driver uses two pulses per revolution.
The driver was tested on AX3000 and AX3005 boards.
Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
---
Documentation/hwmon/axiado-pwm-fan.rst | 38 +++
Documentation/hwmon/index.rst | 1 +
MAINTAINERS | 2 +
drivers/hwmon/Kconfig | 12 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/axiado-pwm-fan.c | 412 +++++++++++++++++++++++++++++++++
6 files changed, 466 insertions(+)
diff --git a/Documentation/hwmon/axiado-pwm-fan.rst b/Documentation/hwmon/axiado-pwm-fan.rst
new file mode 100644
index 000000000000..d110e170b0df
--- /dev/null
+++ b/Documentation/hwmon/axiado-pwm-fan.rst
@@ -0,0 +1,38 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver axiado-pwm-fan
+============================
+
+Supported chips:
+
+ * Axiado AX3000
+ * Axiado AX3005
+
+ Datasheet: Not publicly available
+
+Author:
+
+ * Petar Stepanovic <pstepanovic@axiado.com>
+
+Description
+-----------
+
+The Axiado AX3000 and AX3005 PWM fan controllers measure the rotational
+speed of one fan using a hardware tachometer block. Fan speed is controlled
+through a PWM signal supplied by an external PWM controller.
+
+The number of tachometer pulses generated per fan revolution is configured
+through the ``pulses-per-revolution`` devicetree property. If the property
+is not specified, the driver uses two pulses per revolution.
+
+Sysfs attributes
+----------------
+
+The driver provides the following standard hwmon attributes:
+
+=============== ====== =====================================================
+fan1_input RO Fan speed in revolutions per minute (RPM).
+
+pwm1 RW Relative PWM control value from 0 to 255. A value of
+ 255 selects the maximum PWM duty cycle.
+=============== ====== =====================================================
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 29130df44d12..857924205861 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -51,6 +51,7 @@ Hardware Monitoring Kernel Drivers
asus_ec_sensors
asus_rog_ryujin
asus_wmi_sensors
+ axiado-pwm-fan
bcm54140
bel-pfe
bpa-rs600
diff --git a/MAINTAINERS b/MAINTAINERS
index f528b1a42e0f..61e7a0939002 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4464,6 +4464,8 @@ M: Prasad Bolisetty <pbolisetty@axiado.com>
L: linux-hwmon@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/hwmon/axiado,ax3000-pwm-fan.yaml
+F: Documentation/hwmon/axiado-pwm-fan.rst
+F: drivers/hwmon/axiado-pwm-fan.c
AXIADO SPI DB DRIVER
M: Vladimir Moravcevic <vmoravcevic@axiado.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2bfbcc033d59..161054ba7312 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -469,6 +469,18 @@ config SENSORS_ATXP1
This driver can also be built as a module. If so, the module
will be called atxp1.
+config SENSORS_AXIADO_PWM_FAN
+ tristate "Axiado PWM fan controller"
+ depends on ARCH_AXIADO || COMPILE_TEST
+ depends on PWM
+ help
+ This driver supports the Axiado PWM fan controller. It uses a PWM
+ output to control fan speed and a tachometer interrupt to report fan
+ speed as RPM through the hardware monitoring sysfs interface.
+
+ This driver can also be built as a module. If so, the module will be
+ called axiado-pwm-fan.
+
config SENSORS_CGBC
tristate "Congatec Board Controller Sensors"
depends on MFD_CGBC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 63effc0ab8d1..78fcb22a1777 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_SENSORS_ASPEED) += aspeed-pwm-tacho.o
obj-$(CONFIG_SENSORS_ASPEED_G6) += aspeed-g6-pwm-tach.o
obj-$(CONFIG_SENSORS_ASUS_ROG_RYUJIN) += asus_rog_ryujin.o
obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o
+obj-$(CONFIG_SENSORS_AXIADO_PWM_FAN) += axiado-pwm-fan.o
obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
obj-$(CONFIG_SENSORS_CGBC) += cgbc-hwmon.o
obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
diff --git a/drivers/hwmon/axiado-pwm-fan.c b/drivers/hwmon/axiado-pwm-fan.c
new file mode 100644
index 000000000000..ac153afded47
--- /dev/null
+++ b/drivers/hwmon/axiado-pwm-fan.c
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021-2026 Axiado Corporation.
+ */
+
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/hwmon.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define AX_TACH_PWM_VAL_MAX 255
+
+/* TACH Register offsets */
+#define AX_TACH_CTRL_REG 0x00
+#define AX_TACH_TIMER_COUNT_REG 0x04
+#define AX_TACH_COUNT_REG 0x08
+#define AX_TACH_INT_STATUS_REG 0x0c
+
+#define AX_TACH_CTRL_ENABLE BIT(0)
+#define AX_TACH_CTRL_INT_ENABLE BIT(1)
+
+#define AX_TACH_INT_PENDING BIT(0)
+
+struct axiado_pwm_fan_tach {
+ int irq;
+ u32 pulses_per_revolution;
+ u32 timer_count;
+ u32 count;
+};
+
+struct axiado_pwm_fan_ctx {
+ /* Serializes PWM updates with PM and shutdown operations. */
+ struct mutex pwm_lock;
+ /* Protects tachometer count updated from interrupt context. */
+ spinlock_t tach_lock;
+ struct pwm_device *pwm;
+ struct pwm_state pwm_state;
+ void __iomem *tach_base;
+ struct axiado_pwm_fan_tach tach;
+ unsigned int pwm_value;
+};
+
+static const struct hwmon_channel_info * const pwm_fan_info[] = {
+ HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT),
+ HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT),
+ NULL
+};
+
+static irqreturn_t axiado_tach_irq_handler(int irq, void *dev)
+{
+ struct axiado_pwm_fan_ctx *ctx = dev;
+ u32 status;
+
+ status = ioread32(ctx->tach_base + AX_TACH_INT_STATUS_REG);
+
+ if (!status)
+ return IRQ_NONE;
+
+ scoped_guard(spinlock_irqsave, &ctx->tach_lock) {
+ ctx->tach.count = ioread32(ctx->tach_base + AX_TACH_COUNT_REG);
+ }
+
+ iowrite32(AX_TACH_INT_PENDING, ctx->tach_base + AX_TACH_INT_STATUS_REG);
+
+ return IRQ_HANDLED;
+}
+
+static int axiado_set_pwm(struct axiado_pwm_fan_ctx *ctx, unsigned int pwm)
+{
+ struct pwm_state state;
+ int ret;
+
+ guard(mutex)(&ctx->pwm_lock);
+
+ if (ctx->pwm_value == pwm)
+ return 0;
+
+ state = ctx->pwm_state;
+ state.duty_cycle = DIV_ROUND_UP_ULL((u64)pwm * state.period,
+ AX_TACH_PWM_VAL_MAX);
+ state.enabled = pwm > 0;
+
+ ret = pwm_apply_might_sleep(ctx->pwm, &state);
+ if (ret)
+ return ret;
+
+ ctx->pwm_state = state;
+ ctx->pwm_value = pwm;
+
+ return 0;
+}
+
+static int axiado_pwm_fan_write(struct device *dev,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel, long val)
+{
+ struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+
+ if (type != hwmon_pwm || attr != hwmon_pwm_input)
+ return -EOPNOTSUPP;
+
+ if (val < 0 || val > AX_TACH_PWM_VAL_MAX)
+ return -EINVAL;
+
+ return axiado_set_pwm(ctx, val);
+}
+
+static unsigned int axiado_tach_get_rpm(struct axiado_pwm_fan_ctx *ctx)
+{
+ u32 pulses_per_revolution = ctx->tach.pulses_per_revolution;
+ u64 pulses, rpm;
+
+ scoped_guard(spinlock_irqsave, &ctx->tach_lock)
+ pulses = ctx->tach.count;
+
+ if (!pulses_per_revolution)
+ return 0;
+
+ rpm = pulses * 60;
+ do_div(rpm, pulses_per_revolution);
+
+ return rpm;
+}
+
+static int axiado_pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+
+ switch (type) {
+ case hwmon_pwm:
+ if (attr != hwmon_pwm_input)
+ return -EOPNOTSUPP;
+
+ *val = ctx->pwm_value;
+
+ return 0;
+
+ case hwmon_fan:
+ if (attr != hwmon_fan_input)
+ return -EOPNOTSUPP;
+
+ *val = axiado_tach_get_rpm(ctx);
+
+ return 0;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static umode_t axiado_pwm_fan_is_visible(const void *data,
+ enum hwmon_sensor_types type, u32 attr,
+ int channel)
+{
+ if (type == hwmon_fan && attr == hwmon_fan_input)
+ return 0444;
+
+ if (type == hwmon_pwm && attr == hwmon_pwm_input)
+ return 0644;
+
+ return 0;
+}
+
+static const struct hwmon_ops pwm_fan_hwmon_ops = {
+ .is_visible = axiado_pwm_fan_is_visible,
+ .read = axiado_pwm_fan_read,
+ .write = axiado_pwm_fan_write,
+};
+
+static const struct hwmon_chip_info pwm_fan_chip_info = {
+ .ops = &pwm_fan_hwmon_ops,
+ .info = pwm_fan_info,
+};
+
+static int axiado_pwm_apply_disabled(struct axiado_pwm_fan_ctx *ctx)
+{
+ struct pwm_state state;
+
+ guard(mutex)(&ctx->pwm_lock);
+
+ if (!ctx->pwm_value)
+ return 0;
+
+ state = ctx->pwm_state;
+ state.duty_cycle = 0;
+ state.enabled = false;
+
+ return pwm_apply_might_sleep(ctx->pwm, &state);
+}
+
+static void axiado_pwm_disable(void *data)
+{
+ struct axiado_pwm_fan_ctx *ctx = data;
+
+ axiado_pwm_apply_disabled(ctx);
+}
+
+static void axiado_tach_enable(struct axiado_pwm_fan_ctx *ctx)
+{
+ iowrite32(AX_TACH_INT_PENDING,
+ ctx->tach_base + AX_TACH_INT_STATUS_REG);
+ iowrite32(ctx->tach.timer_count,
+ ctx->tach_base + AX_TACH_TIMER_COUNT_REG);
+ iowrite32(AX_TACH_CTRL_ENABLE | AX_TACH_CTRL_INT_ENABLE,
+ ctx->tach_base + AX_TACH_CTRL_REG);
+}
+
+static void axiado_tach_disable(void *data)
+{
+ struct axiado_pwm_fan_ctx *ctx = data;
+
+ iowrite32(0, ctx->tach_base + AX_TACH_CTRL_REG);
+ iowrite32(AX_TACH_INT_PENDING,
+ ctx->tach_base + AX_TACH_INT_STATUS_REG);
+}
+
+static int axiado_pwm_fan_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *fan_node;
+ struct axiado_pwm_fan_ctx *ctx;
+ unsigned long tach_clk_rate;
+ struct device *hwmon;
+ struct clk *tach_clk;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ mutex_init(&ctx->pwm_lock);
+ spin_lock_init(&ctx->tach_lock);
+
+ ctx->tach_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(ctx->tach_base))
+ return PTR_ERR(ctx->tach_base);
+
+ fan_node = device_get_named_child_node(dev, "fan");
+ if (!fan_node)
+ return dev_err_probe(dev, -EINVAL,
+ "Missing fan child node\n");
+
+ ctx->pwm = devm_fwnode_pwm_get(dev, fan_node, NULL);
+ if (IS_ERR(ctx->pwm)) {
+ ret = dev_err_probe(dev, PTR_ERR(ctx->pwm),
+ "Could not get PWM\n");
+ goto put_fan_node;
+ }
+
+ ctx->tach.pulses_per_revolution = 2;
+
+ ret = fwnode_property_read_u32(fan_node, "pulses-per-revolution",
+ &ctx->tach.pulses_per_revolution);
+ if (ret && ret != -EINVAL) {
+ ret = dev_err_probe(dev, ret,
+ "Failed to read pulses-per-revolution\n");
+ goto put_fan_node;
+ }
+
+ if (!ctx->tach.pulses_per_revolution) {
+ ret = dev_err_probe(dev, -EINVAL,
+ "pulses-per-revolution cannot be zero\n");
+ goto put_fan_node;
+ }
+
+ fwnode_handle_put(fan_node);
+
+ platform_set_drvdata(pdev, ctx);
+
+ pwm_init_state(ctx->pwm, &ctx->pwm_state);
+
+ if (!ctx->pwm_state.period)
+ return dev_err_probe(dev, -EINVAL, "PWM period is zero\n");
+
+ tach_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(tach_clk))
+ return dev_err_probe(dev, PTR_ERR(tach_clk),
+ "Failed to get tachometer clock\n");
+
+ tach_clk_rate = clk_get_rate(tach_clk);
+ if (!tach_clk_rate || tach_clk_rate > U32_MAX)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid tachometer clock rate: %lu\n",
+ tach_clk_rate);
+
+ ctx->tach.timer_count = tach_clk_rate;
+
+ ctx->tach.irq = platform_get_irq(pdev, 0);
+ if (ctx->tach.irq < 0)
+ return dev_err_probe(dev, ctx->tach.irq,
+ "Failed to get tachometer IRQ\n");
+
+ ret = devm_request_irq(dev, ctx->tach.irq, axiado_tach_irq_handler, 0,
+ dev_name(dev), ctx);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request tach IRQ\n");
+
+ ret = devm_add_action_or_reset(dev, axiado_tach_disable, ctx);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "Fan tachometer: irq=%d, pulses_per_revolution=%u\n",
+ ctx->tach.irq, ctx->tach.pulses_per_revolution);
+
+ axiado_tach_enable(ctx);
+
+ ret = axiado_set_pwm(ctx, AX_TACH_PWM_VAL_MAX);
+
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to configure PWM\n");
+
+ ret = devm_add_action_or_reset(dev, axiado_pwm_disable, ctx);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to add PWM disable action\n");
+
+ hwmon = devm_hwmon_device_register_with_info(dev, "axpwmfan", ctx,
+ &pwm_fan_chip_info, NULL);
+ if (IS_ERR(hwmon))
+ return dev_err_probe(dev, PTR_ERR(hwmon),
+ "Failed to register hwmon device\n");
+
+ return 0;
+
+put_fan_node:
+ fwnode_handle_put(fan_node);
+
+ return ret;
+}
+
+static int axiado_pwm_fan_disable(struct device *dev)
+{
+ struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ ret = axiado_pwm_apply_disabled(ctx);
+ if (ret)
+ return ret;
+
+ axiado_tach_disable(ctx);
+ synchronize_irq(ctx->tach.irq);
+
+ return 0;
+}
+
+static void axiado_pwm_fan_shutdown(struct platform_device *pdev)
+{
+ struct axiado_pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
+
+ /* Best effort during shutdown. */
+ axiado_pwm_apply_disabled(ctx);
+
+ axiado_tach_disable(ctx);
+ synchronize_irq(ctx->tach.irq);
+}
+
+static int axiado_pwm_fan_suspend(struct device *dev)
+{
+ return axiado_pwm_fan_disable(dev);
+}
+
+static int axiado_pwm_fan_resume(struct device *dev)
+{
+ struct axiado_pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+ int ret;
+
+ axiado_tach_enable(ctx);
+
+ scoped_guard(mutex, &ctx->pwm_lock)
+ ret = pwm_apply_might_sleep(ctx->pwm, &ctx->pwm_state);
+
+ if (ret)
+ axiado_tach_disable(ctx);
+
+ return ret;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(axiado_pwm_fan_pm,
+ axiado_pwm_fan_suspend,
+ axiado_pwm_fan_resume);
+
+static const struct of_device_id axiado_pwm_fan_match[] = {
+ { .compatible = "axiado,ax3000-pwm-fan" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axiado_pwm_fan_match);
+
+static struct platform_driver axiado_pwm_fan_driver = {
+ .probe = axiado_pwm_fan_probe,
+ .shutdown = axiado_pwm_fan_shutdown,
+ .driver = {
+ .name = "axiado-pwm-fan",
+ .pm = pm_sleep_ptr(&axiado_pwm_fan_pm),
+ .of_match_table = axiado_pwm_fan_match,
+ },
+};
+module_platform_driver(axiado_pwm_fan_driver);
+
+MODULE_AUTHOR("Axiado Corporation");
+MODULE_DESCRIPTION("Axiado PWM fan controller driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller
2026-09-03 8:38 ` [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller Petar Stepanovic
@ 2026-09-03 8:43 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-03 8:43 UTC (permalink / raw)
To: Petar Stepanovic; +Cc: robh, conor+dt, linux-hwmon, devicetree
> Add a Devicetree binding for the PWM fan controller found on the
> Axiado AX3000 and AX3005 SoCs.
>
> The controller measures fan speed using a hardware tachometer block.
> Fan speed is controlled through a PWM signal supplied by an external
> PWM controller.
>
> Add a MAINTAINERS entry for the binding.
>
> Signed-off-by: Petar Stepanovic <pstepanovic@axiado.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260903-axiado-ax3000-pwm-fan-v2-0-ef43df46163e@axiado.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver
2026-09-03 8:38 ` [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver Petar Stepanovic
@ 2026-09-03 8:48 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-03 8:48 UTC (permalink / raw)
To: Petar Stepanovic; +Cc: devicetree, linux-hwmon, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The interrupt handler incorrectly returns IRQ_HANDLED for unhandled device interrupts, potentially causing an IRQ storm.
--
commit ac0edffb2c72d464c05c340c5df147fdcf10b0f4
Author: Petar Stepanovic <pstepanovic@axiado.com>
hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver
This commit adds support for the PWM fan controller found on the Axiado AX3000
and AX3005 SoCs. It measures fan speed via a hardware tachometer interrupt and
controls the fan via an external PWM signal.
> diff --git a/drivers/hwmon/axiado-pwm-fan.c b/drivers/hwmon/axiado-pwm-fan.c
> new file mode 100644
> index 0000000000000..ac153afded47a
> --- /dev/null
> +++ b/drivers/hwmon/axiado-pwm-fan.c
[ ... ]
> +static irqreturn_t axiado_tach_irq_handler(int irq, void *dev)
> +{
> + struct axiado_pwm_fan_ctx *ctx = dev;
> + u32 status;
> +
> + status = ioread32(ctx->tach_base + AX_TACH_INT_STATUS_REG);
> +
> + if (!status)
> + return IRQ_NONE;
[Severity: High]
Will this handler cause an IRQ storm if the hardware asserts an interrupt
where the status is non-zero but AX_TACH_INT_PENDING is not set?
Since the condition only checks if status is zero, an unexpected bit could
bypass the IRQ_NONE return path. The handler would then proceed to clear only
the AX_TACH_INT_PENDING bit and return IRQ_HANDLED, leaving the actual
hardware interrupt active and bypassing the kernel's spurious IRQ protection.
Should this explicitly check if the AX_TACH_INT_PENDING bit is set before
processing the interrupt?
> +
> + scoped_guard(spinlock_irqsave, &ctx->tach_lock) {
> + ctx->tach.count = ioread32(ctx->tach_base + AX_TACH_COUNT_REG);
> + }
> +
> + iowrite32(AX_TACH_INT_PENDING, ctx->tach_base + AX_TACH_INT_STATUS_REG);
> +
> + return IRQ_HANDLED;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260903-axiado-ax3000-pwm-fan-v2-0-ef43df46163e@axiado.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-03 8:48 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 8:38 [PATCH v2 0/2] hwmon: add Axiado AX3000 and AX3005 PWM fan support Petar Stepanovic
2026-09-03 8:38 ` [PATCH v2 1/2] dt-bindings: hwmon: add Axiado AX3000 and AX3005 PWM fan controller Petar Stepanovic
2026-09-03 8:43 ` sashiko-bot
2026-09-03 8:38 ` [PATCH v2 2/2] hwmon: add Axiado AX3000 and AX3005 PWM fan controller driver Petar Stepanovic
2026-09-03 8:48 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox