linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [v2 0/3] upport pwm/tach driver for aspeed ast26xx
@ 2022-11-01  9:51 Billy Tsai
  2022-11-01  9:51 ` [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach Billy Tsai
                   ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Billy Tsai @ 2022-11-01  9:51 UTC (permalink / raw)
  To: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, billy_tsai,
	linux-hwmon, devicetree, linux-arm-kernel, linux-aspeed,
	linux-kernel, linux-pwm, BMC-SW, garnermic

Unlike the old design that the register setting of the TACH should based
on the configure of the PWM. In ast26xx, the dependency between pwm and
tach controller is eliminated and becomes a separate hardware block. One
is used to provide pwm output and another is used to monitor the frequency
of the input. Therefore, this patch serials implements them by writing the
two driver "pwm-aspeed-ast2600.c" and "tach-aspeed-ast2600.c". The former
is following the pwm subsystem which can apply the existed driver to
controller the fan(pwm-fan.c), beeper(pwm-beeper.c) and so on. The latter
is following the sysfs interface of hwmon to creat the node for fan
monitor.

Changes since v1:
- tach:
  - Add the document tach-aspeed-ast2600.rst
  - Use devm_* api to simplify the error cleanup.
  - Change hwmon register api to devm_hwmon_device_register_with_info

Billy Tsai (3):
  dt-bindings: Add bindings for aspeed pwm-tach.
  pwm: Add Aspeed ast2600 PWM support
  hwmon: Add Aspeed ast2600 TACH support

 .../bindings/hwmon/aspeed,ast2600-tach.yaml   |  48 ++
 .../bindings/mfd/aspeed,ast2600-pwm-tach.yaml |  76 +++
 .../bindings/pwm/aspeed,ast2600-pwm.yaml      |  64 +++
 Documentation/hwmon/tach-aspeed-ast2600.rst   |  28 +
 drivers/hwmon/Kconfig                         |   9 +
 drivers/hwmon/Makefile                        |   1 +
 drivers/hwmon/tach-aspeed-ast2600.c           | 484 ++++++++++++++++++
 drivers/pwm/Kconfig                           |  10 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-aspeed-ast2600.c              | 325 ++++++++++++
 10 files changed, 1046 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
 create mode 100644 Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml
 create mode 100644 Documentation/hwmon/tach-aspeed-ast2600.rst
 create mode 100644 drivers/hwmon/tach-aspeed-ast2600.c
 create mode 100644 drivers/pwm/pwm-aspeed-ast2600.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-01  9:51 [v2 0/3] upport pwm/tach driver for aspeed ast26xx Billy Tsai
@ 2022-11-01  9:51 ` Billy Tsai
  2022-11-01 18:40   ` Rob Herring
  2022-11-01  9:51 ` [v2 2/3] pwm: Add Aspeed ast2600 PWM support Billy Tsai
  2022-11-01  9:51 ` [v2 3/3] hwmon: Add Aspeed ast2600 TACH support Billy Tsai
  2 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-01  9:51 UTC (permalink / raw)
  To: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, billy_tsai,
	linux-hwmon, devicetree, linux-arm-kernel, linux-aspeed,
	linux-kernel, linux-pwm, BMC-SW, garnermic

This patch adds device binding for aspeed pwm-tach device which is a
multi-function device include pwm and tach function and pwm/tach device
bindings which should be the child-node of pwm-tach device.

Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
 .../bindings/hwmon/aspeed,ast2600-tach.yaml   | 48 ++++++++++++
 .../bindings/mfd/aspeed,ast2600-pwm-tach.yaml | 76 +++++++++++++++++++
 .../bindings/pwm/aspeed,ast2600-pwm.yaml      | 64 ++++++++++++++++
 3 files changed, 188 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
 create mode 100644 Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
 create mode 100644 Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml

diff --git a/Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml b/Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
new file mode 100644
index 000000000000..838200fae30e
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2021 Aspeed, Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/aspeed,ast2600-tach.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Aspeed Ast2600 Tach controller
+
+maintainers:
+  - Billy Tsai <billy_tsai@aspeedtech.com>
+
+description: |
+  The Aspeed Tach controller can support upto 16 fan input.
+  This module is part of the ast2600-pwm-tach multi-function device. For more
+  details see ../mfd/aspeed,ast2600-pwm-tach.yaml.
+
+properties:
+  compatible:
+    enum:
+      - aspeed,ast2600-tach
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  pinctrl-0: true
+
+  pinctrl-names:
+    const: default
+
+required:
+  - compatible
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties:
+  type: object
+  properties:
+    reg:
+      description:
+        The tach channel used for this node.
+      maxItems: 1
+
+  required:
+    - reg
diff --git a/Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml b/Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
new file mode 100644
index 000000000000..1eaf6fab2752
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2021 Aspeed, Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/aspeed,ast2600-pwm-tach.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: PWM Tach controller Device Tree Bindings
+
+description: |
+  The PWM Tach controller is represented as a multi-function device which
+  includes:
+    PWM
+    Tach
+
+maintainers:
+  - Billy Tsai <billy_tsai@aspeedtech.com>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - aspeed,ast2600-pwm-tach
+      - const: syscon
+      - const: simple-mfd
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - resets
+
+patternProperties:
+  "^pwm(@[0-9a-f]+)?$":
+    $ref: ../pwm/aspeed,ast2600-pwm.yaml
+
+  "^tach(@[0-9a-f]+)?$":
+    $ref: ../hwmon/aspeed,ast2600-tach.yaml
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/ast2600-clock.h>
+    pwm_tach: pwm_tach@1e610000 {
+      compatible = "aspeed,ast2600-pwm-tach", "syscon", "simple-mfd";
+      reg = <0x1e610000 0x100>;
+      clocks = <&syscon ASPEED_CLK_AHB>;
+      resets = <&syscon ASPEED_RESET_PWM>;
+
+      pwm: pwm {
+        compatible = "aspeed,ast2600-pwm";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        #pwm-cells = <3>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_pwm0_default>;
+      };
+
+      tach: tach {
+        compatible = "aspeed,ast2600-tach";
+        #address-cells = <1>;
+        #size-cells = <0>;
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_tach0_default>;
+      };
+    };
diff --git a/Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml b/Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml
new file mode 100644
index 000000000000..f501f8a769df
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2021 Aspeed, Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/aspeed,ast2600-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Aspeed Ast2600 PWM controller
+
+maintainers:
+  - Billy Tsai <billy_tsai@aspeedtech.com>
+
+description: |
+  The Aspeed PWM controller can support upto 16 PWM outputs.
+  This module is part of the ast2600-pwm-tach multi-function device. For more
+  details see ../mfd/aspeed,ast2600-pwm-tach.yaml.
+
+properties:
+  compatible:
+    enum:
+      - aspeed,ast2600-pwm
+
+  "#pwm-cells":
+    const: 3
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  pinctrl-0: true
+
+  pinctrl-names:
+    const: default
+
+required:
+  - compatible
+  - "#pwm-cells"
+  - "#address-cells"
+  - "#size-cells"
+
+additionalProperties:
+  description: Set extend properties for each pwm channel.
+  type: object
+  properties:
+    reg:
+      description:
+        The pwm channel index.
+      maxItems: 1
+
+    aspeed,wdt-reload-enable:
+      type: boolean
+      description:
+        Enable the function of wdt reset reload duty point.
+
+    aspeed,wdt-reload-duty-point:
+      description:
+        Define the duty point after wdt reset, 0 = 100%
+      minimum: 0
+      maximum: 255
+
+  required:
+    - reg
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [v2 2/3] pwm: Add Aspeed ast2600 PWM support
  2022-11-01  9:51 [v2 0/3] upport pwm/tach driver for aspeed ast26xx Billy Tsai
  2022-11-01  9:51 ` [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach Billy Tsai
@ 2022-11-01  9:51 ` Billy Tsai
  2022-11-01 10:03   ` Christophe JAILLET
  2022-11-01  9:51 ` [v2 3/3] hwmon: Add Aspeed ast2600 TACH support Billy Tsai
  2 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-01  9:51 UTC (permalink / raw)
  To: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, billy_tsai,
	linux-hwmon, devicetree, linux-arm-kernel, linux-aspeed,
	linux-kernel, linux-pwm, BMC-SW, garnermic

This patch add the support of PWM controller which can be found at aspeed
ast2600 soc. The pwm supoorts up to 16 channels and it's part function
of multi-function device "pwm-tach controller".

Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
 drivers/pwm/Kconfig              |  10 +
 drivers/pwm/Makefile             |   1 +
 drivers/pwm/pwm-aspeed-ast2600.c | 325 +++++++++++++++++++++++++++++++
 3 files changed, 336 insertions(+)
 create mode 100644 drivers/pwm/pwm-aspeed-ast2600.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index aa29841bbb79..3313f074f15e 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -51,6 +51,16 @@ config PWM_AB8500
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-ab8500.
 
+config PWM_ASPEED_AST2600
+	tristate "Aspeed ast2600 PWM support"
+	depends on ARCH_ASPEED || COMPILE_TEST
+	depends on HAVE_CLK && HAS_IOMEM
+	help
+	  This driver provides support for Aspeed ast2600 PWM controllers.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-aspeed-ast2600.
+
 config PWM_ATMEL
 	tristate "Atmel PWM support"
 	depends on ARCH_AT91 || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 708840b7fba8..6be0c67bf08a 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -2,6 +2,7 @@
 obj-$(CONFIG_PWM)		+= core.o
 obj-$(CONFIG_PWM_SYSFS)		+= sysfs.o
 obj-$(CONFIG_PWM_AB8500)	+= pwm-ab8500.o
+obj-$(CONFIG_PWM_ASPEED_AST2600)	+= pwm-aspeed-ast2600.o
 obj-$(CONFIG_PWM_ATMEL)		+= pwm-atmel.o
 obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM)	+= pwm-atmel-hlcdc.o
 obj-$(CONFIG_PWM_ATMEL_TCB)	+= pwm-atmel-tcb.o
diff --git a/drivers/pwm/pwm-aspeed-ast2600.c b/drivers/pwm/pwm-aspeed-ast2600.c
new file mode 100644
index 000000000000..bf54eeb70372
--- /dev/null
+++ b/drivers/pwm/pwm-aspeed-ast2600.c
@@ -0,0 +1,325 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Aspeed Technology Inc.
+ *
+ * PWM controller driver for Aspeed ast2600 SoCs.
+ * This drivers doesn't support earlier version of the IP.
+ *
+ * The hardware operates in time quantities of length
+ * Q := (DIV_L + 1) << DIV_H / input-clk
+ * The length of a PWM period is (DUTY_CYCLE_PERIOD + 1) * Q.
+ * The maximal value for DUTY_CYCLE_PERIOD is used here to provide
+ * a fine grained selection for the duty cycle.
+ *
+ * This driver uses DUTY_CYCLE_RISING_POINT = 0, so from the start of a
+ * period the output is active until DUTY_CYCLE_FALLING_POINT * Q. Note
+ * that if DUTY_CYCLE_RISING_POINT = DUTY_CYCLE_FALLING_POINT the output is
+ * always active.
+ *
+ * Register usage:
+ * PIN_ENABLE: When it is unset the pwm controller will emit inactive level to the external.
+ * Use to determine whether the PWM channel is enabled or disabled
+ * CLK_ENABLE: When it is unset the pwm controller will assert the duty counter reset and
+ * emit inactive level to the PIN_ENABLE mux after that the driver can still change the pwm period
+ * and duty and the value will apply when CLK_ENABLE be set again.
+ * Use to determine whether duty_cycle bigger than 0.
+ * PWM_ASPEED_CTRL_INVERSE: When it is toggled the output value will inverse immediately.
+ * PWM_ASPEED_DUTY_CYCLE_FALLING_POINT/PWM_ASPEED_DUTY_CYCLE_RISING_POINT: When these two
+ * values are equal it means the duty cycle = 100%.
+ *
+ * The glitch may generate at:
+ * - Enabled changing when the duty_cycle bigger than 0% and less than 100%.
+ * - Polarity changing when the duty_cycle bigger than 0% and less than 100%.
+ *
+ * Limitations:
+ * - When changing both duty cycle and period, we cannot prevent in
+ *   software that the output might produce a period with mixed
+ *   settings.
+ * - Disabling the PWM doesn't complete the current period.
+ *
+ * Improvements:
+ * - When only changing one of duty cycle or period, our pwm controller will not
+ *   generate the glitch, the configure will change at next cycle of pwm.
+ *   This improvement can disable/enable through PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE.
+ */
+
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/reset.h>
+#include <linux/regmap.h>
+#include <linux/bitfield.h>
+#include <linux/slab.h>
+#include <linux/pwm.h>
+#include <linux/math64.h>
+
+/* The channel number of Aspeed pwm controller */
+#define PWM_ASPEED_NR_PWMS 16
+
+/* PWM Control Register */
+#define PWM_ASPEED_CTRL(ch) ((ch) * 0x10 + 0x00)
+#define PWM_ASPEED_CTRL_LOAD_SEL_RISING_AS_WDT BIT(19)
+#define PWM_ASPEED_CTRL_DUTY_LOAD_AS_WDT_ENABLE BIT(18)
+#define PWM_ASPEED_CTRL_DUTY_SYNC_DISABLE BIT(17)
+#define PWM_ASPEED_CTRL_CLK_ENABLE BIT(16)
+#define PWM_ASPEED_CTRL_LEVEL_OUTPUT BIT(15)
+#define PWM_ASPEED_CTRL_INVERSE BIT(14)
+#define PWM_ASPEED_CTRL_OPEN_DRAIN_ENABLE BIT(13)
+#define PWM_ASPEED_CTRL_PIN_ENABLE BIT(12)
+#define PWM_ASPEED_CTRL_CLK_DIV_H GENMASK(11, 8)
+#define PWM_ASPEED_CTRL_CLK_DIV_L GENMASK(7, 0)
+
+/* PWM Duty Cycle Register */
+#define PWM_ASPEED_DUTY_CYCLE(ch) ((ch) * 0x10 + 0x04)
+#define PWM_ASPEED_DUTY_CYCLE_PERIOD GENMASK(31, 24)
+#define PWM_ASPEED_DUTY_CYCLE_POINT_AS_WDT GENMASK(23, 16)
+#define PWM_ASPEED_DUTY_CYCLE_FALLING_POINT GENMASK(15, 8)
+#define PWM_ASPEED_DUTY_CYCLE_RISING_POINT GENMASK(7, 0)
+
+/* PWM fixed value */
+#define PWM_ASPEED_FIXED_PERIOD FIELD_MAX(PWM_ASPEED_DUTY_CYCLE_PERIOD)
+
+struct aspeed_pwm_data {
+	struct pwm_chip chip;
+	struct clk *clk;
+	struct regmap *regmap;
+	struct reset_control *reset;
+};
+
+static inline struct aspeed_pwm_data *
+aspeed_pwm_chip_to_data(struct pwm_chip *chip)
+{
+	return container_of(chip, struct aspeed_pwm_data, chip);
+}
+
+static void aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				 struct pwm_state *state)
+{
+	struct device *dev = chip->dev;
+	struct aspeed_pwm_data *priv = aspeed_pwm_chip_to_data(chip);
+	u32 hwpwm = pwm->hwpwm;
+	bool polarity,	pin_en, clk_en;
+	u32 duty_pt, val;
+	unsigned long rate;
+	u64 div_h, div_l, duty_cycle_period, dividend;
+
+	regmap_read(priv->regmap, PWM_ASPEED_CTRL(hwpwm), &val);
+	polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val);
+	pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val);
+	clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val);
+	div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val);
+	div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val);
+	regmap_read(priv->regmap, PWM_ASPEED_DUTY_CYCLE(hwpwm), &val);
+	duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val);
+	duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val);
+
+	rate = clk_get_rate(priv->clk);
+
+	/*
+	 * This multiplication doesn't overflow, the upper bound is
+	 * 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000
+	 */
+	dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1)
+		       << div_h;
+	state->period = DIV_ROUND_UP_ULL(dividend, rate);
+
+	if (clk_en && duty_pt) {
+		dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt
+				 << div_h;
+		state->duty_cycle = DIV_ROUND_UP_ULL(dividend, rate);
+	} else {
+		state->duty_cycle = clk_en ? state->period : 0;
+	}
+	state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
+	state->enabled = pin_en;
+	dev_dbg(dev, "get period: %lldns, duty_cycle: %lldns", state->period,
+		state->duty_cycle);
+}
+
+static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			    const struct pwm_state *state)
+{
+	struct device *dev = chip->dev;
+	struct aspeed_pwm_data *priv = aspeed_pwm_chip_to_data(chip);
+	u32 hwpwm = pwm->hwpwm, duty_pt;
+	unsigned long rate;
+	u64 div_h, div_l, divisor, expect_period;
+	bool clk_en;
+
+	rate = clk_get_rate(priv->clk);
+	expect_period = min(div64_u64(ULLONG_MAX, (u64)rate), state->period);
+	dev_dbg(dev, "expect period: %lldns, duty_cycle: %lldns", expect_period,
+		state->duty_cycle);
+	/*
+	 * Pick the smallest value for div_h so that div_l can be the biggest
+	 * which results in a finer resolution near the target period value.
+	 */
+	divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) *
+		  (FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1);
+	div_h = order_base_2(DIV64_U64_ROUND_UP(rate * expect_period, divisor));
+	if (div_h > 0xf)
+		div_h = 0xf;
+
+	divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h;
+	div_l = div64_u64(rate * expect_period, divisor);
+
+	if (div_l == 0)
+		return -ERANGE;
+
+	div_l -= 1;
+
+	if (div_l > 255)
+		div_l = 255;
+
+	dev_dbg(dev, "clk source: %ld div_h %lld, div_l : %lld\n", rate, div_h,
+		div_l);
+	/* duty_pt = duty_cycle * (PERIOD + 1) / period */
+	duty_pt = div64_u64(state->duty_cycle * rate,
+			    (u64)NSEC_PER_SEC * (div_l + 1) << div_h);
+	dev_dbg(dev, "duty_cycle = %lld, duty_pt = %d\n", state->duty_cycle,
+		 duty_pt);
+
+	/*
+	 * Fixed DUTY_CYCLE_PERIOD to its max value to get a
+	 * fine-grained resolution for duty_cycle at the expense of a
+	 * coarser period resolution.
+	 */
+	regmap_update_bits(priv->regmap, PWM_ASPEED_DUTY_CYCLE(hwpwm),
+			   PWM_ASPEED_DUTY_CYCLE_PERIOD,
+			   FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_PERIOD,
+				      PWM_ASPEED_FIXED_PERIOD));
+	if (duty_pt == 0) {
+		/* emit inactive level and assert the duty counter reset */
+		clk_en = 0;
+	} else {
+		clk_en = 1;
+		if (duty_pt >= (PWM_ASPEED_FIXED_PERIOD + 1))
+			duty_pt = 0;
+		regmap_update_bits(
+			priv->regmap, PWM_ASPEED_DUTY_CYCLE(hwpwm),
+			PWM_ASPEED_DUTY_CYCLE_RISING_POINT |
+				PWM_ASPEED_DUTY_CYCLE_FALLING_POINT,
+			FIELD_PREP(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT,
+				   duty_pt));
+	}
+
+	regmap_update_bits(
+		priv->regmap, PWM_ASPEED_CTRL(hwpwm),
+		PWM_ASPEED_CTRL_CLK_DIV_H | PWM_ASPEED_CTRL_CLK_DIV_L |
+			PWM_ASPEED_CTRL_PIN_ENABLE |
+			PWM_ASPEED_CTRL_CLK_ENABLE | PWM_ASPEED_CTRL_INVERSE,
+		FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_H, div_h) |
+			FIELD_PREP(PWM_ASPEED_CTRL_CLK_DIV_L, div_l) |
+			FIELD_PREP(PWM_ASPEED_CTRL_PIN_ENABLE, state->enabled) |
+			FIELD_PREP(PWM_ASPEED_CTRL_CLK_ENABLE, clk_en) |
+			FIELD_PREP(PWM_ASPEED_CTRL_INVERSE, state->polarity));
+	return 0;
+}
+
+static const struct pwm_ops aspeed_pwm_ops = {
+	.apply = aspeed_pwm_apply,
+	.get_state = aspeed_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
+static int aspeed_pwm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+	struct aspeed_pwm_data *priv;
+	struct device_node *np;
+	struct platform_device *parent_dev;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	np = pdev->dev.parent->of_node;
+	if (!of_device_is_compatible(np, "aspeed,ast2600-pwm-tach"))
+		return dev_err_probe(dev, -ENODEV,
+				     "Unsupported pwm device binding\n");
+
+	priv->regmap = syscon_node_to_regmap(np);
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->regmap),
+				     "Couldn't get regmap\n");
+
+	parent_dev = of_find_device_by_node(np);
+	priv->clk = devm_clk_get(&parent_dev->dev, 0);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "Couldn't get clock\n");
+
+	priv->reset = devm_reset_control_get_shared(&parent_dev->dev, NULL);
+	if (IS_ERR(priv->reset))
+		return dev_err_probe(dev, PTR_ERR(priv->reset),
+				     "Couldn't get reset control\n");
+
+	ret = clk_prepare_enable(priv->clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "Couldn't enable clock\n");
+
+	ret = reset_control_deassert(priv->reset);
+	if (ret) {
+		dev_err_probe(dev, ret, "Couldn't deassert reset control\n");
+		goto err_disable_clk;
+	}
+
+	priv->chip.dev = dev;
+	priv->chip.ops = &aspeed_pwm_ops;
+	priv->chip.npwm = PWM_ASPEED_NR_PWMS;
+
+	ret = pwmchip_add(&priv->chip);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+		goto err_assert_reset;
+	}
+	dev_set_drvdata(dev, priv);
+	return 0;
+err_assert_reset:
+	reset_control_assert(priv->reset);
+err_disable_clk:
+	clk_disable_unprepare(priv->clk);
+	return ret;
+}
+
+static int aspeed_pwm_remove(struct platform_device *dev)
+{
+	struct aspeed_pwm_data *priv = platform_get_drvdata(dev);
+
+	pwmchip_remove(&priv->chip);
+	reset_control_assert(priv->reset);
+	clk_disable_unprepare(priv->clk);
+
+	return 0;
+}
+
+static const struct of_device_id of_pwm_match_table[] = {
+	{
+		.compatible = "aspeed,ast2600-pwm",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_pwm_match_table);
+
+static struct platform_driver aspeed_pwm_driver = {
+	.probe = aspeed_pwm_probe,
+	.remove	= aspeed_pwm_remove,
+	.driver	= {
+		.name = "aspeed-pwm",
+		.of_match_table = of_pwm_match_table,
+	},
+};
+
+module_platform_driver(aspeed_pwm_driver);
+
+MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
+MODULE_DESCRIPTION("Aspeed ast2600 PWM device driver");
+MODULE_LICENSE("GPL v2");
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-01  9:51 [v2 0/3] upport pwm/tach driver for aspeed ast26xx Billy Tsai
  2022-11-01  9:51 ` [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach Billy Tsai
  2022-11-01  9:51 ` [v2 2/3] pwm: Add Aspeed ast2600 PWM support Billy Tsai
@ 2022-11-01  9:51 ` Billy Tsai
  2022-11-01 10:16   ` Christophe JAILLET
  2022-11-01 13:14   ` Guenter Roeck
  2 siblings, 2 replies; 19+ messages in thread
From: Billy Tsai @ 2022-11-01  9:51 UTC (permalink / raw)
  To: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, billy_tsai,
	linux-hwmon, devicetree, linux-arm-kernel, linux-aspeed,
	linux-kernel, linux-pwm, BMC-SW, garnermic

This patch add the support of Tachometer which can use to monitor the
frequency of the input. The tach supports up to 16 channels and it's part
function of multi-function device "pwm-tach controller".

Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
---
 Documentation/hwmon/tach-aspeed-ast2600.rst |  28 ++
 drivers/hwmon/Kconfig                       |   9 +
 drivers/hwmon/Makefile                      |   1 +
 drivers/hwmon/tach-aspeed-ast2600.c         | 484 ++++++++++++++++++++
 4 files changed, 522 insertions(+)
 create mode 100644 Documentation/hwmon/tach-aspeed-ast2600.rst
 create mode 100644 drivers/hwmon/tach-aspeed-ast2600.c

diff --git a/Documentation/hwmon/tach-aspeed-ast2600.rst b/Documentation/hwmon/tach-aspeed-ast2600.rst
new file mode 100644
index 000000000000..8967f60672dc
--- /dev/null
+++ b/Documentation/hwmon/tach-aspeed-ast2600.rst
@@ -0,0 +1,28 @@
+Kernel driver tach-aspeed-ast2600
+==============================
+
+Supported chips:
+	ASPEED AST2600
+
+Authors:
+	<billy_tsai@aspeedtech.com>
+
+Description:
+------------
+This driver implements support for ASPEED AST2600 Fan Tacho controller.
+The controller supports up to 16 tachometer inputs.
+
+The driver provides the following sensor accesses in sysfs:
+=============== ======= =====================================================
+fanX_input	ro	provide current fan rotation value in RPM as reported
+			by the fan to the device.
+fanX_div	rw	Fan divisor: Supported value are power of 4 (1, 4, 16
+                        64, ... 4194304)
+                        The larger divisor, the less rpm accuracy and the less
+                        affected by fan signal glitch.
+fanX_min	rw      Fan minimum RPM which can used to change the timeout
+                        value for controller polling the result.
+fanX_max	rw      Fan maximum RPM which can used to change the polling
+                        period of the driver.
+fanX_pulses	rw      Fan pulses per resolution.
+=============== ======= ======================================================
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index fa2356398744..a84c15b73aa6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -397,6 +397,15 @@ config SENSORS_ASPEED
 	  This driver can also be built as a module. If so, the module
 	  will be called aspeed_pwm_tacho.
 
+config SENSORS_TACH_ASPEED_AST2600
+	tristate "ASPEED ast2600 Tachometer support"
+	select REGMAP
+	help
+	  This driver provides support for Aspeed ast2600 Tachometer.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called tach-aspeed-ast2600.
+
 config SENSORS_ATXP1
 	tristate "Attansic ATXP1 VID controller"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index d2497b2644e6..7e2d708e93b8 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_SENSORS_ARM_SCMI)	+= scmi-hwmon.o
 obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
 obj-$(CONFIG_SENSORS_AS370)	+= as370-hwmon.o
 obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
+obj-$(CONFIG_SENSORS_TACH_ASPEED_AST2600) += tach-aspeed-ast2600.o
 obj-$(CONFIG_SENSORS_ASPEED)	+= aspeed-pwm-tacho.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
diff --git a/drivers/hwmon/tach-aspeed-ast2600.c b/drivers/hwmon/tach-aspeed-ast2600.c
new file mode 100644
index 000000000000..599ed5db17a8
--- /dev/null
+++ b/drivers/hwmon/tach-aspeed-ast2600.c
@@ -0,0 +1,484 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) ASPEED Technology Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/sysfs.h>
+#include <linux/reset.h>
+#include <linux/regmap.h>
+#include <linux/bitfield.h>
+
+/* The channel number of Aspeed tach controller */
+#define TACH_ASPEED_NR_TACHS 16
+/* TACH Control Register */
+#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08)
+#define TACH_ASPEED_IER BIT(31)
+#define TACH_ASPEED_INVERS_LIMIT BIT(30)
+#define TACH_ASPEED_LOOPBACK BIT(29)
+#define TACH_ASPEED_ENABLE BIT(28)
+#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26)
+#define TACH_ASPEED_DEBOUNCE_BIT (26)
+#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24)
+#define TACH_ASPEED_IO_EDGE_BIT (24)
+#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20)
+#define TACH_ASPEED_CLK_DIV_BIT (20)
+#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0)
+/* [27:26] */
+#define DEBOUNCE_3_CLK 0x00
+#define DEBOUNCE_2_CLK 0x01
+#define DEBOUNCE_1_CLK 0x02
+#define DEBOUNCE_0_CLK 0x03
+/* [25:24] */
+#define F2F_EDGES 0x00
+#define R2R_EDGES 0x01
+#define BOTH_EDGES 0x02
+/* [23:20] */
+/* divisor = 4 to the nth power, n = register value */
+#define DEFAULT_TACH_DIV 1024
+#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1)
+
+/* TACH Status Register */
+#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C)
+
+/*PWM_TACH_STS */
+#define TACH_ASPEED_ISR BIT(31)
+#define TACH_ASPEED_PWM_OUT BIT(25)
+#define TACH_ASPEED_PWM_OEN BIT(24)
+#define TACH_ASPEED_DEB_INPUT BIT(23)
+#define TACH_ASPEED_RAW_INPUT BIT(22)
+#define TACH_ASPEED_VALUE_UPDATE BIT(21)
+#define TACH_ASPEED_FULL_MEASUREMENT BIT(20)
+#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0)
+/**********************************************************
+ * Software setting
+ *********************************************************/
+#define DEFAULT_FAN_MIN_RPM 1000
+#define DEFAULT_FAN_PULSE_PR 2
+/*
+ * Add this value to avoid CPU consuming a lot of resources in waiting rpm
+ * updating. Assume the max rpm of fan is 60000, the fastest period of updating
+ * tach value will be equal to (1000000 * 2 * 60) / (2 * max_rpm) = 1000us.
+ */
+#define DEFAULT_FAN_MAX_RPM 60000
+
+struct aspeed_tach_channel_params {
+	int limited_inverse;
+	u16 threshold;
+	u8 tach_edge;
+	u8 tach_debounce;
+	u8 pulse_pr;
+	u32 min_rpm;
+	u32 max_rpm;
+	u32 divisor;
+	u32 sample_period; /* unit is us */
+	u32 polling_period; /* unit is us */
+};
+
+struct aspeed_tach_data {
+	struct device *dev;
+	struct regmap *regmap;
+	struct clk *clk;
+	struct reset_control *reset;
+	bool tach_present[TACH_ASPEED_NR_TACHS];
+	struct aspeed_tach_channel_params *tach_channel;
+};
+
+static void aspeed_update_tach_sample_period(struct aspeed_tach_data *priv,
+					     u8 fan_tach_ch)
+{
+	u32 tach_period_us;
+	u8 pulse_pr = priv->tach_channel[fan_tach_ch].pulse_pr;
+	u32 min_rpm = priv->tach_channel[fan_tach_ch].min_rpm;
+
+	/*
+	 * min(Tach input clock) = (PulsePR * minRPM) / 60
+	 * max(Tach input period) = 60 / (PulsePR * minRPM)
+	 * Tach sample period > 2 * max(Tach input period) = (2*60) / (PulsePR * minRPM)
+	 */
+	tach_period_us = (USEC_PER_SEC * 2 * 60) / (pulse_pr * min_rpm);
+	/* Add the margin (about 1.5) of tach sample period to avoid sample miss */
+	tach_period_us = (tach_period_us * 1500) >> 10;
+	dev_dbg(priv->dev, "tach%d sample period = %dus", fan_tach_ch, tach_period_us);
+	priv->tach_channel[fan_tach_ch].sample_period = tach_period_us;
+}
+
+static void aspeed_update_tach_polling_period(struct aspeed_tach_data *priv,
+					     u8 fan_tach_ch)
+{
+	u32 tach_period_us;
+	u8 pulse_pr = priv->tach_channel[fan_tach_ch].pulse_pr;
+	u32 max_rpm = priv->tach_channel[fan_tach_ch].max_rpm;
+
+	tach_period_us = (USEC_PER_SEC * 2 * 60) / (pulse_pr * max_rpm);
+	dev_dbg(priv->dev, "tach%d polling period = %dus", fan_tach_ch, tach_period_us);
+	priv->tach_channel[fan_tach_ch].polling_period = tach_period_us;
+}
+
+static void aspeed_tach_ch_enable(struct aspeed_tach_data *priv, u8 tach_ch,
+				  bool enable)
+{
+	if (enable)
+		regmap_set_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+				TACH_ASPEED_ENABLE);
+	else
+		regmap_clear_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+				   TACH_ASPEED_ENABLE);
+}
+
+static int aspeed_get_fan_tach_ch_rpm(struct aspeed_tach_data *priv,
+				      u8 fan_tach_ch)
+{
+	u32 raw_data, tach_div, val;
+	unsigned long clk_source;
+	u64 rpm;
+	int ret;
+
+	/* Restart the Tach channel to guarantee the value is fresh */
+	aspeed_tach_ch_enable(priv, fan_tach_ch, false);
+	aspeed_tach_ch_enable(priv, fan_tach_ch, true);
+	ret = regmap_read_poll_timeout(
+		priv->regmap, TACH_ASPEED_STS(fan_tach_ch), val,
+		(val & TACH_ASPEED_FULL_MEASUREMENT) &&
+			(val & TACH_ASPEED_VALUE_UPDATE),
+		priv->tach_channel[fan_tach_ch].polling_period,
+		priv->tach_channel[fan_tach_ch].sample_period);
+
+	if (ret) {
+		/* return 0 if we didn't get an answer because of timeout*/
+		if (ret == -ETIMEDOUT)
+			return 0;
+		else
+			return ret;
+	}
+
+	raw_data = val & TACH_ASPEED_VALUE_MASK;
+	/*
+	 * We need the mode to determine if the raw_data is double (from
+	 * counting both edges).
+	 */
+	if (priv->tach_channel[fan_tach_ch].tach_edge == BOTH_EDGES)
+		raw_data <<= 1;
+
+	tach_div = raw_data * (priv->tach_channel[fan_tach_ch].divisor) *
+		   (priv->tach_channel[fan_tach_ch].pulse_pr);
+
+	clk_source = clk_get_rate(priv->clk);
+	dev_dbg(priv->dev, "clk %ld, raw_data %d , tach_div %d\n", clk_source,
+		raw_data, tach_div);
+
+	if (tach_div == 0)
+		return -EDOM;
+
+	rpm = (u64)clk_source * 60;
+	do_div(rpm, tach_div);
+
+	return rpm;
+}
+
+static int aspeed_tach_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			int channel, long *val)
+{
+	struct aspeed_tach_data *priv = dev_get_drvdata(dev);
+	u32 reg_val;
+	int ret;
+
+	switch (attr) {
+	case hwmon_fan_input:
+		ret = aspeed_get_fan_tach_ch_rpm(priv, channel);
+		if (ret < 0)
+			return ret;
+		*val = ret;
+		break;
+	case hwmon_fan_min:
+		*val = priv->tach_channel[channel].min_rpm;
+		break;
+	case hwmon_fan_max:
+		*val = priv->tach_channel[channel].max_rpm;
+		break;
+	case hwmon_fan_div:
+		regmap_read(priv->regmap, TACH_ASPEED_CTRL(channel), &reg_val);
+		reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val);
+		*val = 1 << (reg_val << 1);
+		break;
+	case hwmon_fan_pulses:
+		*val = priv->tach_channel[channel].pulse_pr;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static int aspeed_tach_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			 int channel, long val)
+{
+	struct aspeed_tach_data *priv = dev_get_drvdata(dev);
+
+	switch (attr) {
+	case hwmon_fan_min:
+		priv->tach_channel[channel].min_rpm = val;
+		aspeed_update_tach_sample_period(priv, channel);
+		break;
+	case hwmon_fan_max:
+		priv->tach_channel[channel].max_rpm = val;
+		aspeed_update_tach_polling_period(priv, channel);
+		break;
+	case hwmon_fan_div:
+		if ((is_power_of_2(val) && !(ilog2(val) % 2))) {
+			priv->tach_channel[channel].divisor = val;
+			regmap_write_bits(
+				priv->regmap, TACH_ASPEED_CTRL(channel),
+				TACH_ASPEED_CLK_DIV_T_MASK,
+				DIV_TO_REG(priv->tach_channel[channel].divisor)
+					<< TACH_ASPEED_CLK_DIV_BIT);
+		} else {
+			dev_err(dev,
+				"fan_div value %ld not supported. Only support power of 4\n",
+				val);
+			return -EINVAL;
+		}
+		break;
+	case hwmon_fan_pulses:
+		priv->tach_channel[channel].pulse_pr = val;
+		aspeed_update_tach_sample_period(priv, channel);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static umode_t aspeed_tach_dev_is_visible(const void *drvdata, enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	const struct aspeed_tach_data *priv = drvdata;
+
+	if (!priv->tach_present[channel])
+		return 0;
+	switch (attr) {
+	case hwmon_fan_input:
+		return 0444;
+	case hwmon_fan_min:
+	case hwmon_fan_max:
+	case hwmon_fan_div:
+	case hwmon_fan_pulses:
+		return 0644;
+	}
+	return 0;
+}
+
+static const struct hwmon_ops aspeed_tach_ops = {
+	.is_visible = aspeed_tach_dev_is_visible,
+	.read = aspeed_tach_hwmon_read,
+	.write = aspeed_tach_hwmon_write,
+};
+
+static const struct hwmon_channel_info *aspeed_tach_info[] = {
+	HWMON_CHANNEL_INFO(fan,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES,
+			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
+				   HWMON_F_DIV | HWMON_F_PULSES),
+	NULL
+};
+
+static const struct hwmon_chip_info aspeed_tach_chip_info = {
+	.ops = &aspeed_tach_ops,
+	.info = aspeed_tach_info,
+};
+
+static void aspeed_create_fan_tach_channel(struct aspeed_tach_data *priv,
+					   u32 tach_ch)
+{
+	priv->tach_present[tach_ch] = true;
+	priv->tach_channel[tach_ch].limited_inverse = 0;
+	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+			  TACH_ASPEED_INVERS_LIMIT,
+			  priv->tach_channel[tach_ch].limited_inverse ?
+				  TACH_ASPEED_INVERS_LIMIT :
+				  0);
+
+	priv->tach_channel[tach_ch].tach_debounce = DEBOUNCE_3_CLK;
+	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+			  TACH_ASPEED_DEBOUNCE_MASK,
+			  priv->tach_channel[tach_ch].tach_debounce
+				  << TACH_ASPEED_DEBOUNCE_BIT);
+
+	priv->tach_channel[tach_ch].tach_edge = F2F_EDGES;
+	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+			  TACH_ASPEED_IO_EDGE_MASK,
+			  priv->tach_channel[tach_ch].tach_edge
+				  << TACH_ASPEED_IO_EDGE_BIT);
+
+	priv->tach_channel[tach_ch].divisor = DEFAULT_TACH_DIV;
+	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+			  TACH_ASPEED_CLK_DIV_T_MASK,
+			  DIV_TO_REG(priv->tach_channel[tach_ch].divisor)
+				  << TACH_ASPEED_CLK_DIV_BIT);
+
+	priv->tach_channel[tach_ch].threshold = 0;
+	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
+			  TACH_ASPEED_THRESHOLD_MASK,
+			  priv->tach_channel[tach_ch].threshold);
+
+	priv->tach_channel[tach_ch].pulse_pr = DEFAULT_FAN_PULSE_PR;
+	priv->tach_channel[tach_ch].min_rpm = DEFAULT_FAN_MIN_RPM;
+	aspeed_update_tach_sample_period(priv, tach_ch);
+
+	priv->tach_channel[tach_ch].max_rpm = DEFAULT_FAN_MAX_RPM;
+	aspeed_update_tach_polling_period(priv, tach_ch);
+
+	aspeed_tach_ch_enable(priv, tach_ch, true);
+}
+
+static int aspeed_tach_create_fan(struct device *dev, struct device_node *child,
+				  struct aspeed_tach_data *priv)
+{
+	u32 tach_channel;
+	int ret;
+
+	ret = of_property_read_u32(child, "reg", &tach_channel);
+	if (ret)
+		return ret;
+
+	aspeed_create_fan_tach_channel(priv, tach_channel);
+
+	return 0;
+}
+
+static void aspeed_tach_reset_assert(void *data)
+{
+	struct reset_control *rst = data;
+
+	reset_control_assert(rst);
+}
+
+static int aspeed_tach_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np, *child;
+	struct aspeed_tach_data *priv;
+	struct device *hwmon;
+	struct platform_device *parent_dev;
+	int ret;
+
+	np = dev->parent->of_node;
+	if (!of_device_is_compatible(np, "aspeed,ast2600-pwm-tach"))
+		return dev_err_probe(dev, -ENODEV,
+				     "Unsupported tach device binding\n");
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->dev = &pdev->dev;
+	priv->tach_channel =
+		devm_kzalloc(dev,
+			     TACH_ASPEED_NR_TACHS * sizeof(*priv->tach_channel),
+			     GFP_KERNEL);
+
+	priv->regmap = syscon_node_to_regmap(np);
+	if (IS_ERR(priv->regmap)) {
+		dev_err(priv->dev, "Couldn't get regmap\n");
+		return -ENODEV;
+	}
+	parent_dev = of_find_device_by_node(np);
+	priv->clk = devm_clk_get_enabled(&parent_dev->dev, 0);
+	if (IS_ERR(priv->clk))
+		return dev_err_probe(dev, PTR_ERR(priv->clk),
+				     "Couldn't get clock\n");
+
+	priv->reset = devm_reset_control_get_shared(&parent_dev->dev, NULL);
+	if (IS_ERR(priv->reset))
+		return dev_err_probe(dev, PTR_ERR(priv->reset),
+				     "Couldn't get reset control\n");
+
+	ret = reset_control_deassert(priv->reset);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Couldn't deassert reset control\n");
+
+	ret = devm_add_action_or_reset(dev, aspeed_tach_reset_assert,
+				       priv->reset);
+	if (ret)
+		return ret;
+
+	for_each_child_of_node(dev->of_node, child) {
+		ret = aspeed_tach_create_fan(dev, child, priv);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+	}
+
+	hwmon = devm_hwmon_device_register_with_info(
+		dev, "aspeed_tach", priv, &aspeed_tach_chip_info, NULL);
+	ret = PTR_ERR_OR_ZERO(hwmon);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to register hwmon device\n");
+	return 0;
+}
+
+static const struct of_device_id of_stach_match_table[] = {
+	{
+		.compatible = "aspeed,ast2600-tach",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, of_stach_match_table);
+
+static struct platform_driver aspeed_tach_driver = {
+	.probe		= aspeed_tach_probe,
+	.driver		= {
+		.name	= "aspeed_tach",
+		.of_match_table = of_stach_match_table,
+	},
+};
+
+module_platform_driver(aspeed_tach_driver);
+
+MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
+MODULE_DESCRIPTION("Aspeed ast2600 TACH device driver");
+MODULE_LICENSE("GPL v2");
+
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 19+ messages in thread

* Re: [v2 2/3] pwm: Add Aspeed ast2600 PWM support
  2022-11-01  9:51 ` [v2 2/3] pwm: Add Aspeed ast2600 PWM support Billy Tsai
@ 2022-11-01 10:03   ` Christophe JAILLET
  0 siblings, 0 replies; 19+ messages in thread
From: Christophe JAILLET @ 2022-11-01 10:03 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, linux-hwmon,
	devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
	linux-pwm, BMC-SW, garnermic

Le 01/11/2022 à 10:51, Billy Tsai a écrit :
> This patch add the support of PWM controller which can be found at aspeed
> ast2600 soc. The pwm supoorts up to 16 channels and it's part function
> of multi-function device "pwm-tach controller".
> 
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>

[...]

> +static int aspeed_pwm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +	struct aspeed_pwm_data *priv;
> +	struct device_node *np;
> +	struct platform_device *parent_dev;
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	np = pdev->dev.parent->of_node;
> +	if (!of_device_is_compatible(np, "aspeed,ast2600-pwm-tach"))
> +		return dev_err_probe(dev, -ENODEV,
> +				     "Unsupported pwm device binding\n");
> +
> +	priv->regmap = syscon_node_to_regmap(np);
> +	if (IS_ERR(priv->regmap))
> +		return dev_err_probe(dev, PTR_ERR(priv->regmap),
> +				     "Couldn't get regmap\n");
> +
> +	parent_dev = of_find_device_by_node(np);
> +	priv->clk = devm_clk_get(&parent_dev->dev, 0);

Hi,

if this helps, using devm_clk_get_enabled() would save a few lines of code.

CJ

> +	if (IS_ERR(priv->clk))
> +		return dev_err_probe(dev, PTR_ERR(priv->clk),
> +				     "Couldn't get clock\n");
> +

[...]

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-01  9:51 ` [v2 3/3] hwmon: Add Aspeed ast2600 TACH support Billy Tsai
@ 2022-11-01 10:16   ` Christophe JAILLET
  2022-11-01 13:14   ` Guenter Roeck
  1 sibling, 0 replies; 19+ messages in thread
From: Christophe JAILLET @ 2022-11-01 10:16 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, linux, robh+dt, joel, andrew, lee.jones,
	thierry.reding, u.kleine-koenig, p.zabel, linux-hwmon,
	devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
	linux-pwm, BMC-SW, garnermic

Le 01/11/2022 à 10:51, Billy Tsai a écrit :
> This patch add the support of Tachometer which can use to monitor the
> frequency of the input. The tach supports up to 16 channels and it's part
> function of multi-function device "pwm-tach controller".
> 
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>

Hi,
a few nits below,

[...]

> +
> +	if (ret) {
> +		/* return 0 if we didn't get an answer because of timeout*/

Missing space at the end of the comment

> +		if (ret == -ETIMEDOUT)
> +			return 0;
> +		else
> +			return ret;

[...]

> +static int aspeed_tach_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np, *child;
> +	struct aspeed_tach_data *priv;
> +	struct device *hwmon;
> +	struct platform_device *parent_dev;
> +	int ret;
> +
> +	np = dev->parent->of_node;
> +	if (!of_device_is_compatible(np, "aspeed,ast2600-pwm-tach"))
> +		return dev_err_probe(dev, -ENODEV,
> +				     "Unsupported tach device binding\n");
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +	priv->dev = &pdev->dev;
> +	priv->tach_channel =
> +		devm_kzalloc(dev,
> +			     TACH_ASPEED_NR_TACHS * sizeof(*priv->tach_channel),
> +			     GFP_KERNEL);

use devm_kcalloc() instead of devm_kzalloc()?
Error handling?

> +
> +	priv->regmap = syscon_node_to_regmap(np);
> +	if (IS_ERR(priv->regmap)) {
> +		dev_err(priv->dev, "Couldn't get regmap\n");

In order to be conistent with the other error handling paths:
return dev_err_probe()?

> +		return -ENODEV;
> +	}

[...]


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-01  9:51 ` [v2 3/3] hwmon: Add Aspeed ast2600 TACH support Billy Tsai
  2022-11-01 10:16   ` Christophe JAILLET
@ 2022-11-01 13:14   ` Guenter Roeck
  2022-11-02  6:54     ` Billy Tsai
  1 sibling, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2022-11-01 13:14 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Tue, Nov 01, 2022 at 05:51:56PM +0800, Billy Tsai wrote:
> This patch add the support of Tachometer which can use to monitor the
> frequency of the input. The tach supports up to 16 channels and it's part
> function of multi-function device "pwm-tach controller".
> 
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
> ---
>  Documentation/hwmon/tach-aspeed-ast2600.rst |  28 ++

Also needs to be added to Documentation/hwmon/index.rst.

>  drivers/hwmon/Kconfig                       |   9 +
>  drivers/hwmon/Makefile                      |   1 +
>  drivers/hwmon/tach-aspeed-ast2600.c         | 484 ++++++++++++++++++++
>  4 files changed, 522 insertions(+)
>  create mode 100644 Documentation/hwmon/tach-aspeed-ast2600.rst
>  create mode 100644 drivers/hwmon/tach-aspeed-ast2600.c
> 
> diff --git a/Documentation/hwmon/tach-aspeed-ast2600.rst b/Documentation/hwmon/tach-aspeed-ast2600.rst
> new file mode 100644
> index 000000000000..8967f60672dc
> --- /dev/null
> +++ b/Documentation/hwmon/tach-aspeed-ast2600.rst
> @@ -0,0 +1,28 @@
> +Kernel driver tach-aspeed-ast2600
> +==============================
> +
> +Supported chips:
> +	ASPEED AST2600
> +
> +Authors:
> +	<billy_tsai@aspeedtech.com>
> +
> +Description:
> +------------
> +This driver implements support for ASPEED AST2600 Fan Tacho controller.
> +The controller supports up to 16 tachometer inputs.
> +
> +The driver provides the following sensor accesses in sysfs:
> +=============== ======= =====================================================
> +fanX_input	ro	provide current fan rotation value in RPM as reported
> +			by the fan to the device.
> +fanX_div	rw	Fan divisor: Supported value are power of 4 (1, 4, 16
> +                        64, ... 4194304)
> +                        The larger divisor, the less rpm accuracy and the less
> +                        affected by fan signal glitch.
> +fanX_min	rw      Fan minimum RPM which can used to change the timeout
> +                        value for controller polling the result.
> +fanX_max	rw      Fan maximum RPM which can used to change the polling
> +                        period of the driver.
> +fanX_pulses	rw      Fan pulses per resolution.
> +=============== ======= ======================================================
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index fa2356398744..a84c15b73aa6 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -397,6 +397,15 @@ config SENSORS_ASPEED
>  	  This driver can also be built as a module. If so, the module
>  	  will be called aspeed_pwm_tacho.
>  
> +config SENSORS_TACH_ASPEED_AST2600
> +	tristate "ASPEED ast2600 Tachometer support"
> +	select REGMAP
> +	help
> +	  This driver provides support for Aspeed ast2600 Tachometer.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called tach-aspeed-ast2600.
> +
>  config SENSORS_ATXP1
>  	tristate "Attansic ATXP1 VID controller"
>  	depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index d2497b2644e6..7e2d708e93b8 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_SENSORS_ARM_SCMI)	+= scmi-hwmon.o
>  obj-$(CONFIG_SENSORS_ARM_SCPI)	+= scpi-hwmon.o
>  obj-$(CONFIG_SENSORS_AS370)	+= as370-hwmon.o
>  obj-$(CONFIG_SENSORS_ASC7621)	+= asc7621.o
> +obj-$(CONFIG_SENSORS_TACH_ASPEED_AST2600) += tach-aspeed-ast2600.o
>  obj-$(CONFIG_SENSORS_ASPEED)	+= aspeed-pwm-tacho.o
>  obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
>  obj-$(CONFIG_SENSORS_AXI_FAN_CONTROL) += axi-fan-control.o
> diff --git a/drivers/hwmon/tach-aspeed-ast2600.c b/drivers/hwmon/tach-aspeed-ast2600.c
> new file mode 100644
> index 000000000000..599ed5db17a8
> --- /dev/null
> +++ b/drivers/hwmon/tach-aspeed-ast2600.c
> @@ -0,0 +1,484 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) ASPEED Technology Inc.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/errno.h>
> +#include <linux/delay.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>

Not needed. Please check if there are other unnecessary includes.

> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/sysfs.h>
> +#include <linux/reset.h>
> +#include <linux/regmap.h>
> +#include <linux/bitfield.h>

Please order include files in alphabetic order.

> +
> +/* The channel number of Aspeed tach controller */
> +#define TACH_ASPEED_NR_TACHS 16

#define<space>NAME<tab>value, please, and align the value.

> +/* TACH Control Register */
> +#define TACH_ASPEED_CTRL(ch) (((ch) * 0x10) + 0x08)
> +#define TACH_ASPEED_IER BIT(31)
> +#define TACH_ASPEED_INVERS_LIMIT BIT(30)
> +#define TACH_ASPEED_LOOPBACK BIT(29)
> +#define TACH_ASPEED_ENABLE BIT(28)
> +#define TACH_ASPEED_DEBOUNCE_MASK GENMASK(27, 26)
> +#define TACH_ASPEED_DEBOUNCE_BIT (26)

() around plain integers is not needed.

> +#define TACH_ASPEED_IO_EDGE_MASK GENMASK(25, 24)
> +#define TACH_ASPEED_IO_EDGE_BIT (24)
> +#define TACH_ASPEED_CLK_DIV_T_MASK GENMASK(23, 20)
> +#define TACH_ASPEED_CLK_DIV_BIT (20)
> +#define TACH_ASPEED_THRESHOLD_MASK GENMASK(19, 0)
> +/* [27:26] */
> +#define DEBOUNCE_3_CLK 0x00
> +#define DEBOUNCE_2_CLK 0x01
> +#define DEBOUNCE_1_CLK 0x02
> +#define DEBOUNCE_0_CLK 0x03
> +/* [25:24] */
> +#define F2F_EDGES 0x00
> +#define R2R_EDGES 0x01
> +#define BOTH_EDGES 0x02
> +/* [23:20] */
> +/* divisor = 4 to the nth power, n = register value */
> +#define DEFAULT_TACH_DIV 1024
> +#define DIV_TO_REG(divisor) (ilog2(divisor) >> 1)
> +
> +/* TACH Status Register */
> +#define TACH_ASPEED_STS(ch) (((ch) * 0x10) + 0x0C)
> +
> +/*PWM_TACH_STS */
> +#define TACH_ASPEED_ISR BIT(31)
> +#define TACH_ASPEED_PWM_OUT BIT(25)
> +#define TACH_ASPEED_PWM_OEN BIT(24)
> +#define TACH_ASPEED_DEB_INPUT BIT(23)
> +#define TACH_ASPEED_RAW_INPUT BIT(22)
> +#define TACH_ASPEED_VALUE_UPDATE BIT(21)
> +#define TACH_ASPEED_FULL_MEASUREMENT BIT(20)
> +#define TACH_ASPEED_VALUE_MASK GENMASK(19, 0)
> +/**********************************************************
> + * Software setting
> + *********************************************************/
> +#define DEFAULT_FAN_MIN_RPM 1000
> +#define DEFAULT_FAN_PULSE_PR 2
> +/*
> + * Add this value to avoid CPU consuming a lot of resources in waiting rpm
> + * updating. Assume the max rpm of fan is 60000, the fastest period of updating
> + * tach value will be equal to (1000000 * 2 * 60) / (2 * max_rpm) = 1000us.
> + */
> +#define DEFAULT_FAN_MAX_RPM 60000
> +
> +struct aspeed_tach_channel_params {
> +	int limited_inverse;
> +	u16 threshold;
> +	u8 tach_edge;
> +	u8 tach_debounce;
> +	u8 pulse_pr;
> +	u32 min_rpm;
> +	u32 max_rpm;
> +	u32 divisor;
> +	u32 sample_period; /* unit is us */
> +	u32 polling_period; /* unit is us */
> +};
> +
> +struct aspeed_tach_data {
> +	struct device *dev;
> +	struct regmap *regmap;
> +	struct clk *clk;
> +	struct reset_control *reset;
> +	bool tach_present[TACH_ASPEED_NR_TACHS];
> +	struct aspeed_tach_channel_params *tach_channel;
> +};
> +
> +static void aspeed_update_tach_sample_period(struct aspeed_tach_data *priv,
> +					     u8 fan_tach_ch)
> +{
> +	u32 tach_period_us;
> +	u8 pulse_pr = priv->tach_channel[fan_tach_ch].pulse_pr;
> +	u32 min_rpm = priv->tach_channel[fan_tach_ch].min_rpm;
> +
> +	/*
> +	 * min(Tach input clock) = (PulsePR * minRPM) / 60
> +	 * max(Tach input period) = 60 / (PulsePR * minRPM)
> +	 * Tach sample period > 2 * max(Tach input period) = (2*60) / (PulsePR * minRPM)
> +	 */
> +	tach_period_us = (USEC_PER_SEC * 2 * 60) / (pulse_pr * min_rpm);
> +	/* Add the margin (about 1.5) of tach sample period to avoid sample miss */
> +	tach_period_us = (tach_period_us * 1500) >> 10;
> +	dev_dbg(priv->dev, "tach%d sample period = %dus", fan_tach_ch, tach_period_us);
> +	priv->tach_channel[fan_tach_ch].sample_period = tach_period_us;
> +}
> +
> +static void aspeed_update_tach_polling_period(struct aspeed_tach_data *priv,
> +					     u8 fan_tach_ch)
> +{
> +	u32 tach_period_us;
> +	u8 pulse_pr = priv->tach_channel[fan_tach_ch].pulse_pr;
> +	u32 max_rpm = priv->tach_channel[fan_tach_ch].max_rpm;
> +
> +	tach_period_us = (USEC_PER_SEC * 2 * 60) / (pulse_pr * max_rpm);
> +	dev_dbg(priv->dev, "tach%d polling period = %dus", fan_tach_ch, tach_period_us);
> +	priv->tach_channel[fan_tach_ch].polling_period = tach_period_us;
> +}
> +
> +static void aspeed_tach_ch_enable(struct aspeed_tach_data *priv, u8 tach_ch,
> +				  bool enable)
> +{
> +	if (enable)
> +		regmap_set_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +				TACH_ASPEED_ENABLE);
> +	else
> +		regmap_clear_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +				   TACH_ASPEED_ENABLE);
> +}
> +
> +static int aspeed_get_fan_tach_ch_rpm(struct aspeed_tach_data *priv,
> +				      u8 fan_tach_ch)
> +{
> +	u32 raw_data, tach_div, val;
> +	unsigned long clk_source;
> +	u64 rpm;
> +	int ret;
> +
> +	/* Restart the Tach channel to guarantee the value is fresh */
> +	aspeed_tach_ch_enable(priv, fan_tach_ch, false);
> +	aspeed_tach_ch_enable(priv, fan_tach_ch, true);

Is that really needed ? Doesn't the controller measure values continuously ?

> +	ret = regmap_read_poll_timeout(
> +		priv->regmap, TACH_ASPEED_STS(fan_tach_ch), val,
> +		(val & TACH_ASPEED_FULL_MEASUREMENT) &&
> +			(val & TACH_ASPEED_VALUE_UPDATE),
> +		priv->tach_channel[fan_tach_ch].polling_period,
> +		priv->tach_channel[fan_tach_ch].sample_period);

Please fix multi-line alignment everywhere. Go up to 100 columns if needed.

> +
> +	if (ret) {
> +		/* return 0 if we didn't get an answer because of timeout*/
> +		if (ret == -ETIMEDOUT)
> +			return 0;
> +		else
> +			return ret;

else after return is unnecessary, and why would a timeout be be ignored ?

> +	}
> +
> +	raw_data = val & TACH_ASPEED_VALUE_MASK;
> +	/*
> +	 * We need the mode to determine if the raw_data is double (from
> +	 * counting both edges).
> +	 */
> +	if (priv->tach_channel[fan_tach_ch].tach_edge == BOTH_EDGES)
> +		raw_data <<= 1;
> +
> +	tach_div = raw_data * (priv->tach_channel[fan_tach_ch].divisor) *
> +		   (priv->tach_channel[fan_tach_ch].pulse_pr);
> +
> +	clk_source = clk_get_rate(priv->clk);
> +	dev_dbg(priv->dev, "clk %ld, raw_data %d , tach_div %d\n", clk_source,
> +		raw_data, tach_div);
> +
> +	if (tach_div == 0)
> +		return -EDOM;

If the fan is off or not connected, would that return an error ?
If so, that would be inappropriate; it should return a speed
of 0 in that case.

> +
> +	rpm = (u64)clk_source * 60;
> +	do_div(rpm, tach_div);
> +
> +	return rpm;
> +}
> +
> +static int aspeed_tach_hwmon_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +			int channel, long *val)
> +{
> +	struct aspeed_tach_data *priv = dev_get_drvdata(dev);
> +	u32 reg_val;
> +	int ret;
> +
> +	switch (attr) {
> +	case hwmon_fan_input:
> +		ret = aspeed_get_fan_tach_ch_rpm(priv, channel);
> +		if (ret < 0)
> +			return ret;
> +		*val = ret;
> +		break;
> +	case hwmon_fan_min:
> +		*val = priv->tach_channel[channel].min_rpm;
> +		break;
> +	case hwmon_fan_max:
> +		*val = priv->tach_channel[channel].max_rpm;
> +		break;
> +	case hwmon_fan_div:
> +		regmap_read(priv->regmap, TACH_ASPEED_CTRL(channel), &reg_val);
> +		reg_val = FIELD_GET(TACH_ASPEED_CLK_DIV_T_MASK, reg_val);
> +		*val = 1 << (reg_val << 1);

BIT() ?

> +		break;
> +	case hwmon_fan_pulses:
> +		*val = priv->tach_channel[channel].pulse_pr;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +	return 0;
> +}
> +
> +static int aspeed_tach_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
> +			 int channel, long val)
> +{
> +	struct aspeed_tach_data *priv = dev_get_drvdata(dev);
> +
> +	switch (attr) {
> +	case hwmon_fan_min:
> +		priv->tach_channel[channel].min_rpm = val;
> +		aspeed_update_tach_sample_period(priv, channel);
> +		break;
> +	case hwmon_fan_max:
> +		priv->tach_channel[channel].max_rpm = val;
> +		aspeed_update_tach_polling_period(priv, channel);
> +		break;
> +	case hwmon_fan_div:
> +		if ((is_power_of_2(val) && !(ilog2(val) % 2))) {
> +			priv->tach_channel[channel].divisor = val;
> +			regmap_write_bits(
> +				priv->regmap, TACH_ASPEED_CTRL(channel),
> +				TACH_ASPEED_CLK_DIV_T_MASK,
> +				DIV_TO_REG(priv->tach_channel[channel].divisor)
> +					<< TACH_ASPEED_CLK_DIV_BIT);
> +		} else {
> +			dev_err(dev,
> +				"fan_div value %ld not supported. Only support power of 4\n",
> +				val);
> +			return -EINVAL;
> +		}

Error case should be handled first, making else unnecessary.

> +		break;
> +	case hwmon_fan_pulses:
> +		priv->tach_channel[channel].pulse_pr = val;
> +		aspeed_update_tach_sample_period(priv, channel);
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static umode_t aspeed_tach_dev_is_visible(const void *drvdata, enum hwmon_sensor_types type,
> +				  u32 attr, int channel)
> +{
> +	const struct aspeed_tach_data *priv = drvdata;
> +
> +	if (!priv->tach_present[channel])
> +		return 0;
> +	switch (attr) {
> +	case hwmon_fan_input:
> +		return 0444;
> +	case hwmon_fan_min:
> +	case hwmon_fan_max:
> +	case hwmon_fan_div:
> +	case hwmon_fan_pulses:
> +		return 0644;
> +	}
> +	return 0;
> +}
> +
> +static const struct hwmon_ops aspeed_tach_ops = {
> +	.is_visible = aspeed_tach_dev_is_visible,
> +	.read = aspeed_tach_hwmon_read,
> +	.write = aspeed_tach_hwmon_write,
> +};
> +
> +static const struct hwmon_channel_info *aspeed_tach_info[] = {
> +	HWMON_CHANNEL_INFO(fan,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES,
> +			   HWMON_F_INPUT | HWMON_F_MIN | HWMON_F_MAX |
> +				   HWMON_F_DIV | HWMON_F_PULSES),
> +	NULL
> +};
> +
> +static const struct hwmon_chip_info aspeed_tach_chip_info = {
> +	.ops = &aspeed_tach_ops,
> +	.info = aspeed_tach_info,
> +};
> +
> +static void aspeed_create_fan_tach_channel(struct aspeed_tach_data *priv,
> +					   u32 tach_ch)
> +{
> +	priv->tach_present[tach_ch] = true;
> +	priv->tach_channel[tach_ch].limited_inverse = 0;
> +	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +			  TACH_ASPEED_INVERS_LIMIT,
> +			  priv->tach_channel[tach_ch].limited_inverse ?
> +				  TACH_ASPEED_INVERS_LIMIT :
> +				  0);
> +
> +	priv->tach_channel[tach_ch].tach_debounce = DEBOUNCE_3_CLK;
> +	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +			  TACH_ASPEED_DEBOUNCE_MASK,
> +			  priv->tach_channel[tach_ch].tach_debounce
> +				  << TACH_ASPEED_DEBOUNCE_BIT);
> +
> +	priv->tach_channel[tach_ch].tach_edge = F2F_EDGES;
> +	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +			  TACH_ASPEED_IO_EDGE_MASK,
> +			  priv->tach_channel[tach_ch].tach_edge
> +				  << TACH_ASPEED_IO_EDGE_BIT);
> +
> +	priv->tach_channel[tach_ch].divisor = DEFAULT_TACH_DIV;
> +	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +			  TACH_ASPEED_CLK_DIV_T_MASK,
> +			  DIV_TO_REG(priv->tach_channel[tach_ch].divisor)
> +				  << TACH_ASPEED_CLK_DIV_BIT);
> +
> +	priv->tach_channel[tach_ch].threshold = 0;
> +	regmap_write_bits(priv->regmap, TACH_ASPEED_CTRL(tach_ch),
> +			  TACH_ASPEED_THRESHOLD_MASK,
> +			  priv->tach_channel[tach_ch].threshold);
> +
> +	priv->tach_channel[tach_ch].pulse_pr = DEFAULT_FAN_PULSE_PR;
> +	priv->tach_channel[tach_ch].min_rpm = DEFAULT_FAN_MIN_RPM;
> +	aspeed_update_tach_sample_period(priv, tach_ch);
> +
> +	priv->tach_channel[tach_ch].max_rpm = DEFAULT_FAN_MAX_RPM;
> +	aspeed_update_tach_polling_period(priv, tach_ch);
> +
> +	aspeed_tach_ch_enable(priv, tach_ch, true);
> +}
> +
> +static int aspeed_tach_create_fan(struct device *dev, struct device_node *child,
> +				  struct aspeed_tach_data *priv)
> +{
> +	u32 tach_channel;
> +	int ret;
> +
> +	ret = of_property_read_u32(child, "reg", &tach_channel);
> +	if (ret)
> +		return ret;
> +
> +	aspeed_create_fan_tach_channel(priv, tach_channel);
> +
> +	return 0;
> +}
> +
> +static void aspeed_tach_reset_assert(void *data)
> +{
> +	struct reset_control *rst = data;
> +
> +	reset_control_assert(rst);
> +}
> +
> +static int aspeed_tach_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np, *child;
> +	struct aspeed_tach_data *priv;
> +	struct device *hwmon;
> +	struct platform_device *parent_dev;
> +	int ret;
> +
> +	np = dev->parent->of_node;
> +	if (!of_device_is_compatible(np, "aspeed,ast2600-pwm-tach"))
> +		return dev_err_probe(dev, -ENODEV,
> +				     "Unsupported tach device binding\n");
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +	priv->dev = &pdev->dev;
> +	priv->tach_channel =
> +		devm_kzalloc(dev,
> +			     TACH_ASPEED_NR_TACHS * sizeof(*priv->tach_channel),
> +			     GFP_KERNEL);
> +
> +	priv->regmap = syscon_node_to_regmap(np);
> +	if (IS_ERR(priv->regmap)) {
> +		dev_err(priv->dev, "Couldn't get regmap\n");
> +		return -ENODEV;

Why replace the reported error ?

> +	}
> +	parent_dev = of_find_device_by_node(np);
> +	priv->clk = devm_clk_get_enabled(&parent_dev->dev, 0);
> +	if (IS_ERR(priv->clk))
> +		return dev_err_probe(dev, PTR_ERR(priv->clk),
> +				     "Couldn't get clock\n");
> +
> +	priv->reset = devm_reset_control_get_shared(&parent_dev->dev, NULL);
> +	if (IS_ERR(priv->reset))
> +		return dev_err_probe(dev, PTR_ERR(priv->reset),
> +				     "Couldn't get reset control\n");
> +
> +	ret = reset_control_deassert(priv->reset);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Couldn't deassert reset control\n");
> +
> +	ret = devm_add_action_or_reset(dev, aspeed_tach_reset_assert,
> +				       priv->reset);
> +	if (ret)
> +		return ret;
> +
> +	for_each_child_of_node(dev->of_node, child) {
> +		ret = aspeed_tach_create_fan(dev, child, priv);
> +		if (ret) {
> +			of_node_put(child);
> +			return ret;
> +		}
> +	}
> +
> +	hwmon = devm_hwmon_device_register_with_info(
> +		dev, "aspeed_tach", priv, &aspeed_tach_chip_info, NULL);

Please watch out for multi-line alignment (checkpatch --strict); go up to 100
columns if needed.

> +	ret = PTR_ERR_OR_ZERO(hwmon);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to register hwmon device\n");
> +	return 0;
> +}
> +
> +static const struct of_device_id of_stach_match_table[] = {
> +	{
> +		.compatible = "aspeed,ast2600-tach",
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, of_stach_match_table);
> +
> +static struct platform_driver aspeed_tach_driver = {
> +	.probe		= aspeed_tach_probe,
> +	.driver		= {
> +		.name	= "aspeed_tach",
> +		.of_match_table = of_stach_match_table,
> +	},
> +};
> +
> +module_platform_driver(aspeed_tach_driver);
> +
> +MODULE_AUTHOR("Billy Tsai <billy_tsai@aspeedtech.com>");
> +MODULE_DESCRIPTION("Aspeed ast2600 TACH device driver");
> +MODULE_LICENSE("GPL v2");
> +
> -- 
> 2.25.1
> 

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-01  9:51 ` [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach Billy Tsai
@ 2022-11-01 18:40   ` Rob Herring
  2022-11-02  3:21     ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2022-11-01 18:40 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, linux, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Tue, Nov 01, 2022 at 05:51:54PM +0800, Billy Tsai wrote:
> This patch adds device binding for aspeed pwm-tach device which is a
> multi-function device include pwm and tach function and pwm/tach device
> bindings which should be the child-node of pwm-tach device.
> 
> Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
> ---
>  .../bindings/hwmon/aspeed,ast2600-tach.yaml   | 48 ++++++++++++
>  .../bindings/mfd/aspeed,ast2600-pwm-tach.yaml | 76 +++++++++++++++++++
>  .../bindings/pwm/aspeed,ast2600-pwm.yaml      | 64 ++++++++++++++++
>  3 files changed, 188 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
>  create mode 100644 Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
>  create mode 100644 Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml

I'm pretty sure I've said this before, but I'm not taking more fan 
controller bindings without comming up with a common binding. Please see 
this series[1] and help ensure it meets your needs.

Rob

[1] https://lore.kernel.org/all/20221013094838.1529153-2-Naresh.Solanki@9elements.com/

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-01 18:40   ` Rob Herring
@ 2022-11-02  3:21     ` Billy Tsai
  2022-11-02 18:19       ` Rob Herring
  0 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-02  3:21 UTC (permalink / raw)
  To: Rob Herring
  Cc: jdelvare, linux, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

Hi Rob,

On 2022/11/2, 2:40 AM, "Rob Herring" <robh@kernel.org> wrote:

  >  On Tue, Nov 01, 2022 at 05:51:54PM +0800, Billy Tsai wrote:
  >  > This patch adds device binding for aspeed pwm-tach device which is a
  >  > multi-function device include pwm and tach function and pwm/tach device
  >  > bindings which should be the child-node of pwm-tach device.
  >  > 
  >  > Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
  >  > ---
  >  >  .../bindings/hwmon/aspeed,ast2600-tach.yaml   | 48 ++++++++++++
  >  >  .../bindings/mfd/aspeed,ast2600-pwm-tach.yaml | 76 +++++++++++++++++++
  >  >  .../bindings/pwm/aspeed,ast2600-pwm.yaml      | 64 ++++++++++++++++
  >  >  3 files changed, 188 insertions(+)
  >  >  create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
  >  >  create mode 100644 Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
  >  >  create mode 100644 Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml

  >  I'm pretty sure I've said this before, but I'm not taking more fan 
  >  controller bindings without comming up with a common binding. Please see 
  >  this series[1] and help ensure it meets your needs.

  >  Rob

  >  [1] 20221013094838.1529153-2-Naresh.Solanki@9elements.com <https://lore.kernel.org/all/<a href=>/">https://lore.kernel.org/all/20221013094838.1529153-2-Naresh.Solanki@9elements.com/

The link you provide doesn't meet my needs. This is fan binding.
As I told before 
"This patch doesn't use to binding the fan control h/w. It is used to binding the two independent h/w blocks.
One is used to provide pwm output and another is used to monitor the speed of the input." 
My patch is used to point out that the pwm and the tach is the different function and don't need to
bind together. You can not only combine them as the fan usage but also treat them as the individual module for
use. For example: the pwm can use to be the beeper (pwm-beeper.c), the tach can be used to monitor any device's speed.

Thanks

Best Regards,
Billy Tsai


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-01 13:14   ` Guenter Roeck
@ 2022-11-02  6:54     ` Billy Tsai
  2022-11-02 17:01       ` Guenter Roeck
  0 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-02  6:54 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

Hi Guenter,

On 2022/11/1, 9:15 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:

    On Tue, Nov 01, 2022 at 05:51:56PM +0800, Billy Tsai wrote:
    > > +
    > > +	/* Restart the Tach channel to guarantee the value is fresh */
    > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, false);
    > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, true);

    > Is that really needed ? Doesn't the controller measure values continuously ?

Yes, the controller will measure values continuously by hardware. I will remove it. 
If the user want to get the fresh value, it should be done by the application layer
(e.g. read two times).

    > > +
    > > +	if (ret) {
    > > +		/* return 0 if we didn't get an answer because of timeout*/
    > > +		if (ret == -ETIMEDOUT)
    > > +			return 0;
    > > +		else
    > > +			return ret;

    > else after return is unnecessary, and why would a timeout be be ignored ?

When the user sets the correct fan information (i.e., min_rpm, max_rpm), the read
poll timeout will only occur if the tach pin does not get any signal (i.e. rpm=0).

    > > +	}
    > > +
    > > +	raw_data = val & TACH_ASPEED_VALUE_MASK;
    > > +	/*
    > > +	 * We need the mode to determine if the raw_data is double (from
    > > +	 * counting both edges).
    > > +	 */
    > > +	if (priv->tach_channel[fan_tach_ch].tach_edge == BOTH_EDGES)
    > > +		raw_data <<= 1;
    > > +
    > > +	tach_div = raw_data * (priv->tach_channel[fan_tach_ch].divisor) *
    > > +		   (priv->tach_channel[fan_tach_ch].pulse_pr);
    > > +
    > > +	clk_source = clk_get_rate(priv->clk);
    > > +	dev_dbg(priv->dev, "clk %ld, raw_data %d , tach_div %d\n", clk_source,
    > > +		raw_data, tach_div);
    > > +
    > > +	if (tach_div == 0)
    > > +		return -EDOM;

    > If the fan is off or not connected, would that return an error ?
    > If so, that would be inappropriate; it should return a speed
    > of 0 in that case.

It will be handled by the regmap_read_poll_timeout.

Thanks

Best Regards,
Billy Tsai


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-02  6:54     ` Billy Tsai
@ 2022-11-02 17:01       ` Guenter Roeck
  2022-11-03  3:52         ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2022-11-02 17:01 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Wed, Nov 02, 2022 at 06:54:43AM +0000, Billy Tsai wrote:
> Hi Guenter,
> 
> On 2022/11/1, 9:15 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:
> 
>     On Tue, Nov 01, 2022 at 05:51:56PM +0800, Billy Tsai wrote:
>     > > +
>     > > +	/* Restart the Tach channel to guarantee the value is fresh */
>     > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, false);
>     > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, true);
> 
>     > Is that really needed ? Doesn't the controller measure values continuously ?
> 
> Yes, the controller will measure values continuously by hardware. I will remove it. 
> If the user want to get the fresh value, it should be done by the application layer
> (e.g. read two times).
> 
>     > > +
>     > > +	if (ret) {
>     > > +		/* return 0 if we didn't get an answer because of timeout*/
>     > > +		if (ret == -ETIMEDOUT)
>     > > +			return 0;
>     > > +		else
>     > > +			return ret;
> 
>     > else after return is unnecessary, and why would a timeout be be ignored ?
> 
> When the user sets the correct fan information (i.e., min_rpm, max_rpm), the read
> poll timeout will only occur if the tach pin does not get any signal (i.e. rpm=0).
> 

In that case it would be appropriate to return -ETIMEDOUT to the caller.

Anyway, that should really not happen. Sysfs attributes such as minimum/maximum fan
speed, the number of fan pulses per revolution, or a divider value should only exist
if the chip needs that information, for example to report a fan error/alarm if the
measured speed is out of range or if the chip actually calculates RPM and provides
the result to the driver. Those values should not be necessary (and should not be
used) to calculate some timeout.

>     > > +	}
>     > > +
>     > > +	raw_data = val & TACH_ASPEED_VALUE_MASK;
>     > > +	/*
>     > > +	 * We need the mode to determine if the raw_data is double (from
>     > > +	 * counting both edges).
>     > > +	 */
>     > > +	if (priv->tach_channel[fan_tach_ch].tach_edge == BOTH_EDGES)
>     > > +		raw_data <<= 1;
>     > > +
>     > > +	tach_div = raw_data * (priv->tach_channel[fan_tach_ch].divisor) *
>     > > +		   (priv->tach_channel[fan_tach_ch].pulse_pr);
>     > > +
>     > > +	clk_source = clk_get_rate(priv->clk);
>     > > +	dev_dbg(priv->dev, "clk %ld, raw_data %d , tach_div %d\n", clk_source,
>     > > +		raw_data, tach_div);
>     > > +
>     > > +	if (tach_div == 0)
>     > > +		return -EDOM;
> 
>     > If the fan is off or not connected, would that return an error ?
>     > If so, that would be inappropriate; it should return a speed
>     > of 0 in that case.
> 
> It will be handled by the regmap_read_poll_timeout.

This would only happen if raw_data is 0, or if any of
priv->tach_channel[fan_tach_ch].divisor or priv->tach_channel[fan_tach_ch].pulse_pr
are 0. The latter should never happen, leaving raw_data. If that is 0, I would assume
that there was no fan pulse. That would indicate that the fan isn't running (or maybe
that no fan is connected). Either case that would not warrant returning -EDOM.
If the fan isn't running (no pulse was reported), the reported fan speed should be 0.
If that is an error, the fanX_alarm (or, if available, fanX_min_alarm) should report 1.
Reading the fan speed should never return an error to the caller unless there was
an actual error when reading the value from the hardware.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-02  3:21     ` Billy Tsai
@ 2022-11-02 18:19       ` Rob Herring
  2022-11-03  7:39         ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Rob Herring @ 2022-11-02 18:19 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, linux, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Tue, Nov 1, 2022 at 10:21 PM Billy Tsai <billy_tsai@aspeedtech.com> wrote:
>
> Hi Rob,
>
> On 2022/11/2, 2:40 AM, "Rob Herring" <robh@kernel.org> wrote:
>
>   >  On Tue, Nov 01, 2022 at 05:51:54PM +0800, Billy Tsai wrote:
>   >  > This patch adds device binding for aspeed pwm-tach device which is a
>   >  > multi-function device include pwm and tach function and pwm/tach device
>   >  > bindings which should be the child-node of pwm-tach device.
>   >  >
>   >  > Signed-off-by: Billy Tsai <billy_tsai@aspeedtech.com>
>   >  > ---
>   >  >  .../bindings/hwmon/aspeed,ast2600-tach.yaml   | 48 ++++++++++++
>   >  >  .../bindings/mfd/aspeed,ast2600-pwm-tach.yaml | 76 +++++++++++++++++++
>   >  >  .../bindings/pwm/aspeed,ast2600-pwm.yaml      | 64 ++++++++++++++++
>   >  >  3 files changed, 188 insertions(+)
>   >  >  create mode 100644 Documentation/devicetree/bindings/hwmon/aspeed,ast2600-tach.yaml
>   >  >  create mode 100644 Documentation/devicetree/bindings/mfd/aspeed,ast2600-pwm-tach.yaml
>   >  >  create mode 100644 Documentation/devicetree/bindings/pwm/aspeed,ast2600-pwm.yaml
>
>   >  I'm pretty sure I've said this before, but I'm not taking more fan
>   >  controller bindings without comming up with a common binding. Please see
>   >  this series[1] and help ensure it meets your needs.
>
>   >  Rob
>
>   >  [1] 20221013094838.1529153-2-Naresh.Solanki@9elements.com <https://lore.kernel.org/all/<a href=>/">https://lore.kernel.org/all/20221013094838.1529153-2-Naresh.Solanki@9elements.com/
>
> The link you provide doesn't meet my needs. This is fan binding.

A PWM and Tach controller is for fans, no?

As I said, contribute to it so that it does meet your needs.

> As I told before
> "This patch doesn't use to binding the fan control h/w. It is used to binding the two independent h/w blocks.
> One is used to provide pwm output and another is used to monitor the speed of the input."
> My patch is used to point out that the pwm and the tach is the different function and don't need to
> bind together. You can not only combine them as the fan usage but also treat them as the individual module for
> use. For example: the pwm can use to be the beeper (pwm-beeper.c), the tach can be used to monitor any device's speed.

That all sounds like requirements that you have which you should
ensure the fan binding can support.

I've already said to use the PWM binding in the fan binding exactly
for the purpose of hooking up the PWMs to other things. Whether the
tach controller is useful for something other than fans, I don't know.
Seems less likely. The max6639 also has a tach controller. So if other
uses are possible for you, then it could be possible for any other h/w
like the max6639.

Rob

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-02 17:01       ` Guenter Roeck
@ 2022-11-03  3:52         ` Billy Tsai
  2022-11-03  4:30           ` Guenter Roeck
  0 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-03  3:52 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On 2022/11/3, 1:01 AM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:

    On Wed, Nov 02, 2022 at 06:54:43AM +0000, Billy Tsai wrote:
    >   > Hi Guenter,
    >   > 
    >   > On 2022/11/1, 9:15 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:
    >   > 
    >   >     On Tue, Nov 01, 2022 at 05:51:56PM +0800, Billy Tsai wrote:
    >   >     > > +
    >   >     > > +	/* Restart the Tach channel to guarantee the value is fresh */
    >   >     > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, false);
    >   >     > > +	aspeed_tach_ch_enable(priv, fan_tach_ch, true);
    >   > 
    >   >     > Is that really needed ? Doesn't the controller measure values continuously ?
    >   > 
    >   > Yes, the controller will measure values continuously by hardware. I will remove it. 
    >   > If the user want to get the fresh value, it should be done by the application layer
    >   > (e.g. read two times).
    >   > 
    >   >     > > +
    >   >     > > +	if (ret) {
    >   >     > > +		/* return 0 if we didn't get an answer because of timeout*/
    >   >     > > +		if (ret == -ETIMEDOUT)
    >   >     > > +			return 0;
    >   >     > > +		else
    >   >     > > +			return ret;
    >   > 
    >   >     > else after return is unnecessary, and why would a timeout be be ignored ?
    >   > 
    >   > When the user sets the correct fan information (i.e., min_rpm, max_rpm), the read
    >   > poll timeout will only occur if the tach pin does not get any signal (i.e. rpm=0).
    >   > 

    >   In that case it would be appropriate to return -ETIMEDOUT to the caller.

    >   Anyway, that should really not happen. Sysfs attributes such as minimum/maximum fan
    >   speed, the number of fan pulses per revolution, or a divider value should only exist
    >   if the chip needs that information, for example to report a fan error/alarm if the
    >   measured speed is out of range or if the chip actually calculates RPM and provides
    >   the result to the driver. Those values should not be necessary (and should not be
    >   used) to calculate some timeout.

Can't I use a min/max RPM to let the driver know a reasonable timeout/polling period when
the driver is trying to get RPM?
Beacause that our tach controller have the falg to indicates the hardware detected the change
in the input signal. I need the proper timout to rule out slow RPMs.

Thanks

Best Regards,
Billy Tsai





^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-03  3:52         ` Billy Tsai
@ 2022-11-03  4:30           ` Guenter Roeck
  2022-11-03  5:40             ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2022-11-03  4:30 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Thu, Nov 03, 2022 at 03:52:59AM +0000, Billy Tsai wrote:
> 
> Can't I use a min/max RPM to let the driver know a reasonable timeout/polling period when
> the driver is trying to get RPM?
> Beacause that our tach controller have the falg to indicates the hardware detected the change
> in the input signal. I need the proper timout to rule out slow RPMs.
> 

If the chip measures the fan speed continuously, why would that ever be a
problem, and why wait in the first place instead of just taking the most
recent result ?

Pretty much every other driver is doing that, so I really don't understand
why that would not work here.

Guenter

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-03  4:30           ` Guenter Roeck
@ 2022-11-03  5:40             ` Billy Tsai
  2022-11-03 14:30               ` Guenter Roeck
  0 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-03  5:40 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On 2022/11/3, 12:30 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:

    On Thu, Nov 03, 2022 at 03:52:59AM +0000, Billy Tsai wrote:
    > > 
    > > Can't I use a min/max RPM to let the driver know a reasonable timeout/polling period when
    > > the driver is trying to get RPM?
    > > Beacause that our tach controller have the falg to indicates the hardware detected the change
    > > in the input signal. I need the proper timout to rule out slow RPMs.

    > If the chip measures the fan speed continuously, why would that ever be a
    > problem, and why wait in the first place instead of just taking the most
    > recent result ?

    > Pretty much every other driver is doing that, so I really don't understand
    > why that would not work here.

When the fan speed drop from a very fast RPM to a very slow RPM. Especially when it is close to stopping.
The most recent result will be no meaningful value. The slower RPM needs more time to sample it. E.g., If
we want to measure the fan with 600 RPM, the controller needs at least 100ms. During this time period, we
will always get the wrong value. So, our tach controller have the flag to avoid this problem:
TACH_ASPEED_VALUE_UPDATE: tach value updated since last read
This flag will be set when the controller detected the change of the signal and clear by read it.
In order to use this flag, the controller needs the proper timeout based on minimum RPM to avoid waiting forever.

Best Regards,
Billy Tsai


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-02 18:19       ` Rob Herring
@ 2022-11-03  7:39         ` Billy Tsai
  2022-11-14  8:26           ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Billy Tsai @ 2022-11-03  7:39 UTC (permalink / raw)
  To: Rob Herring
  Cc: jdelvare, linux, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On 2022/11/3, 2:19 AM, "Rob Herring" <robh@kernel.org> wrote:

    On Tue, Nov 1, 2022 at 10:21 PM Billy Tsai <billy_tsai@aspeedtech.com> wrote:
    
    > That all sounds like requirements that you have which you should
    > ensure the fan binding can support.

    > I've already said to use the PWM binding in the fan binding exactly
    > for the purpose of hooking up the PWMs to other things. Whether the
    > tach controller is useful for something other than fans, I don't know.
    > Seems less likely. The max6639 also has a tach controller. So if other
    > uses are possible for you, then it could be possible for any other h/w
    > like the max6639.

The linux kernel already have the similar binding:
https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/hwmon/pwm-fan.txt 
Therefore, I want to reuse it and the pwm-fan.c instead of creating another similar fan binding and driver.
I am referring to the following files:
https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/mfd/kontron%2Csl28cpld.yaml 
https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/pwm/kontron%2Csl28cpld-pwm.yaml 
https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/hwmon/kontron%2Csl28cpld-hwmon.yaml 
It will also reuse the pwm-fan instead creating it own pwm-fan binding for their hwmon(tach).
https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var3-ads2.dts#L20 

Thanks

Best Regards,
Billy Tsai



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-03  5:40             ` Billy Tsai
@ 2022-11-03 14:30               ` Guenter Roeck
  2022-11-08  7:17                 ` Billy Tsai
  0 siblings, 1 reply; 19+ messages in thread
From: Guenter Roeck @ 2022-11-03 14:30 UTC (permalink / raw)
  To: Billy Tsai
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

On Thu, Nov 03, 2022 at 05:40:44AM +0000, Billy Tsai wrote:
> On 2022/11/3, 12:30 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:
> 
>     On Thu, Nov 03, 2022 at 03:52:59AM +0000, Billy Tsai wrote:
>     > > 
>     > > Can't I use a min/max RPM to let the driver know a reasonable timeout/polling period when
>     > > the driver is trying to get RPM?
>     > > Beacause that our tach controller have the falg to indicates the hardware detected the change
>     > > in the input signal. I need the proper timout to rule out slow RPMs.
> 
>     > If the chip measures the fan speed continuously, why would that ever be a
>     > problem, and why wait in the first place instead of just taking the most
>     > recent result ?
> 
>     > Pretty much every other driver is doing that, so I really don't understand
>     > why that would not work here.
> 
> When the fan speed drop from a very fast RPM to a very slow RPM. Especially when it is close to stopping.
> The most recent result will be no meaningful value. The slower RPM needs more time to sample it. E.g., If
> we want to measure the fan with 600 RPM, the controller needs at least 100ms. During this time period, we
> will always get the wrong value. So, our tach controller have the flag to avoid this problem:
> TACH_ASPEED_VALUE_UPDATE: tach value updated since last read
> This flag will be set when the controller detected the change of the signal and clear by read it.
> In order to use this flag, the controller needs the proper timeout based on minimum RPM to avoid waiting forever.
> 

I am not going to accept this patch as-is. If userspace wants to have
values accurate down to ms, this kind of approach is just wrong. Users
will have to live with the fact that measurements may be a bit (in the 
< 1 second range) out of date. Many older drivers even implement code
which avoids reading registers again for a second or longer. Older
temperature sensors may take several seconds to provide new readings.
That is not a reason to block userspace until a new value is available.
I do not see that as a problem. In my opinion it is much more of a
problem if the driver returns a completely bad value such as 0 or even
an error code because its software parameters did not match reality and
the driver didn't wait long enough for a new value. That would be _much_
worse than providing a value which is a few 100 ms out of date, and your
code is vulnerable to that problem.

Besides, for a fan to reduce its speed that quickly, it has to be manually
stopped. Normally fans take several seconds to stop if power is taken away
completely. Your code is adding a lot of complexity (and unnecessary
attributes) for no good reason.

Guenter

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 3/3] hwmon: Add Aspeed ast2600 TACH support
  2022-11-03 14:30               ` Guenter Roeck
@ 2022-11-08  7:17                 ` Billy Tsai
  0 siblings, 0 replies; 19+ messages in thread
From: Billy Tsai @ 2022-11-08  7:17 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: jdelvare, robh+dt, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic


On 2022/11/3, 10:31 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:

    On Thu, Nov 03, 2022 at 05:40:44AM +0000, Billy Tsai wrote:
    > > On 2022/11/3, 12:30 PM, "Guenter Roeck" <groeck7@gmail.com on behalf of linux@roeck-us.net> wrote:
    > > 
    > >     On Thu, Nov 03, 2022 at 03:52:59AM +0000, Billy Tsai wrote:
    > >     > > 
    > >     > > Can't I use a min/max RPM to let the driver know a reasonable timeout/polling period when
    > >     > > the driver is trying to get RPM?
    > >     > > Beacause that our tach controller have the falg to indicates the hardware detected the change
    > >     > > in the input signal. I need the proper timout to rule out slow RPMs.
    > > 
    > >     > If the chip measures the fan speed continuously, why would that ever be a
    > >     > problem, and why wait in the first place instead of just taking the most
    > >     > recent result ?
    > > 
    > >     > Pretty much every other driver is doing that, so I really don't understand
    > >     > why that would not work here.
    > > 
    > > When the fan speed drop from a very fast RPM to a very slow RPM. Especially when it is close to stopping.
    > > The most recent result will be no meaningful value. The slower RPM needs more time to sample it. E.g., If
    > > we want to measure the fan with 600 RPM, the controller needs at least 100ms. During this time period, we
    > > will always get the wrong value. So, our tach controller have the flag to avoid this problem:
    > > TACH_ASPEED_VALUE_UPDATE: tach value updated since last read
    > > This flag will be set when the controller detected the change of the signal and clear by read it.
    > > In order to use this flag, the controller needs the proper timeout based on minimum RPM to avoid waiting forever.
    > > 

    > I am not going to accept this patch as-is. If userspace wants to have
    > values accurate down to ms, this kind of approach is just wrong. Users
    > will have to live with the fact that measurements may be a bit (in the 
    > < 1 second range) out of date. Many older drivers even implement code
    > which avoids reading registers again for a second or longer. Older
    > temperature sensors may take several seconds to provide new readings.
    > That is not a reason to block userspace until a new value is available.
    > I do not see that as a problem. In my opinion it is much more of a
    > problem if the driver returns a completely bad value such as 0 or even
    > an error code because its software parameters did not match reality and
    > the driver didn't wait long enough for a new value. That would be _much_
    > worse than providing a value which is a few 100 ms out of date, and your
    > code is vulnerable to that problem.

    > Besides, for a fan to reduce its speed that quickly, it has to be manually
    > stopped. Normally fans take several seconds to stop if power is taken away
    > completely. Your code is adding a lot of complexity (and unnecessary
    > attributes) for no good reason.

Thanks for your detailed explanations.
I will remove the min and max RPMs and return the tach value directly without any polling or delay.

Best Regards,
Billy Tsai



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach.
  2022-11-03  7:39         ` Billy Tsai
@ 2022-11-14  8:26           ` Billy Tsai
  0 siblings, 0 replies; 19+ messages in thread
From: Billy Tsai @ 2022-11-14  8:26 UTC (permalink / raw)
  To: Rob Herring
  Cc: jdelvare, linux, joel, andrew, lee.jones, thierry.reding,
	u.kleine-koenig, p.zabel, linux-hwmon, devicetree,
	linux-arm-kernel, linux-aspeed, linux-kernel, linux-pwm, BMC-SW,
	garnermic

Hi Rob,

On 2022/11/3, 3:39 PM, "Billy Tsai" <billy_tsai@aspeedtech.com> wrote:

    On 2022/11/3, 2:19 AM, "Rob Herring" <robh@kernel.org> wrote:

        On Tue, Nov 1, 2022 at 10:21 PM Billy Tsai <billy_tsai@aspeedtech.com> wrote:

    >    > That all sounds like requirements that you have which you should
    >    > ensure the fan binding can support.

    >    > I've already said to use the PWM binding in the fan binding exactly
    >    > for the purpose of hooking up the PWMs to other things. Whether the
    >    > tach controller is useful for something other than fans, I don't know.
    >    > Seems less likely. The max6639 also has a tach controller. So if other
    >    > uses are possible for you, then it could be possible for any other h/w
    >    > like the max6639.

    > The linux kernel already have the similar binding:
    > https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/hwmon/pwm-fan.txt 
    > Therefore, I want to reuse it and the pwm-fan.c instead of creating another similar fan binding and driver.
    > I am referring to the following files:
    > https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/mfd/kontron%2Csl28cpld.yaml 
    > https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/pwm/kontron%2Csl28cpld-pwm.yaml 
    > https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/hwmon/kontron%2Csl28cpld-hwmon.yaml 
    > It will also reuse the pwm-fan instead creating it own pwm-fan binding for their hwmon(tach).
    > https://github.com/torvalds/linux/blob/master/arch/arm64/boot/dts/freescale/fsl-ls1028a-kontron-sl28-var3-ads2.dts#L20 

Do you have any sugguest about the binding like the link above?
We can just creat the pure PWM provider and use the pwm-fan as the common fan binding instead of creating another fan-common.yaml.

Thanks

Best Regards,
Billy Tsai




^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2022-11-14  8:27 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-01  9:51 [v2 0/3] upport pwm/tach driver for aspeed ast26xx Billy Tsai
2022-11-01  9:51 ` [v2 1/3] dt-bindings: Add bindings for aspeed pwm-tach Billy Tsai
2022-11-01 18:40   ` Rob Herring
2022-11-02  3:21     ` Billy Tsai
2022-11-02 18:19       ` Rob Herring
2022-11-03  7:39         ` Billy Tsai
2022-11-14  8:26           ` Billy Tsai
2022-11-01  9:51 ` [v2 2/3] pwm: Add Aspeed ast2600 PWM support Billy Tsai
2022-11-01 10:03   ` Christophe JAILLET
2022-11-01  9:51 ` [v2 3/3] hwmon: Add Aspeed ast2600 TACH support Billy Tsai
2022-11-01 10:16   ` Christophe JAILLET
2022-11-01 13:14   ` Guenter Roeck
2022-11-02  6:54     ` Billy Tsai
2022-11-02 17:01       ` Guenter Roeck
2022-11-03  3:52         ` Billy Tsai
2022-11-03  4:30           ` Guenter Roeck
2022-11-03  5:40             ` Billy Tsai
2022-11-03 14:30               ` Guenter Roeck
2022-11-08  7:17                 ` Billy Tsai

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).