devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] Introduce Allwinner H616 PWM controller
@ 2025-12-05 10:02 Richard Genoud
  2025-12-05 10:02 ` [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616 Richard Genoud
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Richard Genoud @ 2025-12-05 10:02 UTC (permalink / raw)
  To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel, Richard Genoud

Allwinner H616 PWM controller is quite different from the A10 one.

It can drive 6 PWM channels, and like for the A10, each channel has a
bypass that permits to output a clock, bypassing the PWM logic, when
enabled.

But, the channels are paired 2 by 2, sharing a first set of
MUX/prescaler/gate.
Then, for each channel, there's another prescaler (that will be bypassed
if the bypass is enabled for this channel).

It looks like that:
            _____      ______      ________
OSC24M --->|     |    |      |    |        |
APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
           |_____|    |______|    |________|
                          ________
                         |        |
                      +->| /div_k |---> PWM_clock_x
                      |  |________|
                      |    ______
                      |   |      |
                      +-->| Gate |----> PWM_bypass_clock_x
                      |   |______|
PWM_clock_src_xy -----+   ________
                      |  |        |
                      +->| /div_k |---> PWM_clock_y
                      |  |________|
                      |    ______
                      |   |      |
                      +-->| Gate |----> PWM_bypass_clock_y
                          |______|

Where xy can be 0/1, 2/3, 4/5

PWM_clock_x/y serve for the PWM purpose.
PWM_bypass_clock_x/y serve for the clock-provider purpose.
The common clock framework has been used to manage those clocks.

This PWM driver serves as a clock-provider for PWM_bypass_clocks.
This is needed for example by the embedded AC300 PHY which clock comes
from PMW5 pin (PB12).

This series is based onto next-20251205

Richard Genoud (4):
  dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  pwm: sun50i: Add H616 PWM support
  arm64: dts: allwinner: h616: add PWM controller
  MAINTAINERS: Add entry on Allwinner H616 PWM driver

 .../pwm/allwinner,sun50i-h616-pwm.yaml        |  67 ++
 MAINTAINERS                                   |   6 +
 .../arm64/boot/dts/allwinner/sun50i-h616.dtsi |  47 +
 drivers/pwm/Kconfig                           |  12 +
 drivers/pwm/Makefile                          |   1 +
 drivers/pwm/pwm-sun50i-h616.c                 | 874 ++++++++++++++++++
 6 files changed, 1007 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
 create mode 100644 drivers/pwm/pwm-sun50i-h616.c


base-commit: 6987d58a9cbc5bd57c983baa514474a86c945d56
-- 
2.47.3


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

* [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  2025-12-05 10:02 [PATCH 0/4] Introduce Allwinner H616 PWM controller Richard Genoud
@ 2025-12-05 10:02 ` Richard Genoud
  2025-12-08  6:52   ` Krzysztof Kozlowski
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Richard Genoud @ 2025-12-05 10:02 UTC (permalink / raw)
  To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel, Richard Genoud

Allwinner H616 SoC contains a PWM controller quite different from the A10.
It has 6 channels than can generate PWM waveforms or clocks if bypass is
enabled.

Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 .../pwm/allwinner,sun50i-h616-pwm.yaml        | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml

diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
new file mode 100644
index 000000000000..b89735ad3a43
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/allwinner,sun50i-h616-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Allwinner H616 PWM
+
+maintainers:
+  - Richard Genoud <richard.genoud@bootlin.com>
+
+description: |
+  Allwinner H616 PWM can generate standard PWM signals with variable pulse width
+  and period.
+  Also, instead of a PWM signal, a channel can be used to provide a clock.
+
+properties:
+  compatible:
+    const: allwinner,sun50i-h616-pwm
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Bus Clock
+
+  clock-names:
+    items:
+      - const: bus
+
+  resets:
+    maxItems: 1
+
+  "#clock-cells":
+    const: 1
+
+  "#pwm-cells":
+    const: 3
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - resets
+
+allOf:
+  - $ref: pwm.yaml#
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/sun50i-h616-ccu.h>
+    #include <dt-bindings/reset/sun50i-h616-ccu.h>
+
+    pwm@300a000 {
+      compatible = "allwinner,sun50i-h616-pwm";
+      reg = <0x0300a000 0x400>;
+      clocks = <&ccu CLK_BUS_PWM>;
+      clock-names = "bus";
+      resets = <&ccu RST_BUS_PWM>;
+      #pwm-cells = <3>;
+      #clock-cells = <1>;
+    };
+...
-- 
2.47.3


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

* [PATCH 2/4] pwm: sun50i: Add H616 PWM support
  2025-12-05 10:02 [PATCH 0/4] Introduce Allwinner H616 PWM controller Richard Genoud
  2025-12-05 10:02 ` [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616 Richard Genoud
@ 2025-12-05 10:02 ` Richard Genoud
  2025-12-06  4:22   ` kernel test robot
                     ` (3 more replies)
  2025-12-05 10:02 ` [PATCH 3/4] arm64: dts: allwinner: h616: add PWM controller Richard Genoud
  2025-12-05 10:02 ` [PATCH 4/4] MAINTAINERS: Add entry on Allwinner H616 PWM driver Richard Genoud
  3 siblings, 4 replies; 13+ messages in thread
From: Richard Genoud @ 2025-12-05 10:02 UTC (permalink / raw)
  To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel, Richard Genoud

Add driver for Allwinner H616 PWM controller, supporting up to 6
channels.
Those channels output can be either a PWM signal output or a clock
output, thanks to the bypass.

The channels are paired (0/1, 2/3 and 4/5) and each pair has a
prescaler/mux/gate.
Moreover, each channel has its own prescaler and bypass.

Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 drivers/pwm/Kconfig           |  12 +
 drivers/pwm/Makefile          |   1 +
 drivers/pwm/pwm-sun50i-h616.c | 874 ++++++++++++++++++++++++++++++++++
 3 files changed, 887 insertions(+)
 create mode 100644 drivers/pwm/pwm-sun50i-h616.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index bf2d101f67a1..1f724e1cf992 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -736,6 +736,18 @@ config PWM_SUN4I
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-sun4i.
 
+config PWM_SUN50I_H616
+	tristate "Allwinner H616 PWM support"
+	depends on ARCH_SUNXI || COMPILE_TEST
+	depends on HAS_IOMEM && COMMON_CLK
+	help
+	  Generic PWM framework driver for Allwinner H616 SoCs.
+	  It supports generic PWM, but can also provides a plain clock.
+	  The AC300 PHY integrated in H616 SoC needs such a clock.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-h616.
+
 config PWM_SUNPLUS
 	tristate "Sunplus PWM support"
 	depends on ARCH_SUNPLUS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 0dc0d2b69025..a16ae9eef9e5 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_PWM_STM32)		+= pwm-stm32.o
 obj-$(CONFIG_PWM_STM32_LP)	+= pwm-stm32-lp.o
 obj-$(CONFIG_PWM_STMPE)		+= pwm-stmpe.o
 obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
+obj-$(CONFIG_PWM_SUN50I_H616)	+= pwm-sun50i-h616.o
 obj-$(CONFIG_PWM_SUNPLUS)	+= pwm-sunplus.o
 obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
 obj-$(CONFIG_PWM_TH1520)	+= pwm_th1520.o
diff --git a/drivers/pwm/pwm-sun50i-h616.c b/drivers/pwm/pwm-sun50i-h616.c
new file mode 100644
index 000000000000..82b4a30d34f1
--- /dev/null
+++ b/drivers/pwm/pwm-sun50i-h616.c
@@ -0,0 +1,874 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Allwinner H616 Pulse Width Modulation Controller
+ *
+ * (C) Copyright 2025 Richard Genoud, Bootlin <richard.genoud@bootlin.com>
+ *
+ * Based on drivers/pwm/pwm-sun4i.c with Copyright:
+ *
+ * Copyright (C) 2014 Alexandre Belloni <alexandre.belloni@bootlin.com>
+ *
+ * Limitations:
+ * - When outputing the source clock directly, the PWM logic is bypassed and the
+ *   currently running period is not guaranteed to be completed.
+ * - As the channels are paired (0/1, 2/3, 4/5), they share the same clock
+ *   source and prescaler(div_m), but they also have their own prescaler(div_k)
+ *   and bypass.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+
+#ifndef UINT32_MAX
+#define UINT32_MAX 0xffffffffU
+#endif
+
+/* PWM IRQ Enable Register */
+#define PWM_IER				0x0
+
+/* PWM IRQ Status Register */
+#define PWM_ISR				0x4
+
+/* PWM Capture IRQ Enable Register */
+#define PWM_CIER			0x10
+
+/* PWM Capture IRQ Status Register */
+#define PWM_CISR			0x14
+
+/* PWMCC Pairs Clock Configuration Registers */
+#define PWM_XY_CLK_CR(pair)		(0x20 + ((pair) * 0x4))
+#define PWM_XY_CLK_CR_SRC_SHIFT		7
+#define PWM_XY_CLK_CR_SRC_MASK		1
+#define PWM_XY_CLK_CR_GATE_BIT		4
+#define PWM_XY_CLK_CR_BYPASS_BIT(chan)	((chan) % 2 + 5)
+#define PWM_XY_CLK_CR_DIV_M_SHIFT	0
+
+/* PWMCC Pairs Dead Zone Control Registers */
+#define PWM_XY_DZ(pair)			(0x30 + ((pair) * 0x4))
+
+/* PWM Enable Register */
+#define PWM_ENR				0x40
+#define PWM_ENABLE(x)			BIT(x)
+
+/* PWM Capture Enable Register */
+#define PWM_CER				0x44
+
+/* PWM Control Register */
+#define PWM_CTRL_REG(chan)		(0x60 + (chan) * 0x20)
+#define PWM_CTRL_PRESCAL_K_SHIFT	0
+#define PWM_CTRL_PRESCAL_K_WIDTH	8
+#define PWM_CTRL_ACTIVE_STATE		BIT(8)
+
+/* PWM Period Register */
+#define PWM_PERIOD_REG(ch)		(0x64 + (ch) * 0x20)
+#define PWM_PERIOD_MASK			GENMASK(31, 16)
+#define PWM_DUTY_MASK			GENMASK(15, 0)
+#define PWM_REG_PERIOD(reg)		(FIELD_GET(PWM_PERIOD_MASK, reg) + 1)
+#define PWM_REG_DUTY(reg)		FIELD_GET(PWM_DUTY_MASK, reg)
+#define PWM_PERIOD(prd)			FIELD_PREP(PWM_PERIOD_MASK, (prd) - 1)
+#define PWM_DUTY(dty)			FIELD_PREP(PWM_DUTY_MASK, dty)
+#define PWM_PERIOD_MAX			FIELD_MAX(PWM_PERIOD_MASK)
+
+
+/* PWM Count Register */
+#define PWM_CNT_REG(x)			(0x68 + (x) * 0x20)
+
+/* PWM Capture Control Register */
+#define PWM_CCR(x)			(0x6c + (x) * 0x20)
+
+/* PWM Capture Rise Lock Register */
+#define PWM_CRLR(x)			(0x70 + (x) * 0x20)
+
+/* PWM Capture Fall Lock Register */
+#define PWM_CFLR(x)			(0x74 + (x) * 0x20)
+
+#define PWM_PAIR_IDX(chan)		((chan) >> 2)
+
+/*
+ * Block diagram of the PWM clock controller:
+ *
+ *             _____      ______      ________
+ * OSC24M --->|     |    |      |    |        |
+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
+ *            |_____|    |______|    |________|
+ *                           ________
+ *                          |        |
+ *                       +->| /div_k |---> PWM_clock_x
+ *                       |  |________|
+ *                       |    ______
+ *                       |   |      |
+ *                       +-->| Gate |----> PWM_bypass_clock_x
+ *                       |   |______|
+ * PWM_clock_src_xy -----+   ________
+ *                       |  |        |
+ *                       +->| /div_k |---> PWM_clock_y
+ *                       |  |________|
+ *                       |    ______
+ *                       |   |      |
+ *                       +-->| Gate |----> PWM_bypass_clock_y
+ *                           |______|
+ *
+ * NB: when the bypass is set, all the PWM logic is bypassed.
+ * So, the duty cycle and polarity can't be modified (we just have a clock).
+ * The bypass in PWM mode is used to achieve a 1/2 duty cycle with the fastest
+ * clock.
+ *
+ * PWM_clock_x/y serve for the PWM purpose.
+ * PWM_bypass_clock_x/y serve for the clock-provider purpose.
+ *
+ */
+
+/*
+ * Table used for /div_m (diviser before obtaining PWM_clock_src_xy)
+ * It's actually CLK_DIVIDER_POWER_OF_TWO, but limited to /256
+ */
+static const struct clk_div_table clk_table_div_m[] = {
+	{ .val = 0, .div = 1, },
+	{ .val = 1, .div = 2, },
+	{ .val = 2, .div = 4, },
+	{ .val = 3, .div = 8, },
+	{ .val = 4, .div = 16, },
+	{ .val = 5, .div = 32, },
+	{ .val = 6, .div = 64, },
+	{ .val = 7, .div = 128, },
+	{ .val = 8, .div = 256, },
+	{ .val = 0, .div = 0, }, /* last entry */
+};
+
+#define PWM_XY_SRC_GATE(_pair, _reg)			\
+struct clk_gate gate_xy_##_pair = {			\
+	.reg = (void *)_reg,				\
+	.bit_idx = PWM_XY_CLK_CR_GATE_BIT,		\
+	.hw.init = &(struct clk_init_data){		\
+		.ops =  &clk_gate_ops,			\
+	}						\
+}
+
+#define PWM_XY_SRC_MUX(_pair, _reg)			\
+struct clk_mux mux_xy_##_pair = {			\
+	.reg = (void *)_reg,				\
+	.shift = PWM_XY_CLK_CR_SRC_SHIFT,		\
+	.mask = PWM_XY_CLK_CR_SRC_MASK,			\
+	.flags = CLK_MUX_ROUND_CLOSEST,			\
+	.hw.init = &(struct clk_init_data){		\
+		.ops =  &clk_mux_ops,			\
+	}						\
+}
+
+#define PWM_XY_SRC_DIV(_pair, _reg)			\
+struct clk_divider rate_xy_##_pair = {			\
+	.reg = (void *)_reg,				\
+	.shift = PWM_XY_CLK_CR_DIV_M_SHIFT,		\
+	.table = clk_table_div_m,			\
+	.hw.init = &(struct clk_init_data){		\
+		.ops =  &clk_divider_ops,		\
+	}						\
+}
+
+#define PWM_X_DIV(_idx, _reg)				\
+struct clk_divider rate_x_##_idx = {			\
+	.reg = (void *)_reg,				\
+	.shift = PWM_CTRL_PRESCAL_K_SHIFT,		\
+	.width = PWM_CTRL_PRESCAL_K_WIDTH,		\
+	.hw.init = &(struct clk_init_data){		\
+		.ops =  &clk_divider_ops,		\
+	}						\
+}
+
+#define PWM_X_BYPASS_GATE(_idx)				\
+struct clk_gate gate_x_bypass_##_idx = {		\
+	.reg = (void *)PWM_ENR,				\
+	.bit_idx = _idx,				\
+	.hw.init = &(struct clk_init_data){		\
+		.ops =  &clk_gate_ops,			\
+	}						\
+}
+
+#define PWM_XY_CLK_SRC(_pair, _reg)			\
+	static PWM_XY_SRC_MUX(_pair, _reg);		\
+	static PWM_XY_SRC_GATE(_pair, _reg);		\
+	static PWM_XY_SRC_DIV(_pair, _reg)
+
+#define PWM_X_CLK(_idx)					\
+	static PWM_X_DIV(_idx, PWM_CTRL_REG(_idx))
+
+#define PWM_X_BYPASS_CLK(_idx)						\
+	PWM_X_BYPASS_GATE(_idx)
+
+#define REF_CLK_XY_SRC(_pair)						\
+	{								\
+		.name = "pwm-clk-src" #_pair,				\
+		.parent_names = (const char *[]){ "osc24M", "apb1" },	\
+		.num_parents = 2,					\
+		.mux_hw = &mux_xy_##_pair.hw,				\
+		.gate_hw = &gate_xy_##_pair.hw,				\
+		.rate_hw = &rate_xy_##_pair.hw,				\
+	}
+
+#define REF_CLK_X(_idx, _pair)						\
+	{								\
+		.name = "pwm-clk" #_idx,				\
+		.parent_names = (const char *[]){ "pwm-clk-src" #_pair }, \
+		.num_parents = 1,					\
+		.rate_hw = &rate_x_##_idx.hw,				\
+		.flags = CLK_SET_RATE_PARENT,				\
+	}
+
+#define REF_CLK_BYPASS(_idx, _pair)					\
+	{								\
+		.name = "pwm-clk-bypass" #_idx,				\
+		.parent_names = (const char *[]){ "pwm-clk-src" #_pair }, \
+		.num_parents = 1,					\
+		.gate_hw = &gate_x_bypass_##_idx.hw,			\
+		.flags = CLK_SET_RATE_PARENT,	\
+	}
+
+/*
+ * PWM_clock_src_xy generation:
+ *             _____      ______      ________
+ * OSC24M --->|     |    |      |    |        |
+ * APB1 ----->| Mux |--->| Gate |--->| /div_m |-----> PWM_clock_src_xy
+ *            |_____|    |______|    |________|
+ */
+PWM_XY_CLK_SRC(01, PWM_XY_CLK_CR(0));
+PWM_XY_CLK_SRC(23, PWM_XY_CLK_CR(1));
+PWM_XY_CLK_SRC(45, PWM_XY_CLK_CR(2));
+
+/*
+ * PWM_clock_x_div generation:
+ *                       ________
+ *                      |        | PWM_clock_x/y
+ * PWM_clock_src_xy --->| /div_k |--------------->
+ *                      |________|
+ */
+PWM_X_CLK(0);
+PWM_X_CLK(1);
+PWM_X_CLK(2);
+PWM_X_CLK(3);
+PWM_X_CLK(4);
+PWM_X_CLK(5);
+
+/*
+ * PWM_bypass_clock_xy generation:
+ *                        ______
+ *                       |      |
+ * PWM_clock_src_xy ---->| Gate |-------> PWM_bypass_clock_x
+ *                       |______|
+ *
+ * The gate is actually PWM_ENR register.
+ */
+PWM_X_BYPASS_CLK(0);
+PWM_X_BYPASS_CLK(1);
+PWM_X_BYPASS_CLK(2);
+PWM_X_BYPASS_CLK(3);
+PWM_X_BYPASS_CLK(4);
+PWM_X_BYPASS_CLK(5);
+
+struct clk_pwm_data {
+	const char *name;
+	const char * const *parent_names;
+	int num_parents;
+	struct clk_hw *mux_hw;
+	struct clk_hw *rate_hw;
+	struct clk_hw *gate_hw;
+	unsigned long flags;
+};
+
+#define CLK_BYPASS(h616chip, ch) ((h616chip)->data->npwm + (ch))
+#define CLK_XY_SRC_IDX(h616chip, ch) ((h616chip)->data->npwm * 2 + ((ch) >> 1))
+static struct clk_pwm_data pwmcc_data[] = {
+	REF_CLK_X(0, 01),
+	REF_CLK_X(1, 01),
+	REF_CLK_X(2, 23),
+	REF_CLK_X(3, 23),
+	REF_CLK_X(4, 45),
+	REF_CLK_X(5, 45),
+	REF_CLK_BYPASS(0, 01),
+	REF_CLK_BYPASS(1, 01),
+	REF_CLK_BYPASS(2, 23),
+	REF_CLK_BYPASS(3, 23),
+	REF_CLK_BYPASS(4, 45),
+	REF_CLK_BYPASS(5, 45),
+	REF_CLK_XY_SRC(01),
+	REF_CLK_XY_SRC(23),
+	REF_CLK_XY_SRC(45),
+	{ /* sentinel */ },
+};
+
+enum h616_pwm_mode {
+	H616_PWM_MODE_NONE,
+	H616_PWM_MODE_PWM,
+	H616_PWM_MODE_CLK,
+};
+
+struct h616_pwm_data {
+	unsigned int npwm;
+};
+
+struct h616_pwm_channel {
+	struct clk *pwm_clk;
+	unsigned long rate;
+	unsigned int entire_cycles;
+	unsigned int active_cycles;
+	enum h616_pwm_mode mode;
+	bool bypass;
+};
+
+struct clk_pwm_pdata {
+	struct clk_hw_onecell_data *hw_data;
+	spinlock_t lock;
+	void __iomem *reg;
+};
+
+struct h616_pwm_chip {
+	struct clk_pwm_pdata *clk_pdata;
+	struct h616_pwm_channel *channels;
+	struct clk *bus_clk;
+	struct reset_control *rst;
+	void __iomem *base;
+	const struct h616_pwm_data *data;
+};
+
+static inline struct h616_pwm_chip *to_h616_pwm_chip(struct pwm_chip *chip)
+{
+	return pwmchip_get_drvdata(chip);
+}
+
+static inline u32 h616_pwm_readl(struct h616_pwm_chip *h616chip,
+				 unsigned long offset)
+{
+	return readl(h616chip->base + offset);
+}
+
+static inline void h616_pwm_writel(struct h616_pwm_chip *h616chip,
+				   u32 val, unsigned long offset)
+{
+	writel(val, h616chip->base + offset);
+}
+
+static void h616_pwm_set_bypass(struct h616_pwm_chip *h616chip, unsigned int idx,
+				bool en_bypass)
+{
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(&h616chip->clk_pdata->lock, flags);
+
+	val = h616_pwm_readl(h616chip, PWM_XY_CLK_CR(PWM_PAIR_IDX(idx)));
+	if (en_bypass)
+		val |= BIT(PWM_XY_CLK_CR_BYPASS_BIT(idx));
+	else
+		val &= ~BIT(PWM_XY_CLK_CR_BYPASS_BIT(idx));
+
+	h616_pwm_writel(h616chip, val, PWM_XY_CLK_CR(PWM_PAIR_IDX(idx)));
+
+	spin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);
+}
+
+static int h616_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
+	struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
+	struct device *dev = pwmchip_parent(chip);
+	unsigned long flags;
+	int ret = 0;
+
+	spin_lock_irqsave(&h616chip->clk_pdata->lock, flags);
+
+	if (chan->mode == H616_PWM_MODE_CLK)
+		ret = -EBUSY;
+	else
+		chan->mode = H616_PWM_MODE_PWM;
+
+	spin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);
+	if (ret)
+		goto out;
+
+	ret = clk_prepare_enable(chan->pwm_clk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable clock %s: %d\n",
+			__clk_get_name(chan->pwm_clk), ret);
+	}
+out:
+	return ret;
+}
+
+static void h616_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
+	struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
+
+	clk_disable_unprepare(chan->pwm_clk);
+	chan->mode = H616_PWM_MODE_NONE;
+}
+
+static int h616_pwm_get_state(struct pwm_chip *chip,
+			      struct pwm_device *pwm,
+			      struct pwm_state *state)
+{
+	struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
+	struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
+	u64 clk_rate, tmp;
+	u32 val;
+
+	clk_rate = clk_get_rate(chan->pwm_clk);
+	if (!clk_rate)
+		return -EINVAL;
+
+	val = h616_pwm_readl(h616chip, PWM_ENR);
+	state->enabled = !!(PWM_ENABLE(pwm->hwpwm) & val);
+
+	val = h616_pwm_readl(h616chip, PWM_XY_CLK_CR(PWM_PAIR_IDX(pwm->hwpwm)));
+	if (val & BIT(PWM_XY_CLK_CR_BYPASS_BIT(pwm->hwpwm))) {
+		/*
+		 * When bypass is enabled, the PWM logic is inactive.
+		 * The PWM_clock_src_xy is directly routed to PWM_clock_x
+		 */
+		state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate);
+		state->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2);
+		state->polarity = PWM_POLARITY_NORMAL;
+		return 0;
+	}
+
+	state->enabled &= !!(BIT(PWM_XY_CLK_CR_GATE_BIT) & val);
+
+	val = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));
+	if (val & PWM_CTRL_ACTIVE_STATE)
+		state->polarity = PWM_POLARITY_NORMAL;
+	else
+		state->polarity = PWM_POLARITY_INVERSED;
+
+	val = h616_pwm_readl(h616chip, PWM_PERIOD_REG(pwm->hwpwm));
+
+	tmp = NSEC_PER_SEC * PWM_REG_DUTY(val);
+	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+
+	tmp = NSEC_PER_SEC * PWM_REG_PERIOD(val);
+	state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
+
+	return 0;
+}
+
+static int h616_pwm_calc(struct pwm_chip *chip, unsigned int idx,
+			 const struct pwm_state *state)
+{
+	struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
+	struct h616_pwm_channel *chan = &h616chip->channels[idx];
+	unsigned int cnt, duty_cnt;
+	unsigned long max_rate;
+	long calc_rate;
+	u64 duty, period, freq;
+
+	duty = state->duty_cycle;
+	period = state->period;
+
+	max_rate = clk_round_rate(chan->pwm_clk, UINT32_MAX);
+
+	dev_dbg(pwmchip_parent(chip), "max_rate: %ld Hz\n", max_rate);
+
+	if ((period * max_rate >= NSEC_PER_SEC) &&
+	    (period * max_rate < 2 * NSEC_PER_SEC) &&
+	    (duty * max_rate * 2 >= NSEC_PER_SEC)) {
+		/*
+		 * If the requested period is to small to be generated by the
+		 * PWM, we can just select the highest clock and bypass the
+		 * PWM logic
+		 */
+		dev_dbg(pwmchip_parent(chip), "Setting bypass (period=%lld)\n",
+			period);
+		freq = div64_u64(NSEC_PER_SEC, period);
+		chan->bypass = true;
+		duty = period / 2;
+	} else {
+		chan->bypass = false;
+		freq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);
+		if (freq > UINT32_MAX)
+			freq = UINT32_MAX;
+	}
+
+	calc_rate = clk_round_rate(chan->pwm_clk, freq);
+	if (calc_rate <= 0) {
+		dev_err(pwmchip_parent(chip),
+			"Invalid source clock frequency %llu\n", freq);
+		return calc_rate ? calc_rate : -EINVAL;
+	}
+
+	dev_dbg(pwmchip_parent(chip), "calc_rate: %ld Hz\n", calc_rate);
+
+	cnt = mul_u64_u64_div_u64(calc_rate, period, NSEC_PER_SEC);
+	if ((cnt == 0) || (cnt > PWM_PERIOD_MAX)) {
+		dev_err(pwmchip_parent(chip), "Period out of range\n");
+		return -EINVAL;
+	}
+
+	duty_cnt = mul_u64_u64_div_u64(calc_rate, duty, NSEC_PER_SEC);
+
+	if (duty_cnt >= cnt)
+		duty_cnt = cnt - 1;
+
+	dev_dbg(pwmchip_parent(chip), "period=%llu cnt=%u duty=%llu duty_cnt=%u\n",
+		period, cnt, duty, duty_cnt);
+
+	chan->active_cycles = duty_cnt;
+	chan->entire_cycles = cnt;
+
+	chan->rate = calc_rate;
+
+	return 0;
+}
+
+static int h616_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			  const struct pwm_state *state)
+{
+	struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
+	struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
+	struct pwm_state cstate;
+	unsigned long flags;
+	u32 val;
+	int ret;
+
+	ret = h616_pwm_calc(chip, pwm->hwpwm, state);
+	if (ret) {
+		dev_err(pwmchip_parent(chip), "period exceeds the maximum value\n");
+		return ret;
+	}
+
+	pwm_get_state(pwm, &cstate);
+
+	ret = clk_set_rate(chan->pwm_clk, chan->rate);
+	if (ret) {
+		dev_err(pwmchip_parent(chip), "failed to set PWM %d clock rate to %lu\n",
+			pwm->hwpwm, chan->rate);
+		return ret;
+	}
+
+	h616_pwm_set_bypass(h616chip, pwm->hwpwm, chan->bypass);
+
+	/*
+	 * If bypass is set, the PWM logic (polarity, duty) can't be applied
+	 */
+
+	if (chan->bypass && (state->polarity == PWM_POLARITY_INVERSED)) {
+		dev_warn(pwmchip_parent(chip),
+			 "Can't set inversed polarity with bypass enabled\n");
+	} else {
+		val = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));
+		val &= ~PWM_CTRL_ACTIVE_STATE;
+		if (state->polarity == PWM_POLARITY_NORMAL)
+			val |= PWM_CTRL_ACTIVE_STATE;
+		h616_pwm_writel(h616chip, val, PWM_CTRL_REG(pwm->hwpwm));
+	}
+
+	if (chan->bypass && (state->duty_cycle * 2 != state->period)) {
+		dev_warn(pwmchip_parent(chip),
+			 "Can't set a duty cycle with bypass enabled\n");
+	}
+
+	if (!chan->bypass) {
+		val = PWM_DUTY(chan->active_cycles);
+		val |= PWM_PERIOD(chan->entire_cycles);
+		h616_pwm_writel(h616chip, val, PWM_PERIOD_REG(pwm->hwpwm));
+	}
+
+	if (state->enabled == cstate.enabled)
+		return 0;
+
+	if (cstate.enabled) {
+		unsigned long delay_us;
+
+		/*
+		 * We need a full period to elapse before
+		 * disabling the channel.
+		 */
+		delay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);
+		fsleep(delay_us);
+	}
+
+	spin_lock_irqsave(&h616chip->clk_pdata->lock, flags);
+
+	val = h616_pwm_readl(h616chip, PWM_ENR);
+	if (state->enabled)
+		val |= PWM_ENABLE(pwm->hwpwm);
+	else
+		val &= ~PWM_ENABLE(pwm->hwpwm);
+	h616_pwm_writel(h616chip, val, PWM_ENR);
+
+	spin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);
+
+	return 0;
+}
+
+static const struct pwm_ops h616_pwm_ops = {
+	.apply = h616_pwm_apply,
+	.get_state = h616_pwm_get_state,
+	.request = h616_pwm_request,
+	.free = h616_pwm_free,
+};
+
+static struct clk_hw *h616_pwm_of_clk_get(struct of_phandle_args *clkspec,
+					  void *data)
+{
+	struct h616_pwm_chip *h616chip = data;
+	struct clk_hw_onecell_data *hw_data = h616chip->clk_pdata->hw_data;
+	unsigned int idx = clkspec->args[0];
+	struct h616_pwm_channel *chan;
+	struct clk_hw *ret_clk = NULL;
+	unsigned long flags;
+
+	if (idx >= h616chip->data->npwm)
+		return ERR_PTR(-EINVAL);
+
+	chan = &h616chip->channels[idx];
+
+	spin_lock_irqsave(&h616chip->clk_pdata->lock, flags);
+
+	if (chan->mode == H616_PWM_MODE_PWM) {
+		ret_clk = ERR_PTR(-EBUSY);
+	} else {
+		chan->mode = H616_PWM_MODE_CLK;
+		ret_clk = hw_data->hws[CLK_BYPASS(h616chip, idx)];
+	}
+	spin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);
+
+	if (IS_ERR(ret_clk))
+		goto out;
+
+	chan->bypass = true;
+	h616_pwm_set_bypass(h616chip, idx, chan->bypass);
+out:
+	return ret_clk;
+}
+
+static int h616_add_composite_clk(const struct clk_pwm_data *data,
+				  void __iomem *reg, spinlock_t *lock,
+				  struct device *dev, struct clk_hw **hw)
+{
+	const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *rate_ops = NULL;
+	struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL;
+
+
+	if (data->mux_hw) {
+		struct clk_mux *mux;
+
+		mux_hw = data->mux_hw;
+		mux = to_clk_mux(mux_hw);
+		mux->lock = lock;
+		mux_ops = mux_hw->init->ops;
+		mux->reg = (u64)mux->reg + reg;
+	}
+
+	if (data->gate_hw) {
+		struct clk_gate *gate;
+
+		gate_hw = data->gate_hw;
+		gate = to_clk_gate(gate_hw);
+		gate->lock = lock;
+		gate_ops = gate_hw->init->ops;
+		gate->reg = (u64)gate->reg + reg;
+	}
+
+	if (data->rate_hw) {
+		struct clk_divider *rate;
+
+		rate_hw = data->rate_hw;
+		rate = to_clk_divider(rate_hw);
+		rate_ops = rate_hw->init->ops;
+		rate->lock = lock;
+		rate->reg = (u64)rate->reg + reg;
+
+		if (rate->table) {
+			const struct clk_div_table *clkt;
+			int table_size = 0;
+
+			for (clkt = rate->table; clkt->div; clkt++)
+				table_size++;
+			rate->width = order_base_2(table_size);
+		}
+	}
+
+	*hw = clk_hw_register_composite(dev, data->name, data->parent_names,
+					data->num_parents, mux_hw,
+					mux_ops, rate_hw, rate_ops,
+					gate_hw, gate_ops, data->flags);
+
+	return PTR_ERR_OR_ZERO(*hw);
+}
+
+static int h616_pwm_init_clocks(struct platform_device *pdev,
+				struct h616_pwm_chip *h616chip)
+{
+	struct clk_pwm_pdata *pdata;
+	struct device *dev = &pdev->dev;
+	int num_clocks = 0;
+	int ret;
+
+	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	while (pwmcc_data[num_clocks].name)
+		num_clocks++;
+
+	pdata->hw_data = devm_kzalloc(dev, struct_size(pdata->hw_data, hws, num_clocks),
+				      GFP_KERNEL);
+	if (!pdata->hw_data)
+		return -ENOMEM;
+
+	pdata->hw_data->num = num_clocks;
+	pdata->reg = h616chip->base;
+
+	spin_lock_init(&pdata->lock);
+
+	for (int i = 0; i < num_clocks; i++) {
+		struct clk_hw **hw = &pdata->hw_data->hws[i];
+
+		ret = h616_add_composite_clk(&pwmcc_data[i], pdata->reg,
+					     &pdata->lock, dev, hw);
+		if (ret) {
+			dev_err_probe(dev, ret,
+				      "Failed to register hw clock %s\n",
+				      pwmcc_data[i].name);
+			for (i--; i >= 0; i--)
+				clk_hw_unregister_composite(pdata->hw_data->hws[i]);
+			return ret;
+		}
+	}
+
+	h616chip->clk_pdata = pdata;
+
+	return 0;
+}
+
+static int h616_pwm_probe(struct platform_device *pdev)
+{
+	const struct h616_pwm_data *data;
+	struct device *dev = &pdev->dev;
+	struct h616_pwm_chip *h616chip;
+	struct pwm_chip *chip;
+	int ret;
+
+	data = of_device_get_match_data(dev);
+	if (!data)
+		return -ENODEV;
+
+	chip = devm_pwmchip_alloc(dev, data->npwm, sizeof(*h616chip));
+	if (IS_ERR(chip))
+		return dev_err_probe(dev, PTR_ERR(chip),
+				     "Failed to allocate pwmchip\n");
+
+	h616chip = to_h616_pwm_chip(chip);
+	h616chip->data = data;
+	h616chip->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(h616chip->base))
+		return dev_err_probe(dev, PTR_ERR(h616chip->base),
+				     "Failed to get PWM base address\n");
+
+	h616chip->bus_clk = devm_clk_get_enabled(dev, "bus");
+	if (IS_ERR(h616chip->bus_clk))
+		return dev_err_probe(dev, PTR_ERR(h616chip->bus_clk),
+				     "Failed to get bus clock\n");
+
+	h616chip->channels = devm_kmalloc_array(dev, data->npwm,
+						sizeof(*(h616chip->channels)),
+						GFP_KERNEL);
+	if (!h616chip->channels)
+		return dev_err_probe(dev, -ENOMEM,
+				     "Failed to allocate %d channels array\n",
+				     data->npwm);
+
+	h616chip->rst = devm_reset_control_get_shared(dev, NULL);
+	if (IS_ERR(h616chip->rst))
+		return dev_err_probe(dev, PTR_ERR(h616chip->rst),
+				     "Failed to get reset control\n");
+
+	chip->ops = &h616_pwm_ops;
+
+	ret = h616_pwm_init_clocks(pdev, h616chip);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to initialize clocks\n");
+
+	for (unsigned int i = 0; i < data->npwm; i++) {
+		struct h616_pwm_channel *chan = &h616chip->channels[i];
+		struct clk_hw **hw = &h616chip->clk_pdata->hw_data->hws[i];
+
+		chan->pwm_clk = devm_clk_hw_get_clk(dev, *hw, NULL);
+		if (IS_ERR(chan->pwm_clk)) {
+			ret = dev_err_probe(dev, PTR_ERR(chan->pwm_clk),
+					    "Failed to register PWM clock %d\n", i);
+			goto err_get_clk;
+		}
+		chan->mode = H616_PWM_MODE_NONE;
+	}
+
+	ret = devm_of_clk_add_hw_provider(dev, h616_pwm_of_clk_get, h616chip);
+	if (ret) {
+		dev_err_probe(dev, ret, "Failed to add HW clock provider\n");
+		goto err_add_clk_provider;
+	}
+
+	/* Deassert reset */
+	ret = reset_control_deassert(h616chip->rst);
+	if (ret) {
+		dev_err_probe(dev, ret, "Cannot deassert reset control\n");
+		goto err_ctrl_deassert;
+	}
+
+	ret = pwmchip_add(chip);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "Failed to add PWM chip\n");
+		goto err_pwm_add;
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	return 0;
+
+err_pwm_add:
+	reset_control_assert(h616chip->rst);
+
+err_ctrl_deassert:
+err_add_clk_provider:
+err_get_clk:
+	for (int i = 0; i < h616chip->clk_pdata->hw_data->num; i++)
+		clk_hw_unregister_composite(h616chip->clk_pdata->hw_data->hws[i]);
+
+	return ret;
+}
+
+static const struct h616_pwm_data sun50i_h616_pwm_data = {
+	.npwm = 6,
+};
+
+static const struct of_device_id h616_pwm_dt_ids[] = {
+	{
+		.compatible = "allwinner,sun50i-h616-pwm",
+		.data = &sun50i_h616_pwm_data,
+	}, {
+		/* sentinel */
+	},
+};
+MODULE_DEVICE_TABLE(of, h616_pwm_dt_ids);
+
+
+static struct platform_driver h616_pwm_driver = {
+	.driver = {
+		.name = "h616-pwm",
+		.of_match_table = h616_pwm_dt_ids,
+	},
+	.probe = h616_pwm_probe,
+};
+module_platform_driver(h616_pwm_driver);
+
+MODULE_ALIAS("platform:h616-pwm");
+MODULE_AUTHOR("Richard Genoud <richard.genoud@bootlin.com>");
+MODULE_DESCRIPTION("Allwinner H616 PWM driver");
+MODULE_LICENSE("GPL");
-- 
2.47.3


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

* [PATCH 3/4] arm64: dts: allwinner: h616: add PWM controller
  2025-12-05 10:02 [PATCH 0/4] Introduce Allwinner H616 PWM controller Richard Genoud
  2025-12-05 10:02 ` [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616 Richard Genoud
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
@ 2025-12-05 10:02 ` Richard Genoud
  2025-12-05 10:02 ` [PATCH 4/4] MAINTAINERS: Add entry on Allwinner H616 PWM driver Richard Genoud
  3 siblings, 0 replies; 13+ messages in thread
From: Richard Genoud @ 2025-12-05 10:02 UTC (permalink / raw)
  To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel, Richard Genoud

The H616 has a PWM controller that can provide PWM signals, but also
plain clocks.

Add the PWM controller node and pins in the device tree.

Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 .../arm64/boot/dts/allwinner/sun50i-h616.dtsi | 47 +++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
index 8d1110c14bad..6bfb234e3075 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-h616.dtsi
@@ -236,6 +236,17 @@ watchdog: watchdog@30090a0 {
 			clocks = <&osc24M>;
 		};
 
+		pwm: pwm@300a000 {
+			compatible = "allwinner,sun50i-h616-pwm";
+			reg = <0x0300a000 0x400>;
+			clocks = <&ccu CLK_BUS_PWM>;
+			clock-names = "bus";
+			resets = <&ccu RST_BUS_PWM>;
+			#pwm-cells = <3>;
+			#clock-cells = <1>;
+			status = "disabled";
+		};
+
 		pio: pinctrl@300b000 {
 			compatible = "allwinner,sun50i-h616-pinctrl";
 			reg = <0x0300b000 0x400>;
@@ -340,6 +351,42 @@ nand_rb1_pin: nand-rb1-pin {
 				bias-pull-up;
 			};
 
+			/omit-if-no-ref/
+			pwm0_pin: pwm0-pin {
+				pins = "PD28";
+				function = "pwm0";
+			};
+
+			/omit-if-no-ref/
+			pwm1_pin: pwm1-pin {
+				pins = "PG19";
+				function = "pwm1";
+			};
+
+			/omit-if-no-ref/
+			pwm2_pin: pwm2-pin {
+				pins = "PH2";
+				function = "pwm2";
+			};
+
+			/omit-if-no-ref/
+			pwm3_pin: pwm3-pin {
+				pins = "PH0";
+				function = "pwm3";
+			};
+
+			/omit-if-no-ref/
+			pwm4_pin: pwm4-pin {
+				pins = "PI14";
+				function = "pwm4";
+			};
+
+			/omit-if-no-ref/
+			pwm5_pin: pwm5-pin {
+				pins = "PA12";
+				function = "pwm5";
+			};
+
 			/omit-if-no-ref/
 			spi0_pins: spi0-pins {
 				pins = "PC0", "PC2", "PC4";
-- 
2.47.3


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

* [PATCH 4/4] MAINTAINERS: Add entry on Allwinner H616 PWM driver
  2025-12-05 10:02 [PATCH 0/4] Introduce Allwinner H616 PWM controller Richard Genoud
                   ` (2 preceding siblings ...)
  2025-12-05 10:02 ` [PATCH 3/4] arm64: dts: allwinner: h616: add PWM controller Richard Genoud
@ 2025-12-05 10:02 ` Richard Genoud
  3 siblings, 0 replies; 13+ messages in thread
From: Richard Genoud @ 2025-12-05 10:02 UTC (permalink / raw)
  To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel, Richard Genoud

Add myself as maintainer of Allwinner H616 PWM driver and device-tree
bindings.

Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
---
 MAINTAINERS | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 578c068b738b..b336c0fff4e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -903,6 +903,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/sound/allwinner,sun50i-h6-dmic.yaml
 F:	sound/soc/sunxi/sun50i-dmic.c
 
+ALLWINNER H616 PWM DRIVER
+M:	Richard Genoud <richard.genoud@bootlin.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
+F:	drivers/pwm/pwm-sun50i-h616.c
+
 ALLWINNER HARDWARE SPINLOCK SUPPORT
 M:	Wilken Gottwalt <wilken.gottwalt@posteo.net>
 S:	Maintained
-- 
2.47.3


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

* Re: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
@ 2025-12-06  4:22   ` kernel test robot
  2025-12-06  4:32   ` kernel test robot
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2025-12-06  4:22 UTC (permalink / raw)
  To: Richard Genoud, Uwe Kleine-König, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Philipp Zabel
  Cc: oe-kbuild-all, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel, Richard Genoud

Hi Richard,

kernel test robot noticed the following build errors:

[auto build test ERROR on 6987d58a9cbc5bd57c983baa514474a86c945d56]

url:    https://github.com/intel-lab-lkp/linux/commits/Richard-Genoud/dt-bindings-pwm-sunxi-add-PWM-controller-for-Allwinner-H616/20251205-214804
base:   6987d58a9cbc5bd57c983baa514474a86c945d56
patch link:    https://lore.kernel.org/r/20251205100239.1563353-3-richard.genoud%40bootlin.com
patch subject: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
config: hexagon-randconfig-r073-20251206 (https://download.01.org/0day-ci/archive/20251206/202512061109.UxaYeMZ9-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 14bf95b06a18b9b59c89601cbc0e5a6f2176b118)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251206/202512061109.UxaYeMZ9-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512061109.UxaYeMZ9-lkp@intel.com/

All errors (new ones prefixed by >>):

>> drivers/pwm/pwm-sun50i-h616.c:452:23: error: call to undeclared function 'FIELD_GET'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     452 |         tmp = NSEC_PER_SEC * PWM_REG_DUTY(val);
         |                              ^
   drivers/pwm/pwm-sun50i-h616.c:76:28: note: expanded from macro 'PWM_REG_DUTY'
      76 | #define PWM_REG_DUTY(reg)               FIELD_GET(PWM_DUTY_MASK, reg)
         |                                         ^
>> drivers/pwm/pwm-sun50i-h616.c:493:40: error: call to undeclared function 'FIELD_MAX'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     493 |                 freq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);
         |                                                      ^
   drivers/pwm/pwm-sun50i-h616.c:79:26: note: expanded from macro 'PWM_PERIOD_MAX'
      79 | #define PWM_PERIOD_MAX                  FIELD_MAX(PWM_PERIOD_MASK)
         |                                         ^
   drivers/pwm/pwm-sun50i-h616.c:508:27: error: call to undeclared function 'FIELD_MAX'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     508 |         if ((cnt == 0) || (cnt > PWM_PERIOD_MAX)) {
         |                                  ^
   drivers/pwm/pwm-sun50i-h616.c:79:26: note: expanded from macro 'PWM_PERIOD_MAX'
      79 | #define PWM_PERIOD_MAX                  FIELD_MAX(PWM_PERIOD_MASK)
         |                                         ^
>> drivers/pwm/pwm-sun50i-h616.c:577:9: error: call to undeclared function 'FIELD_PREP'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     577 |                 val = PWM_DUTY(chan->active_cycles);
         |                       ^
   drivers/pwm/pwm-sun50i-h616.c:78:25: note: expanded from macro 'PWM_DUTY'
      78 | #define PWM_DUTY(dty)                   FIELD_PREP(PWM_DUTY_MASK, dty)
         |                                         ^
   4 errors generated.


vim +/FIELD_GET +452 drivers/pwm/pwm-sun50i-h616.c

   413	
   414	static int h616_pwm_get_state(struct pwm_chip *chip,
   415				      struct pwm_device *pwm,
   416				      struct pwm_state *state)
   417	{
   418		struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
   419		struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
   420		u64 clk_rate, tmp;
   421		u32 val;
   422	
   423		clk_rate = clk_get_rate(chan->pwm_clk);
   424		if (!clk_rate)
   425			return -EINVAL;
   426	
   427		val = h616_pwm_readl(h616chip, PWM_ENR);
   428		state->enabled = !!(PWM_ENABLE(pwm->hwpwm) & val);
   429	
   430		val = h616_pwm_readl(h616chip, PWM_XY_CLK_CR(PWM_PAIR_IDX(pwm->hwpwm)));
   431		if (val & BIT(PWM_XY_CLK_CR_BYPASS_BIT(pwm->hwpwm))) {
   432			/*
   433			 * When bypass is enabled, the PWM logic is inactive.
   434			 * The PWM_clock_src_xy is directly routed to PWM_clock_x
   435			 */
   436			state->period = DIV_ROUND_UP_ULL(NSEC_PER_SEC, clk_rate);
   437			state->duty_cycle = DIV_ROUND_UP_ULL(state->period, 2);
   438			state->polarity = PWM_POLARITY_NORMAL;
   439			return 0;
   440		}
   441	
   442		state->enabled &= !!(BIT(PWM_XY_CLK_CR_GATE_BIT) & val);
   443	
   444		val = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));
   445		if (val & PWM_CTRL_ACTIVE_STATE)
   446			state->polarity = PWM_POLARITY_NORMAL;
   447		else
   448			state->polarity = PWM_POLARITY_INVERSED;
   449	
   450		val = h616_pwm_readl(h616chip, PWM_PERIOD_REG(pwm->hwpwm));
   451	
 > 452		tmp = NSEC_PER_SEC * PWM_REG_DUTY(val);
   453		state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
   454	
   455		tmp = NSEC_PER_SEC * PWM_REG_PERIOD(val);
   456		state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
   457	
   458		return 0;
   459	}
   460	
   461	static int h616_pwm_calc(struct pwm_chip *chip, unsigned int idx,
   462				 const struct pwm_state *state)
   463	{
   464		struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
   465		struct h616_pwm_channel *chan = &h616chip->channels[idx];
   466		unsigned int cnt, duty_cnt;
   467		unsigned long max_rate;
   468		long calc_rate;
   469		u64 duty, period, freq;
   470	
   471		duty = state->duty_cycle;
   472		period = state->period;
   473	
   474		max_rate = clk_round_rate(chan->pwm_clk, UINT32_MAX);
   475	
   476		dev_dbg(pwmchip_parent(chip), "max_rate: %ld Hz\n", max_rate);
   477	
   478		if ((period * max_rate >= NSEC_PER_SEC) &&
   479		    (period * max_rate < 2 * NSEC_PER_SEC) &&
   480		    (duty * max_rate * 2 >= NSEC_PER_SEC)) {
   481			/*
   482			 * If the requested period is to small to be generated by the
   483			 * PWM, we can just select the highest clock and bypass the
   484			 * PWM logic
   485			 */
   486			dev_dbg(pwmchip_parent(chip), "Setting bypass (period=%lld)\n",
   487				period);
   488			freq = div64_u64(NSEC_PER_SEC, period);
   489			chan->bypass = true;
   490			duty = period / 2;
   491		} else {
   492			chan->bypass = false;
 > 493			freq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);
   494			if (freq > UINT32_MAX)
   495				freq = UINT32_MAX;
   496		}
   497	
   498		calc_rate = clk_round_rate(chan->pwm_clk, freq);
   499		if (calc_rate <= 0) {
   500			dev_err(pwmchip_parent(chip),
   501				"Invalid source clock frequency %llu\n", freq);
   502			return calc_rate ? calc_rate : -EINVAL;
   503		}
   504	
   505		dev_dbg(pwmchip_parent(chip), "calc_rate: %ld Hz\n", calc_rate);
   506	
   507		cnt = mul_u64_u64_div_u64(calc_rate, period, NSEC_PER_SEC);
   508		if ((cnt == 0) || (cnt > PWM_PERIOD_MAX)) {
   509			dev_err(pwmchip_parent(chip), "Period out of range\n");
   510			return -EINVAL;
   511		}
   512	
   513		duty_cnt = mul_u64_u64_div_u64(calc_rate, duty, NSEC_PER_SEC);
   514	
   515		if (duty_cnt >= cnt)
   516			duty_cnt = cnt - 1;
   517	
   518		dev_dbg(pwmchip_parent(chip), "period=%llu cnt=%u duty=%llu duty_cnt=%u\n",
   519			period, cnt, duty, duty_cnt);
   520	
   521		chan->active_cycles = duty_cnt;
   522		chan->entire_cycles = cnt;
   523	
   524		chan->rate = calc_rate;
   525	
   526		return 0;
   527	}
   528	
   529	static int h616_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
   530				  const struct pwm_state *state)
   531	{
   532		struct h616_pwm_chip *h616chip = to_h616_pwm_chip(chip);
   533		struct h616_pwm_channel *chan = &h616chip->channels[pwm->hwpwm];
   534		struct pwm_state cstate;
   535		unsigned long flags;
   536		u32 val;
   537		int ret;
   538	
   539		ret = h616_pwm_calc(chip, pwm->hwpwm, state);
   540		if (ret) {
   541			dev_err(pwmchip_parent(chip), "period exceeds the maximum value\n");
   542			return ret;
   543		}
   544	
   545		pwm_get_state(pwm, &cstate);
   546	
   547		ret = clk_set_rate(chan->pwm_clk, chan->rate);
   548		if (ret) {
   549			dev_err(pwmchip_parent(chip), "failed to set PWM %d clock rate to %lu\n",
   550				pwm->hwpwm, chan->rate);
   551			return ret;
   552		}
   553	
   554		h616_pwm_set_bypass(h616chip, pwm->hwpwm, chan->bypass);
   555	
   556		/*
   557		 * If bypass is set, the PWM logic (polarity, duty) can't be applied
   558		 */
   559	
   560		if (chan->bypass && (state->polarity == PWM_POLARITY_INVERSED)) {
   561			dev_warn(pwmchip_parent(chip),
   562				 "Can't set inversed polarity with bypass enabled\n");
   563		} else {
   564			val = h616_pwm_readl(h616chip, PWM_CTRL_REG(pwm->hwpwm));
   565			val &= ~PWM_CTRL_ACTIVE_STATE;
   566			if (state->polarity == PWM_POLARITY_NORMAL)
   567				val |= PWM_CTRL_ACTIVE_STATE;
   568			h616_pwm_writel(h616chip, val, PWM_CTRL_REG(pwm->hwpwm));
   569		}
   570	
   571		if (chan->bypass && (state->duty_cycle * 2 != state->period)) {
   572			dev_warn(pwmchip_parent(chip),
   573				 "Can't set a duty cycle with bypass enabled\n");
   574		}
   575	
   576		if (!chan->bypass) {
 > 577			val = PWM_DUTY(chan->active_cycles);
   578			val |= PWM_PERIOD(chan->entire_cycles);
   579			h616_pwm_writel(h616chip, val, PWM_PERIOD_REG(pwm->hwpwm));
   580		}
   581	
   582		if (state->enabled == cstate.enabled)
   583			return 0;
   584	
   585		if (cstate.enabled) {
   586			unsigned long delay_us;
   587	
   588			/*
   589			 * We need a full period to elapse before
   590			 * disabling the channel.
   591			 */
   592			delay_us = DIV_ROUND_UP_ULL(cstate.period, NSEC_PER_USEC);
   593			fsleep(delay_us);
   594		}
   595	
   596		spin_lock_irqsave(&h616chip->clk_pdata->lock, flags);
   597	
   598		val = h616_pwm_readl(h616chip, PWM_ENR);
   599		if (state->enabled)
   600			val |= PWM_ENABLE(pwm->hwpwm);
   601		else
   602			val &= ~PWM_ENABLE(pwm->hwpwm);
   603		h616_pwm_writel(h616chip, val, PWM_ENR);
   604	
   605		spin_unlock_irqrestore(&h616chip->clk_pdata->lock, flags);
   606	
   607		return 0;
   608	}
   609	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
  2025-12-06  4:22   ` kernel test robot
@ 2025-12-06  4:32   ` kernel test robot
  2025-12-06 14:24   ` kernel test robot
  2025-12-08  6:09   ` Krzysztof Kozlowski
  3 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2025-12-06  4:32 UTC (permalink / raw)
  To: Richard Genoud, Uwe Kleine-König, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Philipp Zabel
  Cc: oe-kbuild-all, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel, Richard Genoud

Hi Richard,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 6987d58a9cbc5bd57c983baa514474a86c945d56]

url:    https://github.com/intel-lab-lkp/linux/commits/Richard-Genoud/dt-bindings-pwm-sunxi-add-PWM-controller-for-Allwinner-H616/20251205-214804
base:   6987d58a9cbc5bd57c983baa514474a86c945d56
patch link:    https://lore.kernel.org/r/20251205100239.1563353-3-richard.genoud%40bootlin.com
patch subject: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
config: parisc-allyesconfig (https://download.01.org/0day-ci/archive/20251206/202512061246.jJ5UrB71-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251206/202512061246.jJ5UrB71-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512061246.jJ5UrB71-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_get_state':
   drivers/pwm/pwm-sun50i-h616.c:76:41: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration]
      76 | #define PWM_REG_DUTY(reg)               FIELD_GET(PWM_DUTY_MASK, reg)
         |                                         ^~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:452:30: note: in expansion of macro 'PWM_REG_DUTY'
     452 |         tmp = NSEC_PER_SEC * PWM_REG_DUTY(val);
         |                              ^~~~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_calc':
   drivers/pwm/pwm-sun50i-h616.c:79:41: error: implicit declaration of function 'FIELD_MAX' [-Wimplicit-function-declaration]
      79 | #define PWM_PERIOD_MAX                  FIELD_MAX(PWM_PERIOD_MASK)
         |                                         ^~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:493:54: note: in expansion of macro 'PWM_PERIOD_MAX'
     493 |                 freq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);
         |                                                      ^~~~~~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_apply':
   drivers/pwm/pwm-sun50i-h616.c:78:41: error: implicit declaration of function 'FIELD_PREP' [-Wimplicit-function-declaration]
      78 | #define PWM_DUTY(dty)                   FIELD_PREP(PWM_DUTY_MASK, dty)
         |                                         ^~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:577:23: note: in expansion of macro 'PWM_DUTY'
     577 |                 val = PWM_DUTY(chan->active_cycles);
         |                       ^~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_add_composite_clk':
>> drivers/pwm/pwm-sun50i-h616.c:666:28: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     666 |                 mux->reg = (u64)mux->reg + reg;
         |                            ^
   drivers/pwm/pwm-sun50i-h616.c:676:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     676 |                 gate->reg = (u64)gate->reg + reg;
         |                             ^
   drivers/pwm/pwm-sun50i-h616.c:686:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     686 |                 rate->reg = (u64)rate->reg + reg;
         |                             ^


vim +666 drivers/pwm/pwm-sun50i-h616.c

   650	
   651	static int h616_add_composite_clk(const struct clk_pwm_data *data,
   652					  void __iomem *reg, spinlock_t *lock,
   653					  struct device *dev, struct clk_hw **hw)
   654	{
   655		const struct clk_ops *mux_ops = NULL, *gate_ops = NULL, *rate_ops = NULL;
   656		struct clk_hw *mux_hw = NULL, *gate_hw = NULL, *rate_hw = NULL;
   657	
   658	
   659		if (data->mux_hw) {
   660			struct clk_mux *mux;
   661	
   662			mux_hw = data->mux_hw;
   663			mux = to_clk_mux(mux_hw);
   664			mux->lock = lock;
   665			mux_ops = mux_hw->init->ops;
 > 666			mux->reg = (u64)mux->reg + reg;
   667		}
   668	
   669		if (data->gate_hw) {
   670			struct clk_gate *gate;
   671	
   672			gate_hw = data->gate_hw;
   673			gate = to_clk_gate(gate_hw);
   674			gate->lock = lock;
   675			gate_ops = gate_hw->init->ops;
   676			gate->reg = (u64)gate->reg + reg;
   677		}
   678	
   679		if (data->rate_hw) {
   680			struct clk_divider *rate;
   681	
   682			rate_hw = data->rate_hw;
   683			rate = to_clk_divider(rate_hw);
   684			rate_ops = rate_hw->init->ops;
   685			rate->lock = lock;
   686			rate->reg = (u64)rate->reg + reg;
   687	
   688			if (rate->table) {
   689				const struct clk_div_table *clkt;
   690				int table_size = 0;
   691	
   692				for (clkt = rate->table; clkt->div; clkt++)
   693					table_size++;
   694				rate->width = order_base_2(table_size);
   695			}
   696		}
   697	
   698		*hw = clk_hw_register_composite(dev, data->name, data->parent_names,
   699						data->num_parents, mux_hw,
   700						mux_ops, rate_hw, rate_ops,
   701						gate_hw, gate_ops, data->flags);
   702	
   703		return PTR_ERR_OR_ZERO(*hw);
   704	}
   705	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
  2025-12-06  4:22   ` kernel test robot
  2025-12-06  4:32   ` kernel test robot
@ 2025-12-06 14:24   ` kernel test robot
  2025-12-08  6:09   ` Krzysztof Kozlowski
  3 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2025-12-06 14:24 UTC (permalink / raw)
  To: Richard Genoud, Uwe Kleine-König, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Philipp Zabel
  Cc: oe-kbuild-all, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel, Richard Genoud

Hi Richard,

kernel test robot noticed the following build errors:

[auto build test ERROR on 6987d58a9cbc5bd57c983baa514474a86c945d56]

url:    https://github.com/intel-lab-lkp/linux/commits/Richard-Genoud/dt-bindings-pwm-sunxi-add-PWM-controller-for-Allwinner-H616/20251205-214804
base:   6987d58a9cbc5bd57c983baa514474a86c945d56
patch link:    https://lore.kernel.org/r/20251205100239.1563353-3-richard.genoud%40bootlin.com
patch subject: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
config: parisc-allyesconfig (https://download.01.org/0day-ci/archive/20251206/202512062245.KFjCln1y-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251206/202512062245.KFjCln1y-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512062245.KFjCln1y-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_get_state':
>> drivers/pwm/pwm-sun50i-h616.c:76:41: error: implicit declaration of function 'FIELD_GET' [-Wimplicit-function-declaration]
      76 | #define PWM_REG_DUTY(reg)               FIELD_GET(PWM_DUTY_MASK, reg)
         |                                         ^~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:452:30: note: in expansion of macro 'PWM_REG_DUTY'
     452 |         tmp = NSEC_PER_SEC * PWM_REG_DUTY(val);
         |                              ^~~~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_calc':
>> drivers/pwm/pwm-sun50i-h616.c:79:41: error: implicit declaration of function 'FIELD_MAX' [-Wimplicit-function-declaration]
      79 | #define PWM_PERIOD_MAX                  FIELD_MAX(PWM_PERIOD_MASK)
         |                                         ^~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:493:54: note: in expansion of macro 'PWM_PERIOD_MAX'
     493 |                 freq = div64_u64(NSEC_PER_SEC * (u64)PWM_PERIOD_MAX, period);
         |                                                      ^~~~~~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_pwm_apply':
>> drivers/pwm/pwm-sun50i-h616.c:78:41: error: implicit declaration of function 'FIELD_PREP' [-Wimplicit-function-declaration]
      78 | #define PWM_DUTY(dty)                   FIELD_PREP(PWM_DUTY_MASK, dty)
         |                                         ^~~~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c:577:23: note: in expansion of macro 'PWM_DUTY'
     577 |                 val = PWM_DUTY(chan->active_cycles);
         |                       ^~~~~~~~
   drivers/pwm/pwm-sun50i-h616.c: In function 'h616_add_composite_clk':
   drivers/pwm/pwm-sun50i-h616.c:666:28: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     666 |                 mux->reg = (u64)mux->reg + reg;
         |                            ^
   drivers/pwm/pwm-sun50i-h616.c:676:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     676 |                 gate->reg = (u64)gate->reg + reg;
         |                             ^
   drivers/pwm/pwm-sun50i-h616.c:686:29: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     686 |                 rate->reg = (u64)rate->reg + reg;
         |                             ^


vim +/FIELD_GET +76 drivers/pwm/pwm-sun50i-h616.c

    70	
    71	/* PWM Period Register */
    72	#define PWM_PERIOD_REG(ch)		(0x64 + (ch) * 0x20)
    73	#define PWM_PERIOD_MASK			GENMASK(31, 16)
    74	#define PWM_DUTY_MASK			GENMASK(15, 0)
    75	#define PWM_REG_PERIOD(reg)		(FIELD_GET(PWM_PERIOD_MASK, reg) + 1)
  > 76	#define PWM_REG_DUTY(reg)		FIELD_GET(PWM_DUTY_MASK, reg)
    77	#define PWM_PERIOD(prd)			FIELD_PREP(PWM_PERIOD_MASK, (prd) - 1)
  > 78	#define PWM_DUTY(dty)			FIELD_PREP(PWM_DUTY_MASK, dty)
  > 79	#define PWM_PERIOD_MAX			FIELD_MAX(PWM_PERIOD_MASK)
    80	
    81	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

* Re: [PATCH 2/4] pwm: sun50i: Add H616 PWM support
  2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
                     ` (2 preceding siblings ...)
  2025-12-06 14:24   ` kernel test robot
@ 2025-12-08  6:09   ` Krzysztof Kozlowski
  3 siblings, 0 replies; 13+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-08  6:09 UTC (permalink / raw)
  To: Richard Genoud, Uwe Kleine-König, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Chen-Yu Tsai, Jernej Skrabec,
	Samuel Holland, Philipp Zabel
  Cc: Thomas Petazzoni, linux-pwm, devicetree, linux-arm-kernel,
	linux-sunxi, linux-kernel

On 05/12/2025 11:02, Richard Genoud wrote:
> +static struct platform_driver h616_pwm_driver = {
> +	.driver = {
> +		.name = "h616-pwm",
> +		.of_match_table = h616_pwm_dt_ids,
> +	},
> +	.probe = h616_pwm_probe,
> +};
> +module_platform_driver(h616_pwm_driver);
> +
> +MODULE_ALIAS("platform:h616-pwm");

You should not need MODULE_ALIAS() in normal cases. If you need it,
usually it means your device ID table is wrong (e.g. misses either
entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
for incomplete ID table.


> +MODULE_AUTHOR("Richard Genoud <richard.genoud@bootlin.com>");
> +MODULE_DESCRIPTION("Allwinner H616 PWM driver");
> +MODULE_LICENSE("GPL");


Best regards,
Krzysztof

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

* Re: [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  2025-12-05 10:02 ` [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616 Richard Genoud
@ 2025-12-08  6:52   ` Krzysztof Kozlowski
  2025-12-12  7:50     ` Richard GENOUD
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-08  6:52 UTC (permalink / raw)
  To: Richard Genoud
  Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel

On Fri, Dec 05, 2025 at 11:02:36AM +0100, Richard Genoud wrote:
> Allwinner H616 SoC contains a PWM controller quite different from the A10.
> It has 6 channels than can generate PWM waveforms or clocks if bypass is
> enabled.
> 
> Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
> ---
>  .../pwm/allwinner,sun50i-h616-pwm.yaml        | 67 +++++++++++++++++++
>  1 file changed, 67 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
> 
> diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
> new file mode 100644
> index 000000000000..b89735ad3a43
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
> @@ -0,0 +1,67 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/allwinner,sun50i-h616-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Allwinner H616 PWM
> +
> +maintainers:
> +  - Richard Genoud <richard.genoud@bootlin.com>
> +
> +description: |

Do not need '|' unless you need to preserve formatting.

> +  Allwinner H616 PWM can generate standard PWM signals with variable pulse width
> +  and period.
> +  Also, instead of a PWM signal, a channel can be used to provide a clock.
> +
> +properties:
> +  compatible:
> +    const: allwinner,sun50i-h616-pwm
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: Bus Clock
> +

Are you sure there is no first clock? Really, really sure? If you add it
later, I would be pretty sad, because that's unnecessary duplication of
binidngs....

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>

Best regards,
Krzysztof


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

* Re: [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  2025-12-08  6:52   ` Krzysztof Kozlowski
@ 2025-12-12  7:50     ` Richard GENOUD
  2025-12-12  8:25       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 13+ messages in thread
From: Richard GENOUD @ 2025-12-12  7:50 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel

Hi Krzysztof,

Le 08/12/2025 à 07:52, Krzysztof Kozlowski a écrit :
> On Fri, Dec 05, 2025 at 11:02:36AM +0100, Richard Genoud wrote:
>> Allwinner H616 SoC contains a PWM controller quite different from the A10.
>> It has 6 channels than can generate PWM waveforms or clocks if bypass is
>> enabled.
>>
>> Signed-off-by: Richard Genoud <richard.genoud@bootlin.com>
>> ---
>>   .../pwm/allwinner,sun50i-h616-pwm.yaml        | 67 +++++++++++++++++++
>>   1 file changed, 67 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
>> new file mode 100644
>> index 000000000000..b89735ad3a43
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/allwinner,sun50i-h616-pwm.yaml
>> @@ -0,0 +1,67 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/pwm/allwinner,sun50i-h616-pwm.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Allwinner H616 PWM
>> +
>> +maintainers:
>> +  - Richard Genoud <richard.genoud@bootlin.com>
>> +
>> +description: |
> 
> Do not need '|' unless you need to preserve formatting.
Ok, I was thinking that it was nicer with the formatting.

> 
>> +  Allwinner H616 PWM can generate standard PWM signals with variable pulse width
>> +  and period.
>> +  Also, instead of a PWM signal, a channel can be used to provide a clock.
>> +
>> +properties:
>> +  compatible:
>> +    const: allwinner,sun50i-h616-pwm
>> +
>> +  reg:
>> +    maxItems: 1
>> +
>> +  clocks:
>> +    items:
>> +      - description: Bus Clock
>> +
> 
> Are you sure there is no first clock? Really, really sure? If you add it
> later, I would be pretty sad, because that's unnecessary duplication of
> binidngs....
I surely don't want to make you sad :)

Having a second look at the sun4i binding, I think there's a way to use it.
The sun4i, as you said, has a module clock (OSC24M) and an optional bus 
clock.
Here, the bus clock is mandatory, but the H616 PWM uses OSC24M and APB1 
as clock sources.

So, I guess that if we add something like that:
    clocks:
      minItems: 1
      items:
        - description: Module Clock
        - description: Bus Clock
+      - description: APB Clock

    clock-names:
      minItems: 1
      items:
        - const: mod
        - const: bus
+      - const: apb

    resets:
      maxItems: 1

In the sun4i pwm binding, we could re-use it for the H616 pwm right?
(APB clock is maybe not the best name, could be secondary module clock)

> 
> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> 
> Best regards,
> Krzysztof
> 
Thanks for your review!

Regards,
Richard


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

* Re: [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  2025-12-12  7:50     ` Richard GENOUD
@ 2025-12-12  8:25       ` Krzysztof Kozlowski
  2025-12-12  8:52         ` Richard GENOUD
  0 siblings, 1 reply; 13+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-12  8:25 UTC (permalink / raw)
  To: Richard GENOUD
  Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel

On 12/12/2025 08:50, Richard GENOUD wrote:
>>> +
>>> +  clocks:
>>> +    items:
>>> +      - description: Bus Clock
>>> +
>>
>> Are you sure there is no first clock? Really, really sure? If you add it
>> later, I would be pretty sad, because that's unnecessary duplication of
>> binidngs....
> I surely don't want to make you sad :)
> 
> Having a second look at the sun4i binding, I think there's a way to use it.
> The sun4i, as you said, has a module clock (OSC24M) and an optional bus 
> clock.
> Here, the bus clock is mandatory, but the H616 PWM uses OSC24M and APB1 
> as clock sources.
> 
> So, I guess that if we add something like that:
>     clocks:
>       minItems: 1
>       items:
>         - description: Module Clock
>         - description: Bus Clock
> +      - description: APB Clock
> 
>     clock-names:
>       minItems: 1
>       items:
>         - const: mod
>         - const: bus
> +      - const: apb
> 
>     resets:
>       maxItems: 1
> 
> In the sun4i pwm binding, we could re-use it for the H616 pwm right?
> (APB clock is maybe not the best name, could be secondary module clock)


apb is probably the bus clock, so you don't need to change the bindings
at all.

Best regards,
Krzysztof

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

* Re: [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616
  2025-12-12  8:25       ` Krzysztof Kozlowski
@ 2025-12-12  8:52         ` Richard GENOUD
  0 siblings, 0 replies; 13+ messages in thread
From: Richard GENOUD @ 2025-12-12  8:52 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Chen-Yu Tsai, Jernej Skrabec, Samuel Holland,
	Philipp Zabel, Thomas Petazzoni, linux-pwm, devicetree,
	linux-arm-kernel, linux-sunxi, linux-kernel

Le 12/12/2025 à 09:25, Krzysztof Kozlowski a écrit :
> On 12/12/2025 08:50, Richard GENOUD wrote:
>>>> +
>>>> +  clocks:
>>>> +    items:
>>>> +      - description: Bus Clock
>>>> +
>>>
>>> Are you sure there is no first clock? Really, really sure? If you add it
>>> later, I would be pretty sad, because that's unnecessary duplication of
>>> binidngs....
>> I surely don't want to make you sad :)
>>
>> Having a second look at the sun4i binding, I think there's a way to use it.
>> The sun4i, as you said, has a module clock (OSC24M) and an optional bus
>> clock.
>> Here, the bus clock is mandatory, but the H616 PWM uses OSC24M and APB1
>> as clock sources.
>>
>> So, I guess that if we add something like that:
>>      clocks:
>>        minItems: 1
>>        items:
>>          - description: Module Clock
>>          - description: Bus Clock
>> +      - description: APB Clock
>>
>>      clock-names:
>>        minItems: 1
>>        items:
>>          - const: mod
>>          - const: bus
>> +      - const: apb
>>
>>      resets:
>>        maxItems: 1
>>
>> In the sun4i pwm binding, we could re-use it for the H616 pwm right?
>> (APB clock is maybe not the best name, could be secondary module clock)
> 
> 
> apb is probably the bus clock, so you don't need to change the bindings
> at all.
Indeed, your're right!
So the only difference will the the #clock-cells for h616.
I'll send a v2 using the sun4i binding.

Thanks!

Regards,
Richard

> 
> Best regards,
> Krzysztof


-- 
Richard Genoud, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

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

end of thread, other threads:[~2025-12-12  8:52 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-05 10:02 [PATCH 0/4] Introduce Allwinner H616 PWM controller Richard Genoud
2025-12-05 10:02 ` [PATCH 1/4] dt-bindings: pwm: sunxi: add PWM controller for Allwinner H616 Richard Genoud
2025-12-08  6:52   ` Krzysztof Kozlowski
2025-12-12  7:50     ` Richard GENOUD
2025-12-12  8:25       ` Krzysztof Kozlowski
2025-12-12  8:52         ` Richard GENOUD
2025-12-05 10:02 ` [PATCH 2/4] pwm: sun50i: Add H616 PWM support Richard Genoud
2025-12-06  4:22   ` kernel test robot
2025-12-06  4:32   ` kernel test robot
2025-12-06 14:24   ` kernel test robot
2025-12-08  6:09   ` Krzysztof Kozlowski
2025-12-05 10:02 ` [PATCH 3/4] arm64: dts: allwinner: h616: add PWM controller Richard Genoud
2025-12-05 10:02 ` [PATCH 4/4] MAINTAINERS: Add entry on Allwinner H616 PWM driver Richard Genoud

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).