* [PATCH v9 0/7] Add Renesas RZ/G3E GPT support
@ 2026-10-29 18:58 Biju Das
2026-10-29 18:58 ` [PATCH v9 1/7] pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion Biju Das
` (6 more replies)
0 siblings, 7 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, Biju Das,
Tommaso Merciai
This series extends the RZ/G2L GPT PWM driver with RZ/G3E SoC support.
The RZ/G3E GPT has a number of hardware differences compared to RZ/G2L:
it exposes 16 channels (across two GPT instances of 8 channels each),
uses a 4-bit GTCR prescaler field (versus 3-bit on RZ/G2L), and has a
discontinuous prescale sequence with powers-of-2 scaling rather than the
continuous powers-of-4 sequence on RZ/G2L. It also has additional clocks
and resets (bus clock and rst_s).
POEG support is dropped from this series and will be added later.
v8->v9:
* Updated commit header for patch#1
* Dropped fixes tag from patch#1 as there is no functional change.
* The prescale always initialised (reuse cache only if the sibling is
running)
* The round_waveform_tohw() no longer writes the prescale cache.
* Disabling an idle channel no longer touches the shared registers/
counter in rzg2l_gpt_write_waveform()
* The shared registers are only written (and the counter only stopped/
restarted) when no sibling is running -> no glitch on a running
sibling in rzg2l_gpt_write_waveform().
* The rzg2l_gpt_write_waveform() re-validates, under the lock, that the
sibling still uses the same prescale/period (returns -EBUSY otherwise)
* Redundant "enable &&" removed in rzg2l_gpt_write_waveform().
* The rzg2l_gpt_is_ch_enabled() fills *gtcr before the early return.
* Updated commit description in bindings channel->channels and
'8channels' to '8 channels'
* Retain the tag as the change is trivial.
* Updated a comment in limitation section '8 Channels'->'8 channels'.
- Link v8: https://lore.kernel.org/all/20260825154847.197947-1-biju.das.jz@bp.renesas.com/
v7->v8:
* Cached prescale value.
* Updated rzg2l_gpt_write_waveform() for caching the period, If the
channel is already enabled and the user changes its period.
* Updated rzg3e_gpt_calculate_prescale() with >= 64.
v6->v7:
* Reordered the patched moving POEG support to last.
* Replaced USEC_PER_SEC->NSEC_PER_MSEC for period/duty cycle
conversion.
* Avoided recalculation of prescale, if is_small_second_period == true.
* Moved `wfhw->gtccr = 0` into the if block in rzg2l_gpt_config().
* Added rzg2l_gpt_poeg_link_channels() and used __free for deallocating
device node pointer.
* Replaced dev_err()->dev_err_probe() in rzg2l_gpt_poeg_link_channels().
* Replaced local variable cells with num_poeg_pairs in
rzg2l_gpt_poeg_init().
v5->v6:
* Added patch for fixing period_ticks overflow.
* Added patch to add missing newlines to dev_err_probe() messages.
* Dropped extra space in poegs variable assignment in
rzg2l_gpt_poeg_init().
* Updated the comment sections of rzg2l_gpt_poeg_init() with POEG and GPT
in upper-case and replaced configure->configures.
* Format specifiers in dev_err() changed from %d to %u in
rzg2l_gpt_poeg_init().
* Dropped local variables pv and dc from rzg2l_gpt_round_waveform_tohw().
* Dropped blank line in rzg2l_gpt_round_waveform_tohw().
* Moved assignment of rzg2l_gpt->period_ticks[ch] to *_write_waveform().
* Restored clamping of duty_ticks with period_ticks in
rzg2l_gpt_round_waveform_tohw().
* Simplified rzg2l_gpt_write_waveform() using else if statement.
* Dropped local variable gptr in rzg2l_gpt_write_waveform().
* Moved mutex after early return in rzg2l_gpt_round_waveform_tohw().
* Dropped extra parameter rzg2l_gpt from rzg2l_gpt_calculate_prescale()
in rzg2l_gpt_round_waveform_tohw().
* Updated comment in rzg2l_gpt_calculate_period_or_duty().
* Updated rzg3e_gpt_calculate_prescale().
v4->v5:
* Merged GPT linking POEG patch series here.
* Dropped suspend/resume patch; will be added later.
* Updated commit description for patches #2, #4, #5, and #6.
* Replaced return type of rzg2l_gpt_poeg_init() from void to int and
probe() check this return value.
* Added more error checks in rzg2l_gpt_poeg_init().
* Added a patch to drop the unused rzg2l_gpt_chip parameter from
rzg2l_gpt_calculate_prescale().
* 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().
v3->v4:
* Added wave form callback conversion back to this patch series.
* Updated rzg2l_gpt_is_ch_enabled() fit into 80-character limit for
consistency with other functions.
* Dropped field_{get,prep} as mainline now support it.
* Updated commit description for patch#3
* Retained RZG2L_GTCR_TPCS bit definitons
* Replaced gtcr_tpcs_mask->gtcr_tpcs
* Updated commit header and description for patch#4
* Renamed prescale_pow_of_two_mult_factor->prescale_mult
* Added RZG3E_GTCR_TPCS bit definition for RZ/G3E and added to
rzg3e_data.
* Added error checks on suspend() and device set to operational state
on failure().
* Added Rb tag from Geert for SoC dtsi.
* Added SW_GPIO9_CAN1_STB check to gpt0 node.
v2->v3:
* Added Rb tag from Rob for bindings patch
* Dropped wave form callback conversion from this patch series as
it is covered in another series[1]
* Added suspend/resume support.
v1->v2:
* Created separate document for RZ/G3E GPT.
* Updated commit header and description for binding patch.
* Added waveform callback conversion to this series.
* Collected tag.
* Added link to hardware manual
* Updated limitation section in driver patch.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
Biju Das (7):
pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion
pwm: rzg2l-gpt: Convert to waveform callbacks
pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip
pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info
pwm: rzg2l-gpt: Add calculate_prescale() callback to struct rzg2l_gpt_info
dt-bindings: pwm: Document RZ/G3E GPT support
pwm: rzg2l-gpt: Add RZ/G3E support
.../devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml | 323 +++++++++++++++++++
drivers/pwm/pwm-rzg2l-gpt.c | 354 ++++++++++++++-------
2 files changed, 565 insertions(+), 112 deletions(-)
---
base-commit: 4e7443d29aa19bc779a2a485622cf92f1c7145e5
change-id: 20261029-gpt_rzg3e-6a896e1dcb7f
Best regards,
--
Biju Das <biju.das.jz@bp.renesas.com>
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v9 1/7] pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 18:58 ` [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks Biju Das
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, Biju Das
From: Biju Das <biju.das.jz@bp.renesas.com>
The driver converts state->period and state->duty_cycle (both in
nanoseconds) into timer ticks by multiplying with rate_khz and
dividing by USEC_PER_SEC. Since rate_khz is expressed in kHz
(cycles per millisecond), the correct divisor is NSEC_PER_MSEC,
not USEC_PER_SEC. Both constants evaluate to 1000000, so this is
a readability/correctness fix with no functional change, and it
keeps the calculation's units self-consistent with the comment
describing the overflow bound.
Update rzg2l_gpt_calculate_period_or_duty() and rzg2l_gpt_config()
accordingly.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v8->v9:
* Updated commit header
* Dropped fixes tag as there is no functional change.
v7->v8:
* No change.
v7:
* New patch.
---
drivers/pwm/pwm-rzg2l-gpt.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 1569f3ba8c53..108fa99f832d 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -208,13 +208,13 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
/*
* The calculation doesn't overflow an u64 because prescale ≤ 5 and so
- * tmp = val << (2 * prescale) * USEC_PER_SEC
+ * tmp = val << (2 * prescale) * NSEC_PER_MSEC
* < 2^32 * 2^10 * 10^6
* < 2^32 * 2^10 * 2^20
* = 2^62
*/
tmp = (u64)val << (2 * prescale);
- tmp *= USEC_PER_SEC;
+ tmp *= NSEC_PER_MSEC;
return DIV64_U64_ROUND_UP(tmp, rzg2l_gpt->rate_khz);
}
@@ -266,7 +266,7 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
u8 prescale;
/* 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(state->period, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
if (period_ticks > RZG2L_MAX_TICKS)
period_ticks = RZG2L_MAX_TICKS;
/*
@@ -288,7 +288,7 @@ static int rzg2l_gpt_config(struct pwm_chip *chip, struct pwm_device *pwm,
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);
+ duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
if (duty_ticks > period_ticks)
duty_ticks = period_ticks;
dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
2026-10-29 18:58 ` [PATCH v9 1/7] pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 19:16 ` sashiko-bot
2026-10-29 18:58 ` [PATCH v9 3/7] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip Biju Das
` (4 subsequent siblings)
6 siblings, 1 reply; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, Biju Das
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>
---
v8->v9:
* The prescale always initialised (reuse cache only if the sibling is
running)
* The round_waveform_tohw() no longer writes the prescale cache.
* Disabling an idle channel no longer touches the shared registers/
counter in rzg2l_gpt_write_waveform()
* The shared registers are only written (and the counter only stopped/
restarted) when no sibling is running -> no glitch on a running
sibling in rzg2l_gpt_write_waveform().
* The rzg2l_gpt_write_waveform() re-validates, under the lock, that the
sibling still uses the same prescale/period (returns -EBUSY otherwise)
* Redundant "enable &&" removed in rzg2l_gpt_write_waveform().
* The rzg2l_gpt_is_ch_enabled() fills *gtcr before the early return.
v7->v8:
* Cached prescale value.
* Updated rzg2l_gpt_write_waveform() for caching the period, If the
channel is already enabled and the user changes its period.
v6->v7:
* Avoided recalculation of prescale, if is_small_second_period == true.
* Moved `wfhw->gtccr = 0` into the if block in rzg2l_gpt_config().
v5->v6:
* Dropped local variables pv and dc from rzg2l_gpt_round_waveform_tohw().
* Dropped blank line in rzg2l_gpt_round_waveform_tohw().
* Moved assignment of rzg2l_gpt->period_ticks[ch] to *_write_waveform().
* Restored clamping of duty_ticks with period_ticks in
rzg2l_gpt_round_waveform_tohw().
* Simplified rzg2l_gpt_write_waveform() using else if statement.
* Dropped local variable gptr in rzg2l_gpt_write_waveform().
* Moved mutex after early return in rzg2l_gpt_round_waveform_tohw().
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 | 273 +++++++++++++++++++++++++++-----------------
1 file changed, 170 insertions(+), 103 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 108fa99f832d..e9ba0f3bdeb1 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -84,6 +84,14 @@ struct rzg2l_gpt_chip {
u64 period_ticks[RZG2L_MAX_HW_CHANNELS];
u32 channel_request_count[RZG2L_MAX_HW_CHANNELS];
u32 channel_enable_count[RZG2L_MAX_HW_CHANNELS];
+ u8 prescale[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)
@@ -152,12 +160,16 @@ 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;
val = rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTCR(ch));
+ if (gtcr)
+ *gtcr = val;
+
if (!(val & RZG2L_GTCR_CST))
return false;
@@ -219,159 +231,214 @@ 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;
+
+ 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, NSEC_PER_MSEC);
+ period_ticks = mul_u64_u64_div_u64(wf->period_length_ns,
+ rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
if (period_ticks > RZG2L_MAX_TICKS)
period_ticks = RZG2L_MAX_TICKS;
+
+ guard(mutex)(&rzg2l_gpt->lock);
/*
* GPT counter is shared by the two IOs of a single channel, so
* prescale and period can NOT be modified when there are multiple IOs
* in use with different settings.
*/
- 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 (period_ticks < rzg2l_gpt->period_ticks[ch])
- return -EBUSY;
-
- period_ticks = rzg2l_gpt->period_ticks[ch];
- }
+ if (rzg2l_gpt->channel_request_count[ch] > 1 &&
+ rzg2l_gpt_is_ch_enabled(rzg2l_gpt, rzg2l_gpt_sibling(pwm->hwpwm), NULL)) {
+ if (period_ticks < rzg2l_gpt->period_ticks[ch])
+ is_small_second_period = true;
+
+ period_ticks = rzg2l_gpt->period_ticks[ch];
+ wfhw->prescale = rzg2l_gpt->prescale[ch];
+ } else {
+ wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
}
- prescale = rzg2l_gpt_calculate_prescale(period_ticks);
- pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
+ wfhw->gtpr = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
+ if (is_small_second_period) {
+ wfhw->gtccr = 0;
+ return 1;
+ }
- duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
+ duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
if (duty_ticks > period_ticks)
duty_ticks = period_ticks;
- dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
-
- /*
- * GPT counter is shared by multiple channels, we cache the period ticks
- * from the first enabled channel and use the same value for both
- * channels.
- */
- rzg2l_gpt->period_ticks[ch] = period_ticks;
+ wfhw->gtccr = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
- /*
- * Counter must be stopped before modifying mode, prescaler, timer
- * counter and buffer enable registers. These registers are shared
- * between both channels. So allow updating these registers only for the
- * first enabled channel.
- */
- if (rzg2l_gpt->channel_enable_count[ch] <= 1) {
- rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0);
-
- /* GPT set operating mode (saw-wave up-counting) */
- rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_MD,
- RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
-
- /* Set count direction */
- 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, prescale));
+ return 0;
+}
- /* Set period */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), pv);
- }
+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;
- /* Set duty cycle */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), dc);
+ 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;
- if (rzg2l_gpt->channel_enable_count[ch] <= 1) {
- /* Set initial value for counter */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCNT(ch), 0);
+ return 0;
+}
- /* Set no buffer operation */
- rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 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);
+ u32 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ struct rzg2l_gpt_waveform *wfhw = _wfhw;
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 gtcr;
- /* Restart the counter after updating the registers */
- rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch),
- RZG2L_GTCR_CST, RZG2L_GTCR_CST);
+ 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_apply(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
+static u64 rzg2l_gpt_calculate_cycles(u32 value, u8 prescale)
{
- struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
- bool enabled = pwm->state.enabled;
- int ret;
+ return (u64)value << (2 * prescale);
+}
- if (state->polarity != PWM_POLARITY_NORMAL)
- return -EINVAL;
+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 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ bool self_enabled, sibling_enabled;
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 gtcr;
guard(mutex)(&rzg2l_gpt->lock);
- if (!state->enabled) {
- if (enabled)
+
+ self_enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm, NULL);
+ sibling_enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch, >cr);
+
+ /* Disable request: shared registers are never touched. */
+ if (!wfhw->gtpr) {
+ if (self_enabled)
rzg2l_gpt_disable(rzg2l_gpt, pwm);
return 0;
}
- ret = rzg2l_gpt_config(chip, pwm, state);
- if (!ret && !enabled)
+ if (sibling_enabled) {
+ /*
+ * The counter, prescale and period are owned by the
+ * running sibling. Do not stop the counter. Re-validate, now
+ * that the lock is held again, that the values computed by
+ * round_waveform_tohw() still match the running configuration.
+ */
+ if (FIELD_GET(RZG2L_GTCR_TPCS, gtcr) != wfhw->prescale ||
+ rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch)) != wfhw->gtpr)
+ return -EBUSY;
+
+ /* Set duty cycle */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), wfhw->gtccr);
+
+ if (!self_enabled)
+ rzg2l_gpt_enable(rzg2l_gpt, pwm);
+
+ return 0;
+ }
+
+ /*
+ * No sibling running: this channel owns the counter. Counter must be
+ * stopped before modifying mode, prescaler, timer counter and buffer
+ * enable registers.
+ */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST, 0);
+
+ /* GPT set operating mode (saw-wave up-counting) */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_MD,
+ RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
+
+ /* Set count direction */
+ 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));
+
+ /* Set period */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), wfhw->gtpr);
+
+ /* Set duty cycle */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCCR(ch, sub_ch), wfhw->gtccr);
+
+ /* Set initial value for counter */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTCNT(ch), 0);
+
+ /* Set no buffer operation */
+ rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0);
+
+ /* Cache period and prescale used by the sibling IO */
+ rzg2l_gpt->period_ticks[ch] = rzg2l_gpt_calculate_cycles(wfhw->gtpr, wfhw->prescale);
+ rzg2l_gpt->prescale[ch] = wfhw->prescale;
+
+ if (self_enabled)
+ /* Restart the counter after updating the registers */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_CST,
+ RZG2L_GTCR_CST);
+ else
+ /* Enables the pin output and starts the counter */
rzg2l_gpt_enable(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,
};
static int rzg2l_gpt_probe(struct platform_device *pdev)
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v9 3/7] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
2026-10-29 18:58 ` [PATCH v9 1/7] pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion Biju Das
2026-10-29 18:58 ` [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 18:58 ` [PATCH v9 4/7] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju Das
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, 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>
---
v8->v9:
* No change.
v7->v8:
* No change.
v6->v7:
* No change.
v5->v6:
* No change.
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 | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index e9ba0f3bdeb1..19476fe7f5a8 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -77,9 +77,14 @@
#define RZG2L_MAX_SCALE_FACTOR 1024
#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR)
+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;
u64 period_ticks[RZG2L_MAX_HW_CHANNELS];
u32 channel_request_count[RZG2L_MAX_HW_CHANNELS];
@@ -324,7 +329,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)
@@ -346,6 +351,7 @@ static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
const void *_wfhw)
{
struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ const struct rzg2l_gpt_info *info = rzg2l_gpt->info;
const struct rzg2l_gpt_waveform *wfhw = _wfhw;
u8 sibling_ch = rzg2l_gpt_sibling(pwm->hwpwm);
u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
@@ -373,7 +379,7 @@ static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
* that the lock is held again, that the values computed by
* round_waveform_tohw() still match the running configuration.
*/
- if (FIELD_GET(RZG2L_GTCR_TPCS, gtcr) != wfhw->prescale ||
+ if (field_get(info->gtcr_tpcs, gtcr) != wfhw->prescale ||
rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch)) != wfhw->gtpr)
return -EBUSY;
@@ -401,8 +407,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), info->gtcr_tpcs,
+ field_prep(info->gtcr_tpcs, wfhw->prescale));
/* Set period */
rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTPR(ch), wfhw->gtpr);
@@ -460,6 +466,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");
@@ -502,8 +510,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] 9+ messages in thread
* [PATCH v9 4/7] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
` (2 preceding siblings ...)
2026-10-29 18:58 ` [PATCH v9 3/7] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 18:58 ` [PATCH v9 5/7] pwm: rzg2l-gpt: Add calculate_prescale() callback " Biju Das
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, Biju Das,
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>
---
v8->v9:
* No change.
v7->v8:
* No change.
v6->v7:
* No change.
v5->v6:
* Dropped extra parameter rzg2l_gpt from rzg2l_gpt_calculate_prescale()
in rzg2l_gpt_round_waveform_tohw().
* Updated comment in rzg2l_gpt_calculate_period_or_duty().
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 | 30 ++++++++++++++++++++----------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 19476fe7f5a8..414aa5db4c4b 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -79,6 +79,7 @@
struct rzg2l_gpt_info {
u32 gtcr_tpcs;
+ u8 prescale_mult;
};
struct rzg2l_gpt_chip {
@@ -221,24 +222,29 @@ 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;
/*
- * The calculation doesn't overflow an u64 because prescale ≤ 5 and so
- * tmp = val << (2 * prescale) * NSEC_PER_MSEC
+ * The calculation doesn't overflow a u64 because,
+ * prescale ≤ 5 for info->prescale_mult = 2 and so
+ * tmp = val << (info->prescale_mult * prescale) * NSEC_PER_MSEC
* < 2^32 * 2^10 * 10^6
* < 2^32 * 2^10 * 2^20
* = 2^62
*/
- tmp = (u64)val << (2 * prescale);
+ tmp = (u64)val << (info->prescale_mult * prescale);
tmp *= NSEC_PER_MSEC;
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);
}
@@ -248,6 +254,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
void *_wfhw)
{
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);
@@ -286,7 +293,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
}
- wfhw->gtpr = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
+ wfhw->gtpr = rzg2l_gpt_calculate_pv_or_dc(info, period_ticks, wfhw->prescale);
if (is_small_second_period) {
wfhw->gtccr = 0;
return 1;
@@ -295,7 +302,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, NSEC_PER_MSEC);
if (duty_ticks > period_ticks)
duty_ticks = period_ticks;
- wfhw->gtccr = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
+ wfhw->gtccr = rzg2l_gpt_calculate_pv_or_dc(info, duty_ticks, wfhw->prescale);
return 0;
}
@@ -341,9 +348,9 @@ static int rzg2l_gpt_read_waveform(struct pwm_chip *chip,
return 0;
}
-static u64 rzg2l_gpt_calculate_cycles(u32 value, u8 prescale)
+static u64 rzg2l_gpt_calculate_cycles(u32 value, u8 mult, u8 prescale)
{
- return (u64)value << (2 * prescale);
+ return (u64)value << (mult * prescale);
}
static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
@@ -423,7 +430,9 @@ static int rzg2l_gpt_write_waveform(struct pwm_chip *chip,
rzg2l_gpt_write(rzg2l_gpt, RZG2L_GTBER(ch), 0);
/* Cache period and prescale used by the sibling IO */
- rzg2l_gpt->period_ticks[ch] = rzg2l_gpt_calculate_cycles(wfhw->gtpr, wfhw->prescale);
+ rzg2l_gpt->period_ticks[ch] = rzg2l_gpt_calculate_cycles(wfhw->gtpr,
+ info->prescale_mult,
+ wfhw->prescale);
rzg2l_gpt->prescale[ch] = wfhw->prescale;
if (self_enabled)
@@ -512,6 +521,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] 9+ messages in thread
* [PATCH v9 5/7] pwm: rzg2l-gpt: Add calculate_prescale() callback to struct rzg2l_gpt_info
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
` (3 preceding siblings ...)
2026-10-29 18:58 ` [PATCH v9 4/7] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 18:58 ` [PATCH v9 6/7] dt-bindings: pwm: Document RZ/G3E GPT support Biju Das
2026-10-29 18:58 ` [PATCH v9 7/7] pwm: rzg2l-gpt: Add RZ/G3E support Biju Das
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, Biju Das,
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>
---
v8->v9:
* No change.
v7->v8:
* No change.
v6->v7:
* No change.
v5->v6:
* No change.
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 414aa5db4c4b..488839e99464 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -78,6 +78,7 @@
#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR)
struct rzg2l_gpt_info {
+ u8 (*calculate_prescale)(u64 period);
u32 gtcr_tpcs;
u8 prescale_mult;
};
@@ -290,7 +291,7 @@ static int rzg2l_gpt_round_waveform_tohw(struct pwm_chip *chip,
period_ticks = rzg2l_gpt->period_ticks[ch];
wfhw->prescale = rzg2l_gpt->prescale[ch];
} else {
- wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
+ wfhw->prescale = info->calculate_prescale(period_ticks);
}
wfhw->gtpr = rzg2l_gpt_calculate_pv_or_dc(info, period_ticks, wfhw->prescale);
@@ -520,6 +521,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] 9+ messages in thread
* [PATCH v9 6/7] dt-bindings: pwm: Document RZ/G3E GPT support
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
` (4 preceding siblings ...)
2026-10-29 18:58 ` [PATCH v9 5/7] pwm: rzg2l-gpt: Add calculate_prescale() callback " Biju Das
@ 2026-10-29 18:58 ` Biju Das
2026-10-29 18:58 ` [PATCH v9 7/7] pwm: rzg2l-gpt: Add RZ/G3E support Biju Das
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, 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 channels
and GPT1: 8 channels). 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>
---
v8->v9:
* Updated commit description channel->channels and '8channels' to
'8 channels'
* Retain the tag as the change is trivial.
v7->v8:
* No change.
v6->v7:
* No change.
v5->v6:
* No change.
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.
---
.../devicetree/bindings/pwm/renesas,rzg3e-gpt.yaml | 323 +++++++++++++++++++++
1 file changed, 323 insertions(+)
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] 9+ messages in thread
* [PATCH v9 7/7] pwm: rzg2l-gpt: Add RZ/G3E support
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
` (5 preceding siblings ...)
2026-10-29 18:58 ` [PATCH v9 6/7] dt-bindings: pwm: Document RZ/G3E GPT support Biju Das
@ 2026-10-29 18:58 ` Biju Das
6 siblings, 0 replies; 9+ messages in thread
From: Biju Das @ 2026-10-29 18:58 UTC (permalink / raw)
To: Uwe Kleine-König, Geert Uytterhoeven, Magnus Damm,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-pwm, linux-kernel, linux-renesas-soc, devicetree, 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>
---
v8->v9:
* Updated a comment in limitation section '8 Channels'->'8 channels'.
v7->v8:
* Updated rzg3e_gpt_calculate_prescale() with >= 64.
v6->v7:
* No change.
v5->v6:
* Updated rzg3e_gpt_calculate_prescale().
* Updated comment in rzg2l_gpt_calculate_period_or_duty().
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 | 45 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 42 insertions(+), 3 deletions(-)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 488839e99464..5ce662559263 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
@@ -47,6 +53,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)
@@ -147,6 +154,22 @@ 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 = 8;
+ else if (prescaled_period_ticks >= 256)
+ prescale = 10;
+ else
+ prescale = fls(prescaled_period_ticks);
+
+ 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);
@@ -228,7 +251,8 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
/*
* The calculation doesn't overflow a u64 because,
- * prescale ≤ 5 for info->prescale_mult = 2 and so
+ * prescale ≤ 5 for info->prescale_mult = 2,
+ * prescale ≤ 10 for info->prescale_mult = 1, and so
* tmp = val << (info->prescale_mult * prescale) * NSEC_PER_MSEC
* < 2^32 * 2^10 * 10^6
* < 2^32 * 2^10 * 2^20
@@ -482,6 +506,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");
@@ -520,6 +552,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,
@@ -527,6 +565,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] 9+ messages in thread
* Re: [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks
2026-10-29 18:58 ` [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks Biju Das
@ 2026-10-29 19:16 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-29 19:16 UTC (permalink / raw)
To: Biju Das; +Cc: devicetree, robh, conor+dt, wsa+renesas
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] pwm: rzg2l-gpt: sub-tick period rounds to 0 in rzg2l_gpt_round_waveform_tohw()
- [Medium] pwm: rzg2l-gpt: zeroed duty in rzg2l_gpt_round_waveform_tohw() on small period
- [Medium] pwm: rzg2l-gpt: sibling state mismatch in rzg2l_gpt_round_waveform_tohw()
--
commit 584269914af54fde24d28d7d11319bda863cde58
Author: Biju Das <biju.das.jz@bp.renesas.com>
pwm: rzg2l-gpt: Convert to waveform callbacks
This commit migrates the rzg2l-gpt PWM driver from the legacy .get_state and
.apply callbacks to the waveform callback interface using struct
rzg2l_gpt_waveform.
> diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
> index 108fa99f832d9..e9ba0f3bdeb1d 100644
> --- a/drivers/pwm/pwm-rzg2l-gpt.c
> +++ b/drivers/pwm/pwm-rzg2l-gpt.c
[ ... ]
> @@ -219,159 +231,214 @@ static u64 rzg2l_gpt_calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
> 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;
> +
> + 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, NSEC_PER_MSEC);
> + period_ticks = mul_u64_u64_div_u64(wf->period_length_ns,
> + rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
> if (period_ticks > RZG2L_MAX_TICKS)
> period_ticks = RZG2L_MAX_TICKS;
[Severity: High]
What happens in rzg2l_gpt_round_waveform_tohw() in drivers/pwm/pwm-rzg2l-gpt.c
when a caller requests a non-zero wf->period_length_ns that is shorter than
one timer clock cycle (0 < wf->period_length_ns < NSEC_PER_MSEC /
rzg2l_gpt->rate_khz, for example 1..9 ns at a 100 MHz GPT clock)?
In that case, mul_u64_u64_div_u64() truncates period_ticks to 0. When the
sibling subchannel is not running, rzg2l_gpt_round_waveform_tohw() computes
wfhw->prescale = 0, wfhw->gtpr = 0, and wfhw->gtccr = 0, and returns 0.
Because wfhw->gtpr == 0 is also used as the sentinel for a disabled waveform
in rzg2l_gpt_write_waveform():
/* Disable request: shared registers are never touched. */
if (!wfhw->gtpr) {
if (self_enabled)
rzg2l_gpt_disable(rzg2l_gpt, pwm);
return 0;
}
and rzg2l_gpt_round_waveform_fromhw() converts wfhw->gtpr == 0 into
wf->period_length_ns = 0, doesn't a sub-tick period request end up disabling
the PWM channel instead of rounding period_ticks up to 1 and returning 1?
For example:
- pwm_gpio_set() passes wf = { .period_length_ns = 1 } to
pwm_round_waveform_might_sleep() to query the smallest supported hardware
period, which rounds to period_length_ns = 0 and causes pwm_gpio_set(..., 1)
to set duty_length_ns = 0 and disable the output instead of driving the pin
high.
- __pwm_apply() and __pwm_set_waveform() with a non-zero sub-tick period
disable the hardware channel via rzg2l_gpt_write_waveform() while recording
pwm->state.enabled = true.
> +
> + guard(mutex)(&rzg2l_gpt->lock);
> /*
> * GPT counter is shared by the two IOs of a single channel, so
> * prescale and period can NOT be modified when there are multiple IOs
> * in use with different settings.
> */
> - 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 (period_ticks < rzg2l_gpt->period_ticks[ch])
> - return -EBUSY;
> -
> - period_ticks = rzg2l_gpt->period_ticks[ch];
> - }
> + if (rzg2l_gpt->channel_request_count[ch] > 1 &&
> + rzg2l_gpt_is_ch_enabled(rzg2l_gpt, rzg2l_gpt_sibling(pwm->hwpwm), NULL)) {
> + if (period_ticks < rzg2l_gpt->period_ticks[ch])
> + is_small_second_period = true;
> +
> + period_ticks = rzg2l_gpt->period_ticks[ch];
> + wfhw->prescale = rzg2l_gpt->prescale[ch];
> + } else {
> + wfhw->prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> }
[Severity: Medium]
Can rzg2l_gpt_round_waveform_tohw() and rzg2l_gpt_write_waveform() in
drivers/pwm/pwm-rzg2l-gpt.c disagree on whether the sibling subchannel is
active and what its period and prescaler are?
In rzg2l_gpt_round_waveform_tohw(), the sibling's period and prescaler are
only used if rzg2l_gpt->channel_request_count[ch] > 1, and they are read from
the software cache rzg2l_gpt->period_ticks[ch] and rzg2l_gpt->prescale[ch].
In contrast, rzg2l_gpt_write_waveform() checks whether the sibling is enabled
in hardware without checking channel_request_count[ch], and validates against
the hardware registers RZG2L_GTCR(ch) and RZG2L_GTPR(ch):
sibling_enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, sibling_ch, >cr);
...
if (sibling_enabled) {
if (FIELD_GET(RZG2L_GTCR_TPCS, gtcr) != wfhw->prescale ||
rzg2l_gpt_read(rzg2l_gpt, RZG2L_GTPR(ch)) != wfhw->gtpr)
return -EBUSY;
If a subchannel is enabled and then freed via rzg2l_gpt_free() (which
decrements channel_request_count[ch] while leaving the hardware channel
running), or if a subchannel was left enabled by the bootloader:
- When only the sibling subchannel is requested (channel_request_count[ch] ==
1), rzg2l_gpt_round_waveform_tohw() skips the sibling check and computes a
new period/prescale, which rzg2l_gpt_write_waveform() then rejects with
-EBUSY.
- If a subchannel was enabled by the bootloader before probe and both
subchannels are requested (channel_request_count[ch] == 2) before
rzg2l_gpt_write_waveform() has ever populated period_ticks[ch] and
prescale[ch], rzg2l_gpt_round_waveform_tohw() reads 0 from
rzg2l_gpt->period_ticks[ch] and sets wfhw->gtpr = 0, causing
rzg2l_gpt_write_waveform() to treat the request as a disable.
Should rzg2l_gpt_round_waveform_tohw() check rzg2l_gpt_is_ch_enabled() and
read the running period and prescaler from the hardware registers in the same
way as rzg2l_gpt_write_waveform()?
>
> - prescale = rzg2l_gpt_calculate_prescale(period_ticks);
> - pv = rzg2l_gpt_calculate_pv_or_dc(period_ticks, prescale);
> + wfhw->gtpr = rzg2l_gpt_calculate_pv_or_dc(period_ticks, wfhw->prescale);
> + if (is_small_second_period) {
> + wfhw->gtccr = 0;
> + return 1;
> + }
>
> - duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
> + duty_ticks = mul_u64_u64_div_u64(wf->duty_length_ns, rzg2l_gpt->rate_khz, NSEC_PER_MSEC);
> if (duty_ticks > period_ticks)
> duty_ticks = period_ticks;
> - dc = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, prescale);
> + wfhw->gtccr = rzg2l_gpt_calculate_pv_or_dc(duty_ticks, wfhw->prescale);
>
> - return 0;
> + return 0;
> +}
[Severity: Medium]
When is_small_second_period is true in rzg2l_gpt_round_waveform_tohw() in
drivers/pwm/pwm-rzg2l-gpt.c (because the sibling subchannel is enabled and the
requested wf->period_length_ns is smaller than the running period), why does
this return early with wfhw->gtccr = 0 instead of rounding wf->duty_length_ns
against the sibling's period and returning 1 at the end of the function?
When a caller uses pwm_round_waveform_might_sleep() or
pwm_set_waveform_might_sleep() with exact = false and wf->duty_length_ns > 0,
the period is rounded up to the running sibling's period, so shouldn't
wfhw->gtccr still be computed from wf->duty_length_ns rather than forcing a 0%
duty cycle?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-gpt_rzg3e-v9-0-f3a5520388d3@bp.renesas.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-29 19:16 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-29 18:58 [PATCH v9 0/7] Add Renesas RZ/G3E GPT support Biju Das
2026-10-29 18:58 ` [PATCH v9 1/7] pwm: rzg2l-gpt: Update macro used for period/duty cycle conversion Biju Das
2026-10-29 18:58 ` [PATCH v9 2/7] pwm: rzg2l-gpt: Convert to waveform callbacks Biju Das
2026-10-29 19:16 ` sashiko-bot
2026-10-29 18:58 ` [PATCH v9 3/7] pwm: rzg2l-gpt: Add info variable to struct rzg2l_gpt_chip Biju Das
2026-10-29 18:58 ` [PATCH v9 4/7] pwm: rzg2l-gpt: Add prescale_mult variable to struct rzg2l_gpt_info Biju Das
2026-10-29 18:58 ` [PATCH v9 5/7] pwm: rzg2l-gpt: Add calculate_prescale() callback " Biju Das
2026-10-29 18:58 ` [PATCH v9 6/7] dt-bindings: pwm: Document RZ/G3E GPT support Biju Das
2026-10-29 18:58 ` [PATCH v9 7/7] pwm: rzg2l-gpt: Add RZ/G3E support Biju Das
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.