* [PATCH v5 1/9] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 2/9] pwm: rzg2l-gpt: Add support for gpt linking with poeg Biju
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm
Cc: Biju Das, linux-pwm, devicetree, linux-kernel, linux-renesas-soc,
Prabhakar Mahadev Lad, Biju Das
From: Biju Das <biju.das.jz@bp.renesas.com>
RZ/G2L GPT IP supports output pin disable function by dead time
error and detecting short-circuits between output pins.
Add documentation for the optional property renesas,poegs to
link a pair of GPT IOs with POEG.
Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v5:
* No change
v24 from [1]:
[1] https://lore.kernel.org/all/20250226144531.176819-1-biju.das.jz@bp.renesas.com/
---
.../bindings/pwm/renesas,rzg2l-gpt.yaml | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
index 13b807765a30..98bcde755fb9 100644
--- a/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
+++ b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
@@ -245,6 +245,28 @@ properties:
resets:
maxItems: 1
+ renesas,poegs:
+ minItems: 1
+ maxItems: 8
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ items:
+ - description: phandle to POEG instance that serves the output disable
+ - enum: [ 0, 1, 2, 3, 4, 5, 6, 7 ]
+ description: |
+ An index identifying pair of GPT channels.
+ <0> : GPT channels 0 and 1
+ <1> : GPT channels 2 and 3
+ <2> : GPT channels 4 and 5
+ <3> : GPT channels 6 and 7
+ <4> : GPT channels 8 and 9
+ <5> : GPT channels 10 and 11
+ <6> : GPT channels 12 and 13
+ <7> : GPT channels 14 and 15
+ description:
+ A list of phandle and channel index pair tuples to the POEGs that handle the
+ output disable for the GPT channels.
+
required:
- compatible
- reg
@@ -375,4 +397,5 @@ examples:
power-domains = <&cpg>;
resets = <&cpg R9A07G044_GPT_RST_C>;
#pwm-cells = <3>;
+ renesas,poegs = <&poeggd 4>;
};
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 2/9] pwm: rzg2l-gpt: Add support for gpt linking with poeg
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
2026-04-20 10:43 ` [PATCH v5 1/9] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 3/9] pwm: rzg2l-gpt: Drop unused rzg2l_gpt_chip parameter from rzg2l_gpt_calculate_prescale() Biju
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm
Cc: Biju Das, linux-pwm, linux-kernel, linux-renesas-soc,
Prabhakar Mahadev Lad, Biju Das
From: Biju Das <biju.das.jz@bp.renesas.com>
The General PWM Timer (GPT) is capable of detecting "dead time error
and short-circuits between output pins" and send Output disable
request to poeg(Port Output Enable for GPT).
Add support for linking poeg group with gpt, so that gpt can control
the output disable function by adding rzg2l_gpt_poeg_init() to parse
the renesas,poegs device tree property and establish links between POEG
groups (A–D) and GPT hardware channels (0–7). For each valid, enabled
POEG phandle entry, the driver:
- Reads the renesas,poeg-id from the POEG node and validates it against
the supported range
- Records the GPT–POEG association in a per-chip bitmap (poeg_gpt_link)
- Configures GTINTAD to route the output disable request to the correct
POEG group
- Configures GTIOR (OADF/OBDF fields) to set both output pins to
high-impedance on an output disable event
Non-enabled POEG nodes are silently skipped.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v5:
* Updated commit description.
* Replaced return type of rzg2l_gpt_poeg_init() from void->int and
probe() checks this return value.
* Added more error checks in rzg2l_gpt_poeg_init()
V24 from [1]:
[1] https://lore.kernel.org/all/20250226144531.176819-1-biju.das.jz@bp.renesas.com/
---
drivers/pwm/pwm-rzg2l-gpt.c | 93 +++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 4856af080e8e..71ae2f891fd2 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -39,6 +39,7 @@
#define RZG2L_GTCR(ch) (0x2c + RZG2L_GET_CH_OFFS(ch))
#define RZG2L_GTUDDTYC(ch) (0x30 + RZG2L_GET_CH_OFFS(ch))
#define RZG2L_GTIOR(ch) (0x34 + RZG2L_GET_CH_OFFS(ch))
+#define RZG2L_GTINTAD(ch) (0x38 + RZG2L_GET_CH_OFFS(ch))
#define RZG2L_GTBER(ch) (0x40 + RZG2L_GET_CH_OFFS(ch))
#define RZG2L_GTCNT(ch) (0x48 + RZG2L_GET_CH_OFFS(ch))
#define RZG2L_GTCCR(ch, sub_ch) (0x4c + RZG2L_GET_CH_OFFS(ch) + 4 * (sub_ch))
@@ -55,12 +56,19 @@
#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF)
#define RZG2L_GTIOR_GTIOA GENMASK(4, 0)
+#define RZG2L_GTIOR_OADF GENMASK(10, 9)
#define RZG2L_GTIOR_GTIOB GENMASK(20, 16)
+#define RZG2L_GTIOR_OBDF GENMASK(26, 25)
#define RZG2L_GTIOR_GTIOx(sub_ch) ((sub_ch) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
#define RZG2L_GTIOR_OAE BIT(8)
#define RZG2L_GTIOR_OBE BIT(24)
#define RZG2L_GTIOR_OxE(sub_ch) ((sub_ch) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE)
+#define RZG2L_GTIOR_OADF_HIGH_IMP_ON_OUT_DISABLE BIT(9)
+#define RZG2L_GTIOR_OBDF_HIGH_IMP_ON_OUT_DISABLE BIT(25)
+#define RZG2L_GTIOR_PIN_DISABLE_SETTING \
+ (RZG2L_GTIOR_OADF_HIGH_IMP_ON_OUT_DISABLE | RZG2L_GTIOR_OBDF_HIGH_IMP_ON_OUT_DISABLE)
+
#define RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE 0x1b
#define RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH \
(RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE | RZG2L_GTIOR_OAE)
@@ -71,12 +79,17 @@
((sub_ch) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \
RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH)
+#define RZG2L_GTINTAD_GRP_MASK GENMASK(25, 24)
+
#define RZG2L_MAX_HW_CHANNELS 8
#define RZG2L_CHANNELS_PER_IO 2
#define RZG2L_MAX_PWM_CHANNELS (RZG2L_MAX_HW_CHANNELS * RZG2L_CHANNELS_PER_IO)
#define RZG2L_MAX_SCALE_FACTOR 1024
#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR)
+#define RZG2L_MAX_POEG_GROUPS 4
+#define RZG2L_LAST_POEG_GROUP 3
+
struct rzg2l_gpt_chip {
void __iomem *mmio;
struct mutex lock; /* lock to protect shared channel resources */
@@ -84,6 +97,7 @@ struct rzg2l_gpt_chip {
u32 period_ticks[RZG2L_MAX_HW_CHANNELS];
u32 channel_request_count[RZG2L_MAX_HW_CHANNELS];
u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS];
+ DECLARE_BITMAP(poeg_gpt_link, RZG2L_MAX_POEG_GROUPS * RZG2L_MAX_HW_CHANNELS);
};
static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
@@ -375,6 +389,81 @@ static const struct pwm_ops rzg2l_gpt_ops = {
.apply = rzg2l_gpt_apply,
};
+/*
+ * This function links a poeg group{A,B,C,D} with a gpt channel{0..7} and
+ * configure the pin for output disable.
+ */
+static int rzg2l_gpt_poeg_init(struct platform_device *pdev,
+ struct rzg2l_gpt_chip *rzg2l_gpt)
+{
+ const char *poeg_name = "renesas,poegs";
+ struct of_phandle_args of_args;
+ struct property *poegs;
+ unsigned int i;
+ u32 poeg_grp;
+ u32 bitpos;
+ int cells;
+ int ret;
+
+ poegs = of_find_property(pdev->dev.of_node, poeg_name, NULL);
+ if (!poegs)
+ return 0;
+
+ cells = of_property_count_u32_elems(pdev->dev.of_node, poeg_name);
+ if (cells < 0)
+ return cells;
+
+ if (cells & 1)
+ return -EINVAL;
+
+ cells >>= 1;
+ for (i = 0; i < cells; i++) {
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ poeg_name, 1, i,
+ &of_args);
+ if (ret)
+ return ret;
+
+ if (of_args.args[0] >= RZG2L_MAX_HW_CHANNELS) {
+ dev_err(&pdev->dev, "Invalid channel %d >= %d\n",
+ of_args.args[0], RZG2L_MAX_HW_CHANNELS);
+ goto err_of_node;
+ }
+
+ if (!of_device_is_available(of_args.np)) {
+ /* It's fine to have a phandle to a non-enabled poeg. */
+ of_node_put(of_args.np);
+ continue;
+ }
+
+ if (!of_property_read_u32(of_args.np, "renesas,poeg-id", &poeg_grp)) {
+ if (poeg_grp > RZG2L_LAST_POEG_GROUP) {
+ dev_err(&pdev->dev, "Invalid poeg group %d > %d\n",
+ poeg_grp, RZG2L_LAST_POEG_GROUP);
+ goto err_of_node;
+ }
+
+ bitpos = of_args.args[0] + poeg_grp * RZG2L_MAX_HW_CHANNELS;
+ set_bit(bitpos, rzg2l_gpt->poeg_gpt_link);
+
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTINTAD(of_args.args[0]),
+ RZG2L_GTINTAD_GRP_MASK, poeg_grp << 24);
+
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTIOR(of_args.args[0]),
+ RZG2L_GTIOR_OBDF | RZG2L_GTIOR_OADF,
+ RZG2L_GTIOR_PIN_DISABLE_SETTING);
+ }
+
+ of_node_put(of_args.np);
+ }
+
+ return 0;
+
+err_of_node:
+ of_node_put(of_args.np);
+ return -EINVAL;
+}
+
static int rzg2l_gpt_probe(struct platform_device *pdev)
{
struct rzg2l_gpt_chip *rzg2l_gpt;
@@ -426,6 +515,10 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
if (rzg2l_gpt->rate_khz * KILO != rate)
return dev_err_probe(dev, -EINVAL, "Rate is not multiple of 1000");
+ ret = rzg2l_gpt_poeg_init(pdev, rzg2l_gpt);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to link gpt with poeg\n");
+
mutex_init(&rzg2l_gpt->lock);
chip->ops = &rzg2l_gpt_ops;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 3/9] pwm: rzg2l-gpt: Drop unused rzg2l_gpt_chip parameter from rzg2l_gpt_calculate_prescale()
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
2026-04-20 10:43 ` [PATCH v5 1/9] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property Biju
2026-04-20 10:43 ` [PATCH v5 2/9] pwm: rzg2l-gpt: Add support for gpt linking with poeg Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks Biju
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Biju Das, linux-pwm, linux-kernel, Geert Uytterhoeven,
Prabhakar Mahadev Lad, Biju Das, linux-renesas-soc
From: Biju Das <biju.das.jz@bp.renesas.com>
The rzg2l_gpt parameter was passed to rzg2l_gpt_calculate_prescale() but
never used inside the function. Remove it and update the sole call site
accordingly.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v5:
* New patch.
---
drivers/pwm/pwm-rzg2l-gpt.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 71ae2f891fd2..659044fa3d2f 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -132,8 +132,7 @@ static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr,
(rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set);
}
-static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt,
- u64 period_ticks)
+static u8 rzg2l_gpt_calculate_prescale(u64 period_ticks)
{
u32 prescaled_period_ticks;
u8 prescale;
@@ -300,7 +299,7 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
}
}
- prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks);
+ prescale = rzg2l_gpt_calculate_prescale(period_ticks);
pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (2 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 3/9] pwm: rzg2l-gpt: Drop unused rzg2l_gpt_chip parameter from rzg2l_gpt_calculate_prescale() Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 17:55 ` Cosmin-Gabriel Tanislav
2026-04-20 10:43 ` [PATCH v5 5/9] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip Biju
` (4 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Biju Das, linux-pwm, linux-kernel, Geert Uytterhoeven,
Prabhakar Mahadev Lad, Biju Das, linux-renesas-soc
From: Biju Das <biju.das.jz@bp.renesas.com>
Migrate the rzg2l-gpt driver from the legacy .get_state/.apply ops to the
new waveform callback interface.
Introduce struct rzg2l_gpt_waveform to represent a hardware waveform
configuration holding the period register value (gtpr), compare/capture
register value (gtccr), and prescaler (prescale).
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v5:
* Updated commit description.
* Updated rzg2l_gpt_round_waveform_tohw() to initialize gtccr when the
period of the second channel is smaller.
* Replaced period_ticks with RZG2L_MAX_TICKS for the duty_ticks maximum
value check in rzg2l_gpt_round_waveform_tohw().
v4 from [1]
[1] https://lore.kernel.org/all/20251208152133.269316-3-biju.das.jz@bp.renesas.com/
---
drivers/pwm/pwm-rzg2l-gpt.c | 197 ++++++++++++++++++++++--------------
1 file changed, 121 insertions(+), 76 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 659044fa3d2f..9e7a897a0b4d 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -100,6 +100,13 @@ struct rzg2l_gpt_chip {
DECLARE_BITMAP(poeg_gpt_link, RZG2L_MAX_POEG_GROUPS * RZG2L_MAX_HW_CHANNELS);
};
+/* This represents a hardware configuration for one channel */
+struct rzg2l_gpt_waveform {
+ u32 gtpr;
+ u32 gtccr;
+ u8 prescale;
+};
+
static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
{
return pwmchip_get_drvdata(chip);
@@ -166,7 +173,8 @@ static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm)
rzg2l_gpt->channel_request_count[ch]--;
}
-static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
+static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm,
+ u32 *gtcr)
{
u8 ch = RZG2L_GET_CH(hwpwm);
u32 val;
@@ -175,6 +183,9 @@ static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
if (!(val & RZG2L_GTCR_CST))
return false;
+ if (gtcr)
+ *gtcr = val;
+
val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTIOR(ch));
return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
@@ -233,54 +244,38 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz);
}
-static int rzg2l_gpt_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
-{
- struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
-
- state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
- if (state->enabled) {
- u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
- u32 ch = RZG2L_GET_CH(pwm->hwpwm);
- u8 prescale;
- u32 val;
-
- val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch));
- prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
-
- val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
- state->period = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
-
- val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch));
- state->duty_cycle = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
- if (state->duty_cycle > state->period)
- state->duty_cycle = state->period;
- }
-
- state->polarity = PWM_POLARITY_NORMAL;
-
- return 0;
-}
-
static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
{
return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)),
U32_MAX);
}
-/* Caller holds the lock while calling rzg2l_gpt_config() */
-static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
+static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+
{
struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
- u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ struct rzg2l_gpt_waveform *wfhw = _wfhw;
+ bool is_small_second_period = false;
u8 ch = RZG2L_GET_CH(pwm->hwpwm);
u64 period_ticks, duty_ticks;
unsigned long pv, dc;
- u8 prescale;
+
+ guard(mutex)(&rzg2l_gpt->lock);
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct rzg2l_gpt_waveform){
+ .gtpr = 0,
+ .gtccr = 0,
+ .prescale = 0,
+ };
+
+ return 0;
+ }
/* Limit period/duty cycle to max value supported by the HW */
- period_ticks = mul_u64_u64_div_u64(state->period, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+ period_ticks = mul_u64_u64_div_u64(wf->period_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
if (period_ticks > RZG2L_MAX_TICKS)
period_ticks = RZG2L_MAX_TICKS;
/*
@@ -291,21 +286,26 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (rzg2l_gpt->channel_request_count[ch] > 1) {
u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
- if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch)) {
+ if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch, NULL)) {
if (period_ticks < rzg2l_gpt->period_ticks[ch])
- return -EBUSY;
+ is_small_second_period = true;
period_ticks = rzg2l_gpt->period_ticks[ch];
}
}
- prescale = rzg2l_gpt_calculate_prescale(period_ticks);
- pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
+ wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
+ pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
+ wfhw->gtpr = pv;
+ wfhw->gtccr = 0;
+ if (is_small_second_period)
+ return 1;
- duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
- if (duty_ticks > period_ticks)
- duty_ticks = period_ticks;
- dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
+ duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+ if (duty_ticks > RZG2L_MAX_TICKS)
+ duty_ticks = RZG2L_MAX_TICKS;
+ dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
+ wfhw->gtccr = dc;
/*
* GPT counter is shared by multiple channels, we cache the period ticks
@@ -314,6 +314,61 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
rzg2l_gpt->period_ticks[ch] = period_ticks;
+ return 0;
+}
+
+static int rzg2l_gpt_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ const struct rzg2l_gpt_waveform *wfhw = _wfhw;
+
+ wf->period_length_ns = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, wfhw->gtpr,
+ wfhw->prescale);
+ wf->duty_length_ns = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, wfhw->gtccr,
+ wfhw->prescale);
+ wf->duty_offset_ns = 0;
+
+ return 0;
+}
+
+static int rzg2l_gpt_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ struct rzg2l_gpt_waveform *wfhw = _wfhw;
+ u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 gtcr;
+
+ guard(mutex)(&rzg2l_gpt->lock);
+ if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm, >cr)) {
+ wfhw->prescale = FIELD_GET(RZG2L_GTCR_TPCS, gtcr);
+ wfhw->gtpr = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
+ wfhw->gtccr = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch));
+ if (wfhw->gtccr > wfhw->gtpr)
+ wfhw->gtccr = wfhw->gtpr;
+ } else {
+ *wfhw = (struct rzg2l_gpt_waveform) { };
+ }
+
+ return 0;
+}
+
+static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ const struct rzg2l_gpt_waveform *wfhw = _wfhw;
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 gptr;
+
+ guard(mutex)(&rzg2l_gpt->lock);
/*
* Counter must be stopped before modifying mode, prescaler, timer
* counter and buffer enable registers. These registers are shared
@@ -332,14 +387,20 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* Select count clock */
rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
- FIELD_PREP(RZG2L_GTCR_TPCS, prescale));
+ FIELD_PREP(RZG2L_GTCR_TPCS, wfhw->prescale));
/* Set period */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), pv);
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), wfhw->gtpr);
+ } else {
+ if (wfhw->gtpr) {
+ gptr = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
+ if (wfhw->gtpr < gptr)
+ return -EBUSY;
+ }
}
/* Set duty cycle */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), dc);
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), wfhw->gtccr);
if (rzg2l_gpt->channel_enable_count[ch] <= 1) {
/* Set initial value for counter */
@@ -348,44 +409,28 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
/* Set no buffer operation */
rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0);
- /* Restart the counter after updating the registers */
- rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch),
- RZG2L_GTCR_CST, RZG2L_GTCR_CST);
+ if (wfhw->gtpr)
+ /* Restart the counter after updating the registers */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch),
+ RZG2L_GTCR_CST, RZG2L_GTCR_CST);
}
- return 0;
-}
-
-static int rzg2l_gpt_apply(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
-{
- struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
- bool enabled = pwm->state.enabled;
- int ret;
-
- if (state->polarity != PWM_POLARITY_NORMAL)
- return -EINVAL;
-
- guard(mutex)(&rzg2l_gpt->lock);
- if (!state->enabled) {
- if (enabled)
- rzg2l_gpt_disable(rzg2l_gpt, pwm);
-
- return 0;
- }
-
- ret = rzg2l_gpt_config(chip, pwm, state);
- if (!ret && !enabled)
+ if (wfhw->gtpr && !rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm, NULL))
rzg2l_gpt_enable(rzg2l_gpt, pwm);
+ else if (!wfhw->gtpr && rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm, NULL))
+ rzg2l_gpt_disable(rzg2l_gpt, pwm);
- return ret;
+ return 0;
}
static const struct pwm_ops rzg2l_gpt_ops = {
.request = rzg2l_gpt_request,
.free = rzg2l_gpt_free,
- .get_state = rzg2l_gpt_get_state,
- .apply = rzg2l_gpt_apply,
+ .sizeof_wfhw = sizeof(struct rzg2l_gpt_waveform),
+ .round_waveform_tohw = rzg2l_gpt_round_waveform_tohw,
+ .round_waveform_fromhw = rzg2l_gpt_round_waveform_fromhw,
+ .read_waveform = rzg2l_gpt_read_waveform,
+ .write_waveform = rzg2l_gpt_write_waveform,
};
/*
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* RE: [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks
2026-04-20 10:43 ` [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks Biju
@ 2026-04-20 17:55 ` Cosmin-Gabriel Tanislav
2026-04-21 8:40 ` Uwe Kleine-König
0 siblings, 1 reply; 13+ messages in thread
From: Cosmin-Gabriel Tanislav @ 2026-04-20 17:55 UTC (permalink / raw)
To: Uwe Kleine-König, biju.das.au
Cc: Biju Das, linux-pwm@vger.kernel.org, linux-kernel@vger.kernel.org,
Geert Uytterhoeven, Prabhakar Mahadev Lad,
linux-renesas-soc@vger.kernel.org
> -----Original Message-----
> From: Biju
> Sent: Monday, April 20, 2026 1:43 PM
> To: Uwe Kleine-König <ukleinek@kernel.org>
> Cc: Biju Das <biju.das.jz@bp.renesas.com>; linux-pwm@vger.kernel.org; linux-kernel@vger.kernel.org;
> Geert Uytterhoeven <geert+renesas@glider.be>; Prabhakar Mahadev Lad <prabhakar.mahadev-
> lad.rj@bp.renesas.com>; Biju Das <biju.das.au@gmail.com>; linux-renesas-soc@vger.kernel.org
> Subject: [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks
>
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> Migrate the rzg2l-gpt driver from the legacy .get_state/.apply ops to the
> new waveform callback interface.
>
> Introduce struct rzg2l_gpt_waveform to represent a hardware waveform
> configuration holding the period register value (gtpr), compare/capture
> register value (gtccr), and prescaler (prescale).
>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> v5:
> * Updated commit description.
> * Updated rzg2l_gpt_round_waveform_tohw() to initialize gtccr when the
> period of the second channel is smaller.
> * Replaced period_ticks with RZG2L_MAX_TICKS for the duty_ticks maximum
> value check in rzg2l_gpt_round_waveform_tohw().
> v4 from [1]
> [1] https://lore.kernel.org/all/20251208152133.269316-3-biju.das.jz@bp.renesas.com/
> ---
> drivers/pwm/pwm-rzg2l-gpt.c | 197 ++++++++++++++++++++++--------------
> 1 file changed, 121 insertions(+), 76 deletions(-)
>
> diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
> index 659044fa3d2f..9e7a897a0b4d 100644
> --- a/drivers/pwm/pwm-rzg2l-gpt.c
> +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> @@ -100,6 +100,13 @@ struct rzg2l_gpt_chip {
> DECLARE_BITMAP(poeg_gpt_link, RZG2L_MAX_POEG_GROUPS * RZG2L_MAX_HW_CHANNELS);
> };
>
> +/* This represents a hardware configuration for one channel */
> +struct rzg2l_gpt_waveform {
> + u32 gtpr;
> + u32 gtccr;
> + u8 prescale;
> +};
> +
> static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
> {
> return pwmchip_get_drvdata(chip);
> @@ -166,7 +173,8 @@ static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm)
> rzg2l_gpt->channel_request_count[ch]--;
> }
>
> -static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
> +static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm,
> + u32 *gtcr)
> {
> u8 ch = RZG2L_GET_CH(hwpwm);
> u32 val;
> @@ -175,6 +183,9 @@ static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
> if (!(val & RZG2L_GTCR_CST))
> return false;
>
> + if (gtcr)
> + *gtcr = val;
> +
> val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTIOR(ch));
>
> return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
> @@ -233,54 +244,38 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
> return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz);
> }
>
> -static int rzg2l_gpt_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> - struct pwm_state *state)
> -{
> - struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> -
> - state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> - if (state->enabled) {
> - u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> - u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> - u8 prescale;
> - u32 val;
> -
> - val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch));
> - prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
> -
> - val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
> - state->period = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
> -
> - val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch));
> - state->duty_cycle = rzg2l_gpt_calculate_period_or_duty(rzg2l_gpt, val, prescale);
> - if (state->duty_cycle > state->period)
> - state->duty_cycle = state->period;
> - }
> -
> - state->polarity = PWM_POLARITY_NORMAL;
> -
> - return 0;
> -}
> -
> static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
> {
> return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)),
> U32_MAX);
> }
>
> -/* Caller holds the lock while calling rzg2l_gpt_config() */
> -static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
> - const struct pwm_state *state)
> +static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + const struct pwm_waveform *wf,
> + void *_wfhw)
> +
> {
> struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> - u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> + struct rzg2l_gpt_waveform *wfhw = _wfhw;
> + bool is_small_second_period = false;
> u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> u64 period_ticks, duty_ticks;
> unsigned long pv, dc;
> - u8 prescale;
> +
> + guard(mutex)(&rzg2l_gpt->lock);
> + if (wf->period_length_ns == 0) {
> + *wfhw = (struct rzg2l_gpt_waveform){
> + .gtpr = 0,
> + .gtccr = 0,
> + .prescale = 0,
> + };
> +
> + return 0;
> + }
>
> /* Limit period/duty cycle to max value supported by the HW */
> - period_ticks = mul_u64_u64_div_u64(state->period, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> + period_ticks = mul_u64_u64_div_u64(wf->period_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> if (period_ticks > RZG2L_MAX_TICKS)
> period_ticks = RZG2L_MAX_TICKS;
> /*
> @@ -291,21 +286,26 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
> if (rzg2l_gpt->channel_request_count[ch] > 1) {
> u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
>
> - if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch)) {
> + if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch, NULL)) {
> if (period_ticks < rzg2l_gpt->period_ticks[ch])
> - return -EBUSY;
> + is_small_second_period = true;
>
> period_ticks = rzg2l_gpt->period_ticks[ch];
> }
> }
>
> - prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> - pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
> + wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> + pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
> + wfhw->gtpr = pv;
> + wfhw->gtccr = 0;
> + if (is_small_second_period)
> + return 1;
>
> - duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> - if (duty_ticks > period_ticks)
> - duty_ticks = period_ticks;
> - dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
> + duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> + if (duty_ticks > RZG2L_MAX_TICKS)
> + duty_ticks = RZG2L_MAX_TICKS;
I know this change from > period_ticks to > RZG2L_MAX_TICKS has been
suggested by you, Uwe, but is this correct if period_ticks was set to a
smaller value in the earlier sibling channel condition?
In that case I think it might be possible for duty_ticks to end up
larger than period_ticks which wouldn't work correctly with the
> RZG2L_MAX_TICKS check, but would have been clamped fine using the
other check.
What do you think?
> + dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
> + wfhw->gtccr = dc;
Is there any reason why the results of rzg2l_gpt_calculate_pv_or_dc()
cannot be assigned to wfhw->gtpr and wfhw->gtccr directly?
That would get rid of the pv and dc variables as we do not need them
for anything else anymore, as far as I can tell.
>
> /*
> * GPT counter is shared by multiple channels, we cache the period ticks
> @@ -314,6 +314,61 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
> */
> rzg2l_gpt->period_ticks[ch] = period_ticks;
>
This should be part of rzg2l_gpt_write_waveform().
Otherwise, if pwm_round_waveform_might_sleep() is called without
pwm_set_waveform_might_sleep() being called immediately after with the
rounded waveform, the software state will become out of sync with the
hardware state.
Uwe, what's your take on this?
> + return 0;
> +}
> +
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks
2026-04-20 17:55 ` Cosmin-Gabriel Tanislav
@ 2026-04-21 8:40 ` Uwe Kleine-König
0 siblings, 0 replies; 13+ messages in thread
From: Uwe Kleine-König @ 2026-04-21 8:40 UTC (permalink / raw)
To: Cosmin-Gabriel Tanislav
Cc: biju.das.au, Biju Das, linux-pwm@vger.kernel.org,
linux-kernel@vger.kernel.org, Geert Uytterhoeven,
Prabhakar Mahadev Lad, linux-renesas-soc@vger.kernel.org
[-- Attachment #1: Type: text/plain, Size: 2591 bytes --]
Hello Cosmin,
On Mon, Apr 20, 2026 at 05:55:07PM +0000, Cosmin-Gabriel Tanislav wrote:
> > @@ -291,21 +286,26 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
> > if (rzg2l_gpt->channel_request_count[ch] > 1) {
> > u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
> >
> > - if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch)) {
> > + if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch, NULL)) {
> > if (period_ticks < rzg2l_gpt->period_ticks[ch])
> > - return -EBUSY;
> > + is_small_second_period = true;
> >
> > period_ticks = rzg2l_gpt->period_ticks[ch];
> > }
> > }
> >
> > - prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> > - pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
> > + wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> > + pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
> > + wfhw->gtpr = pv;
> > + wfhw->gtccr = 0;
> > + if (is_small_second_period)
> > + return 1;
> >
> > - duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> > - if (duty_ticks > period_ticks)
> > - duty_ticks = period_ticks;
> > - dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
> > + duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> > + if (duty_ticks > RZG2L_MAX_TICKS)
> > + duty_ticks = RZG2L_MAX_TICKS;
>
> I know this change from > period_ticks to > RZG2L_MAX_TICKS has been
> suggested by you, Uwe, but is this correct if period_ticks was set to a
> smaller value in the earlier sibling channel condition?
Indeed this is irritating. I assume I missed that and take the blame for
the wrong suggestions. Depending on how hardware copes with such a
configuration it might be ok to keep the code as is, but a comment would
be justified in this case.
> > /*
> > * GPT counter is shared by multiple channels, we cache the period ticks
> > @@ -314,6 +314,61 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
> > */
> > rzg2l_gpt->period_ticks[ch] = period_ticks;
> >
>
> This should be part of rzg2l_gpt_write_waveform().
>
> Otherwise, if pwm_round_waveform_might_sleep() is called without
> pwm_set_waveform_might_sleep() being called immediately after with the
> rounded waveform, the software state will become out of sync with the
> hardware state.
Indeed, the tohw and fromhw callbacks must have no side effects.
There isn't a set_waveform call after each round_waveform.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5 5/9] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (3 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 4/9] pwm: rzg2l-gpt: Convert to waveform callbacks Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm
Cc: Biju Das, linux-pwm, linux-kernel, linux-renesas-soc,
Prabhakar Mahadev Lad, Biju Das, Tommaso Merciai
From: Biju Das <biju.das.jz@bp.renesas.com>
Introduce struct rzg2l_gpt_info to capture SoC-specific hardware
differences, starting with the gtcr_tpcs field mask for the prescaler
bitfield in GTCR. This is needed because the RZ/G3E GPT has a 4-bit
prescaler field versus the 3-bit field on RZ/G2L.
Reviewed-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* Updated commit description.
v3->v4:
* Dropped field_{get,prep} as mainline now support it.
* Updated commit description.
* Retained RZG2L_GTCR_TPCS bit definitons
* Replaced gtcr_tpcs_mask->gtcr_tpcs
v2->v3:
* No change.
v1->v2:
* Collected tag.
---
drivers/pwm/pwm-rzg2l-gpt.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 9e7a897a0b4d..af594c1ce536 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -90,9 +90,14 @@
#define RZG2L_MAX_POEG_GROUPS 4
#define RZG2L_LAST_POEG_GROUP 3
+struct rzg2l_gpt_info {
+ u32 gtcr_tpcs;
+};
+
struct rzg2l_gpt_chip {
void __iomem *mmio;
struct mutex lock; /* lock to protect shared channel resources */
+ const struct rzg2l_gpt_info *info;
unsigned long rate_khz;
u32 period_ticks[RZG2L_MAX_HW_CHANNELS];
u32 channel_request_count[RZG2L_MAX_HW_CHANNELS];
@@ -346,7 +351,7 @@ static int rzg2l_gpt_read_waveform(struct pwm_chip *chip,
guard(mutex)(&rzg2l_gpt->lock);
if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm, >cr)) {
- wfhw->prescale = FIELD_GET(RZG2L_GTCR_TPCS, gtcr);
+ wfhw->prescale = field_get(rzg2l_gpt->info->gtcr_tpcs, gtcr);
wfhw->gtpr = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch));
wfhw->gtccr = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch));
if (wfhw->gtccr > wfhw->gtpr)
@@ -386,8 +391,8 @@ static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTUDDTYC(ch), RZG2L_GTUDDTYC_UP_COUNTING);
/* Select count clock */
- rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
- FIELD_PREP(RZG2L_GTCR_TPCS, wfhw->prescale));
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), rzg2l_gpt->info->gtcr_tpcs,
+ field_prep(rzg2l_gpt->info->gtcr_tpcs, wfhw->prescale));
/* Set period */
rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), wfhw->gtpr);
@@ -527,6 +532,8 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
if (IS_ERR(rzg2l_gpt->mmio))
return PTR_ERR(rzg2l_gpt->mmio);
+ rzg2l_gpt->info = of_device_get_match_data(dev);
+
rstc = devm_reset_control_get_exclusive_deasserted(dev, NULL);
if (IS_ERR(rstc))
return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert reset control\n");
@@ -573,8 +580,12 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
return 0;
}
+static const struct rzg2l_gpt_info rzg2l_data = {
+ .gtcr_tpcs = RZG2L_GTCR_TPCS,
+};
+
static const struct of_device_id rzg2l_gpt_of_table[] = {
- { .compatible = "renesas,rzg2l-gpt", },
+ { .compatible = "renesas,rzg2l-gpt", .data = &rzg2l_data },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (4 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 5/9] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 17:52 ` Biju Das
2026-04-20 10:43 ` [PATCH v5 7/9] pwm: rzg2l-gpt: Add calculate_prescale() callback " Biju
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Biju Das, linux-pwm, linux-kernel, Geert Uytterhoeven,
Prabhakar Mahadev Lad, Biju Das, linux-renesas-soc,
Tommaso Merciai
From: Biju Das <biju.das.jz@bp.renesas.com>
RZ/G3E GPT IP has prescale factor power of 2 where as that of RZ/G2L is 4.
Add prescale_mult variable to struct rzg2l_gpt_info for handling this
difference.
Reviewed-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* No change.
v3->v4:
* Updated commit header and description
* Renamed prescale_pow_of_two_mult_factor->prescale_mult
v2->v3:
* No change.
v1->v2:
* Collected tag.
---
drivers/pwm/pwm-rzg2l-gpt.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index af594c1ce536..4324ffc8629d 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -92,6 +92,7 @@
struct rzg2l_gpt_info {
u32 gtcr_tpcs;
+ u8 prescale_mult;
};
struct rzg2l_gpt_chip {
@@ -234,6 +235,7 @@ static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt,
static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
u32 val, u8 prescale)
{
+ const struct rzg2l_gpt_info *info = rzg2l_gpt->info;
u64 tmp;
/*
@@ -243,15 +245,18 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
* < 2^32 * 2^10 * 2^20
* = 2^62
*/
- tmp = (u64)val << (2 * prescale);
+ tmp = (u64)val << (info->prescale_mult * prescale);
tmp *= USEC_PER_SEC;
return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz);
}
-static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
+static u32 rzg2l_gpt_calculate_pv_or_dc(const struct rzg2l_gpt_info *info,
+ u64 period_or_duty_cycle, u8 prescale)
{
- return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)),
+ return min_t(u64,
+ DIV_ROUND_DOWN_ULL(period_or_duty_cycle,
+ 1 << (info->prescale_mult * prescale)),
U32_MAX);
}
@@ -262,6 +267,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
{
struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ const struct rzg2l_gpt_info *info = rzg2l_gpt->info;
struct rzg2l_gpt_waveform *wfhw = _wfhw;
bool is_small_second_period = false;
u8 ch = RZG2L_GET_CH(pwm->hwpwm);
@@ -299,8 +305,8 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
}
}
- wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
- pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
+ wfhw->prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks);
+ pv = rzg2l_gpt_calculate_pv_or_dc(info, period_ticks, wfhw->prescale);
wfhw->gtpr = pv;
wfhw->gtccr = 0;
if (is_small_second_period)
@@ -309,7 +315,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, USEC_PER_SEC);
if (duty_ticks > RZG2L_MAX_TICKS)
duty_ticks = RZG2L_MAX_TICKS;
- dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
+ dc = rzg2l_gpt_calculate_pv_or_dc(info, duty_ticks, wfhw->prescale);
wfhw->gtccr = dc;
/*
@@ -582,6 +588,7 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
static const struct rzg2l_gpt_info rzg2l_data = {
.gtcr_tpcs = RZG2L_GTCR_TPCS,
+ .prescale_mult = 2,
};
static const struct of_device_id rzg2l_gpt_of_table[] = {
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* RE: [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info
2026-04-20 10:43 ` [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju
@ 2026-04-20 17:52 ` Biju Das
0 siblings, 0 replies; 13+ messages in thread
From: Biju Das @ 2026-04-20 17:52 UTC (permalink / raw)
To: biju.das.au, Uwe Kleine-König
Cc: linux-pwm@vger.kernel.org, linux-kernel@vger.kernel.org,
Geert Uytterhoeven, Prabhakar Mahadev Lad, biju.das.au,
linux-renesas-soc@vger.kernel.org, Tommaso Merciai
Hi,
> -----Original Message-----
> From: Biju <biju.das.au@gmail.com>
> Sent: 20 April 2026 11:43
> Subject: [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info
>
> From: Biju Das <biju.das.jz@bp.renesas.com>
>
> RZ/G3E GPT IP has prescale factor power of 2 where as that of RZ/G2L is 4.
> Add prescale_mult variable to struct rzg2l_gpt_info for handling this difference.
>
> Reviewed-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> v4->v5:
> * No change.
> v3->v4:
> * Updated commit header and description
> * Renamed prescale_pow_of_two_mult_factor->prescale_mult
> v2->v3:
> * No change.
> v1->v2:
> * Collected tag.
> ---
> drivers/pwm/pwm-rzg2l-gpt.c | 19 +++++++++++++------
> 1 file changed, 13 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c index af594c1ce536..4324ffc8629d
> 100644
> --- a/drivers/pwm/pwm-rzg2l-gpt.c
> +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> @@ -92,6 +92,7 @@
>
> struct rzg2l_gpt_info {
> u32 gtcr_tpcs;
> + u8 prescale_mult;
> };
>
> struct rzg2l_gpt_chip {
> @@ -234,6 +235,7 @@ static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt, static u64
> rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
> u32 val, u8 prescale)
> {
> + const struct rzg2l_gpt_info *info = rzg2l_gpt->info;
> u64 tmp;
>
> /*
> @@ -243,15 +245,18 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
> * < 2^32 * 2^10 * 2^20
> * = 2^62
> */
> - tmp = (u64)val << (2 * prescale);
> + tmp = (u64)val << (info->prescale_mult * prescale);
> tmp *= USEC_PER_SEC;
>
> return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz); }
>
> -static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
> +static u32 rzg2l_gpt_calculate_pv_or_dc(const struct rzg2l_gpt_info *info,
> + u64 period_or_duty_cycle, u8 prescale)
> {
> - return min_t(u64, DIV_ROUND_DOWN_ULL(period_or_duty_cycle, 1 << (2 * prescale)),
> + return min_t(u64,
> + DIV_ROUND_DOWN_ULL(period_or_duty_cycle,
> + 1 << (info->prescale_mult * prescale)),
> U32_MAX);
> }
>
> @@ -262,6 +267,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
>
> {
> struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> + const struct rzg2l_gpt_info *info = rzg2l_gpt->info;
> struct rzg2l_gpt_waveform *wfhw = _wfhw;
> bool is_small_second_period = false;
> u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> @@ -299,8 +305,8 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
> }
> }
>
> - wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> - pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
> + wfhw->prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks);
This is wrong here, as I dropped rzg2l_gpt in patch #3.
Cheers,
Biju
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5 7/9] pwm: rzg2l-gpt: Add calculate_prescale() callback to struct rzg2l_gpt_info
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (5 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 6/9] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 8/9] dt-bindings: pwm: Document RZ/G3E GPT support Biju
2026-04-20 10:43 ` [PATCH v5 9/9] pwm: rzg2l-gpt: Add RZ/G3E support Biju
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Biju Das, linux-pwm, linux-kernel, Geert Uytterhoeven,
Prabhakar Mahadev Lad, Biju Das, linux-renesas-soc,
Tommaso Merciai
From: Biju Das <biju.das.jz@bp.renesas.com>
The RZ/G2L GPT prescaler steps are continuous powers of 4, while RZ/G3E
uses powers of 2 but with a discontinuous sequence. Add a
calculate_prescale function pointer to struct rzg2l_gpt_info to allow
per-SoC prescaler selection logic. Replace the direct call to
rzg2l_gpt_calculate_prescale() in rzg2l_gpt_round_waveform_tohw() with an
indirect call through info->calculate_prescale(). Wire the existing
rzg2l_gpt_calculate_prescale() into rzg2l_data to preserve current RZ/G2L
behaviour.
Reviewed-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* Updated commit description.
v3->v4:
* No change.
v2->v3:
* No change.
v1->v2:
* Collected tag.
---
drivers/pwm/pwm-rzg2l-gpt.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 4324ffc8629d..de68c02b2d50 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -91,6 +91,7 @@
#define RZG2L_LAST_POEG_GROUP 3
struct rzg2l_gpt_info {
+ u8 (*calculate_prescale)(u64 period);
u32 gtcr_tpcs;
u8 prescale_mult;
};
@@ -305,7 +306,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
}
}
- wfhw->prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_ticks);
+ wfhw->prescale = info->calculate_prescale(period_ticks);
pv = rzg2l_gpt_calculate_pv_or_dc(info, period_ticks, wfhw->prescale);
wfhw->gtpr = pv;
wfhw->gtccr = 0;
@@ -587,6 +588,7 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
}
static const struct rzg2l_gpt_info rzg2l_data = {
+ .calculate_prescale = rzg2l_gpt_calculate_prescale,
.gtcr_tpcs = RZG2L_GTCR_TPCS,
.prescale_mult = 2,
};
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 8/9] dt-bindings: pwm: Document RZ/G3E GPT support
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (6 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 7/9] pwm: rzg2l-gpt: Add calculate_prescale() callback " Biju
@ 2026-04-20 10:43 ` Biju
2026-04-20 10:43 ` [PATCH v5 9/9] pwm: rzg2l-gpt: Add RZ/G3E support Biju
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Geert Uytterhoeven, Magnus Damm
Cc: Biju Das, linux-pwm, devicetree, linux-kernel, linux-renesas-soc,
Prabhakar Mahadev Lad, Biju Das
From: Biju Das <biju.das.jz@bp.renesas.com>
Document support for the GPT found on the Renesas RZ/G3E (R9A09G047)
SoC.
The GPT is a 32-bit timer with 16 hardware channels (GPT0: 8 channel
and GPT1: 8channels). The hardware supports simultaneous control of
all channels. PWM waveforms can be generated by controlling the
up-counter, downcounter, or up- and down-counter.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* No change.
v3->v4:
* No change.
v2->v3:
* Added Rb tag from Rob.
v1->v2:
* Created separate document for RZ/G3E GPT.
* Updated commit header and description.
---
.../bindings/pwm/renesas,rzg3e-gpt.yaml | 323 ++++++++++++++++++
1 file changed, 323 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml
diff --git a/Documentation/devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml b/Documentation/devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml
new file mode 100644
index 000000000000..cb4ffab5f47f
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml
@@ -0,0 +1,323 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/renesas,rzg3e-gpt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/G3E General PWM Timer (GPT)
+
+maintainers:
+ - Biju Das <biju.das.jz@bp.renesas.com>
+
+description: |
+ RZ/G3E General PWM Timer (GPT) composed of 16 channels with 32-bit
+ timer. It supports the following functions
+ * 32 bits x 16 channels.
+ * Up-counting or down-counting (saw waves) or up/down-counting
+ (triangle waves) for each counter.
+ * Clock sources independently selectable for each channel.
+ * Four I/O pins per channel.
+ * Two output compare/input capture registers per channel.
+ * For the two output compare/input capture registers of each channel,
+ four registers are provided as buffer registers and are capable of
+ operating as comparison registers when buffering is not in use.
+ * In output compare operation, buffer switching can be at crests or
+ troughs, enabling the generation of laterally asymmetric PWM waveforms.
+ * Registers for setting up frame cycles in each channel (with capability
+ for generating interrupts at overflow or underflow)
+ * Generation of dead times in PWM operation.
+ * Synchronous starting, stopping and clearing counters for arbitrary
+ channels.
+ * Count start, count stop, count clear, up-count, down-count, or input
+ capture operation in response to a maximum of 8 ELC events.
+ * Count start, count stop, count clear, up-count, down-count, or input
+ capture operation in response to the status of two input pins.
+ * Starting, clearing, stopping and up/down counters in response to a
+ maximum of four external triggers.
+ * Output pin disable function by detected short-circuits between output
+ pins.
+ * A/D converter start triggers can be generated.
+ * Compare match A to F event and overflow/underflow event can be output
+ to the ELC.
+ * Enables the noise filter for input capture.
+ * Logical operation between the channel output.
+
+properties:
+ compatible:
+ items:
+ - const: renesas,r9a09g047-gpt # RZ/G3E
+
+ reg:
+ maxItems: 1
+
+ '#pwm-cells':
+ const: 3
+
+ interrupts:
+ items:
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.0
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.0
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.0
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.0
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.0
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.0
+ - description: A and B both high interrupt for channel GPT{0,1}.0
+ - description: A and B both low interrupt for channel GPT{0,1}.0
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.1
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.1
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.1
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.1
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.1
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.1
+ - description: A and B both high interrupt for channel GPT{0,1}.1
+ - description: A and B both low interrupt for channel GPT{0,1}.1
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.2
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.2
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.2
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.2
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.2
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.2
+ - description: A and B both high interrupt for channel GPT{0,1}.2
+ - description: A and B both low interrupt for channel GPT{0,1}.2
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.3
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.3
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.3
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.3
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.3
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.3
+ - description: A and B both high interrupt for channel GPT{0,1}.3
+ - description: A and B both low interrupt for channel GPT{0,1}.3
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.4
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.4
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.4
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.4
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.4
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.4
+ - description: A and B both high interrupt for channel GPT{0,1}.4
+ - description: A and B both low interrupt for channel GPT{0,1}.4
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.5
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.5
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.5
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.5
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.5
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.5
+ - description: A and B both high interrupt for channel GPT{0,1}.5
+ - description: A and B both low interrupt for channel GPT{0,1}.5
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.6
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.6
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.6
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.6
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.6
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.6
+ - description: A and B both high interrupt for channel GPT{0,1}.6
+ - description: A and B both low interrupt for channel GPT{0,1}.6
+ - description: Input capture/compare match of the GTCCRA for channel GPT{0,1}.7
+ - description: Input capture/compare match of the GTCCRB for channel GPT{0,1}.7
+ - description: Compare match with the GTCCRC for channel GPT{0,1}.7
+ - description: Compare match with the GTCCRD for channel GPT{0,1}.7
+ - description: Compare match with the GTCCRE for channel GPT{0,1}.7
+ - description: Compare match with the GTCCRF for channel GPT{0,1}.7
+ - description: A and B both high interrupt for channel GPT{0,1}.7
+ - description: A and B both low interrupt for channel GPT{0,1}.7
+
+ interrupt-names:
+ items:
+ - const: gtcia0
+ - const: gtcib0
+ - const: gtcic0
+ - const: gtcid0
+ - const: gtcie0
+ - const: gtcif0
+ - const: gtcih0
+ - const: gtcil0
+ - const: gtcia1
+ - const: gtcib1
+ - const: gtcic1
+ - const: gtcid1
+ - const: gtcie1
+ - const: gtcif1
+ - const: gtcih1
+ - const: gtcil1
+ - const: gtcia2
+ - const: gtcib2
+ - const: gtcic2
+ - const: gtcid2
+ - const: gtcie2
+ - const: gtcif2
+ - const: gtcih2
+ - const: gtcil2
+ - const: gtcia3
+ - const: gtcib3
+ - const: gtcic3
+ - const: gtcid3
+ - const: gtcie3
+ - const: gtcif3
+ - const: gtcih3
+ - const: gtcil3
+ - const: gtcia4
+ - const: gtcib4
+ - const: gtcic4
+ - const: gtcid4
+ - const: gtcie4
+ - const: gtcif4
+ - const: gtcih4
+ - const: gtcil4
+ - const: gtcia5
+ - const: gtcib5
+ - const: gtcic5
+ - const: gtcid5
+ - const: gtcie5
+ - const: gtcif5
+ - const: gtcih5
+ - const: gtcil5
+ - const: gtcia6
+ - const: gtcib6
+ - const: gtcic6
+ - const: gtcid6
+ - const: gtcie6
+ - const: gtcif6
+ - const: gtcih6
+ - const: gtcil6
+ - const: gtcia7
+ - const: gtcib7
+ - const: gtcic7
+ - const: gtcid7
+ - const: gtcie7
+ - const: gtcif7
+ - const: gtcih7
+ - const: gtcil7
+
+ clocks:
+ items:
+ - description: Core clock (PCLKD)
+ - description: Bus clock (PCLKA)
+
+ clock-names:
+ items:
+ - const: core
+ - const: bus
+
+ power-domains:
+ maxItems: 1
+
+ resets:
+ items:
+ - description: Reset for bus clock (PCLKA/PCLKD)
+ - description: Reset for core clock (PCLKD)
+
+ reset-names:
+ items:
+ - const: rst_p
+ - const: rst_s
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - clocks
+ - clock-names
+ - power-domains
+ - resets
+ - reset-names
+
+allOf:
+ - $ref: pwm.yaml#
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/renesas,r9a09g047-cpg.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ pwm@13010000 {
+ compatible = "renesas,r9a09g047-gpt";
+ reg = <0x13010000 0x10000>;
+ interrupts = <GIC_SPI 538 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 546 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 554 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 562 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 570 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 578 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 586 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 594 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 539 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 547 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 555 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 563 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 571 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 579 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 587 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 595 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 540 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 548 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 556 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 564 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 572 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 580 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 588 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 596 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 541 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 549 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 557 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 565 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 573 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 581 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 589 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 597 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 542 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 550 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 558 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 566 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 574 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 582 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 590 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 598 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 543 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 551 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 559 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 567 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 575 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 583 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 591 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 599 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 544 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 552 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 560 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 568 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 576 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 584 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 592 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 600 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 545 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 553 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 561 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 569 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 577 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 585 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 593 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 601 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "gtcia0", "gtcib0", "gtcic0", "gtcid0",
+ "gtcie0", "gtcif0", "gtcih0", "gtcil0",
+ "gtcia1", "gtcib1", "gtcic1", "gtcid1",
+ "gtcie1", "gtcif1", "gtcih1", "gtcil1",
+ "gtcia2", "gtcib2", "gtcic2", "gtcid2",
+ "gtcie2", "gtcif2", "gtcih2", "gtcil2",
+ "gtcia3", "gtcib3", "gtcic3", "gtcid3",
+ "gtcie3", "gtcif3", "gtcih3", "gtcil3",
+ "gtcia4", "gtcib4", "gtcic4", "gtcid4",
+ "gtcie4", "gtcif4", "gtcih4", "gtcil4",
+ "gtcia5", "gtcib5", "gtcic5", "gtcid5",
+ "gtcie5", "gtcif5", "gtcih5", "gtcil5",
+ "gtcia6", "gtcib6", "gtcic6", "gtcid6",
+ "gtcie6", "gtcif6", "gtcih6", "gtcil6",
+ "gtcia7", "gtcib7", "gtcic7", "gtcid7",
+ "gtcie7", "gtcif7", "gtcih7", "gtcil7";
+ clocks = <&cpg CPG_MOD 0x31>, <&cpg CPG_MOD 0x31>;
+ clock-names = "core", "bus";
+ power-domains = <&cpg>;
+ resets = <&cpg 0x59>, <&cpg 0x5a>;
+ reset-names = "rst_p", "rst_s";
+ #pwm-cells = <3>;
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH v5 9/9] pwm: rzg2l-gpt: Add RZ/G3E support
2026-04-20 10:43 [PATCH v5 0/9] Add Renesas RZ/G3E GPT support Biju
` (7 preceding siblings ...)
2026-04-20 10:43 ` [PATCH v5 8/9] dt-bindings: pwm: Document RZ/G3E GPT support Biju
@ 2026-04-20 10:43 ` Biju
8 siblings, 0 replies; 13+ messages in thread
From: Biju @ 2026-04-20 10:43 UTC (permalink / raw)
To: Uwe Kleine-König, Philipp Zabel, Geert Uytterhoeven,
Magnus Damm
Cc: Biju Das, linux-pwm, linux-kernel, linux-renesas-soc,
Prabhakar Mahadev Lad, Biju Das, Tommaso Merciai
From: Biju Das <biju.das.jz@bp.renesas.com>
Add RZ/G3E GPT support. It has multiple clocks and resets compared to
RZ/G2L. Also prescale field width and factor for calculating prescale
are different.
Reviewed-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v4->v5:
* No change.
v3->v4:
* Added RZG3E_GTCR_TPCS bit definition for RZ/G3E and added to
rzg3e_data.
v2->v3:
* No change.
v1->v2:
* Added link to hardware manual
* Updated limitation section
* Collected tag
---
drivers/pwm/pwm-rzg2l-gpt.c | 47 +++++++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index de68c02b2d50..8cb3e67f4fdb 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -6,15 +6,21 @@
*
* Hardware manual for this IP can be found here
* https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-users-manual-hardware-0?language=en
+ * https://www.renesas.com/en/document/mah/rzg3e-group-users-manual-hardware
*
* Limitations:
* - Counter must be stopped before modifying Mode and Prescaler.
* - When PWM is disabled, the output is driven to inactive.
* - While the hardware supports both polarities, the driver (for now)
* only handles normal polarity.
- * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
- * each HW channel have 2 IOs.
+ * - For RZ/G2L, the General PWM Timer (GPT) has 8 HW channels for PWM
+ operations and each HW channel have 2 IOs (GTIOCn{A, B}).
* - Each IO is modelled as an independent PWM channel.
+ * - For RZ/G3E, the General PWM Timer (GPT) has 16 HW channels for PWM
+ operations (GPT0: 8 channels, GPT1: 8 Channels) and each HW channel
+ have 4 IOs (GTIOCn{A,AN,B,BN}). The 2 extra IOs GTIOCnAN and GTIOCnBN
+ in RZ/G3E are anti-phase signals of GTIOCnA and GTIOCnB. The
+ anti-phase signals of RZ/G3E are not modelled as PWM channel.
* - When both channels are used, disabling the channel on one stops the
* other.
* - When both channels are used, the period of both IOs in the HW channel
@@ -48,6 +54,7 @@
#define RZG2L_GTCR_CST BIT(0)
#define RZG2L_GTCR_MD GENMASK(18, 16)
#define RZG2L_GTCR_TPCS GENMASK(26, 24)
+#define RZG3E_GTCR_TPCS GENMASK(26, 23)
#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0)
@@ -160,6 +167,27 @@ static u8 rzg2l_gpt_calculate_prescale(u64 period_ticks)
return prescale;
}
+static u8 rzg3e_gpt_calculate_prescale(u64 period_ticks)
+{
+ u32 prescaled_period_ticks;
+ u8 prescale;
+
+ prescaled_period_ticks = period_ticks >> 32;
+ if (prescaled_period_ticks >= 64 && prescaled_period_ticks < 256) {
+ prescale = 6;
+ } else if (prescaled_period_ticks >= 256 && prescaled_period_ticks < 1024) {
+ prescale = 8;
+ } else if (prescaled_period_ticks >= 1024) {
+ prescale = 10;
+ } else {
+ prescale = fls(prescaled_period_ticks);
+ if (prescale > 1)
+ prescale -= 1;
+ }
+
+ return prescale;
+}
+
static int rzg2l_gpt_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
@@ -545,6 +573,14 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
if (IS_ERR(rstc))
return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert reset control\n");
+ rstc = devm_reset_control_get_optional_exclusive_deasserted(dev, "rst_s");
+ if (IS_ERR(rstc))
+ return dev_err_probe(dev, PTR_ERR(rstc), "Cannot deassert rst_s reset\n");
+
+ clk = devm_clk_get_optional_enabled(dev, "bus");
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "Cannot get bus clock\n");
+
clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(clk))
return dev_err_probe(dev, PTR_ERR(clk), "Cannot get clock\n");
@@ -587,6 +623,12 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
return 0;
}
+static const struct rzg2l_gpt_info rzg3e_data = {
+ .calculate_prescale = rzg3e_gpt_calculate_prescale,
+ .gtcr_tpcs = RZG3E_GTCR_TPCS,
+ .prescale_mult = 1,
+};
+
static const struct rzg2l_gpt_info rzg2l_data = {
.calculate_prescale = rzg2l_gpt_calculate_prescale,
.gtcr_tpcs = RZG2L_GTCR_TPCS,
@@ -594,6 +636,7 @@ static const struct rzg2l_gpt_info rzg2l_data = {
};
static const struct of_device_id rzg2l_gpt_of_table[] = {
+ { .compatible = "renesas,r9a09g047-gpt", .data = &rzg3e_data },
{ .compatible = "renesas,rzg2l-gpt", .data = &rzg2l_data },
{ /* Sentinel */ }
};
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread