Re: [PATCH v2 2/2] hwmon: (starfive-fan-tach) Add fan tach driver for StarFive JHB100

Changhuang Liang <[email protected]>
Newsgroups org.kernel.vger.linux-hwmon,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel
Message-ID <ZQ4PR01MB12025F1BFB7E3F897228AA40F2A42@ZQ4PR01MB1202.CHNPR01.prod.partner.outlook.cn>
Hi, Guenter

Thanks for the review.

> On 8/19/26 02:16, Changhuang Liang wrote:
> > Add fan tach driver for StarFive JHB100 SoC.
> >
> > The controller supports up to 16 independent fan tachometer inputs and
> > reports fan speed in RPM through the hwmon sysfs interface, along with
> > stall and low-speed alarms.
> >
> > For this controller, the special clock and reset operation sequence is:
> >    probe: clk_prepare_enable() than reset_control_deassert()
> >    remove: clk_disable_unprepare() than reset_control_assert()
> 
> s/than/then/
> 
> >
> > Co-developed-by: William Qiu <[email protected]>
> > Signed-off-by: William Qiu <[email protected]>
> > Signed-off-by: Changhuang Liang <[email protected]>
> > ---
> >   Documentation/hwmon/index.rst             |   1 +
> >   Documentation/hwmon/starfive-fan-tach.rst |  34 ++
> >   MAINTAINERS                               |   8 +
> >   drivers/hwmon/Kconfig                     |  14 +
> >   drivers/hwmon/Makefile                    |   1 +
> >   drivers/hwmon/starfive-fan-tach.c         | 561
> ++++++++++++++++++++++
> >   6 files changed, 619 insertions(+)
> >   create mode 100644 Documentation/hwmon/starfive-fan-tach.rst
> >   create mode 100644 drivers/hwmon/starfive-fan-tach.c
> >
> > diff --git a/Documentation/hwmon/index.rst
> > b/Documentation/hwmon/index.rst index 29130df44d12..0d64e4cf1051
> > 100644
> > --- a/Documentation/hwmon/index.rst
> > +++ b/Documentation/hwmon/index.rst
> > @@ -253,6 +253,7 @@ Hardware Monitoring Kernel Drivers
> >      smsc47m1
> >      sparx5-temp
> >      spd5118
> > +   starfive-fan-tach
> >      stpddc60
> >      surface_fan
> >      sy7636a-hwmon
> > diff --git a/Documentation/hwmon/starfive-fan-tach.rst
> > b/Documentation/hwmon/starfive-fan-tach.rst
> > new file mode 100644
> > index 000000000000..5ca03b52ad58
> > --- /dev/null
> > +++ b/Documentation/hwmon/starfive-fan-tach.rst
> > @@ -0,0 +1,34 @@
> > +.. SPDX-License-Identifier: GPL-2.0
> > +
> > +Kernel driver starfive-fan-tach
> > +===============================
> > +
> > +Supported chips:
> > +
> > +  * StarFive JHB100
> > +
> > +    Prefix: 'starfive_fan_tach'
> > +
> > +    Addresses scanned: -
> > +
> > +Authors:
> > +      - Changhuang Liang <[email protected]>
> > +
> > +Description
> > +-----------
> > +
> > +This driver implements support for the fan tachometer controller
> > +found on the StarFive JHB100 SoC. The controller supports up to 16
> > +independent fan tachometer inputs. Each tachometer channel measures
> > +the number of pulses within a fixed 100 ms window.
> > +
> > +Sysfs entries
> > +-------------
> > +
> > +====================
> =======================================================
> > +fan[1-16]_input      Fan speed in RPM (read-only)
> > +fan[1-16]_min        Lower fan speed limit in RPM (read/write)
> > +fan[1-16]_enable     Enable/disable the tachometer channel
> (read/write)
> > +fan[1-16]_fault      Fan stall indication (read-only)
> > +fan[1-16]_min_alarm  Fan speed below fan[1-16]_min (read-only)
> > +====================
> > +=======================================================
> > diff --git a/MAINTAINERS b/MAINTAINERS index
> > c545aa121d51..83318d2e3e2e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -25897,6 +25897,14 @@ S:	Supported
> >   F:
> 	Documentation/devicetree/bindings/interrupt-controller/starfive,jhb100
> -intc.yaml
> >   F:	drivers/irqchip/irq-starfive-jhb100-intc.c
> >
> > +STARFIVE JHB100 FAN-TACH DRIVER
> > +M:	Changhuang Liang <[email protected]>
> > +L:	[email protected]
> > +S:	Supported
> > +F:
> 	Documentation/devicetree/bindings/hwmon/starfive,jhb100-fan-tach.ya
> ml
> > +F:	Documentation/hwmon/starfive-fan-tach.rst
> > +F:	drivers/hwmon/starfive-fan-tach.c
> > +
> >   STATIC BRANCH/CALL
> >   M:	Peter Zijlstra <[email protected]>
> >   M:	Josh Poimboeuf <[email protected]>
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index
> > 5c2d3ff5fce8..2c0000f8e777 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -2383,6 +2383,20 @@ config SENSORS_SPD5118_DETECT
> >
> >   	  If unsure, say Y.
> >
> > +config SENSORS_STARFIVE_FAN_TACH
> > +	tristate "StarFive Fan-Tach driver"
> > +	depends on ARCH_STARFIVE || COMPILE_TEST
> > +	depends on HAS_IOMEM
> > +	help
> > +	  This driver provides support for the fan tachometer controller
> > +	  found on StarFive SoCs such as the JHB100. The controller supports
> > +	  up to 16 independent fan tachometer inputs and reports fan speed
> > +	  in RPM through the hwmon sysfs interface, along with stall and
> > +	  low-speed alarms.
> > +
> > +	  This driver can also be built as a module. If so, the module
> > +	  will be called starfive-fan-tach.
> > +
> >   config SENSORS_TC74
> >   	tristate "Microchip TC74"
> >   	depends on I2C
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index
> > 63effc0ab8d1..80b2704b94e5 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -223,6 +223,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1)	+=
> smsc47m1.o
> >   obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
> >   obj-$(CONFIG_SENSORS_SPARX5)	+= sparx5-temp.o
> >   obj-$(CONFIG_SENSORS_SPD5118)	+= spd5118.o
> > +obj-$(CONFIG_SENSORS_STARFIVE_FAN_TACH)	+= starfive-fan-tach.o
> >   obj-$(CONFIG_SENSORS_STTS751)	+= stts751.o
> >   obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o
> >   obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o diff --git
> > a/drivers/hwmon/starfive-fan-tach.c
> > b/drivers/hwmon/starfive-fan-tach.c
> > new file mode 100644
> > index 000000000000..1e6399db424d
> > --- /dev/null
> > +++ b/drivers/hwmon/starfive-fan-tach.c
> > @@ -0,0 +1,561 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * FAN-TACH controller driver for StarFive JHB100
> > + *
> > + * Copyright (C) 2018-2026 StarFive Technology Co., Ltd.
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/completion.h>
> > +#include <linux/delay.h>
> > +#include <linux/errno.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/jiffies.h>
> > +#include <linux/kernel.h>
> > +#include <linux/math64.h>
> > +#include <linux/module.h>
> > +#include <linux/mutex.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/reset.h>
> > +#include <linux/spinlock.h>
> > +
> > +#define STARFIVE_FAN_TACH_CH			16
> > +
> > +/* Fan-tach register offest */
> > +#define STARFIVE_FAN_TACH_STATUS		0x0c
> > +
> > +#define STARFIVE_FAN_TACH_SPEED(ch)		(((ch) * 0x04) + 0x10)
> > +#define STARFIVE_FAN_TACH_SPEED_VALID		BIT(31)
> > +#define STARFIVE_FAN_TACH_VALUE_MASK		GENMASK(30, 0)
> > +
> > +#define STARFIVE_FAN_TACH_THRESHOLD(ch)		(((ch) * 0x04) +
> 0x50)
> > +
> > +#define STARFIVE_FAN_TACH_INT_EN		0x90
> > +#define STARFIVE_FAN_TACH_STALL_INT_MASK	GENMASK(15, 0)
> > +#define STARFIVE_FAN_TACH_SLOW_INT_MASK		GENMASK(31, 16)
> > +#define STARFIVE_FAN_TACH_STALL_INT(ch)		BIT(ch)
> > +#define STARFIVE_FAN_TACH_SLOW_INT(ch)		(BIT(ch) << 16)
> > +
> > +#define STARFIVE_FAN_TACH_MEASURE_TIME		0x94
> > +
> > +#define STARFIVE_FAN_TACH_CH_EN			0x98
> > +#define STARFIVE_FAN_TACH_EN(ch)		BIT(ch)
> > +
> > +#define STARFIVE_FAN_DEFAULT_PULSE_PR		2
> > +#define STARFIVE_FAN_DEFAULT_MEASURE_RATIO	10
> > +#define STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME	(60 *
> STARFIVE_FAN_DEFAULT_MEASURE_RATIO)
> > +
> > +#define STARFIVE_FAN_TACH_TIMEOUT \
> > +	(USEC_PER_SEC / STARFIVE_FAN_DEFAULT_MEASURE_RATIO)
> > +
> > +#define STARFIVE_FAN_TACH_TIMEOUT_JIFFIES \
> > +	(msecs_to_jiffies(1000) / STARFIVE_FAN_DEFAULT_MEASURE_RATIO)
> > +
> > +#define FAN_ATTRIBUTE_SET \
> > +	(HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_ENABLE | \
> > +	 HWMON_F_FAULT | HWMON_F_MIN_ALARM)
> > +
> > +struct starfive_fan_tach_data {
> > +	struct device *dev;
> > +	/*
> > +	 * Serialises the read-modify-write sequences on CH_EN and INT_EN,
> > +	 * and the arm/disarm window of the fault/min_alarm read paths.
> > +	 */
> > +	struct mutex lock;
> 
> I don't see why this would be needed on top of the hwmon subsystem lock.

Will drop it.

> 
> > +	void __iomem *regs;
> > +	struct reset_control *rst;
> > +	struct clk *clk;
> > +	bool tach_present[STARFIVE_FAN_TACH_CH];
> > +	unsigned long clk_rate; /* APB clock frequency */
> > +	u8 pulses_per_rev[STARFIVE_FAN_TACH_CH];
> > +	struct completion comp_stall[STARFIVE_FAN_TACH_CH];
> > +	struct completion comp_slow[STARFIVE_FAN_TACH_CH];
> > +	u8 fan_stall[STARFIVE_FAN_TACH_CH];
> > +	u8 fan_slow[STARFIVE_FAN_TACH_CH];
> > +	u32 armed_stall;
> > +	u32 armed_slow;
> > +	/* protects fan_stall[]/fan_slow[]/armed_* and the STATUS register */
> > +	spinlock_t irq_lock;
> > +};
> > +
> > +static void starfive_fan_tach_ch_enable(struct starfive_fan_tach_data
> *priv, u8 tach_ch,
> > +					bool enable)
> > +{
> > +	if (enable) {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_CH_EN) |
> > +		       STARFIVE_FAN_TACH_EN(tach_ch),
> > +		       priv->regs + STARFIVE_FAN_TACH_CH_EN);
> > +	} else {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_CH_EN) &
> > +		       ~(STARFIVE_FAN_TACH_EN(tach_ch)),
> > +		       priv->regs + STARFIVE_FAN_TACH_CH_EN);
> > +	}
> > +}
> > +
> > +static void starfive_fan_tach_ch_stall_unmask(struct
> starfive_fan_tach_data *priv, u8 tach_ch,
> > +					      bool unmask)
> > +{
> > +	if (unmask) {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) |
> > +		       STARFIVE_FAN_TACH_STALL_INT(tach_ch),
> > +		       priv->regs + STARFIVE_FAN_TACH_INT_EN);
> > +	} else {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) &
> > +		       ~STARFIVE_FAN_TACH_STALL_INT(tach_ch),
> > +		       priv->regs + STARFIVE_FAN_TACH_INT_EN);
> > +	}
> > +}
> > +
> > +static void starfive_fan_tach_ch_slow_unmask(struct
> starfive_fan_tach_data *priv, u8 tach_ch,
> > +					     bool unmask)
> > +{
> > +	if (unmask) {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) |
> > +		       STARFIVE_FAN_TACH_SLOW_INT(tach_ch),
> > +		       priv->regs + STARFIVE_FAN_TACH_INT_EN);
> > +	} else {
> > +		writel(readl(priv->regs + STARFIVE_FAN_TACH_INT_EN) &
> > +		       ~STARFIVE_FAN_TACH_SLOW_INT(tach_ch),
> > +		       priv->regs + STARFIVE_FAN_TACH_INT_EN);
> > +	}
> > +}
> > +
> > +static u32 starfive_fan_tach_rpm_to_val(struct starfive_fan_tach_data
> > +*priv, u8 tach_ch, u32 rpm) {
> > +	u64 tach_val;
> > +	u8 ppr;
> > +
> > +	ppr = priv->pulses_per_rev[tach_ch];
> > +
> > +	tach_val = (u64)rpm * ppr;
> > +	tach_val = div_u64(tach_val,
> STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME);
> > +
> > +	return min_t(u64, tach_val, STARFIVE_FAN_TACH_VALUE_MASK); }
> > +
> > +static long starfive_fan_tach_val_to_rpm(struct starfive_fan_tach_data
> *priv, u8 tach_ch,
> > +					 u32 tach_val)
> > +{
> > +	u64 rpm;
> > +	u8 ppr;
> > +
> > +	ppr = priv->pulses_per_rev[tach_ch];
> > +
> > +	rpm = (u64)tach_val * STARFIVE_FAN_DEFAULT_RPM_PAUSE_TIME;
> > +
> > +	return div_u64(rpm, ppr);
> > +}
> > +
> > +static int starfive_fan_tach_get_rpm(struct starfive_fan_tach_data *priv,
> > +				     u8 tach_ch, long *rpm)
> > +{
> > +	u32 val;
> > +	int ret;
> > +
> > +	ret = readl_poll_timeout(priv->regs +
> STARFIVE_FAN_TACH_SPEED(tach_ch),
> > +				 val, val & STARFIVE_FAN_TACH_SPEED_VALID,
> > +				 100, STARFIVE_FAN_TACH_TIMEOUT);
> > +	if (ret)
> > +		return -ENODATA;
> > +
> > +	val = FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val);
> > +	if (!val) {
> > +		/* Sampling = 0 may mean no valid capture; wait 1.5 more cycles for
> valid read. */
> > +		fsleep(STARFIVE_FAN_TACH_TIMEOUT +
> STARFIVE_FAN_TACH_TIMEOUT / 2);
> > +		ret = readl_poll_timeout(priv->regs +
> STARFIVE_FAN_TACH_SPEED(tach_ch),
> > +					 val, val & STARFIVE_FAN_TACH_SPEED_VALID,
> > +					 100, STARFIVE_FAN_TACH_TIMEOUT);
> > +		if (ret)
> > +			return -ENODATA;
> > +
> > +		val = FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val);
> > +	}
> > +
> > +	*rpm = starfive_fan_tach_val_to_rpm(priv, tach_ch, val);
> > +
> > +	return 0;
> > +}
> > +
> > +static long starfive_fan_tach_get_rpm_threshold(struct
> starfive_fan_tach_data *priv,
> > +						u8 tach_ch)
> > +{
> > +	u32 val;
> > +
> > +	val = readl(priv->regs + STARFIVE_FAN_TACH_THRESHOLD(tach_ch));
> > +
> > +	val = FIELD_GET(STARFIVE_FAN_TACH_VALUE_MASK, val);
> > +
> > +	return starfive_fan_tach_val_to_rpm(priv, tach_ch, val); }
> > +
> > +static void starfive_set_tach_rpm_threshold(struct starfive_fan_tach_data
> *priv,
> > +					    u8 tach_ch, u32 val)
> > +{
> > +	u32 reg_val;
> > +
> > +	reg_val = starfive_fan_tach_rpm_to_val(priv, tach_ch, val);
> > +	reg_val = clamp_val(reg_val, 1, STARFIVE_FAN_TACH_VALUE_MASK);
> > +
> > +	writel(reg_val, priv->regs + STARFIVE_FAN_TACH_THRESHOLD(tach_ch));
> > +}
> > +
> > +static int starfive_fan_tach_get_ch_en(struct starfive_fan_tach_data
> *priv,
> > +				       u8 tach_ch)
> 
> Why not bool ?
> 
> > +{
> > +	u32 enable;
> > +
> > +	enable = readl(priv->regs + STARFIVE_FAN_TACH_CH_EN);
> > +
> > +	return (enable & (1 << tach_ch)) ? 1 : 0;
> 
> 	return !!(enable & STARFIVE_FAN_TACH_EN(tach_ch));
> 
> > +}
> > +
> > +static int starfive_fan_tach_hwmon_read(struct device *dev,
> > +					enum hwmon_sensor_types type, u32 attr,
> > +					int channel, long *val)
> > +{
> > +	struct starfive_fan_tach_data *priv = dev_get_drvdata(dev);
> > +	int ret = 0;
> > +
> > +	switch (attr) {
> > +	case hwmon_fan_fault:
> > +		scoped_guard(mutex, &priv->lock) {
> > +			scoped_guard(spinlock_irqsave, &priv->irq_lock) {
> > +				writel(STARFIVE_FAN_TACH_STALL_INT(channel),
> > +				       priv->regs + STARFIVE_FAN_TACH_STATUS);
> > +				/* clear fan_stall first */
> > +				priv->fan_stall[channel] = 0;
> > +				reinit_completion(&priv->comp_stall[channel]);
> > +				priv->armed_stall |= BIT(channel);
> > +			}
> > +
> > +			starfive_fan_tach_ch_stall_unmask(priv, channel, true);
> > +
> > +			/* Waiting for hardware to measure */
> > +			wait_for_completion_timeout(&priv->comp_stall[channel],
> > +						    2 *
> STARFIVE_FAN_TACH_TIMEOUT_JIFFIES);
> > +
> > +			starfive_fan_tach_ch_stall_unmask(priv, channel, false);
> > +
> > +			scoped_guard(spinlock_irqsave, &priv->irq_lock) {
> > +				priv->armed_stall &= ~BIT(channel);
> > +				*val = priv->fan_stall[channel];
> > +			}
> > +		}
> > +
> 
> I'd really be interested to see how long it takes to read the alarm and fault
> status of all fans. And, yes, Sashiko has a point: the attributes are supported to
> report a sticky status, not something that is calculated on the fly. Is this based
> on some heuristics ? I don't claim to understand how fault and min_alarm are
> calculated. A comment in the code describing how this works would be
> helpful.

There is no real calculation for fault and min_alarm, the hardware simply counts the 
corresponding pulses within a specified time period and reports the counts via interrupt 
when the counting period expires. I will try to modify it so that the previous statistical 
result is recorded, and when reading, the previous result is returned,  thus eliminating 
the need to wait for the measurement.

> 
> > +		break;
> > +	case hwmon_fan_input:
> > +		ret = starfive_fan_tach_get_ch_en(priv, channel);
> > +		if (!ret)
> > +			return -ENODATA;
> 
> I don't see why the assignment to ret would add value here.
> 
> 		if (!starfive_fan_tach_get_ch_en(priv, channel))
> 			return -ENODATA;
> 
> > +
> > +		ret = starfive_fan_tach_get_rpm(priv, channel, val);
> > +		if (ret < 0)
> > +			return ret;
> > +
> > +		break;
> > +	case hwmon_fan_min:
> > +		*val = starfive_fan_tach_get_rpm_threshold(priv, channel);
> > +
> > +		break;
> > +	case hwmon_fan_min_alarm:
> > +		scoped_guard(mutex, &priv->lock) {
> > +			scoped_guard(spinlock_irqsave, &priv->irq_lock) {
> > +				writel(STARFIVE_FAN_TACH_SLOW_INT(channel),
> > +				       priv->regs + STARFIVE_FAN_TACH_STATUS);
> > +				/* clear fan_slow first */
> > +				priv->fan_slow[channel] = 0;
> > +				reinit_completion(&priv->comp_slow[channel]);
> > +				priv->armed_slow |= BIT(channel);
> > +			}
> > +
> > +			starfive_fan_tach_ch_slow_unmask(priv, channel, true);
> > +
> > +			/* Waiting for hardware to measure */
> > +			wait_for_completion_timeout(&priv->comp_slow[channel],
> > +						    2 *
> STARFIVE_FAN_TACH_TIMEOUT_JIFFIES);
> > +
> > +			starfive_fan_tach_ch_slow_unmask(priv, channel, false);
> > +
> > +			scoped_guard(spinlock_irqsave, &priv->irq_lock) {
> > +				priv->armed_slow &= ~BIT(channel);
> > +				*val = priv->fan_slow[channel];
> > +			}
> > +		}
> > +
> > +		break;
> > +	case hwmon_fan_enable:
> > +		*val = starfive_fan_tach_get_ch_en(priv, channel);
> > +
> > +		break;
> > +	default:
> > +		return -EOPNOTSUPP;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int starfive_fan_tach_hwmon_write(struct device *dev,
> > +					 enum hwmon_sensor_types type, u32 attr,
> > +					 int channel, long val)
> > +{
> > +	struct starfive_fan_tach_data *priv = dev_get_drvdata(dev);
> > +
> > +	guard(mutex)(&priv->lock);
> > +
> > +	switch (attr) {
> > +	case hwmon_fan_min:
> > +		if (val < 0 || val > U32_MAX)
> > +			return -EINVAL;
> > +
> > +		starfive_set_tach_rpm_threshold(priv, channel, val);
> > +		break;
> > +	case hwmon_fan_enable:
> > +		if (val != 0 && val != 1)
> > +			return -EINVAL;
> > +
> > +		starfive_fan_tach_ch_enable(priv, channel, val);
> > +		break;
> > +	default:
> > +		return -EOPNOTSUPP;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static umode_t starfive_fan_tach_dev_is_visible(const void *drvdata,
> > +						enum hwmon_sensor_types type,
> > +						u32 attr, int channel)
> > +{
> > +	const struct starfive_fan_tach_data *priv = drvdata;
> > +
> > +	if (!priv->tach_present[channel])
> > +		return 0;
> > +
> > +	switch (attr) {
> > +	case hwmon_fan_input:
> > +	case hwmon_fan_fault:
> > +	case hwmon_fan_min_alarm:
> > +		return 0444;
> > +	case hwmon_fan_min:
> > +	case hwmon_fan_enable:
> > +		return 0644;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct hwmon_ops starfive_fan_tach_ops = {
> > +	.is_visible = starfive_fan_tach_dev_is_visible,
> > +	.read = starfive_fan_tach_hwmon_read,
> > +	.write = starfive_fan_tach_hwmon_write };
> > +
> > +static const struct hwmon_channel_info *starfive_fan_tach_info[] = {
> > +	HWMON_CHANNEL_INFO(fan,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET,
> > +			   FAN_ATTRIBUTE_SET, FAN_ATTRIBUTE_SET),
> > +	NULL
> > +};
> > +
> > +static const struct hwmon_chip_info starfive_fan_tach_chip_info = {
> > +	.ops = &starfive_fan_tach_ops,
> > +	.info = starfive_fan_tach_info,
> > +};
> > +
> > +static int starfive_fan_tach_create_fan(struct device *dev, struct
> device_node *child,
> > +					struct starfive_fan_tach_data *priv) {
> > +	u8 tach_ch[STARFIVE_FAN_TACH_CH];
> > +	int ret, count;
> > +	u32 ppr, index;
> > +
> > +	count = of_property_count_u8_elems(child, "tach-ch");
> > +	if (count < 1 || count > STARFIVE_FAN_TACH_CH)
> > +		return -EINVAL;
> > +
> > +	ret = of_property_read_u8_array(child, "tach-ch", tach_ch, count);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* Parse pulses-per-revolution, default to 2 if not specified */
> > +	ppr = STARFIVE_FAN_DEFAULT_PULSE_PR;
> > +	of_property_read_u32(child, "pulses-per-revolution", &ppr);
> > +
> > +	if (!ppr || ppr > 4) {
> > +		dev_err(dev, "Invalid pulses-per-revolution %u, must be 1-4\n",
> ppr);
> > +		return -EINVAL;
> > +	}
> > +
> > +	for (index = 0; index < count; index++) {
> > +		u8 ch = tach_ch[index];
> > +
> > +		if (ch >= STARFIVE_FAN_TACH_CH) {
> > +			dev_warn(priv->dev, "Invalid tach-ch %d, skipping\n", ch);
> > +			continue;
> 
> This should also return an error. Also, this is the only use of priv->dev, and
> priv->dev == dev. priv->dev is therefore unnecessary.
> 

Thank you for pointing that out. I noticed that the `struct device *dev` passed in the function 
starfive_fan_tach_create_fan() is somewhat redundant, so I plan to remove it directly and 
keep `priv->dev` instead.

> > +		}
> > +
> > +		priv->pulses_per_rev[ch] = ppr;
> 
> What is the point of storing the same ppr value separately for each channel ?
> 
> > +		priv->tach_present[ch] = true;
> > +		starfive_fan_tach_ch_enable(priv, ch, true);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static irqreturn_t starfive_fan_tach_irq(int irq, void *dev_id) {
> > +	struct starfive_fan_tach_data *priv = dev_id;
> > +	u32 fan_status;
> > +	int i;
> > +
> > +	guard(spinlock)(&priv->irq_lock);
> > +
> > +	fan_status = readl(priv->regs + STARFIVE_FAN_TACH_STATUS);
> > +	if (!fan_status)
> > +		return IRQ_NONE;
> > +
> > +	writel(fan_status, priv->regs + STARFIVE_FAN_TACH_STATUS);
> > +
> > +	for (i = 0; i < STARFIVE_FAN_TACH_CH; i++) {
> > +		if (!priv->tach_present[i])
> > +			continue;
> > +
> > +		if ((fan_status & STARFIVE_FAN_TACH_STALL_INT(i)) &&
> > +		    (priv->armed_stall & BIT(i))) {
> > +			priv->fan_stall[i] = 1;
> > +			complete(&priv->comp_stall[i]);
> > +		}
> > +
> > +		if ((fan_status & STARFIVE_FAN_TACH_SLOW_INT(i)) &&
> > +		    (priv->armed_slow & BIT(i))) {
> > +			priv->fan_slow[i] = 1;
> > +			complete(&priv->comp_slow[i]);
> > +		}
> > +	}
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static void starfive_fan_tach_disable(void *data) {
> > +	struct starfive_fan_tach_data *priv = data;
> > +
> > +	clk_disable_unprepare(priv->clk);
> > +	reset_control_assert(priv->rst);
> > +}
> > +
> > +static int starfive_fan_tach_probe(struct platform_device *pdev) {
> > +	struct device *hwmon_dev, *dev = &pdev->dev;
> > +	struct starfive_fan_tach_data *priv;
> > +	int irq;
> > +	int ret;
> > +	u32 i;
> > +
> > +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	priv->dev = dev;
> > +	spin_lock_init(&priv->irq_lock);
> > +
> > +	for (i = 0; i < STARFIVE_FAN_TACH_CH; i++) {
> > +		init_completion(&priv->comp_stall[i]);
> > +		init_completion(&priv->comp_slow[i]);
> > +	}
> > +
> > +	priv->regs = devm_platform_ioremap_resource(pdev, 0);
> > +	if (IS_ERR(priv->regs))
> > +		return dev_err_probe(dev, PTR_ERR(priv->regs),
> > +				     "Unable to map IO resources\n");
> > +
> > +	priv->clk = devm_clk_get(dev, NULL);
> > +	if (IS_ERR(priv->clk))
> > +		return dev_err_probe(dev, PTR_ERR(priv->clk),
> > +				     "Unable to get fan tach's clock\n");
> > +
> > +	priv->rst = devm_reset_control_get_exclusive(dev, NULL);
> > +	if (IS_ERR(priv->rst))
> > +		return dev_err_probe(dev, PTR_ERR(priv->rst),
> > +				     "Unable to get fan tach's reset\n");
> > +
> > +	ret = clk_prepare_enable(priv->clk);
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "Failed to enable clock\n");
> > +
> > +	priv->clk_rate = clk_get_rate(priv->clk);
> > +	if (!priv->clk_rate) {
> > +		clk_disable_unprepare(priv->clk);
> > +		return dev_err_probe(dev, -EINVAL, "Unable to get clock's rate\n");
> > +	}
> > +
> > +	ret = reset_control_deassert(priv->rst);
> > +	if (ret) {
> > +		clk_disable_unprepare(priv->clk);
> > +		return dev_err_probe(dev, ret, "Failed to deassert reset\n");
> > +	}
> > +
> > +	ret = devm_add_action_or_reset(dev, starfive_fan_tach_disable, priv);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_mutex_init(dev, &priv->lock);
> > +	if (ret)
> > +		return ret;
> > +
> > +	writel(STARFIVE_FAN_TACH_STALL_INT_MASK |
> STARFIVE_FAN_TACH_SLOW_INT_MASK,
> > +	       priv->regs + STARFIVE_FAN_TACH_STATUS);
> > +	writel(priv->clk_rate / STARFIVE_FAN_DEFAULT_MEASURE_RATIO,
> > +	       priv->regs + STARFIVE_FAN_TACH_MEASURE_TIME);
> > +
> > +	for_each_child_of_node_scoped(dev->of_node, child) {
> > +		ret = starfive_fan_tach_create_fan(dev, child, priv);
> > +		if (ret)
> > +			return dev_err_probe(dev, ret, "Failed to create fan %pOF\n",
> child);
> > +	}
> > +
> > +	irq = platform_get_irq(pdev, 0);
> > +	if (irq < 0)
> > +		return dev_err_probe(dev, irq, "Failed to get IRQ\n");
> > +
> > +	ret = devm_request_irq(dev, irq, starfive_fan_tach_irq, 0, pdev->name,
> priv);
> > +	if (ret)
> > +		return ret;
> > +
> > +	hwmon_dev = devm_hwmon_device_register_with_info(dev,
> "starfive_fan_tach",
> > +							 priv, &starfive_fan_tach_chip_info,
> > +							 NULL);
> > +	return PTR_ERR_OR_ZERO(hwmon_dev);
> > +}
> > +
> > +static const struct of_device_id starfive_fan_tach_of_match[] = {
> > +	{ .compatible = "starfive,jhb100-fan-tach", },
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, starfive_fan_tach_of_match);
> > +
> > +static struct platform_driver starfive_fan_tach_driver = {
> > +	.probe = starfive_fan_tach_probe,
> > +	.driver	= {
> > +		.name = "starfive-fan-tach",
> > +		.of_match_table = starfive_fan_tach_of_match,
> > +	},
> > +};
> > +
> > +module_platform_driver(starfive_fan_tach_driver);
> > +
> > +MODULE_AUTHOR("William Qiu <[email protected]>");
> > +MODULE_AUTHOR("Changhuang Liang
> > +<[email protected]>");
> > +MODULE_DESCRIPTION("StarFive JHB100 Fan Tach device driver");
> > +MODULE_LICENSE("GPL");

Best Regards,
Changhuang
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.