* [PATCH v20 0/4] Add support for RZ/G2L GPT
@ 2024-06-14 15:42 Biju Das
2024-06-14 15:42 ` [PATCH v20 1/4] dt-bindings: pwm: Add RZ/G2L GPT binding Biju Das
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Biju Das @ 2024-06-14 15:42 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: Biju Das, Geert Uytterhoeven, Magnus Damm, linux-pwm, devicetree,
Fabrizio Castro, linux-renesas-soc, Prabhakar Mahadev Lad,
Biju Das
RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer
(GPT32E). It supports the following functions
* 32 bits x 8 channels
* Up-counting or down-counting (saw waves) or up/down-counting
(triangle waves) for each counter.
* Clock sources independently selectable for each channel
* Two 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
* Starting, stopping, clearing and up/down counters in response to input
level comparison
* Starting, clearing, stopping and up/down counters in response to a
maximum of four external triggers
* Output pin disable function by dead time error and detected
short-circuits between output pins
* A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
* Enables the noise filter for input capture and external trigger
operation
This patch series aims to add basic pwm support for RZ/G2L GPT driver
by creating separate logical channels for each IOs.
v19->v20:
* Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
* Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
* Added a comment in calculate_period_or_duty() related to overflow.
* Replaced ch_en_bits->bootloader_enabled_channels and used this variable
in probe(), apply() and remove() for simplification
* Replaced pm_runtime_enable()->devm_pm_runtime_enable()
v18->v19:
* Replaced RZG2L_UP_COUNTING->RZG2L_GTUDDTYC_UP_COUNTING macro.
* Aligned RZG2L_GET_CH and RZG2L_GET_CH_OFFS macro
* Dropped chip and clk from struct rzg2l_gpt_chip as started using
devm_pwmchip_alloc() and devm_clk_rate_exclusive_get() to replace it.
* Replaced rate->rate_khz in struct rzg2l_gpt_chip and added a check in
probe() to make sure rate is multiple of 1000.
* Replaced container_of->pwmchip_get_drvdata() to get device data.
* Added a check in rzg2l_gpt_disable() not to decrement enable_count if
ch_en_bits is set by the probe.
* Dropped rzg2l_gpt_mul_u64_u64_div_u64()
* Simplified calculate_period_or_duty() using rate_khz
* Simplified rzg2l_gpt_config() using min macro for calculating period
and duty_cycle.
* Added checks in rzg2l_gpt_config() to prevent second channel setting
shared register.
* Updated error handling rzg2l_gpt_apply()
* Added local variable dev for &pdev->dev in probe()
* Added local varibles rate, chip and clk in probe()
* Dropped err_clk_rate_put label as started using
devm_clk_rate_exclusive_get()
* Replaced rzg2l_gpt->chip as data for devm_add_action_or_reset().
* Added error message for rate > 1GHz in probe.
v17->v18:
* Added units.h for KILO macro.
* Replaced RZG2L_GTCCR{A,B}->RZG2L_GTCCR(i)
* Introduced macros RZG2L_GTIOR_{GTIOx,OxE} to handle subchannels.
* Replaced RZG2L_IS_IOB()->rzg2l_gpt_subchannel()
* Replaced the cache period->period_cycles.
* Updated rzg2l_gpt_is_ch_enabled() to return early if counter is not
running.
* Updated calculate_period_or_duty() for avoiding overflows.
* Updated rzg2l_gpt_calculate_pv_or_dc() with simplified calculation for
DIV64_U64_ROUND_UP() and dropped the cast for U32_MAX in min_t.
* Replaced mul_u64_u32_div->rzg2l_gpt_mul_u64_u64_div_u64() helper.
* Dropped pm pointer from struct rzg2l_gpt_driver() and simplified clk
handling in probe().
* Updated copyright from 2023->2024.
* Moved bitpos near to the user in patch#4.
v16->v17:
* Added ret = dev_err_probe() to avoid return success in probe().
* Dropped unneeded MODULE_ALIAS().
* Dropped .owner from struct rzg2l_gpt_ops.
* Fixed build issue reported by kernel test robot <lkp@intel.com> by
replacing DIV_ROUND_UP()->DIV64_U64_ROUND_UP() in
rzg2l_gpt_calculate_pv_or_dc().
* Added max_val to struct rzg2l_gpt_chip to compute maximum period
supported by the HW in probe() and limit its value in apply() to
avoid 64-bit overflow with computation.
* Added helper function calculate_period_or_duty() to avoid losing
precision for smaller period/duty cycle values
((2^32 * 10^9 << 2) < 2^64), by not processing the rounded values.
* Replaced mul_u64_u64_div_u64()->mul_u64_u32_div() as the former is
giving warnings with CONFIG_PWM_DEBUG enabled for very high values.
v15->v16:
* Replaced the macro DIV_ROUND_UP_ULL->DIV64_U64_ROUND_UP
* Added DIV_ROUND_UP in rzg2l_gpt_calculate_pv_or_dc() to avoid loss of
precision.
* Replaced min->min_t() in rzg2l_gpt_calculate_pv_or_dc().
* Added a comment for rzg2l_gpt_config()
* Replaced mul_u64_u32_div()->mul_u64_u64_div_u64() in rzg2l_gpt_config()
* Fixed the logical condition related to counter stop in
rzg2l_gpt_config().
* Dropped pm_runtime_resume_*() from rzg2l_gpt_config() as it is managed
by rzg2l_gpt_apply().
* Moved pm_runtime_resume_*() from rzg2l_gpt_{en,dis}able() to
rzg2l_gpt_apply().
v14->v15:
* Added enable_count and ch_en_bits variables to struct rzg2l_gpt_chip
based on feedback for pwm_mtu3 driver.
* Updated copyright header and commit description by replacing "This patch
adds"-> "Add"
* Replaced macro RZG2L_GET_CH_INDEX->RZG2L_GET_CH and replaced ch_index->ch
throughout
* rzg2l_gpt_{enable,disable}() enables/disables PWM based on the
enable_count.
* Replaced pm_runtime_get_sync->pm_runtime_resume_and_get and propogated
the error in rzg2l_gpt_get_state() and rzg2l_gpt_config()
* Reduced variable scope in rzg2l_gpt_get_state() by moving most of
variables inside the if statement.
* Updated rzg2l_gpt_get_state() by moving duty > period check
inside the top if block.
* Added helper functions rzg2l_gpt_calculate_pv_or_dc()to simplify config.
Also Improved the logic in rzg2l_gpt_calculate_pv_or_dc() by using
min(period_or_duty_cycle >> (2 * prescale), (u64)U32_MAX);
* Updated rzg2l_gpt_get_state() by moving duty > period check
inside the top if block.
* Simplified rzg2l_gpt_config() for updating registers
* Dropped pm_runtime_get_sync() and used bitmap variable "ch_en_bits"
to make balanced PM usage count in rzg2l_gpt_reset_assert_pm_disable()
For case were unbind is called before apply where pwm is enabled by
bootloader.
* Added error check for clk_rate_exclusive_get() and clk_get_rate() in
probe().
* Dropped prescale from struct rzg2l_gpt_chip.
* Replaced of_match_ptr(rzg2l_gpt_of_table)->rzg2l_gpt_of_table in struct
rzg2l_gpt_driver
* Updated commit description of patch#4 by replacing "This patch add"->
"Add".
v13->v14:
* Moved the patch from series[1] to here.
[1] https://lore.kernel.org/linux-renesas-soc/20221215205843.4074504-1-biju.das.jz@bp.renesas.com/T/#t
* Add Rb tag from Rob for patch#2
* Removed parenthesis for RZG2L_MAX_HW_CHANNELS and RZG2L_CHANNELS_PER_IO
* Removed duty_cycle variable from struct rzg2l_gpt_chip and added comment
for cache for prescale variable.
* Fixed a bug in rzg2l_gpt_cntr_need_stop().
* Reordered rzg2l_gpt_config() just above apply()
* Replaced pwm_is_enabled()->pwm->state.enabled in config
* Replaced pm_runtime_resume_and_get with unconditional
pm_runtime_get_sync() in config().
* Restored duty_cycle > period check in rzg2l_gpt_get_state().
* Added error check for clk_prepare_enable() in probe() and propagating
error to the caller for pm_runtime_resume()
* clk_get_rate() is called after enabling the clock and
clk_rate_exclusive_get()
* Simplified rzg2l_gpt_probe() by removing bitmap variables.
* Added pm_runtime_idle() to suspend the device during probe.
* Moved overflow condition check from config->probe().
* Simplified rzg2l_gpt_reset_assert_pm_disable().
* Removed the parenthesis for RZG2L_MAX_POEG_GROUPS.
* Renamed rzg2l_gpt_parse_properties()->rzg2l_gpt_poeg_init() as it
not only parse the properties but also implements the needed register
writes.
* Added acomment here about the purpose of the function
rzg2l_gpt_poeg_init()
* Removed magic numbers from rzg2l_gpt_poeg_init()
* Fixed resource leak in rzg2l_gpt_poeg_init().
v12->v13:
* Added test logs in [1] below
* Replaced Kconfig dependency from ARCH_RENESAS->ARCH_RZG2L
* Sorted #include <linux/limits.h> alphabetically
* Added a comment for mutex_lock to fix check patch warning
* Replaced data type of duty_cycle from unsigned int->u32 as
the maximum value stored is U32_MAX.
* Improved rzg2l_gpt_config() by removing unwanted duty_cycle related
code.
* Improved rzg2l_gpt_get_state() by setting
"val = rzg2l_gpt->duty_cycle[pwm->hwpwm];", and factor
"tmp = NSEC_PER_SEC * (u64)val;" out of the if-statement.
* Started using DEFINE_RUNTIME_DEV_PM_OPS(), and dropped __maybe_unused
from the callbacks.
v11->v12:
* Added return code for get_state()
* Cache duty cycle/prescale as the driver cannot read the current duty
cycle/prescale from the hardware if the hardware is disabled. Cache the
last programmed duty cycle/prescale value to return in that case.
* Updated rzg2l_gpt_enable to enable the clocks.
* Updated rzg2l_gpt_disable to disable the clocks.
* Updated rzg2l_gpt_config() to cache duty cucle/prescale value
* Updated rzg2l_gpt_get_state to use cached value of duty cycle/prescale,
If the PWM is disabled.
* Simplified rzg2l_gpt_apply()
* Added comments in rzg2l_gpt_reset_assert_pm_disable()
v10->v11:
* Used bitmap_zero for initializing bitmap varable.
* Fixed clock imbalance during remove for the case bootloader turning
on PWM and module unload is called just after the boot.
* Fixed over flow condition in get_state() for a prescale value of
2 & more.
* Improved rzg2l_gpt_cntr_need_stop() based on prescale as it is the
only runtime variable.
* Added array for Cache variables state_period and prescale
* Probe caches the prescale value set by the bootloader.
* Updated rzg2l_gpt_config() to make use of array variables.
v9->v10:
* Updated the example gpt4: pwm@10048400-> gpt: pwm@10048000
* Keep Rb tag from Rob as the above change is trivial one.
* Updated the error handling in probe(), clk_disable_unprepare called
on the error path.
* Removed ch_en array and started using bitmask instead.
v8->v9:
* Added Rb tag from Rob.
* deassert after devm_clk_get() to avoid reset stays deasserted,in case
clk_get() fails.
* Removed ch_offs from struct rzg2l_gpt_chip and use macro instead.
* Updated error handling in probe()
v7->v8:
* Removed Rb tags from Rob and Geert as it modelled as single GPT
device handling multiple channels.
* Updated description
* Updated interrupts and interrupt-names properties
* Updated binding example
* Modelled as single PWM device handling multiple channels
* Replaced shared reset->devm_reset_control_get_exclusive()
* Added PM runtime callbacks
* Updated PM handling and removed "pwm_enabled_by_bootloader" variable
* Replaced iowrite32->writel and ioread32->readl
* Updated prescale calculation
* Introduced rzg2l_gpt_is_ch_enabled for checking enable status on both
IO's
* Moved enable/disable output pins from config->enable/disable.
* Added rzg2l_gpt_cntr_need_stop() for caching prescalar/mode values.
v6->v7:
* Added the comment for cacheing rzg2l_gpt->state_period.
* Fixed boundary values for pv and dc.
* Added comment for modifying mode, prescaler, timer counter and buffer
enable registers.
* Fixed buffer overflow in get_state()
* Removed unnecessary assignment of state->period value in get_state().
* Fixed state->duty_cycle value in get_state().
* Added a limitation for disabling the channels, when both channels used
v5->v6:
* Updated macros RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH and
RZG2L_GTIOR_GTIOB_OUT_LO_END_TOGGLE_CMP_MATCH with computation
involving FIELD_PREP macro.
* Removed struct rzg2l_gpt_phase and started using RZG2L_GTCCR macro
for duty_offset.
* replaced misnomer real_period->state_period.
* Added handling for values >= (1024 << 32) for both period
and duty cycle.
* Added comments for pwm {en,dis}abled by bootloader during probe.
v4->v5:
* Added Hardware manual details
* Replaced the comment GTCNT->Counter
* Removed the macros RZG2L_GPT_IO_PER_CHANNEL and chip.npwm directly
used in probe.
* Removed the unsed macro RZG2L_GTPR_MAX_VALUE
* Added driver prefix for the type name and the variable.
* Initialization of per_channel data moved from request->probe.
* Updated clr parameter for rzg2l_gpt_modify for Start count.
* Started using mutex and usage_count for handling shared
period and prescalar for the 2 channels.
* Updated the comment cycle->period.
* Removed clk_disable from rzg2l_gpt_reset_assert_pm_disable()
* Replaced pc->rzg2l_gpt.
* Updated prescale calculation.
* Moved pm_runtime_{get_sync,put} from {request,free}->{enable,disable}
* Removed platform_set_drvdata as it is unused
* Removed the variable pwm_enabled_by_bootloader
* Added dev_err_probe in various probe error path.
* Added an error message, if devm_pwmchip_add fails.
v3->v4:
* Changed the local variable type i from u16->u8 and prescaled_period_
cycles from u64->u32 in calculate_prescale().
* Replaced mul_u64_u64_div_u64()->mul_u64_u32_div()
* Dropped the comma after the sentinel.
* Add a variable to track pwm enabled by bootloader and added comments
in probe().
* Removed unnecessary rzg2l_gpt_reset_assert_pm_disable() from probe.
* Replaced devm_clk_get()->devm_clk_get_prepared()
* Removed devm_clk_get_optional_enabled()
v2->v3:
* Added Rb tag from Rob for the bindings.
* Updated limitation section
* Added prefix "RZG2L_" for all macros
* Modified prescale calculation
* Removed pwm_set_chip_data
* Updated comment related to modifying Mode and Prescaler
* Updated setting of prescale value in rzg2l_gpt_config()
* Removed else branch from rzg2l_gpt_get_state()
* removed the err label from rzg2l_gpt_apply()
* Added devm_clk_get_optional_enabled() to retain clk on status,
in case bootloader turns on the clk of pwm.
* Replaced devm_reset_control_get_exclusive->devm_reset_control_get_shared
as single reset shared between 8 channels.
v1->v2:
* Added '|' after 'description:' to preserve formatting.
* Removed description for pwm_cells as it is common property.
* Changed the reg size in example from 0xa4->0x100
* Added Rb tag from Geert for bindings.
* Added Limitations section
* dropped "_MASK" from the define names.
* used named initializer for struct phase
* Added gpt_pwm_device into a flexible array member in rzg2l_gpt_chip
* Revised the logic for prescale
* Added .get_state callback
* Improved error handling in rzg2l_gpt_apply
* Removed .remove callback
* Tested the driver with PWM_DEBUG enabled.
RFC->v1:
* Added Description in binding patch
* Removed comments from reg and clock
* replaced rzg2l_gpt_write_mask()->rzg2l_gpt_modify()
* Added rzg2l_gpt_read() and updated macros
* Removed dtsi patches, will send it separately
RFC:
* https://lore.kernel.org/linux-renesas-soc/20220430075915.5036-1-biju.das.jz@bp.renesas.com/T/#t
Biju Das (4):
dt-bindings: pwm: Add RZ/G2L GPT binding
dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property
pwm: Add support for RZ/G2L GPT
pwm: rzg2l-gpt: Add support for gpt linking with poeg
.../bindings/pwm/renesas,rzg2l-gpt.yaml | 401 +++++++++++
drivers/pwm/Kconfig | 11 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-rzg2l-gpt.c | 637 ++++++++++++++++++
4 files changed, 1050 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
base-commit: aa32efbe5b833a7a4d67294f32456563f328668c
--
2.25.1
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v20 1/4] dt-bindings: pwm: Add RZ/G2L GPT binding
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
@ 2024-06-14 15:42 ` Biju Das
2024-06-14 15:42 ` [PATCH v20 2/4] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property Biju Das
` (3 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Biju Das @ 2024-06-14 15:42 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Biju Das, Geert Uytterhoeven, Magnus Damm, linux-pwm, devicetree,
Fabrizio Castro, linux-renesas-soc, Prabhakar Mahadev Lad,
Biju Das
Add device tree bindings for the General PWM Timer (GPT).
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
v19->v20:
* No change.
v18->v19:
* No change.
v17->v18:
* No change.
v16->v17:
* No change.
v15->v16:
* No change.
v14->v15:
* No change.
v13->v14:
* No change.
v12->v13:
* No change.
v11->v12:
* No change.
v10->v11:
* No change.
v9->v10:
* Updated the example gpt4: pwm@10048400-> gpt: pwm@10048000
* Keep Rb tag from Rob as the above change is trivial one.
v8->v9:
* Added Rb tag from Rob.
v7->v8:
* Removed Rb tags from Rob and Geert as it modelled as single GPT
device handling multiple channels.
* Updated description
* Updated interrupts and interrupt-names properties
* Updated example
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:
* Added '|' after 'description:' to preserve formatting.
* Removed description for pwm_cells as it is common property.
* Changed the reg size in example from 0xa4->0x100
* Added Rb tag from Geert.
RFC->v1:
* Added Description
* Removed comments from reg and clock
---
.../bindings/pwm/renesas,rzg2l-gpt.yaml | 378 ++++++++++++++++++
1 file changed, 378 insertions(+)
create mode 100644 Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
diff --git a/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
new file mode 100644
index 000000000000..d9374144d82d
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
@@ -0,0 +1,378 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/renesas,rzg2l-gpt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Renesas RZ/G2L General PWM Timer (GPT)
+
+maintainers:
+ - Biju Das <biju.das.jz@bp.renesas.com>
+
+description: |
+ RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer
+ (GPT32E). It supports the following functions
+ * 32 bits x 8 channels.
+ * Up-counting or down-counting (saw waves) or up/down-counting
+ (triangle waves) for each counter.
+ * Clock sources independently selectable for each channel.
+ * Two 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.
+ * Starting, stopping, clearing and up/down counters in response to input
+ level comparison.
+ * Starting, clearing, stopping and up/down counters in response to a
+ maximum of four external triggers.
+ * Output pin disable function by dead time error and detected
+ short-circuits between output pins.
+ * A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
+ * Enables the noise filter for input capture and external trigger
+ operation.
+
+ The below pwm channels are supported.
+ pwm0 - GPT32E0.GTIOC0A channel
+ pwm1 - GPT32E0.GTIOC0B channel
+ pwm2 - GPT32E1.GTIOC1A channel
+ pwm3 - GPT32E1.GTIOC1B channel
+ pwm4 - GPT32E2.GTIOC2A channel
+ pwm5 - GPT32E2.GTIOC2B channel
+ pwm6 - GPT32E3.GTIOC3A channel
+ pwm7 - GPT32E3.GTIOC3B channel
+ pwm8 - GPT32E4.GTIOC4A channel
+ pwm9 - GPT32E4.GTIOC4B channel
+ pwm10 - GPT32E5.GTIOC5A channel
+ pwm11 - GPT32E5.GTIOC5B channel
+ pwm12 - GPT32E6.GTIOC6A channel
+ pwm13 - GPT32E6.GTIOC6B channel
+ pwm14 - GPT32E7.GTIOC7A channel
+ pwm15 - GPT32E7.GTIOC7B channel
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - renesas,r9a07g044-gpt # RZ/G2{L,LC}
+ - renesas,r9a07g054-gpt # RZ/V2L
+ - const: renesas,rzg2l-gpt
+
+ reg:
+ maxItems: 1
+
+ '#pwm-cells':
+ const: 2
+
+ interrupts:
+ items:
+ - description: GPT32E0.GTCCRA input capture/compare match
+ - description: GPT32E0.GTCCRB input capture/compare
+ - description: GPT32E0.GTCCRC compare match
+ - description: GPT32E0.GTCCRD compare match
+ - description: GPT32E0.GTCCRE compare match
+ - description: GPT32E0.GTCCRF compare match
+ - description: GPT32E0.GTADTRA compare match
+ - description: GPT32E0.GTADTRB compare match
+ - description: GPT32E0.GTCNT overflow/GTPR compare match
+ - description: GPT32E0.GTCNT underflow
+ - description: GPT32E1.GTCCRA input capture/compare match
+ - description: GPT32E1.GTCCRB input capture/compare
+ - description: GPT32E1.GTCCRC compare match
+ - description: GPT32E1.GTCCRD compare match
+ - description: GPT32E1.GTCCRE compare match
+ - description: GPT32E1.GTCCRF compare match
+ - description: GPT32E1.GTADTRA compare match
+ - description: GPT32E1.GTADTRB compare match
+ - description: GPT32E1.GTCNT overflow/GTPR compare match
+ - description: GPT32E1.GTCNT underflow
+ - description: GPT32E2.GTCCRA input capture/compare match
+ - description: GPT32E2.GTCCRB input capture/compare
+ - description: GPT32E2.GTCCRC compare match
+ - description: GPT32E2.GTCCRD compare match
+ - description: GPT32E2.GTCCRE compare match
+ - description: GPT32E2.GTCCRF compare match
+ - description: GPT32E2.GTADTRA compare match
+ - description: GPT32E2.GTADTRB compare match
+ - description: GPT32E2.GTCNT overflow/GTPR compare match
+ - description: GPT32E2.GTCNT underflow
+ - description: GPT32E3.GTCCRA input capture/compare match
+ - description: GPT32E3.GTCCRB input capture/compare
+ - description: GPT32E3.GTCCRC compare match
+ - description: GPT32E3.GTCCRD compare match
+ - description: GPT32E3.GTCCRE compare match
+ - description: GPT32E3.GTCCRF compare match
+ - description: GPT32E3.GTADTRA compare match
+ - description: GPT32E3.GTADTRB compare match
+ - description: GPT32E3.GTCNT overflow/GTPR compare match
+ - description: GPT32E3.GTCNT underflow
+ - description: GPT32E4.GTCCRA input capture/compare match
+ - description: GPT32E4.GTCCRB input capture/compare
+ - description: GPT32E4.GTCCRC compare match
+ - description: GPT32E4.GTCCRD compare match
+ - description: GPT32E4.GTCCRE compare match
+ - description: GPT32E4.GTCCRF compare match
+ - description: GPT32E4.GTADTRA compare match
+ - description: GPT32E4.GTADTRB compare match
+ - description: GPT32E4.GTCNT overflow/GTPR compare match
+ - description: GPT32E4.GTCNT underflow
+ - description: GPT32E5.GTCCRA input capture/compare match
+ - description: GPT32E5.GTCCRB input capture/compare
+ - description: GPT32E5.GTCCRC compare match
+ - description: GPT32E5.GTCCRD compare match
+ - description: GPT32E5.GTCCRE compare match
+ - description: GPT32E5.GTCCRF compare match
+ - description: GPT32E5.GTADTRA compare match
+ - description: GPT32E5.GTADTRB compare match
+ - description: GPT32E5.GTCNT overflow/GTPR compare match
+ - description: GPT32E5.GTCNT underflow
+ - description: GPT32E6.GTCCRA input capture/compare match
+ - description: GPT32E6.GTCCRB input capture/compare
+ - description: GPT32E6.GTCCRC compare match
+ - description: GPT32E6.GTCCRD compare match
+ - description: GPT32E6.GTCCRE compare match
+ - description: GPT32E6.GTCCRF compare match
+ - description: GPT32E6.GTADTRA compare match
+ - description: GPT32E6.GTADTRB compare match
+ - description: GPT32E6.GTCNT overflow/GTPR compare match
+ - description: GPT32E6.GTCNT underflow
+ - description: GPT32E7.GTCCRA input capture/compare match
+ - description: GPT32E7.GTCCRB input capture/compare
+ - description: GPT32E7.GTCCRC compare match
+ - description: GPT32E7.GTCCRD compare match
+ - description: GPT32E7.GTCCRE compare match
+ - description: GPT32E7.GTCCRF compare match
+ - description: GPT32E7.GTADTRA compare match
+ - description: GPT32E7.GTADTRB compare match
+ - description: GPT32E7.GTCNT overflow/GTPR compare match
+ - description: GPT32E7.GTCNT underflow
+
+ interrupt-names:
+ items:
+ - const: ccmpa0
+ - const: ccmpb0
+ - const: cmpc0
+ - const: cmpd0
+ - const: cmpe0
+ - const: cmpf0
+ - const: adtrga0
+ - const: adtrgb0
+ - const: ovf0
+ - const: unf0
+ - const: ccmpa1
+ - const: ccmpb1
+ - const: cmpc1
+ - const: cmpd1
+ - const: cmpe1
+ - const: cmpf1
+ - const: adtrga1
+ - const: adtrgb1
+ - const: ovf1
+ - const: unf1
+ - const: ccmpa2
+ - const: ccmpb2
+ - const: cmpc2
+ - const: cmpd2
+ - const: cmpe2
+ - const: cmpf2
+ - const: adtrga2
+ - const: adtrgb2
+ - const: ovf2
+ - const: unf2
+ - const: ccmpa3
+ - const: ccmpb3
+ - const: cmpc3
+ - const: cmpd3
+ - const: cmpe3
+ - const: cmpf3
+ - const: adtrga3
+ - const: adtrgb3
+ - const: ovf3
+ - const: unf3
+ - const: ccmpa4
+ - const: ccmpb4
+ - const: cmpc4
+ - const: cmpd4
+ - const: cmpe4
+ - const: cmpf4
+ - const: adtrga4
+ - const: adtrgb4
+ - const: ovf4
+ - const: unf4
+ - const: ccmpa5
+ - const: ccmpb5
+ - const: cmpc5
+ - const: cmpd5
+ - const: cmpe5
+ - const: cmpf5
+ - const: adtrga5
+ - const: adtrgb5
+ - const: ovf5
+ - const: unf5
+ - const: ccmpa6
+ - const: ccmpb6
+ - const: cmpc6
+ - const: cmpd6
+ - const: cmpe6
+ - const: cmpf6
+ - const: adtrga6
+ - const: adtrgb6
+ - const: ovf6
+ - const: unf6
+ - const: ccmpa7
+ - const: ccmpb7
+ - const: cmpc7
+ - const: cmpd7
+ - const: cmpe7
+ - const: cmpf7
+ - const: adtrga7
+ - const: adtrgb7
+ - const: ovf7
+ - const: unf7
+
+ clocks:
+ maxItems: 1
+
+ power-domains:
+ maxItems: 1
+
+ resets:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - clocks
+ - power-domains
+ - resets
+
+allOf:
+ - $ref: pwm.yaml#
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/r9a07g044-cpg.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ gpt: pwm@10048000 {
+ compatible = "renesas,r9a07g044-gpt", "renesas,rzg2l-gpt";
+ reg = <0x10048000 0x800>;
+ interrupts = <GIC_SPI 218 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 219 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 220 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 221 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 222 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 223 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 224 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 225 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 226 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 227 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 231 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 232 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 233 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 234 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 235 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 236 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 237 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 238 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 239 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 240 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 244 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 245 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 246 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 247 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 248 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 249 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 250 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 251 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 252 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 253 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 257 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 258 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 259 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 260 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 261 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 262 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 263 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 264 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 265 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 266 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 270 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 271 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 272 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 273 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 274 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 275 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 276 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 277 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 278 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 279 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 283 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 284 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 285 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 286 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 287 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 288 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 289 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 290 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 291 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 292 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 296 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 297 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 298 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 299 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 300 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 301 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 302 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 303 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 304 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 305 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 309 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 310 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 311 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 312 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 313 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 314 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 315 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 316 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 317 IRQ_TYPE_EDGE_RISING>,
+ <GIC_SPI 318 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "ccmpa0", "ccmpb0", "cmpc0", "cmpd0",
+ "cmpe0", "cmpf0", "adtrga0", "adtrgb0",
+ "ovf0", "unf0",
+ "ccmpa1", "ccmpb1", "cmpc1", "cmpd1",
+ "cmpe1", "cmpf1", "adtrga1", "adtrgb1",
+ "ovf1", "unf1",
+ "ccmpa2", "ccmpb2", "cmpc2", "cmpd2",
+ "cmpe2", "cmpf2", "adtrga2", "adtrgb2",
+ "ovf2", "unf2",
+ "ccmpa3", "ccmpb3", "cmpc3", "cmpd3",
+ "cmpe3", "cmpf3", "adtrga3", "adtrgb3",
+ "ovf3", "unf3",
+ "ccmpa4", "ccmpb4", "cmpc4", "cmpd4",
+ "cmpe4", "cmpf4", "adtrga4", "adtrgb4",
+ "ovf4", "unf4",
+ "ccmpa5", "ccmpb5", "cmpc5", "cmpd5",
+ "cmpe5", "cmpf5", "adtrga5", "adtrgb5",
+ "ovf5", "unf5",
+ "ccmpa6", "ccmpb6", "cmpc6", "cmpd6",
+ "cmpe6", "cmpf6", "adtrga6", "adtrgb6",
+ "ovf6", "unf6",
+ "ccmpa7", "ccmpb7", "cmpc7", "cmpd7",
+ "cmpe7", "cmpf7", "adtrga7", "adtrgb7",
+ "ovf7", "unf7";
+ clocks = <&cpg CPG_MOD R9A07G044_GPT_PCLK>;
+ power-domains = <&cpg>;
+ resets = <&cpg R9A07G044_GPT_RST_C>;
+ #pwm-cells = <2>;
+ };
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v20 2/4] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
2024-06-14 15:42 ` [PATCH v20 1/4] dt-bindings: pwm: Add RZ/G2L GPT binding Biju Das
@ 2024-06-14 15:42 ` Biju Das
2024-06-14 15:42 ` [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT Biju Das
` (2 subsequent siblings)
4 siblings, 0 replies; 12+ messages in thread
From: Biju Das @ 2024-06-14 15:42 UTC (permalink / raw)
To: Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley
Cc: Biju Das, Geert Uytterhoeven, Magnus Damm, Fabrizio Castro,
linux-pwm, devicetree, linux-renesas-soc, Prabhakar Mahadev Lad,
Biju Das
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.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
v19->v20:
* No change.
v18->v19:
* No change.
v17->v18:
* No change.
v16->v17:
* No change.
v15->v16:
* No change.
v14->v15:
* No change.
v3->v14:
* Add Rb tag from Rob.
* Moved the patch from series[1] to here.
[1] https://lore.kernel.org/linux-renesas-soc/20221215205843.4074504-1-biju.das.jz@bp.renesas.com/T/#t
v2->v3:
* Moved minItems/MaxItems one level up.
v1->v2:
* removed quotes from ref
* Added maxItems and minItems for renesas,poegs property
* Added enums for gpt index
---
.../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 d9374144d82d..957cf28b2c4c 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 = <2>;
+ renesas,poegs = <&poeggd 4>;
};
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
2024-06-14 15:42 ` [PATCH v20 1/4] dt-bindings: pwm: Add RZ/G2L GPT binding Biju Das
2024-06-14 15:42 ` [PATCH v20 2/4] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property Biju Das
@ 2024-06-14 15:42 ` Biju Das
2024-07-30 19:54 ` Uwe Kleine-König
2024-06-14 15:42 ` [PATCH v20 4/4] pwm: rzg2l-gpt: Add support for gpt linking with poeg Biju Das
2024-07-02 19:19 ` [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
4 siblings, 1 reply; 12+ messages in thread
From: Biju Das @ 2024-06-14 15:42 UTC (permalink / raw)
To: Uwe Kleine-König, Philipp Zabel
Cc: Biju Das, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das
RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer
(GPT32E). It supports the following functions
* 32 bits x 8 channels
* Up-counting or down-counting (saw waves) or up/down-counting
(triangle waves) for each counter.
* Clock sources independently selectable for each channel
* Two 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
* Starting, stopping, clearing and up/down counters in response to input
level comparison
* Starting, clearing, stopping and up/down counters in response to a
maximum of four external triggers
* Output pin disable function by dead time error and detected
short-circuits between output pins
* A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
* Enables the noise filter for input capture and external trigger
operation
Add basic pwm support for RZ/G2L GPT driver by creating separate
logical channels for each IOs.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v19->v20:
* Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
* Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
* Added a comment in calculate_period_or_duty() related to overflow.
* Replaced ch_en_bits->bootloader_enabled_channels and used this variable
in probe(), apply() and remove() for simplification
* Replaced pm_runtime_enable()->devm_pm_runtime_enable()
v18->v19:
* Replaced RZG2L_UP_COUNTING->RZG2L_GTUDDTYC_UP_COUNTING macro.
* Aligned RZG2L_GET_CH and RZG2L_GET_CH_OFFS macro
* Dropped chip and clk from struct rzg2l_gpt_chip as started using
devm_pwmchip_alloc() and devm_clk_rate_exclusive_get() to replace it.
* Replaced rate->rate_khz in struct rzg2l_gpt_chip and added a check in
probe() to make sure rate is multiple of 1000.
* Replaced container_of->pwmchip_get_drvdata() to get device data.
* Added a check in rzg2l_gpt_disable() not to decrement enable_count if
ch_en_bits is set by the probe.
* Dropped rzg2l_gpt_mul_u64_u64_div_u64()
* Simplified calculate_period_or_duty() using rate_khz
* Simplified rzg2l_gpt_config() using min macro for calculating period
and duty_cycle.
* Added checks in rzg2l_gpt_config() to prevent second channel setting
shared register.
* Updated error handling rzg2l_gpt_apply()
* Added local variable dev for &pdev->dev in probe()
* Added local varibles rate, chip and clk in probe()
* Dropped err_clk_rate_put label as started using
devm_clk_rate_exclusive_get()
* Replaced rzg2l_gpt->chip as data for devm_add_action_or_reset().
* Added error message for rate > 1GHz in probe.
v17->v18:
* Updated copyright from 2023->2024.
* Added units.h for KILO macro.
* Replaced RZG2L_GTCCR{A,B}->RZG2L_GTCCR(i)
* Introduced macros RZG2L_GTIOR_{GTIOx,OxE} to handle subchannels.
* Replaced RZG2L_IS_IOB()->rzg2l_gpt_subchannel()
* Replaced the cache period->period_cycles.
* Updated rzg2l_gpt_is_ch_enabled() to return early if counter is not
running.
* Updated calculate_period_or_duty() for avoiding overflows.
* Updated rzg2l_gpt_calculate_pv_or_dc() with simplified calculation for
DIV64_U64_ROUND_UP() and dropped the cast for U32_MAX in min_t.
* Replaced mul_u64_u32_div->rzg2l_gpt_mul_u64_u64_div_u64() helper.
* Dropped pm pointer from struct rzg2l_gpt_driver() and simplified clk
handling in probe().
v16->v17:
* Added ret = dev_err_probe() to avoid return success in probe().
* Dropped unneeded MODULE_ALIAS().
* Dropped .owner from struct rzg2l_gpt_ops.
* Fixed build issue reported by kernel test robot <lkp@intel.com> by
replacing DIV_ROUND_UP()->DIV64_U64_ROUND_UP() in
rzg2l_gpt_calculate_pv_or_dc().
* Added max_val to struct rzg2l_gpt_chip to compute maximum period
supported by the HW in probe() and limit its value in apply() to
avoid 64-bit overflow with computation.
* Added helper function calculate_period_or_duty() to avoid losing
precision for smaller period/duty cycle values
((2^32 * 10^9 << 2) < 2^64), by not processing the rounded values.
* Replaced mul_u64_u64_div_u64()->mul_u64_u32_div() as the former is
giving warnings with CONFIG_PWM_DEBUG enabled for very high values.
eg:
echo "###### Medium setting 11000 sec = 3hours ##"
echo 11000000000000 > /sys/class/pwm/$PWMCHIP/pwm${IO_1}/period
echo 5500000000000 > /sys/class/pwm/$PWMCHIP/pwm${IO_1}/duty_cycle
dumpgptreg
echo "###### High setting##"
echo 43980465100800 > /sys/class/pwm/$PWMCHIP/pwm${IO_1}/period
echo 23980465100800 > /sys/class/pwm/$PWMCHIP/pwm${IO_1}/duty_cycle
dumpgptreg
with mul_u64_u32_div():
###### Medium setting 11000 sec = 3hours ##
Read at address 0x10048464 (0xffffb9426464): 0x400746FE
Read at address 0x1004844C (0xffff8fdfb44c): 0x2003A37F
Read at address 0x1004842C (0xffff9855b42c): 0x05000001
###### High setting##
Read at address 0x10048464 (0xffff9101b464): 0xFFFFFFFF
Read at address 0x1004844C (0xffffaee0544c): 0x8B95AD77
Read at address 0x1004842C (0xffffbbc8a42c): 0x05000001
with mul_u64_u64_div_u64():
###### Medium setting 11000 sec = 3hours ##
Read at address 0x10048464 (0xffffb3185464): 0x400746FE
Read at address 0x1004844C (0xffff81ebb44c): 0x2003A37F
Read at address 0x1004842C (0xffff904fd42c): 0x05000001
######[ 304.213944] pwm-rzg2l-gpt 10048000.pwm: .apply is not idempotent (ena=1 pol=0 5500000000000/43980352512000) -> (ena=1 pol=0 5500000000000/43980239923200)
High setting##
[ 304.230854] pwm-rzg2l-gpt 10048000.pwm: .apply is not idempotent (ena=1 pol=0 23980465100800/43980352512000) -> (ena=1 pol=0 23980465100800/43980239923200)
Read at address 0x10048464 (0xffffb5bb3464): 0xFFFFAA19
Read at address 0x1004844C (0xffff99b8c44c): 0x8B95AD77
Read at address 0x1004842C (0xffffbba2342c): 0x05000001
v15->v16:
* Replaced the macro DIV_ROUND_UP_ULL->DIV64_U64_ROUND_UP
* Added DIV_ROUND_UP in rzg2l_gpt_calculate_pv_or_dc() to avoid loss of
precision.
* Replaced min->min_t() in rzg2l_gpt_calculate_pv_or_dc().
* Added a comment for rzg2l_gpt_config()
* Replaced mul_u64_u32_div()->mul_u64_u64_div_u64() in rzg2l_gpt_config()
* Fixed the logical condition related to counter stop in
rzg2l_gpt_config().
* Dropped pm_runtime_resume_*() from rzg2l_gpt_config() as it is managed
by rzg2l_gpt_apply().
* Moved pm_runtime_resume_*() from rzg2l_gpt_{en,dis}able() to
rzg2l_gpt_apply().
v14->v15:
* Added enable_count and ch_en_bits variables to struct rzg2l_gpt_chip
based on feedback for pwm_mtu3 driver.
* Updated copyright header and commit description by replacing "This patch
adds"-> "Add"
* Replaced macro RZG2L_GET_CH_INDEX->RZG2L_GET_CH and replaced ch_index->ch
throughout
* rzg2l_gpt_{enable,disable}() enables/disables PWM based on the
enable_count.
* Replaced pm_runtime_get_sync->pm_runtime_resume_and_get and propogated
the error in rzg2l_gpt_get_state() and rzg2l_gpt_config()
* Reduced variable scope in rzg2l_gpt_get_state() by moving most of variables
inside the if statement.
* Updated rzg2l_gpt_get_state() by moving duty > period check
inside the top if block.
* Added helper functions rzg2l_gpt_calculate_pv_or_dc()to simplify config.
Also Improved the logic in rzg2l_gpt_calculate_pv_or_dc() by using
min(period_or_duty_cycle >> (2 * prescale), (u64)U32_MAX);
* Updated rzg2l_gpt_get_state() by moving duty > period check
inside the top if block.
* Simplified rzg2l_gpt_config() for updating registers
* Dropped pm_runtime_get_sync() and used bitmap variable "ch_en_bits"
to make balanced PM usage count in rzg2l_gpt_reset_assert_pm_disable()
For case were unbind is called before apply where pwm is enabled by
bootloader.
* Added error check for clk_rate_exclusive_get() and clk_get_rate() in
probe().
* Dropped prescale from struct rzg2l_gpt_chip.
* Replaced of_match_ptr(rzg2l_gpt_of_table)->rzg2l_gpt_of_table in struct
rzg2l_gpt_driver
v13->v14:
* Removed parenthesis for RZG2L_MAX_HW_CHANNELS and RZG2L_CHANNELS_PER_IO
* Removed duty_cycle variable from struct rzg2l_gpt_chip and added comment
for cache for prescale variable.
* Fixed a bug in rzg2l_gpt_cntr_need_stop().
* Reordered rzg2l_gpt_config() just above apply()
* Replaced pwm_is_enabled()->pwm->state.enabled in config
* Replaced pm_runtime_resume_and_get with unconditional pm_runtime_get_sync()
in config().
* Restored duty_cycle > period check in rzg2l_gpt_get_state().
* Added error check for clk_prepare_enable() in probe() and propagating error
to the caller for pm_runtime_resume()
* clk_get_rate() is called after enabling the clock and clk_rate_exclusive_get()
* Simplified rzg2l_gpt_probe() by removing bitmap variables.
* Added pm_runtime_idle() to suspend the device during probe.
* Moved overflow condition check from config->probe().
* Simplified rzg2l_gpt_reset_assert_pm_disable().
v12->v13:
* Replaced Kconfig dependency from ARCH_RENESAS->ARCH_RZG2L
* Sorted #include <linux/limits.h> alphabetically
* Added a comment for mutex_lock to fix check patch warning
* Replaced data type of duty_cycle from unsigned int->u32 as
the maximum value stored is U32_MAX.
* Improved rzg2l_gpt_config() by removing unwanted duty_cycle related code.
* Improved rzg2l_gpt_get_state() by setting "val = rzg2l_gpt->duty_cycle[pwm->hwpwm];",
and factor "tmp = NSEC_PER_SEC * (u64)val;" out of the if-statement.
* Started using DEFINE_RUNTIME_DEV_PM_OPS(), and dropped __maybe_unused
from the callbacks.
v11->v12:
* Added return code for get_state()
* Cache duty cycle/prescale as the driver cannot read the current duty
cycle/prescale from the hardware if the hardware is disabled. Cache the
last programmed duty cycle/prescale value to return in that case.
* Updated rzg2l_gpt_enable to enable the clocks.
* Updated rzg2l_gpt_disable to disable the clocks.
* Updated rzg2l_gpt_config() to cache duty cucle/prescale value
* Updated rzg2l_gpt_get_state to use cached value of duty cycle/prescale,If the PWM
is disabled.
* Simplified rzg2l_gpt_apply()
* Added comments in rzg2l_gpt_reset_assert_pm_disable()
v10->v11:
* Used bitmap_zero for initializing bitmap varable.
* Fixed clock imbalance during remove for the case bootloader turning
on PWM and module unload is called just after the boot.
* Fixed over flow condition in get_state() for a prescale value of 2 & more.
* Improved rzg2l_gpt_cntr_need_stop() based on prescale as it is the
only runtime variable.
* Added array for Cache variables state_period and prescale
* Probe caches the prescale value set by the bootloader.
* Updated rzg2l_gpt_config() to make use of array variables.
v9->v10:
* Updated the error handling in probe(), clk_disable_unprepare called
on the error path.
* Removed ch_en array and started using bitmask instead.
v8->v9:
* deassert after devm_clk_get() to avoid reset stays deasserted,in case
clk_get() fails.
* Removed ch_offs from struct rzg2l_gpt_chip and use macro instead.
* Updated error handling in probe()
v7->v8:
* Modelled as single PWM device handling multiple channels
* Replaced shared reset->devm_reset_control_get_exclusive()
* Replaced iowrite32->writel and ioread32->readl
* Updated prescale calculation
* Added PM runtime callbacks
* Updated PM handling and removed "pwm_enabled_by_bootloader" variable
* Introduced rzg2l_gpt_is_ch_enabled for checking enable status on both
IO's
* Moved enable/disable output pins from config->enable/disable.
* Added rzg2l_gpt_cntr_need_stop() for caching prescalar/mode values.
v6->v7:
* Added the comment for cacheing rzg2l_gpt->state_period.
* Fixed boundary values for pv and dc.
* Added comment for modifying mode, prescaler, timer counter and buffer enable
registers.
* Fixed buffer overflow in get_state()
* Removed unnecessary assignment of state->period value in get_state().
* Fixed state->duty_cycle value in get_state().
* Added a limitation for disabling the channels.
v5->v6:
* Updated macros RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH and
RZG2L_GTIOR_GTIOB_OUT_LO_END_TOGGLE_CMP_MATCH with computation
involving FIELD_PREP macro.
* Removed struct rzg2l_gpt_phase and started using RZG2L_GTCCR macro
for duty_offset.
* replaced misnomer real_period->state_period.
* Added handling for values >= (1024 << 32) for both period
and duty cycle.
* Added comments for pwm {en,dis}abled by bootloader during probe.
v4->v5:
* Added Hardware manual details
* Replaced the comment GTCNT->Counter
* Removed the macros RZG2L_GPT_IO_PER_CHANNEL and chip.npwm directly
used in probe.
* Removed the unsed macro RZG2L_GTPR_MAX_VALUE
* Added driver prefix for the type name and the variable.
* Initialization of per_channel data moved from request->probe.
* Updated clr parameter for rzg2l_gpt_modify for Start count.
* Started using mutex and usage_count for handling shared
period and prescalar for the 2 channels.
* Updated the comment cycle->period.
* Removed clk_disable from rzg2l_gpt_reset_assert_pm_disable()
* Replaced pc->rzg2l_gpt.
* Updated prescale calculation.
* Moved pm_runtime_{get_sync,put} from {request,free}->{enable,disable}
* Removed platform_set_drvdata as it is unused
* Removed the variable pwm_enabled_by_bootloader
* Added dev_err_probe in various error paths in probe.
* Added an error message, if devm_pwmchip_add() fails.
v3->v4:
* Changed the local variable type i from u16->u8 and prescaled_period_
cycles from u64->u32 in calculate_prescale().
* Replaced mul_u64_u64_div_u64()->mul_u64_u32_div()
* Dropped the comma after the sentinel.
* Add a variable to track pwm enabled by bootloader and added comments
in probe().
* Removed unnecessary rzg2l_gpt_reset_assert_pm_disable() from probe.
* Replaced devm_clk_get()->devm_clk_get_prepared()
* Removed devm_clk_get_optional_enabled()
v2->v3:
* Updated limitation section
* Added prefix "RZG2L_" for all macros
* Modified prescale calculation
* Removed pwm_set_chip_data
* Updated comment related to modifying Mode and Prescaler
* Updated setting of prescale value in rzg2l_gpt_config()
* Removed else branch from rzg2l_gpt_get_state()
* removed the err label from rzg2l_gpt_apply()
* Added devm_clk_get_optional_enabled() to retain clk on status,
in case bootloader turns on the clk of pwm.
* Replaced devm_reset_control_get_exclusive->devm_reset_control_get_shared
as single reset shared between 8 channels.
v1->v2:
* Added Limitations section
* dropped "_MASK" from the define names.
* used named initializer for struct phase
* Added gpt_pwm_device into a flexible array member in rzg2l_gpt_chip
* Revised the logic for prescale
* Added .get_state callback
* Improved error handling in rzg2l_gpt_apply
* Removed .remove callback
* Tested driver with PWM_DEBUG enabled
RFC->V1:
* Updated macros
* replaced rzg2l_gpt_write_mask()->rzg2l_gpt_modify()
* Added rzg2l_gpt_read()
---
drivers/pwm/Kconfig | 11 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-rzg2l-gpt.c | 555 ++++++++++++++++++++++++++++++++++++
3 files changed, 567 insertions(+)
create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 00a543de8f82..3d398b308e3f 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -522,6 +522,17 @@ config PWM_ROCKCHIP
Generic PWM framework driver for the PWM controller found on
Rockchip SoCs.
+config PWM_RZG2L_GPT
+ tristate "Renesas RZ/G2L General PWM Timer support"
+ depends on ARCH_RZG2L || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ This driver exposes the General PWM Timer controller found in Renesas
+ RZ/G2L like chips through the PWM API.
+
+ To compile this driver as a module, choose M here: the module
+ will be called pwm-rzg2l-gpt.
+
config PWM_RZ_MTU3
tristate "Renesas RZ/G2L MTU3a PWM Timer support"
depends on RZ_MTU3
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 6964ba45c795..fb9a2d9b9adb 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
+obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
new file mode 100644
index 000000000000..6005a689173e
--- /dev/null
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -0,0 +1,555 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Renesas RZ/G2L General PWM Timer (GPT) driver
+ *
+ * Copyright (C) 2024 Renesas Electronics Corporation
+ *
+ * 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
+ *
+ * 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.
+ * - When both channels are used, disabling the channel on one stops the
+ * other.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
+#include <linux/reset.h>
+#include <linux/time.h>
+#include <linux/units.h>
+
+#define RZG2L_GTCR 0x2c
+#define RZG2L_GTUDDTYC 0x30
+#define RZG2L_GTIOR 0x34
+#define RZG2L_GTBER 0x40
+#define RZG2L_GTCNT 0x48
+#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
+#define RZG2L_GTPR 0x64
+
+#define RZG2L_GTCR_CST BIT(0)
+#define RZG2L_GTCR_MD GENMASK(18, 16)
+#define RZG2L_GTCR_TPCS GENMASK(26, 24)
+
+#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0)
+
+#define RZG2L_GTUDDTYC_UP BIT(0)
+#define RZG2L_GTUDDTYC_UDF BIT(1)
+#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF)
+
+#define RZG2L_GTIOR_GTIOA GENMASK(4, 0)
+#define RZG2L_GTIOR_GTIOB GENMASK(20, 16)
+#define RZG2L_GTIOR_GTIOx(a) ((a) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
+#define RZG2L_GTIOR_OAE BIT(8)
+#define RZG2L_GTIOR_OBE BIT(24)
+#define RZG2L_GTIOR_OxE(a) ((a) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE)
+
+#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)
+#define RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \
+ (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE) | RZG2L_GTIOR_OBE)
+
+#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(a) \
+ ((a) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \
+ RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH)
+
+#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_GET_CH(a) ((a) / 2)
+
+#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
+
+struct rzg2l_gpt_chip {
+ void __iomem *mmio;
+ struct reset_control *rstc;
+ struct mutex lock; /* lock to protect shared channel resources */
+ unsigned long rate_khz;
+ u64 max_val;
+ u32 period_cycles[RZG2L_MAX_HW_CHANNELS];
+ u32 user_count[RZG2L_MAX_HW_CHANNELS];
+ u32 enable_count[RZG2L_MAX_HW_CHANNELS];
+ u8 bootloader_enabled_channels[RZG2L_MAX_PWM_CHANNELS];
+};
+
+static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm)
+{
+ return hwpwm & 0x1;
+}
+
+static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data)
+{
+ writel(data, rzg2l_gpt->mmio + reg);
+}
+
+static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg)
+{
+ return readl(rzg2l_gpt->mmio + reg);
+}
+
+static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr,
+ u32 set)
+{
+ rzg2l_gpt_write(rzg2l_gpt, reg,
+ (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set);
+}
+
+static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt,
+ u64 period_cycles)
+{
+ u32 prescaled_period_cycles;
+ u8 prescale;
+
+ prescaled_period_cycles = period_cycles >> 32;
+ if (prescaled_period_cycles >= 256)
+ prescale = 5;
+ else
+ prescale = (fls(prescaled_period_cycles) + 1) / 2;
+
+ 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);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ mutex_lock(&rzg2l_gpt->lock);
+ rzg2l_gpt->user_count[ch]++;
+ mutex_unlock(&rzg2l_gpt->lock);
+
+ return 0;
+}
+
+static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+
+ mutex_lock(&rzg2l_gpt->lock);
+ rzg2l_gpt->user_count[ch]--;
+ mutex_unlock(&rzg2l_gpt->lock);
+}
+
+static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
+{
+ u8 ch = RZG2L_GET_CH(hwpwm);
+ u32 offs = RZG2L_GET_CH_OFFS(ch);
+ u32 val;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
+ if (!(val & RZG2L_GTCR_CST))
+ return false;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTIOR);
+
+ return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
+}
+
+static int rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt,
+ struct pwm_device *pwm)
+{
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 offs = RZG2L_GET_CH_OFFS(ch);
+
+ mutex_lock(&rzg2l_gpt->lock);
+ /* Enable pin output */
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, val,
+ RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch));
+
+ if (!rzg2l_gpt->enable_count[ch])
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, 0, RZG2L_GTCR_CST);
+
+ rzg2l_gpt->enable_count[ch]++;
+ mutex_unlock(&rzg2l_gpt->lock);
+
+ return 0;
+}
+
+static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt,
+ struct pwm_device *pwm)
+{
+ u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 offs = RZG2L_GET_CH_OFFS(ch);
+
+ /* Stop count, Output low on GTIOCx pin when counting stops */
+ mutex_lock(&rzg2l_gpt->lock);
+ rzg2l_gpt->enable_count[ch]--;
+
+ if (!rzg2l_gpt->enable_count[ch])
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
+
+ /* Disable pin output */
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, RZG2L_GTIOR_OxE(sub_ch), 0);
+ mutex_unlock(&rzg2l_gpt->lock);
+}
+
+static u64 calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt, u32 val, u8 prescale)
+{
+ u64 tmp;
+
+ /*
+ * This cannot overflow because,
+ * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
+ */
+ tmp = (u64)val << (2 * prescale);
+ tmp *= USEC_PER_SEC;
+
+ 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);
+ int rc;
+
+ rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (rc)
+ return rc;
+
+ state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
+ if (state->enabled) {
+ u32 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 offs = RZG2L_GET_CH_OFFS(ch);
+ u8 prescale;
+ u32 val;
+
+ val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
+ prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
+
+ val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTPR);
+ state->period = calculate_period_or_duty(rzg2l_gpt, val, prescale);
+
+ val = rzg2l_gpt_read(rzg2l_gpt,
+ offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)));
+ state->duty_cycle = calculate_period_or_duty(rzg2l_gpt, val, prescale);
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+ }
+
+ state->polarity = PWM_POLARITY_NORMAL;
+ pm_runtime_put(pwmchip_parent(chip));
+
+ return 0;
+}
+
+static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
+{
+ return min_t(u64, (period_or_duty_cycle + (1 << (2 * prescale)) - 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)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u64 period, duty_cycle, period_cycles, duty_cycles;
+ u8 ch = RZG2L_GET_CH(pwm->hwpwm);
+ u32 offs = RZG2L_GET_CH_OFFS(ch);
+ unsigned long pv, dc;
+ u8 prescale;
+
+ /* Limit period/duty cycle to max value supported by the HW */
+ period = min(state->period, rzg2l_gpt->max_val);
+ period_cycles = mul_u64_u64_div_u64(period, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+
+ /*
+ * GPT counter is shared by multiple channels, so prescale and period
+ * can NOT be modified when there are multiple channels in use with
+ * different settings.
+ */
+ if (rzg2l_gpt->user_count[ch] > 1 && period_cycles < rzg2l_gpt->period_cycles[ch])
+ return -EBUSY;
+
+ prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_cycles);
+ pv = rzg2l_gpt_calculate_pv_or_dc(period_cycles, prescale);
+
+ duty_cycle = min(state->duty_cycle, rzg2l_gpt->max_val);
+ duty_cycles = mul_u64_u64_div_u64(duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
+ dc = rzg2l_gpt_calculate_pv_or_dc(duty_cycles, prescale);
+
+ /*
+ * GPT counter is shared by multiple channels, we cache the period cycles
+ * from the first enabled channel and use the same value for both
+ * channels.
+ */
+ rzg2l_gpt->period_cycles[ch] = period_cycles;
+
+ /*
+ * 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->enable_count[ch] <= 1) {
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
+
+ /* GPT set operating mode (saw-wave up-counting) */
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_MD,
+ RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
+
+ /* Set count direction */
+ rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTUDDTYC, RZG2L_GTUDDTYC);
+
+ /* Select count clock */
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_TPCS,
+ FIELD_PREP(RZG2L_GTCR_TPCS, prescale));
+
+ /* Set period */
+ rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTPR, pv);
+ }
+
+ /* Set duty cycle */
+ rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)),
+ dc);
+
+ if (rzg2l_gpt->enable_count[ch] <= 1) {
+ /* Set initial value for counter */
+ rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCNT, 0);
+
+ /* Set no buffer operation */
+ rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTBER, 0);
+
+ /* Restart the counter after updating the registers */
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR,
+ 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;
+
+ if (!state->enabled) {
+ if (enabled) {
+ /*
+ * Probe() sets bootloader_enabled_channels. In such case,
+ * clearing the flag will avoid errors during unbind.
+ */
+ if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm])
+ rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
+
+ rzg2l_gpt_disable(rzg2l_gpt, pwm);
+ pm_runtime_put_sync(pwmchip_parent(chip));
+ }
+
+ return 0;
+ }
+
+ if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm]) {
+ /*
+ * it's already be on. Instead of reenabling in hardware
+ * just take over from the bootloader
+ */
+ rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
+ } else {
+ if (!enabled) {
+ ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
+ if (ret)
+ return ret;
+ }
+ }
+
+ mutex_lock(&rzg2l_gpt->lock);
+ ret = rzg2l_gpt_config(chip, pwm, state);
+ mutex_unlock(&rzg2l_gpt->lock);
+ if (ret)
+ goto err_pm_runtime_put;
+
+ if (!enabled) {
+ ret = rzg2l_gpt_enable(rzg2l_gpt, pwm);
+ if (ret)
+ goto err_pm_runtime_put;
+ }
+
+ return 0;
+
+err_pm_runtime_put:
+ pm_runtime_put_sync(pwmchip_parent(chip));
+ return ret;
+}
+
+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,
+};
+
+static void rzg2l_gpt_reset_assert(void *data)
+{
+ struct pwm_chip *chip = data;
+ struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+ u32 i;
+
+ /*
+ * The below check is for making balanced PM usage count
+ * eg: boot loader is turning on PWM and probe increments the PM usage
+ * count. Before apply, if there is unbind/remove callback we need to
+ * decrement the PM usage count.
+ */
+ for (i = 0; i < chip->npwm; i++) {
+ if (rzg2l_gpt->bootloader_enabled_channels[i])
+ pm_runtime_put(pwmchip_parent(chip));
+ }
+
+ reset_control_assert(rzg2l_gpt->rstc);
+}
+
+static int rzg2l_gpt_probe(struct platform_device *pdev)
+{
+ struct rzg2l_gpt_chip *rzg2l_gpt;
+ struct device *dev = &pdev->dev;
+ struct pwm_chip *chip;
+ unsigned long rate;
+ struct clk *clk;
+ int ret;
+ u32 i;
+
+ chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt));
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ rzg2l_gpt = to_rzg2l_gpt_chip(chip);
+
+ rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rzg2l_gpt->mmio))
+ return PTR_ERR(rzg2l_gpt->mmio);
+
+ rzg2l_gpt->rstc = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(rzg2l_gpt->rstc))
+ return dev_err_probe(dev, PTR_ERR(rzg2l_gpt->rstc),
+ "get reset failed\n");
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "cannot get clock\n");
+
+ ret = reset_control_deassert(rzg2l_gpt->rstc);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot deassert reset control\n");
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ goto err_reset;
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ goto err_reset;
+
+ ret = devm_clk_rate_exclusive_get(dev, clk);
+ if (ret)
+ goto err_pm_put;
+
+ rate = clk_get_rate(clk);
+ if (!rate) {
+ ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is 0");
+ goto err_pm_put;
+ }
+
+ /*
+ * Refuse clk rates > 1 GHz to prevent overflow later for computing
+ * period and duty cycle.
+ */
+ if (rate > NSEC_PER_SEC) {
+ ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is > 1GHz");
+ goto err_pm_put;
+ }
+
+ /*
+ * Rate is in MHz and is always integer for peripheral clk
+ * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
+ * So make sure rate is multiple of 1000.
+ */
+ rzg2l_gpt->rate_khz = rate / KILO;
+ if (rzg2l_gpt->rate_khz * KILO != rate) {
+ ret = dev_err_probe(dev, -EINVAL, "rate is not multiple of 1000");
+ goto err_pm_put;
+ }
+
+ rzg2l_gpt->max_val = div64_u64((u64)U32_MAX * USEC_PER_SEC,
+ rzg2l_gpt->rate_khz) * RZG2L_MAX_SCALE_FACTOR;
+
+ /*
+ * We need to keep the clock on, in case the bootloader has enabled the
+ * PWM and is running during probe().
+ */
+ for (i = 0; i < RZG2L_MAX_PWM_CHANNELS; i++) {
+ if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, i)) {
+ u8 ch = RZG2L_GET_CH(i);
+
+ rzg2l_gpt->bootloader_enabled_channels[i] = 1;
+ rzg2l_gpt->enable_count[ch]++;
+ pm_runtime_get_sync(dev);
+ }
+ }
+
+ pm_runtime_put(dev);
+
+ mutex_init(&rzg2l_gpt->lock);
+ ret = devm_add_action_or_reset(dev, rzg2l_gpt_reset_assert, chip);
+ if (ret < 0)
+ return ret;
+
+ chip->ops = &rzg2l_gpt_ops;
+ ret = devm_pwmchip_add(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to add PWM chip\n");
+
+ return 0;
+
+err_pm_put:
+ pm_runtime_put(dev);
+err_reset:
+ reset_control_assert(rzg2l_gpt->rstc);
+ return ret;
+}
+
+static const struct of_device_id rzg2l_gpt_of_table[] = {
+ { .compatible = "renesas,rzg2l-gpt", },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table);
+
+static struct platform_driver rzg2l_gpt_driver = {
+ .driver = {
+ .name = "pwm-rzg2l-gpt",
+ .of_match_table = rzg2l_gpt_of_table,
+ },
+ .probe = rzg2l_gpt_probe,
+};
+module_platform_driver(rzg2l_gpt_driver);
+
+MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
+MODULE_DESCRIPTION("Renesas RZ/G2L General PWM Timer (GPT) Driver");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v20 4/4] pwm: rzg2l-gpt: Add support for gpt linking with poeg
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
` (2 preceding siblings ...)
2024-06-14 15:42 ` [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT Biju Das
@ 2024-06-14 15:42 ` Biju Das
2024-07-02 19:19 ` [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
4 siblings, 0 replies; 12+ messages in thread
From: Biju Das @ 2024-06-14 15:42 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Biju Das, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das
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.
Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
---
v19->v20:
* No change
v18->v19:
* No change
v17->v18:
* Moved bitpos near to the user.
v16->v17:
* No change
v15->v16:
* No change.
v14->v15:
* Updated commit description by replacing "This patch add"-> "Add".
v3->v14:
* Removed the parenthesis for RZG2L_MAX_POEG_GROUPS.
* Renamed rzg2l_gpt_parse_properties()->rzg2l_gpt_poeg_init() as it not only parse
the properties but also implements the needed register writes.
* Added acomment here about the purpose of the function rzg2l_gpt_poeg_init()
* Removed magic numbers from rzg2l_gpt_poeg_init()
* Fixed resource leak in rzg2l_gpt_poeg_init().
* Moved the patch from series[1] to here
[1] https://lore.kernel.org/linux-renesas-soc/20221215205843.4074504-1-biju.das.jz@bp.renesas.com/T/#t
v2->v3:
* Updated commit header and description
* Added check for poeg group in rzg2l_gpt_parse_properties().
v1->v2:
* Replaced id->poeg-id as per poeg bindings.
This patch depend upon [1]
[1] https://patchwork.kernel.org/project/linux-renesas-soc/patch/20221214132232.2835828-3-biju.das.jz@bp.renesas.com/
---
drivers/pwm/pwm-rzg2l-gpt.c | 82 +++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
index 6005a689173e..24cc34d41921 100644
--- a/drivers/pwm/pwm-rzg2l-gpt.c
+++ b/drivers/pwm/pwm-rzg2l-gpt.c
@@ -32,6 +32,7 @@
#define RZG2L_GTCR 0x2c
#define RZG2L_GTUDDTYC 0x30
#define RZG2L_GTIOR 0x34
+#define RZG2L_GTINTAD 0x38
#define RZG2L_GTBER 0x40
#define RZG2L_GTCNT 0x48
#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
@@ -48,11 +49,17 @@
#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_GTIOx(a) ((a) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
+#define RZG2L_GTIOR_OBDF GENMASK(26, 25)
#define RZG2L_GTIOR_OAE BIT(8)
#define RZG2L_GTIOR_OBE BIT(24)
#define RZG2L_GTIOR_OxE(a) ((a) ? 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 \
@@ -64,6 +71,8 @@
((a) ? 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)
@@ -73,6 +82,9 @@
#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
+#define RZG2L_MAX_POEG_GROUPS 4
+#define RZG2L_LAST_POEG_GROUP 3
+
struct rzg2l_gpt_chip {
void __iomem *mmio;
struct reset_control *rstc;
@@ -83,6 +95,7 @@ struct rzg2l_gpt_chip {
u32 user_count[RZG2L_MAX_HW_CHANNELS];
u32 enable_count[RZG2L_MAX_HW_CHANNELS];
u8 bootloader_enabled_channels[RZG2L_MAX_PWM_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)
@@ -427,6 +440,74 @@ static void rzg2l_gpt_reset_assert(void *data)
reset_control_assert(rzg2l_gpt->rstc);
}
+/*
+ * This function links a poeg group{A,B,C,D} with a gpt channel{0..7} and
+ * configure the pin for output disable.
+ */
+static void rzg2l_gpt_poeg_init(struct platform_device *pdev,
+ struct rzg2l_gpt_chip *rzg2l_gpt)
+{
+ struct of_phandle_args of_args;
+ unsigned int i;
+ u32 poeg_grp;
+ u32 bitpos;
+ int cells;
+ u32 offs;
+ int ret;
+
+ cells = of_property_count_u32_elems(pdev->dev.of_node, "renesas,poegs");
+ if (cells == -EINVAL)
+ return;
+
+ cells >>= 1;
+ for (i = 0; i < cells; i++) {
+ ret = of_parse_phandle_with_fixed_args(pdev->dev.of_node,
+ "renesas,poegs", 1, i,
+ &of_args);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to parse 'renesas,poegs' property\n");
+ return;
+ }
+
+ 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);
+ of_node_put(of_args.np);
+ return;
+ }
+
+ 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)) {
+ offs = RZG2L_GET_CH_OFFS(of_args.args[0]);
+ if (poeg_grp > RZG2L_LAST_POEG_GROUP) {
+ dev_err(&pdev->dev, "Invalid poeg group %d > %d\n",
+ poeg_grp, RZG2L_LAST_POEG_GROUP);
+ of_node_put(of_args.np);
+ return;
+ }
+
+ bitpos = of_args.args[0] + poeg_grp * RZG2L_MAX_HW_CHANNELS;
+ set_bit(bitpos, rzg2l_gpt->poeg_gpt_link);
+
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTINTAD,
+ RZG2L_GTINTAD_GRP_MASK,
+ poeg_grp << 24);
+
+ rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR,
+ RZG2L_GTIOR_OBDF | RZG2L_GTIOR_OADF,
+ RZG2L_GTIOR_PIN_DISABLE_SETTING);
+ }
+
+ of_node_put(of_args.np);
+ }
+}
+
static int rzg2l_gpt_probe(struct platform_device *pdev)
{
struct rzg2l_gpt_chip *rzg2l_gpt;
@@ -514,6 +595,7 @@ static int rzg2l_gpt_probe(struct platform_device *pdev)
}
}
+ rzg2l_gpt_poeg_init(pdev, rzg2l_gpt);
pm_runtime_put(dev);
mutex_init(&rzg2l_gpt->lock);
--
2.25.1
^ permalink raw reply related [flat|nested] 12+ messages in thread
* RE: [PATCH v20 0/4] Add support for RZ/G2L GPT
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
` (3 preceding siblings ...)
2024-06-14 15:42 ` [PATCH v20 4/4] pwm: rzg2l-gpt: Add support for gpt linking with poeg Biju Das
@ 2024-07-02 19:19 ` Biju Das
4 siblings, 0 replies; 12+ messages in thread
From: Biju Das @ 2024-07-02 19:19 UTC (permalink / raw)
To: Biju Das, Uwe Kleine-König, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel
Cc: Geert Uytterhoeven, Magnus Damm, linux-pwm@vger.kernel.org,
devicetree@vger.kernel.org, Fabrizio Castro,
linux-renesas-soc@vger.kernel.org, Prabhakar Mahadev Lad,
biju.das.au, linux-kernel@vger.kernel.org
Hi Uwe,
Gentle ping. Are you happy with this patch series?
Cheers,
Biju
> -----Original Message-----
> From: Biju Das <biju.das.jz@bp.renesas.com>
> Sent: Friday, June 14, 2024 4:43 PM
> Subject: [PATCH v20 0/4] Add support for RZ/G2L GPT
>
> RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer (GPT32E). It supports the
> following functions
> * 32 bits x 8 channels
> * Up-counting or down-counting (saw waves) or up/down-counting
> (triangle waves) for each counter.
> * Clock sources independently selectable for each channel
> * Two 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
> * Starting, stopping, clearing and up/down counters in response to input
> level comparison
> * Starting, clearing, stopping and up/down counters in response to a
> maximum of four external triggers
> * Output pin disable function by dead time error and detected
> short-circuits between output pins
> * A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
> * Enables the noise filter for input capture and external trigger
> operation
>
> This patch series aims to add basic pwm support for RZ/G2L GPT driver by creating separate logical
> channels for each IOs.
>
> v19->v20:
> * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> * Added a comment in calculate_period_or_duty() related to overflow.
> * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> in probe(), apply() and remove() for simplification
> * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> v18->v19:
> * Replaced RZG2L_UP_COUNTING->RZG2L_GTUDDTYC_UP_COUNTING macro.
> * Aligned RZG2L_GET_CH and RZG2L_GET_CH_OFFS macro
> * Dropped chip and clk from struct rzg2l_gpt_chip as started using
> devm_pwmchip_alloc() and devm_clk_rate_exclusive_get() to replace it.
> * Replaced rate->rate_khz in struct rzg2l_gpt_chip and added a check in
> probe() to make sure rate is multiple of 1000.
> * Replaced container_of->pwmchip_get_drvdata() to get device data.
> * Added a check in rzg2l_gpt_disable() not to decrement enable_count if
> ch_en_bits is set by the probe.
> * Dropped rzg2l_gpt_mul_u64_u64_div_u64()
> * Simplified calculate_period_or_duty() using rate_khz
> * Simplified rzg2l_gpt_config() using min macro for calculating period
> and duty_cycle.
> * Added checks in rzg2l_gpt_config() to prevent second channel setting
> shared register.
> * Updated error handling rzg2l_gpt_apply()
> * Added local variable dev for &pdev->dev in probe()
> * Added local varibles rate, chip and clk in probe()
> * Dropped err_clk_rate_put label as started using
> devm_clk_rate_exclusive_get()
> * Replaced rzg2l_gpt->chip as data for devm_add_action_or_reset().
> * Added error message for rate > 1GHz in probe.
> v17->v18:
> * Added units.h for KILO macro.
> * Replaced RZG2L_GTCCR{A,B}->RZG2L_GTCCR(i)
> * Introduced macros RZG2L_GTIOR_{GTIOx,OxE} to handle subchannels.
> * Replaced RZG2L_IS_IOB()->rzg2l_gpt_subchannel()
> * Replaced the cache period->period_cycles.
> * Updated rzg2l_gpt_is_ch_enabled() to return early if counter is not
> running.
> * Updated calculate_period_or_duty() for avoiding overflows.
> * Updated rzg2l_gpt_calculate_pv_or_dc() with simplified calculation for
> DIV64_U64_ROUND_UP() and dropped the cast for U32_MAX in min_t.
> * Replaced mul_u64_u32_div->rzg2l_gpt_mul_u64_u64_div_u64() helper.
> * Dropped pm pointer from struct rzg2l_gpt_driver() and simplified clk
> handling in probe().
> * Updated copyright from 2023->2024.
> * Moved bitpos near to the user in patch#4.
> v16->v17:
> * Added ret = dev_err_probe() to avoid return success in probe().
> * Dropped unneeded MODULE_ALIAS().
> * Dropped .owner from struct rzg2l_gpt_ops.
> * Fixed build issue reported by kernel test robot <lkp@intel.com> by
> replacing DIV_ROUND_UP()->DIV64_U64_ROUND_UP() in
> rzg2l_gpt_calculate_pv_or_dc().
> * Added max_val to struct rzg2l_gpt_chip to compute maximum period
> supported by the HW in probe() and limit its value in apply() to
> avoid 64-bit overflow with computation.
> * Added helper function calculate_period_or_duty() to avoid losing
> precision for smaller period/duty cycle values
> ((2^32 * 10^9 << 2) < 2^64), by not processing the rounded values.
> * Replaced mul_u64_u64_div_u64()->mul_u64_u32_div() as the former is
> giving warnings with CONFIG_PWM_DEBUG enabled for very high values.
> v15->v16:
> * Replaced the macro DIV_ROUND_UP_ULL->DIV64_U64_ROUND_UP
> * Added DIV_ROUND_UP in rzg2l_gpt_calculate_pv_or_dc() to avoid loss of
> precision.
> * Replaced min->min_t() in rzg2l_gpt_calculate_pv_or_dc().
> * Added a comment for rzg2l_gpt_config()
> * Replaced mul_u64_u32_div()->mul_u64_u64_div_u64() in rzg2l_gpt_config()
> * Fixed the logical condition related to counter stop in
> rzg2l_gpt_config().
> * Dropped pm_runtime_resume_*() from rzg2l_gpt_config() as it is managed
> by rzg2l_gpt_apply().
> * Moved pm_runtime_resume_*() from rzg2l_gpt_{en,dis}able() to
> rzg2l_gpt_apply().
> v14->v15:
> * Added enable_count and ch_en_bits variables to struct rzg2l_gpt_chip
> based on feedback for pwm_mtu3 driver.
> * Updated copyright header and commit description by replacing "This patch
> adds"-> "Add"
> * Replaced macro RZG2L_GET_CH_INDEX->RZG2L_GET_CH and replaced ch_index->ch
> throughout
> * rzg2l_gpt_{enable,disable}() enables/disables PWM based on the
> enable_count.
> * Replaced pm_runtime_get_sync->pm_runtime_resume_and_get and propogated
> the error in rzg2l_gpt_get_state() and rzg2l_gpt_config()
> * Reduced variable scope in rzg2l_gpt_get_state() by moving most of
> variables inside the if statement.
> * Updated rzg2l_gpt_get_state() by moving duty > period check
> inside the top if block.
> * Added helper functions rzg2l_gpt_calculate_pv_or_dc()to simplify config.
> Also Improved the logic in rzg2l_gpt_calculate_pv_or_dc() by using
> min(period_or_duty_cycle >> (2 * prescale), (u64)U32_MAX);
> * Updated rzg2l_gpt_get_state() by moving duty > period check
> inside the top if block.
> * Simplified rzg2l_gpt_config() for updating registers
> * Dropped pm_runtime_get_sync() and used bitmap variable "ch_en_bits"
> to make balanced PM usage count in rzg2l_gpt_reset_assert_pm_disable()
> For case were unbind is called before apply where pwm is enabled by
> bootloader.
> * Added error check for clk_rate_exclusive_get() and clk_get_rate() in
> probe().
> * Dropped prescale from struct rzg2l_gpt_chip.
> * Replaced of_match_ptr(rzg2l_gpt_of_table)->rzg2l_gpt_of_table in struct
> rzg2l_gpt_driver
> * Updated commit description of patch#4 by replacing "This patch add"->
> "Add".
> v13->v14:
> * Moved the patch from series[1] to here.
> [1] https://lore.kernel.org/linux-renesas-soc/20221215205843.4074504-1-
> biju.das.jz@bp.renesas.com/T/#t
> * Add Rb tag from Rob for patch#2
> * Removed parenthesis for RZG2L_MAX_HW_CHANNELS and RZG2L_CHANNELS_PER_IO
> * Removed duty_cycle variable from struct rzg2l_gpt_chip and added comment
> for cache for prescale variable.
> * Fixed a bug in rzg2l_gpt_cntr_need_stop().
> * Reordered rzg2l_gpt_config() just above apply()
> * Replaced pwm_is_enabled()->pwm->state.enabled in config
> * Replaced pm_runtime_resume_and_get with unconditional
> pm_runtime_get_sync() in config().
> * Restored duty_cycle > period check in rzg2l_gpt_get_state().
> * Added error check for clk_prepare_enable() in probe() and propagating
> error to the caller for pm_runtime_resume()
> * clk_get_rate() is called after enabling the clock and
> clk_rate_exclusive_get()
> * Simplified rzg2l_gpt_probe() by removing bitmap variables.
> * Added pm_runtime_idle() to suspend the device during probe.
> * Moved overflow condition check from config->probe().
> * Simplified rzg2l_gpt_reset_assert_pm_disable().
> * Removed the parenthesis for RZG2L_MAX_POEG_GROUPS.
> * Renamed rzg2l_gpt_parse_properties()->rzg2l_gpt_poeg_init() as it
> not only parse the properties but also implements the needed register
> writes.
> * Added acomment here about the purpose of the function
> rzg2l_gpt_poeg_init()
> * Removed magic numbers from rzg2l_gpt_poeg_init()
> * Fixed resource leak in rzg2l_gpt_poeg_init().
> v12->v13:
> * Added test logs in [1] below
> * Replaced Kconfig dependency from ARCH_RENESAS->ARCH_RZG2L
> * Sorted #include <linux/limits.h> alphabetically
> * Added a comment for mutex_lock to fix check patch warning
> * Replaced data type of duty_cycle from unsigned int->u32 as
> the maximum value stored is U32_MAX.
> * Improved rzg2l_gpt_config() by removing unwanted duty_cycle related
> code.
> * Improved rzg2l_gpt_get_state() by setting
> "val = rzg2l_gpt->duty_cycle[pwm->hwpwm];", and factor
> "tmp = NSEC_PER_SEC * (u64)val;" out of the if-statement.
> * Started using DEFINE_RUNTIME_DEV_PM_OPS(), and dropped __maybe_unused
> from the callbacks.
> v11->v12:
> * Added return code for get_state()
> * Cache duty cycle/prescale as the driver cannot read the current duty
> cycle/prescale from the hardware if the hardware is disabled. Cache the
> last programmed duty cycle/prescale value to return in that case.
> * Updated rzg2l_gpt_enable to enable the clocks.
> * Updated rzg2l_gpt_disable to disable the clocks.
> * Updated rzg2l_gpt_config() to cache duty cucle/prescale value
> * Updated rzg2l_gpt_get_state to use cached value of duty cycle/prescale,
> If the PWM is disabled.
> * Simplified rzg2l_gpt_apply()
> * Added comments in rzg2l_gpt_reset_assert_pm_disable()
> v10->v11:
> * Used bitmap_zero for initializing bitmap varable.
> * Fixed clock imbalance during remove for the case bootloader turning
> on PWM and module unload is called just after the boot.
> * Fixed over flow condition in get_state() for a prescale value of
> 2 & more.
> * Improved rzg2l_gpt_cntr_need_stop() based on prescale as it is the
> only runtime variable.
> * Added array for Cache variables state_period and prescale
> * Probe caches the prescale value set by the bootloader.
> * Updated rzg2l_gpt_config() to make use of array variables.
> v9->v10:
> * Updated the example gpt4: pwm@10048400-> gpt: pwm@10048000
> * Keep Rb tag from Rob as the above change is trivial one.
> * Updated the error handling in probe(), clk_disable_unprepare called
> on the error path.
> * Removed ch_en array and started using bitmask instead.
> v8->v9:
> * Added Rb tag from Rob.
> * deassert after devm_clk_get() to avoid reset stays deasserted,in case
> clk_get() fails.
> * Removed ch_offs from struct rzg2l_gpt_chip and use macro instead.
> * Updated error handling in probe()
> v7->v8:
> * Removed Rb tags from Rob and Geert as it modelled as single GPT
> device handling multiple channels.
> * Updated description
> * Updated interrupts and interrupt-names properties
> * Updated binding example
> * Modelled as single PWM device handling multiple channels
> * Replaced shared reset->devm_reset_control_get_exclusive()
> * Added PM runtime callbacks
> * Updated PM handling and removed "pwm_enabled_by_bootloader" variable
> * Replaced iowrite32->writel and ioread32->readl
> * Updated prescale calculation
> * Introduced rzg2l_gpt_is_ch_enabled for checking enable status on both
> IO's
> * Moved enable/disable output pins from config->enable/disable.
> * Added rzg2l_gpt_cntr_need_stop() for caching prescalar/mode values.
> v6->v7:
> * Added the comment for cacheing rzg2l_gpt->state_period.
> * Fixed boundary values for pv and dc.
> * Added comment for modifying mode, prescaler, timer counter and buffer
> enable registers.
> * Fixed buffer overflow in get_state()
> * Removed unnecessary assignment of state->period value in get_state().
> * Fixed state->duty_cycle value in get_state().
> * Added a limitation for disabling the channels, when both channels used
> v5->v6:
> * Updated macros RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH and
> RZG2L_GTIOR_GTIOB_OUT_LO_END_TOGGLE_CMP_MATCH with computation
> involving FIELD_PREP macro.
> * Removed struct rzg2l_gpt_phase and started using RZG2L_GTCCR macro
> for duty_offset.
> * replaced misnomer real_period->state_period.
> * Added handling for values >= (1024 << 32) for both period
> and duty cycle.
> * Added comments for pwm {en,dis}abled by bootloader during probe.
> v4->v5:
> * Added Hardware manual details
> * Replaced the comment GTCNT->Counter
> * Removed the macros RZG2L_GPT_IO_PER_CHANNEL and chip.npwm directly
> used in probe.
> * Removed the unsed macro RZG2L_GTPR_MAX_VALUE
> * Added driver prefix for the type name and the variable.
> * Initialization of per_channel data moved from request->probe.
> * Updated clr parameter for rzg2l_gpt_modify for Start count.
> * Started using mutex and usage_count for handling shared
> period and prescalar for the 2 channels.
> * Updated the comment cycle->period.
> * Removed clk_disable from rzg2l_gpt_reset_assert_pm_disable()
> * Replaced pc->rzg2l_gpt.
> * Updated prescale calculation.
> * Moved pm_runtime_{get_sync,put} from {request,free}->{enable,disable}
> * Removed platform_set_drvdata as it is unused
> * Removed the variable pwm_enabled_by_bootloader
> * Added dev_err_probe in various probe error path.
> * Added an error message, if devm_pwmchip_add fails.
> v3->v4:
> * Changed the local variable type i from u16->u8 and prescaled_period_
> cycles from u64->u32 in calculate_prescale().
> * Replaced mul_u64_u64_div_u64()->mul_u64_u32_div()
> * Dropped the comma after the sentinel.
> * Add a variable to track pwm enabled by bootloader and added comments
> in probe().
> * Removed unnecessary rzg2l_gpt_reset_assert_pm_disable() from probe.
> * Replaced devm_clk_get()->devm_clk_get_prepared()
> * Removed devm_clk_get_optional_enabled()
> v2->v3:
> * Added Rb tag from Rob for the bindings.
> * Updated limitation section
> * Added prefix "RZG2L_" for all macros
> * Modified prescale calculation
> * Removed pwm_set_chip_data
> * Updated comment related to modifying Mode and Prescaler
> * Updated setting of prescale value in rzg2l_gpt_config()
> * Removed else branch from rzg2l_gpt_get_state()
> * removed the err label from rzg2l_gpt_apply()
> * Added devm_clk_get_optional_enabled() to retain clk on status,
> in case bootloader turns on the clk of pwm.
> * Replaced devm_reset_control_get_exclusive->devm_reset_control_get_shared
> as single reset shared between 8 channels.
> v1->v2:
> * Added '|' after 'description:' to preserve formatting.
> * Removed description for pwm_cells as it is common property.
> * Changed the reg size in example from 0xa4->0x100
> * Added Rb tag from Geert for bindings.
> * Added Limitations section
> * dropped "_MASK" from the define names.
> * used named initializer for struct phase
> * Added gpt_pwm_device into a flexible array member in rzg2l_gpt_chip
> * Revised the logic for prescale
> * Added .get_state callback
> * Improved error handling in rzg2l_gpt_apply
> * Removed .remove callback
> * Tested the driver with PWM_DEBUG enabled.
>
> RFC->v1:
> * Added Description in binding patch
> * Removed comments from reg and clock
> * replaced rzg2l_gpt_write_mask()->rzg2l_gpt_modify()
> * Added rzg2l_gpt_read() and updated macros
> * Removed dtsi patches, will send it separately
>
> RFC:
> * https://lore.kernel.org/linux-renesas-soc/20220430075915.5036-1-biju.das.jz@bp.renesas.com/T/#t
>
> Biju Das (4):
> dt-bindings: pwm: Add RZ/G2L GPT binding
> dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property
> pwm: Add support for RZ/G2L GPT
> pwm: rzg2l-gpt: Add support for gpt linking with poeg
>
> .../bindings/pwm/renesas,rzg2l-gpt.yaml | 401 +++++++++++
> drivers/pwm/Kconfig | 11 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-rzg2l-gpt.c | 637 ++++++++++++++++++
> 4 files changed, 1050 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/pwm/renesas,rzg2l-gpt.yaml
> create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
>
>
> base-commit: aa32efbe5b833a7a4d67294f32456563f328668c
> --
> 2.25.1
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-06-14 15:42 ` [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT Biju Das
@ 2024-07-30 19:54 ` Uwe Kleine-König
2024-08-02 7:02 ` Biju Das
0 siblings, 1 reply; 12+ messages in thread
From: Uwe Kleine-König @ 2024-07-30 19:54 UTC (permalink / raw)
To: Biju Das
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm, linux-renesas-soc, Prabhakar Mahadev Lad, Biju Das
[-- Attachment #1: Type: text/plain, Size: 25441 bytes --]
Hello,
I'm a bit unlucky about this driver. I have the impression it is
complicated and wonder if that is necessary because the hardware is
unusual or if we just have to spot some simplifications.
I guess another problem is that the time between two consecutive reviews
is long and I forget most things I learned about the hardware from one
to the other. While this is mostly my problem, the same problem arises
if the driver is touched later again. So I wonder if some more
documentation is needed about the relation between channels and outputs
and subchannels. If the driver only supported one output per channel, it
could be considerably simpler (I think). But I guess that would be a
practically relevant restriction??
Some simplifications spotted below.
On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit timer
> (GPT32E). It supports the following functions
> * 32 bits x 8 channels
> * Up-counting or down-counting (saw waves) or up/down-counting
> (triangle waves) for each counter.
> * Clock sources independently selectable for each channel
> * Two 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
> * Starting, stopping, clearing and up/down counters in response to input
> level comparison
> * Starting, clearing, stopping and up/down counters in response to a
> maximum of four external triggers
> * Output pin disable function by dead time error and detected
> short-circuits between output pins
> * A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
> * Enables the noise filter for input capture and external trigger
> operation
>
> Add basic pwm support for RZ/G2L GPT driver by creating separate
> logical channels for each IOs.
>
> Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> ---
> v19->v20:
> * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> * Added a comment in calculate_period_or_duty() related to overflow.
> * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> in probe(), apply() and remove() for simplification
> * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> [...]
> ---
> drivers/pwm/Kconfig | 11 +
> drivers/pwm/Makefile | 1 +
> drivers/pwm/pwm-rzg2l-gpt.c | 555 ++++++++++++++++++++++++++++++++++++
> 3 files changed, 567 insertions(+)
> create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
>
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 00a543de8f82..3d398b308e3f 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> Generic PWM framework driver for the PWM controller found on
> Rockchip SoCs.
>
> +config PWM_RZG2L_GPT
> + tristate "Renesas RZ/G2L General PWM Timer support"
> + depends on ARCH_RZG2L || COMPILE_TEST
> + depends on HAS_IOMEM
> + help
> + This driver exposes the General PWM Timer controller found in Renesas
> + RZ/G2L like chips through the PWM API.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-rzg2l-gpt.
> +
> config PWM_RZ_MTU3
> tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> depends on RZ_MTU3
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 6964ba45c795..fb9a2d9b9adb 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
> new file mode 100644
> index 000000000000..6005a689173e
> --- /dev/null
> +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> @@ -0,0 +1,555 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Renesas RZ/G2L General PWM Timer (GPT) driver
> + *
> + * Copyright (C) 2024 Renesas Electronics Corporation
> + *
> + * 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
> + *
> + * 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.
> + * - When both channels are used, disabling the channel on one stops the
> + * other.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/limits.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/pwm.h>
> +#include <linux/reset.h>
> +#include <linux/time.h>
> +#include <linux/units.h>
> +
> +#define RZG2L_GTCR 0x2c
> +#define RZG2L_GTUDDTYC 0x30
> +#define RZG2L_GTIOR 0x34
> +#define RZG2L_GTBER 0x40
> +#define RZG2L_GTCNT 0x48
> +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> +#define RZG2L_GTPR 0x64
> +
> +#define RZG2L_GTCR_CST BIT(0)
> +#define RZG2L_GTCR_MD GENMASK(18, 16)
> +#define RZG2L_GTCR_TPCS GENMASK(26, 24)
> +
> +#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0)
> +
> +#define RZG2L_GTUDDTYC_UP BIT(0)
> +#define RZG2L_GTUDDTYC_UDF BIT(1)
> +#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF)
> +
> +#define RZG2L_GTIOR_GTIOA GENMASK(4, 0)
> +#define RZG2L_GTIOR_GTIOB GENMASK(20, 16)
> +#define RZG2L_GTIOR_GTIOx(a) ((a) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
> +#define RZG2L_GTIOR_OAE BIT(8)
> +#define RZG2L_GTIOR_OBE BIT(24)
> +#define RZG2L_GTIOR_OxE(a) ((a) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE)
> +
> +#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)
> +#define RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \
> + (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE) | RZG2L_GTIOR_OBE)
> +
> +#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(a) \
> + ((a) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \
> + RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH)
> +
> +#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_GET_CH(a) ((a) / 2)
> +
> +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> +
> +struct rzg2l_gpt_chip {
> + void __iomem *mmio;
> + struct reset_control *rstc;
> + struct mutex lock; /* lock to protect shared channel resources */
> + unsigned long rate_khz;
> + u64 max_val;
> + u32 period_cycles[RZG2L_MAX_HW_CHANNELS];
> + u32 user_count[RZG2L_MAX_HW_CHANNELS];
> + u32 enable_count[RZG2L_MAX_HW_CHANNELS];
> + u8 bootloader_enabled_channels[RZG2L_MAX_PWM_CHANNELS];
Semantically bootloader_enabled_channels[x] is a bool, right?
> +};
> +
> +static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct pwm_chip *chip)
> +{
> + return pwmchip_get_drvdata(chip);
> +}
> +
> +static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm)
> +{
> + return hwpwm & 0x1;
> +}
> +
> +static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 data)
> +{
> + writel(data, rzg2l_gpt->mmio + reg);
> +}
> +
> +static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg)
> +{
> + return readl(rzg2l_gpt->mmio + reg);
> +}
> +
> +static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr,
> + u32 set)
> +{
> + rzg2l_gpt_write(rzg2l_gpt, reg,
> + (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set);
> +}
> +
> +static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt,
> + u64 period_cycles)
> +{
> + u32 prescaled_period_cycles;
> + u8 prescale;
> +
> + prescaled_period_cycles = period_cycles >> 32;
> + if (prescaled_period_cycles >= 256)
> + prescale = 5;
> + else
> + prescale = (fls(prescaled_period_cycles) + 1) / 2;
> +
> + 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);
> + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> +
> + mutex_lock(&rzg2l_gpt->lock);
> + rzg2l_gpt->user_count[ch]++;
> + mutex_unlock(&rzg2l_gpt->lock);
> +
> + return 0;
> +}
> +
> +static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> +
> + mutex_lock(&rzg2l_gpt->lock);
> + rzg2l_gpt->user_count[ch]--;
> + mutex_unlock(&rzg2l_gpt->lock);
> +}
> +
> +static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt, u8 hwpwm)
> +{
> + u8 ch = RZG2L_GET_CH(hwpwm);
> + u32 offs = RZG2L_GET_CH_OFFS(ch);
> + u32 val;
> +
> + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> + if (!(val & RZG2L_GTCR_CST))
> + return false;
> +
> + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTIOR);
> +
> + return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
> +}
> +
> +static int rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt,
> + struct pwm_device *pwm)
> +{
> + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> + u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch);
> + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> + u32 offs = RZG2L_GET_CH_OFFS(ch);
> +
> + mutex_lock(&rzg2l_gpt->lock);
> + /* Enable pin output */
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, val,
> + RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch));
> +
> + if (!rzg2l_gpt->enable_count[ch])
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, 0, RZG2L_GTCR_CST);
> +
> + rzg2l_gpt->enable_count[ch]++;
> + mutex_unlock(&rzg2l_gpt->lock);
> +
> + return 0;
> +}
> +
> +static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt,
> + struct pwm_device *pwm)
> +{
> + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> + u32 offs = RZG2L_GET_CH_OFFS(ch);
> +
> + /* Stop count, Output low on GTIOCx pin when counting stops */
> + mutex_lock(&rzg2l_gpt->lock);
> + rzg2l_gpt->enable_count[ch]--;
> +
> + if (!rzg2l_gpt->enable_count[ch])
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
> +
> + /* Disable pin output */
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, RZG2L_GTIOR_OxE(sub_ch), 0);
> + mutex_unlock(&rzg2l_gpt->lock);
> +}
> +
> +static u64 calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt, u32 val, u8 prescale)
> +{
> + u64 tmp;
> +
> + /*
> + * This cannot overflow because,
> + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
I find the notation here hard to parse. I personally would prefer:
/*
* The calculation doesn't overflow an u64 because prescale ≤ 5 and so
* tmp = val << (2 * prescale) * USEC_PER_SEC
* < 2^32 * 2^10 * 10^6
* < 2^32 * 2^10 * 2^20
* = 2^62
*/
Is it only me?
> + */
> + tmp = (u64)val << (2 * prescale);
> + tmp *= USEC_PER_SEC;
> +
> + 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);
> + int rc;
> +
> + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> + if (rc)
> + return rc;
> +
> + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> + if (state->enabled) {
> + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> + u32 offs = RZG2L_GET_CH_OFFS(ch);
> + u8 prescale;
> + u32 val;
> +
> + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
Can it happen that prescale is > 5 here?
> + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTPR);
> + state->period = calculate_period_or_duty(rzg2l_gpt, val, prescale);
> +
> + val = rzg2l_gpt_read(rzg2l_gpt,
> + offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)));
> + state->duty_cycle = calculate_period_or_duty(rzg2l_gpt, val, prescale);
> + if (state->duty_cycle > state->period)
> + state->duty_cycle = state->period;
> + }
> +
> + state->polarity = PWM_POLARITY_NORMAL;
> + pm_runtime_put(pwmchip_parent(chip));
> +
> + return 0;
> +}
> +
> +static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8 prescale)
> +{
> + return min_t(u64, (period_or_duty_cycle + (1 << (2 * prescale)) - 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)
> +{
> + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> + u64 period, duty_cycle, period_cycles, duty_cycles;
> + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> + u32 offs = RZG2L_GET_CH_OFFS(ch);
> + unsigned long pv, dc;
> + u8 prescale;
> +
> + /* Limit period/duty cycle to max value supported by the HW */
> + period = min(state->period, rzg2l_gpt->max_val);
> + period_cycles = mul_u64_u64_div_u64(period, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> +
> + /*
> + * GPT counter is shared by multiple channels, so prescale and period
> + * can NOT be modified when there are multiple channels in use with
> + * different settings.
> + */
> + if (rzg2l_gpt->user_count[ch] > 1 && period_cycles < rzg2l_gpt->period_cycles[ch])
> + return -EBUSY;
> +
> + prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_cycles);
> + pv = rzg2l_gpt_calculate_pv_or_dc(period_cycles, prescale);
> +
> + duty_cycle = min(state->duty_cycle, rzg2l_gpt->max_val);
> + duty_cycles = mul_u64_u64_div_u64(duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
These two variable names are too similar. One is in nano seconds, the
other in hw ticks. Maybe rename the latter to duty_ticks, or duty_count.
> + dc = rzg2l_gpt_calculate_pv_or_dc(duty_cycles, prescale);
If you use mul_u64_u64_div_u64() anyhow you can implement the check if
the value is too big later, that is:
#define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR) /* is this right? */
...
duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
if (duty_ticks > RZG2L_MAX_TICKS)
duty_ticks = RZG2L_MAX_TICKS;
Then you could get rid of .max_val I think.
> + /*
> + * GPT counter is shared by multiple channels, we cache the period cycles
> + * from the first enabled channel and use the same value for both
> + * channels.
> + */
> + rzg2l_gpt->period_cycles[ch] = period_cycles;
> +
> + /*
> + * 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->enable_count[ch] <= 1) {
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
Maybe instead of using the local variable offs introduce a helper à la:
static void rzg2l_gpt_ch_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u8 ch, u32 reg, u32 clr, u32 set)
{
u32 offs = RZG2L_GET_CH_OFFS(ch);
rrzg2l_gpt_modify(rzg2l_gpt, offs + reg, clr, set);
}
or define RZG2L_GTCR as:
#define RZG2L_GTCR(ch) (RZG2L_GET_CH_OFFS(ch) + 0x2c)
. I think I like the latter better.
For RZG2L_GTCCR (which depends on channel and subchannel) maybe pass
hwpwm as macro parameter. (It's a bit ugly to have different parameters
for different register offsets, but passing hwpwm if only the channel
matters is also strange. Hmm, unsure; your chance to have a strong
opinion :-)
> + /* GPT set operating mode (saw-wave up-counting) */
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_MD,
> + RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
> +
> + /* Set count direction */
> + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTUDDTYC, RZG2L_GTUDDTYC);
> +
> + /* Select count clock */
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_TPCS,
> + FIELD_PREP(RZG2L_GTCR_TPCS, prescale));
> +
> + /* Set period */
> + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTPR, pv);
> + }
> +
> + /* Set duty cycle */
> + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)),
> + dc);
> +
> + if (rzg2l_gpt->enable_count[ch] <= 1) {
> + /* Set initial value for counter */
> + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCNT, 0);
> +
> + /* Set no buffer operation */
> + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTBER, 0);
> +
> + /* Restart the counter after updating the registers */
> + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR,
> + 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;
> +
> + if (!state->enabled) {
> + if (enabled) {
> + /*
> + * Probe() sets bootloader_enabled_channels. In such case,
> + * clearing the flag will avoid errors during unbind.
> + */
> + if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm])
> + rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
> +
> + rzg2l_gpt_disable(rzg2l_gpt, pwm);
> + pm_runtime_put_sync(pwmchip_parent(chip));
> + }
> +
> + return 0;
> + }
> +
> + if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm]) {
> + /*
> + * it's already be on. Instead of reenabling in hardware
> + * just take over from the bootloader
> + */
> + rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
If .apply() is called, bootloader_enabled_channels[pwm->hwpwm] is
certainly 0 afterwards, right? I think this means it could be
simplified by only handling bootloader_enabled_channels[pwm->hwpwm] ==
true in a single place?!
> + } else {
> + if (!enabled) {
> + ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
> + if (ret)
> + return ret;
> + }
> + }
> +
> + mutex_lock(&rzg2l_gpt->lock);
> + ret = rzg2l_gpt_config(chip, pwm, state);
> + mutex_unlock(&rzg2l_gpt->lock);
> + if (ret)
> + goto err_pm_runtime_put;
So if rzg2l_gpt_config() fails you call pm_runtime_put_sync(). But the
next time .apply() is called we might still have pwm->state.enabled ==
true and so assume we're still holding a pm_runtime reference. That's a
bug, right?
> +
> + if (!enabled) {
> + ret = rzg2l_gpt_enable(rzg2l_gpt, pwm);
> + if (ret)
> + goto err_pm_runtime_put;
> + }
> +
> + return 0;
> +
> +err_pm_runtime_put:
> + pm_runtime_put_sync(pwmchip_parent(chip));
> + return ret;
> +}
> +
> +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,
> +};
> +
> +static void rzg2l_gpt_reset_assert(void *data)
> +{
> + struct pwm_chip *chip = data;
> + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> + u32 i;
> +
> + /*
> + * The below check is for making balanced PM usage count
> + * eg: boot loader is turning on PWM and probe increments the PM usage
> + * count. Before apply, if there is unbind/remove callback we need to
> + * decrement the PM usage count.
> + */
> + for (i = 0; i < chip->npwm; i++) {
> + if (rzg2l_gpt->bootloader_enabled_channels[i])
> + pm_runtime_put(pwmchip_parent(chip));
> + }
> +
> + reset_control_assert(rzg2l_gpt->rstc);
> +}
If you split this in two cleanups, and register the one for
reset_control_assert(rzg2l_gpt->rstc) earlier in .probe() the error
handling there gets easier. This would also simplify conversion to
devm_reset_control_get_exclusive_deasserted() once that lands in
mainline.
> +static int rzg2l_gpt_probe(struct platform_device *pdev)
> +{
> + struct rzg2l_gpt_chip *rzg2l_gpt;
> + struct device *dev = &pdev->dev;
> + struct pwm_chip *chip;
> + unsigned long rate;
> + struct clk *clk;
> + int ret;
> + u32 i;
> +
> + chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt));
> + if (IS_ERR(chip))
> + return PTR_ERR(chip);
> + rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> +
> + rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(rzg2l_gpt->mmio))
> + return PTR_ERR(rzg2l_gpt->mmio);
> +
> + rzg2l_gpt->rstc = devm_reset_control_get_exclusive(dev, NULL);
> + if (IS_ERR(rzg2l_gpt->rstc))
> + return dev_err_probe(dev, PTR_ERR(rzg2l_gpt->rstc),
> + "get reset failed\n");
> +
> + clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk), "cannot get clock\n");
> +
> + ret = reset_control_deassert(rzg2l_gpt->rstc);
> + if (ret)
> + return dev_err_probe(dev, ret, "cannot deassert reset control\n");
> +
> + ret = devm_pm_runtime_enable(dev);
> + if (ret)
> + goto err_reset;
> +
> + ret = pm_runtime_resume_and_get(dev);
> + if (ret)
> + goto err_reset;
> +
> + ret = devm_clk_rate_exclusive_get(dev, clk);
> + if (ret)
> + goto err_pm_put;
> +
> + rate = clk_get_rate(clk);
> + if (!rate) {
> + ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is 0");
> + goto err_pm_put;
> + }
> +
> + /*
> + * Refuse clk rates > 1 GHz to prevent overflow later for computing
> + * period and duty cycle.
> + */
> + if (rate > NSEC_PER_SEC) {
> + ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is > 1GHz");
> + goto err_pm_put;
> + }
> +
> + /*
> + * Rate is in MHz and is always integer for peripheral clk
> + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
> + * So make sure rate is multiple of 1000.
> + */
> + rzg2l_gpt->rate_khz = rate / KILO;
> + if (rzg2l_gpt->rate_khz * KILO != rate) {
> + ret = dev_err_probe(dev, -EINVAL, "rate is not multiple of 1000");
> + goto err_pm_put;
> + }
> +
> + rzg2l_gpt->max_val = div64_u64((u64)U32_MAX * USEC_PER_SEC,
> + rzg2l_gpt->rate_khz) * RZG2L_MAX_SCALE_FACTOR;
This might loose precision (not entirely sure this is relevant, still
more if you drop .max_val as suggested above).
> +
> + /*
> + * We need to keep the clock on, in case the bootloader has enabled the
> + * PWM and is running during probe().
> + */
> + for (i = 0; i < RZG2L_MAX_PWM_CHANNELS; i++) {
> + if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, i)) {
> + u8 ch = RZG2L_GET_CH(i);
> +
> + rzg2l_gpt->bootloader_enabled_channels[i] = 1;
> + rzg2l_gpt->enable_count[ch]++;
> + pm_runtime_get_sync(dev);
> + }
> + }
> +
> + pm_runtime_put(dev);
> +
> + mutex_init(&rzg2l_gpt->lock);
> + ret = devm_add_action_or_reset(dev, rzg2l_gpt_reset_assert, chip);
> + if (ret < 0)
> + return ret;
> +
> + chip->ops = &rzg2l_gpt_ops;
> + ret = devm_pwmchip_add(dev, chip);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> +
> + return 0;
> +
> +err_pm_put:
> + pm_runtime_put(dev);
A guard (à la guard(pm_runtime_resume)(dev), similar to the guards for
mutexes and spinlocks) would be elegant here and simplify error
handling. (Only if you're motivated, I wouldn't make this a precondition
for accepting your driver.)
> +err_reset:
> + reset_control_assert(rzg2l_gpt->rstc);
> + return ret;
> +}
> +
> +static const struct of_device_id rzg2l_gpt_of_table[] = {
> + { .compatible = "renesas,rzg2l-gpt", },
> + { /* Sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, rzg2l_gpt_of_table);
> +
> +static struct platform_driver rzg2l_gpt_driver = {
> + .driver = {
> + .name = "pwm-rzg2l-gpt",
> + .of_match_table = rzg2l_gpt_of_table,
> + },
> + .probe = rzg2l_gpt_probe,
> +};
> +module_platform_driver(rzg2l_gpt_driver);
> +
> +MODULE_AUTHOR("Biju Das <biju.das.jz@bp.renesas.com>");
> +MODULE_DESCRIPTION("Renesas RZ/G2L General PWM Timer (GPT) Driver");
> +MODULE_LICENSE("GPL");
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-07-30 19:54 ` Uwe Kleine-König
@ 2024-08-02 7:02 ` Biju Das
2024-08-06 6:46 ` Uwe Kleine-König
0 siblings, 1 reply; 12+ messages in thread
From: Biju Das @ 2024-08-02 7:02 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Prabhakar Mahadev Lad, biju.das.au
Hi Uwe,
Thanks for the feedback.
> -----Original Message-----
> From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> Sent: Tuesday, July 30, 2024 8:54 PM
> Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
>
> Hello,
>
> I'm a bit unlucky about this driver. I have the impression it is complicated and wonder if that is
> necessary because the hardware is unusual or if we just have to spot some simplifications.
I agree it is little bit complex driver. Once this driver is accepted, going forward, I need to support
other drivers like (Counter , ADC triggering and POEG(Output disable) support).
> I guess another problem is that the time between two consecutive reviews is long and I forget most
> things I learned about the hardware from one to the other. While this is mostly my problem, the same
> problem arises if the driver is touched later again. So I wonder if some more documentation is needed
> about the relation between channels and outputs and subchannels. If the driver only supported one
> output per channel, it could be considerably simpler (I think). But I guess that would be a
> practically relevant restriction??
Yes, one output per channel means, we cannot use POEG IP which is for short circuit protection in
switching circuits. So, we need to use both IOs in the channel.
I will add the below documentation to make it clear.
* - General PWM Timer (GPT) has 8 HW channels for PWM operations and
* each HW channel have 2 IOs.
* - Each IO is modelled as an independent PWM channel.
Please let me know is it ok with respect to the initial driver?
>
> Some simplifications spotted below.
>
> On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> > RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit
> > timer (GPT32E). It supports the following functions
> > * 32 bits x 8 channels
> > * Up-counting or down-counting (saw waves) or up/down-counting
> > (triangle waves) for each counter.
> > * Clock sources independently selectable for each channel
> > * Two 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
> > * Starting, stopping, clearing and up/down counters in response to input
> > level comparison
> > * Starting, clearing, stopping and up/down counters in response to a
> > maximum of four external triggers
> > * Output pin disable function by dead time error and detected
> > short-circuits between output pins
> > * A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
> > * Enables the noise filter for input capture and external trigger
> > operation
> >
> > Add basic pwm support for RZ/G2L GPT driver by creating separate
> > logical channels for each IOs.
> >
> > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > ---
> > v19->v20:
> > * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> > * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> > * Added a comment in calculate_period_or_duty() related to overflow.
> > * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> > in probe(), apply() and remove() for simplification
> > * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> > [...]
> > ---
> > drivers/pwm/Kconfig | 11 +
> > drivers/pwm/Makefile | 1 +
> > drivers/pwm/pwm-rzg2l-gpt.c | 555
> > ++++++++++++++++++++++++++++++++++++
> > 3 files changed, 567 insertions(+)
> > create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
> >
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > 00a543de8f82..3d398b308e3f 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> > Generic PWM framework driver for the PWM controller found on
> > Rockchip SoCs.
> >
> > +config PWM_RZG2L_GPT
> > + tristate "Renesas RZ/G2L General PWM Timer support"
> > + depends on ARCH_RZG2L || COMPILE_TEST
> > + depends on HAS_IOMEM
> > + help
> > + This driver exposes the General PWM Timer controller found in Renesas
> > + RZ/G2L like chips through the PWM API.
> > +
> > + To compile this driver as a module, choose M here: the module
> > + will be called pwm-rzg2l-gpt.
> > +
> > config PWM_RZ_MTU3
> > tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> > depends on RZ_MTU3
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > 6964ba45c795..fb9a2d9b9adb 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> > +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> > obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> > diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
> > new file mode 100644 index 000000000000..6005a689173e
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> > @@ -0,0 +1,555 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Renesas RZ/G2L General PWM Timer (GPT) driver
> > + *
> > + * Copyright (C) 2024 Renesas Electronics Corporation
> > + *
> > + * Hardware manual for this IP can be found here
> > + *
> > +https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-u
> > +sers-manual-hardware-0?language=en
> > + *
> > + * 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.
> > + * - When both channels are used, disabling the channel on one stops the
> > + * other.
> > + */
> > +
> > +#include <linux/bitfield.h>
> > +#include <linux/clk.h>
> > +#include <linux/io.h>
> > +#include <linux/limits.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/pwm.h>
> > +#include <linux/reset.h>
> > +#include <linux/time.h>
> > +#include <linux/units.h>
> > +
> > +#define RZG2L_GTCR 0x2c
> > +#define RZG2L_GTUDDTYC 0x30
> > +#define RZG2L_GTIOR 0x34
> > +#define RZG2L_GTBER 0x40
> > +#define RZG2L_GTCNT 0x48
> > +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> > +#define RZG2L_GTPR 0x64
These will be replaced as
+#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
+
+#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))
+#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
> > +
> > +#define RZG2L_GTCR_CST BIT(0)
> > +#define RZG2L_GTCR_MD GENMASK(18, 16)
> > +#define RZG2L_GTCR_TPCS GENMASK(26, 24)
> > +
> > +#define RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE FIELD_PREP(RZG2L_GTCR_MD, 0)
> > +
> > +#define RZG2L_GTUDDTYC_UP BIT(0)
> > +#define RZG2L_GTUDDTYC_UDF BIT(1)
> > +#define RZG2L_GTUDDTYC_UP_COUNTING (RZG2L_GTUDDTYC_UP | RZG2L_GTUDDTYC_UDF)
> > +
> > +#define RZG2L_GTIOR_GTIOA GENMASK(4, 0)
> > +#define RZG2L_GTIOR_GTIOB GENMASK(20, 16)
> > +#define RZG2L_GTIOR_GTIOx(a) ((a) ? RZG2L_GTIOR_GTIOB : RZG2L_GTIOR_GTIOA)
> > +#define RZG2L_GTIOR_OAE BIT(8)
> > +#define RZG2L_GTIOR_OBE BIT(24)
> > +#define RZG2L_GTIOR_OxE(a) ((a) ? RZG2L_GTIOR_OBE : RZG2L_GTIOR_OAE)
> > +
> > +#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) #define
> > +RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH \
> > + (FIELD_PREP(RZG2L_GTIOR_GTIOB, RZG2L_INIT_OUT_HI_OUT_HI_END_TOGGLE)
> > +| RZG2L_GTIOR_OBE)
> > +
> > +#define RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(a) \
> > + ((a) ? RZG2L_GTIOR_GTIOB_OUT_HI_END_TOGGLE_CMP_MATCH : \
> > + RZG2L_GTIOR_GTIOA_OUT_HI_END_TOGGLE_CMP_MATCH)
> > +
> > +#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_GET_CH(a) ((a) / 2)
> > +
> > +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> > +
> > +struct rzg2l_gpt_chip {
> > + void __iomem *mmio;
> > + struct reset_control *rstc;
> > + struct mutex lock; /* lock to protect shared channel resources */
> > + unsigned long rate_khz;
> > + u64 max_val;
> > + u32 period_cycles[RZG2L_MAX_HW_CHANNELS];
> > + u32 user_count[RZG2L_MAX_HW_CHANNELS];
> > + u32 enable_count[RZG2L_MAX_HW_CHANNELS];
> > + u8 bootloader_enabled_channels[RZG2L_MAX_PWM_CHANNELS];
>
> Semantically bootloader_enabled_channels[x] is a bool, right?
OK will change it to bool.
>
> > +};
> > +
> > +static inline struct rzg2l_gpt_chip *to_rzg2l_gpt_chip(struct
> > +pwm_chip *chip) {
> > + return pwmchip_get_drvdata(chip);
> > +}
> > +
> > +static inline unsigned int rzg2l_gpt_subchannel(unsigned int hwpwm) {
> > + return hwpwm & 0x1;
> > +}
> > +
> > +static void rzg2l_gpt_write(struct rzg2l_gpt_chip *rzg2l_gpt, u32
> > +reg, u32 data) {
> > + writel(data, rzg2l_gpt->mmio + reg); }
> > +
> > +static u32 rzg2l_gpt_read(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg)
> > +{
> > + return readl(rzg2l_gpt->mmio + reg); }
> > +
> > +static void rzg2l_gpt_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u32 reg, u32 clr,
> > + u32 set)
> > +{
> > + rzg2l_gpt_write(rzg2l_gpt, reg,
> > + (rzg2l_gpt_read(rzg2l_gpt, reg) & ~clr) | set); }
> > +
> > +static u8 rzg2l_gpt_calculate_prescale(struct rzg2l_gpt_chip *rzg2l_gpt,
> > + u64 period_cycles)
> > +{
> > + u32 prescaled_period_cycles;
> > + u8 prescale;
> > +
> > + prescaled_period_cycles = period_cycles >> 32;
> > + if (prescaled_period_cycles >= 256)
> > + prescale = 5;
> > + else
> > + prescale = (fls(prescaled_period_cycles) + 1) / 2;
> > +
> > + 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);
> > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > +
> > + mutex_lock(&rzg2l_gpt->lock);
> > + rzg2l_gpt->user_count[ch]++;
> > + mutex_unlock(&rzg2l_gpt->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static void rzg2l_gpt_free(struct pwm_chip *chip, struct pwm_device
> > +*pwm) {
> > + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > +
> > + mutex_lock(&rzg2l_gpt->lock);
> > + rzg2l_gpt->user_count[ch]--;
> > + mutex_unlock(&rzg2l_gpt->lock);
> > +}
> > +
> > +static bool rzg2l_gpt_is_ch_enabled(struct rzg2l_gpt_chip *rzg2l_gpt,
> > +u8 hwpwm) {
> > + u8 ch = RZG2L_GET_CH(hwpwm);
> > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > + u32 val;
> > +
> > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > + if (!(val & RZG2L_GTCR_CST))
> > + return false;
> > +
> > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTIOR);
> > +
> > + return val & RZG2L_GTIOR_OxE(rzg2l_gpt_subchannel(hwpwm));
> > +}
> > +
> > +static int rzg2l_gpt_enable(struct rzg2l_gpt_chip *rzg2l_gpt,
> > + struct pwm_device *pwm)
> > +{
> > + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> > + u32 val = RZG2L_GTIOR_GTIOx(sub_ch) | RZG2L_GTIOR_OxE(sub_ch);
> > + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > +
> > + mutex_lock(&rzg2l_gpt->lock);
> > + /* Enable pin output */
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, val,
> > + RZG2L_GTIOR_GTIOx_OUT_HI_END_TOGGLE_CMP_MATCH(sub_ch));
> > +
> > + if (!rzg2l_gpt->enable_count[ch])
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, 0, RZG2L_GTCR_CST);
> > +
> > + rzg2l_gpt->enable_count[ch]++;
> > + mutex_unlock(&rzg2l_gpt->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static void rzg2l_gpt_disable(struct rzg2l_gpt_chip *rzg2l_gpt,
> > + struct pwm_device *pwm)
> > +{
> > + u8 sub_ch = rzg2l_gpt_subchannel(pwm->hwpwm);
> > + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > +
> > + /* Stop count, Output low on GTIOCx pin when counting stops */
> > + mutex_lock(&rzg2l_gpt->lock);
> > + rzg2l_gpt->enable_count[ch]--;
> > +
> > + if (!rzg2l_gpt->enable_count[ch])
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
> > +
> > + /* Disable pin output */
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTIOR, RZG2L_GTIOR_OxE(sub_ch), 0);
> > + mutex_unlock(&rzg2l_gpt->lock);
> > +}
> > +
> > +static u64 calculate_period_or_duty(struct rzg2l_gpt_chip *rzg2l_gpt,
> > +u32 val, u8 prescale) {
> > + u64 tmp;
> > +
> > + /*
> > + * This cannot overflow because,
> > + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
>
> I find the notation here hard to parse. I personally would prefer:
>
> /*
> * The calculation doesn't overflow an u64 because prescale ≤ 5 and so
> * tmp = val << (2 * prescale) * USEC_PER_SEC
> * < 2^32 * 2^10 * 10^6
> * < 2^32 * 2^10 * 2^20
> * = 2^62
> */
>
> Is it only me?
Agreed, will change like this as it is clear to every one.
>
> > + */
> > + tmp = (u64)val << (2 * prescale);
> > + tmp *= USEC_PER_SEC;
> > +
> > + 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);
> > + int rc;
> > +
> > + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > + if (rc)
> > + return rc;
> > +
> > + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> > + if (state->enabled) {
> > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > + u8 prescale;
> > + u32 val;
> > +
> > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
>
> Can it happen that prescale is > 5 here?
Yes, if bootloader wrongly set it to 6 or 7. I will add a check in
Probe and forcefully set to 5, if that the case. Is it ok?
+ if (prescale > 5) {
+ dev_warn(dev, "Invalid prescale %d > 5, force setting to 5", prescale);
+ /* Set prescale value of 5. */
+ rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
+ FIELD_PREP(RZG2L_GTCR_TPCS, 5));
+ }
>
> > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTPR);
> > + state->period = calculate_period_or_duty(rzg2l_gpt, val, prescale);
> > +
> > + val = rzg2l_gpt_read(rzg2l_gpt,
> > + offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)));
> > + state->duty_cycle = calculate_period_or_duty(rzg2l_gpt, val, prescale);
> > + if (state->duty_cycle > state->period)
> > + state->duty_cycle = state->period;
> > + }
> > +
> > + state->polarity = PWM_POLARITY_NORMAL;
> > + pm_runtime_put(pwmchip_parent(chip));
> > +
> > + return 0;
> > +}
> > +
> > +static u32 rzg2l_gpt_calculate_pv_or_dc(u64 period_or_duty_cycle, u8
> > +prescale) {
> > + return min_t(u64, (period_or_duty_cycle + (1 << (2 * prescale)) - 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) {
> > + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> > + u64 period, duty_cycle, period_cycles, duty_cycles;
> > + u8 ch = RZG2L_GET_CH(pwm->hwpwm);
> > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > + unsigned long pv, dc;
> > + u8 prescale;
> > +
> > + /* Limit period/duty cycle to max value supported by the HW */
> > + period = min(state->period, rzg2l_gpt->max_val);
> > + period_cycles = mul_u64_u64_div_u64(period, rzg2l_gpt->rate_khz,
> > +USEC_PER_SEC);
> > +
> > + /*
> > + * GPT counter is shared by multiple channels, so prescale and period
> > + * can NOT be modified when there are multiple channels in use with
> > + * different settings.
> > + */
> > + if (rzg2l_gpt->user_count[ch] > 1 && period_cycles < rzg2l_gpt->period_cycles[ch])
> > + return -EBUSY;
> > +
> > + prescale = rzg2l_gpt_calculate_prescale(rzg2l_gpt, period_cycles);
> > + pv = rzg2l_gpt_calculate_pv_or_dc(period_cycles, prescale);
> > +
> > + duty_cycle = min(state->duty_cycle, rzg2l_gpt->max_val);
> > + duty_cycles = mul_u64_u64_div_u64(duty_cycle, rzg2l_gpt->rate_khz,
> > +USEC_PER_SEC);
>
> These two variable names are too similar. One is in nano seconds, the other in hw ticks. Maybe rename
> the latter to duty_ticks, or duty_count.
OK. Will change period_cycles->period_ticks as well.
>
> > + dc = rzg2l_gpt_calculate_pv_or_dc(duty_cycles, prescale);
>
> If you use mul_u64_u64_div_u64() anyhow you can implement the check if the value is too big later,
> that is:
>
> #define RZG2L_MAX_TICKS ((u64)U32_MAX * RZG2L_MAX_SCALE_FACTOR) /* is this right? */
Yes.
> ...
> duty_ticks = mul_u64_u64_div_u64(state->duty_cycle, rzg2l_gpt->rate_khz, USEC_PER_SEC);
> if (duty_ticks > RZG2L_MAX_TICKS)
> duty_ticks = RZG2L_MAX_TICKS;
>
> Then you could get rid of .max_val I think.
Agreed, it can be dropped.
>
> > + /*
> > + * GPT counter is shared by multiple channels, we cache the period cycles
> > + * from the first enabled channel and use the same value for both
> > + * channels.
> > + */
> > + rzg2l_gpt->period_cycles[ch] = period_cycles;
> > +
> > + /*
> > + * 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->enable_count[ch] <= 1) {
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_CST, 0);
>
> Maybe instead of using the local variable offs introduce a helper à la:
>
> static void rzg2l_gpt_ch_modify(struct rzg2l_gpt_chip *rzg2l_gpt, u8 ch, u32 reg, u32 clr, u32
> set)
> {
> u32 offs = RZG2L_GET_CH_OFFS(ch);
>
> rrzg2l_gpt_modify(rzg2l_gpt, offs + reg, clr, set);
> }
>
> or define RZG2L_GTCR as:
>
> #define RZG2L_GTCR(ch) (RZG2L_GET_CH_OFFS(ch) + 0x2c)
>
> . I think I like the latter better.
OK.
>
> For RZG2L_GTCCR (which depends on channel and subchannel) maybe pass hwpwm as macro parameter. (It's a
> bit ugly to have different parameters for different register offsets, but passing hwpwm if only the
> channel matters is also strange. Hmm, unsure; your chance to have a strong opinion :-)
#define RZG2L_GTCCR(ch, sub_ch) (0x4c + RZG2L_GET_CH_OFFS(ch) + 4 * (sub_ch))
I will use this macro here.
>
> > + /* GPT set operating mode (saw-wave up-counting) */
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_MD,
> > + RZG2L_GTCR_MD_SAW_WAVE_PWM_MODE);
> > +
> > + /* Set count direction */
> > + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTUDDTYC, RZG2L_GTUDDTYC);
> > +
> > + /* Select count clock */
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR, RZG2L_GTCR_TPCS,
> > + FIELD_PREP(RZG2L_GTCR_TPCS, prescale));
> > +
> > + /* Set period */
> > + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTPR, pv);
> > + }
> > +
> > + /* Set duty cycle */
> > + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCCR(rzg2l_gpt_subchannel(pwm->hwpwm)),
> > + dc);
> > +
> > + if (rzg2l_gpt->enable_count[ch] <= 1) {
> > + /* Set initial value for counter */
> > + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTCNT, 0);
> > +
> > + /* Set no buffer operation */
> > + rzg2l_gpt_write(rzg2l_gpt, offs + RZG2L_GTBER, 0);
> > +
> > + /* Restart the counter after updating the registers */
> > + rzg2l_gpt_modify(rzg2l_gpt, offs + RZG2L_GTCR,
> > + 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;
> > +
> > + if (!state->enabled) {
> > + if (enabled) {
> > + /*
> > + * Probe() sets bootloader_enabled_channels. In such case,
> > + * clearing the flag will avoid errors during unbind.
> > + */
> > + if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm])
> > + rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
> > +
> > + rzg2l_gpt_disable(rzg2l_gpt, pwm);
> > + pm_runtime_put_sync(pwmchip_parent(chip));
> > + }
> > +
> > + return 0;
> > + }
> > +
> > + if (rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm]) {
> > + /*
> > + * it's already be on. Instead of reenabling in hardware
> > + * just take over from the bootloader
> > + */
> > + rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = 0;
>
> If .apply() is called, bootloader_enabled_channels[pwm->hwpwm] is certainly 0 afterwards, right? I
> think this means it could be simplified by only handling bootloader_enabled_channels[pwm->hwpwm] ==
> true in a single place?!
Yes will add at the top to handle it in single place.
+ /*
+ * Probe() sets bootloader_enabled_channels. In such case,
+ * clearing the flag will avoid errors during unbind.
+ */
+ if (enabled && rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm])
+ rzg2l_gpt->bootloader_enabled_channels[pwm->hwpwm] = false;
+
>
> > + } else {
> > + if (!enabled) {
> > + ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > + if (ret)
> > + return ret;
> > + }
> > + }
> > +
> > + mutex_lock(&rzg2l_gpt->lock);
> > + ret = rzg2l_gpt_config(chip, pwm, state);
> > + mutex_unlock(&rzg2l_gpt->lock);
> > + if (ret)
> > + goto err_pm_runtime_put;
>
> So if rzg2l_gpt_config() fails you call pm_runtime_put_sync(). But the next time .apply() is called we
> might still have pwm->state.enabled == true and so assume we're still holding a pm_runtime reference.
> That's a bug, right?
You are right, I should not drop pm_run time reference during error.
With enable()->acquire pm_runtime reference
disable()-->release the pm_runtime reference
For config error case, don't call pm_runtime_put_sync().
>
> > +
> > + if (!enabled) {
> > + ret = rzg2l_gpt_enable(rzg2l_gpt, pwm);
> > + if (ret)
> > + goto err_pm_runtime_put;
> > + }
> > +
> > + return 0;
> > +
> > +err_pm_runtime_put:
> > + pm_runtime_put_sync(pwmchip_parent(chip));
> > + return ret;
> > +}
> > +
> > +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,
> > +};
> > +
> > +static void rzg2l_gpt_reset_assert(void *data) {
> > + struct pwm_chip *chip = data;
> > + struct rzg2l_gpt_chip *rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> > + u32 i;
> > +
> > + /*
> > + * The below check is for making balanced PM usage count
> > + * eg: boot loader is turning on PWM and probe increments the PM usage
> > + * count. Before apply, if there is unbind/remove callback we need to
> > + * decrement the PM usage count.
> > + */
> > + for (i = 0; i < chip->npwm; i++) {
> > + if (rzg2l_gpt->bootloader_enabled_channels[i])
> > + pm_runtime_put(pwmchip_parent(chip));
> > + }
> > +
> > + reset_control_assert(rzg2l_gpt->rstc);
> > +}
>
> If you split this in two cleanups, and register the one for
> reset_control_assert(rzg2l_gpt->rstc) earlier in .probe() the error handling there gets easier. This
> would also simplify conversion to
> devm_reset_control_get_exclusive_deasserted() once that lands in mainline.
Agreed.
>
> > +static int rzg2l_gpt_probe(struct platform_device *pdev) {
> > + struct rzg2l_gpt_chip *rzg2l_gpt;
> > + struct device *dev = &pdev->dev;
> > + struct pwm_chip *chip;
> > + unsigned long rate;
> > + struct clk *clk;
> > + int ret;
> > + u32 i;
> > +
> > + chip = devm_pwmchip_alloc(dev, RZG2L_MAX_PWM_CHANNELS, sizeof(*rzg2l_gpt));
> > + if (IS_ERR(chip))
> > + return PTR_ERR(chip);
> > + rzg2l_gpt = to_rzg2l_gpt_chip(chip);
> > +
> > + rzg2l_gpt->mmio = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(rzg2l_gpt->mmio))
> > + return PTR_ERR(rzg2l_gpt->mmio);
> > +
> > + rzg2l_gpt->rstc = devm_reset_control_get_exclusive(dev, NULL);
> > + if (IS_ERR(rzg2l_gpt->rstc))
> > + return dev_err_probe(dev, PTR_ERR(rzg2l_gpt->rstc),
> > + "get reset failed\n");
> > +
> > + clk = devm_clk_get(dev, NULL);
> > + if (IS_ERR(clk))
> > + return dev_err_probe(dev, PTR_ERR(clk), "cannot get clock\n");
> > +
> > + ret = reset_control_deassert(rzg2l_gpt->rstc);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "cannot deassert reset control\n");
> > +
> > + ret = devm_pm_runtime_enable(dev);
> > + if (ret)
> > + goto err_reset;
> > +
> > + ret = pm_runtime_resume_and_get(dev);
> > + if (ret)
> > + goto err_reset;
> > +
> > + ret = devm_clk_rate_exclusive_get(dev, clk);
> > + if (ret)
> > + goto err_pm_put;
> > +
> > + rate = clk_get_rate(clk);
> > + if (!rate) {
> > + ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is 0");
> > + goto err_pm_put;
> > + }
> > +
> > + /*
> > + * Refuse clk rates > 1 GHz to prevent overflow later for computing
> > + * period and duty cycle.
> > + */
> > + if (rate > NSEC_PER_SEC) {
> > + ret = dev_err_probe(dev, -EINVAL, "gpt clk rate is > 1GHz");
> > + goto err_pm_put;
> > + }
> > +
> > + /*
> > + * Rate is in MHz and is always integer for peripheral clk
> > + * 2^32 * 2^10 (prescalar) * 10^6 (rate_khz) < 2^64
> > + * So make sure rate is multiple of 1000.
> > + */
> > + rzg2l_gpt->rate_khz = rate / KILO;
> > + if (rzg2l_gpt->rate_khz * KILO != rate) {
> > + ret = dev_err_probe(dev, -EINVAL, "rate is not multiple of 1000");
> > + goto err_pm_put;
> > + }
> > +
> > + rzg2l_gpt->max_val = div64_u64((u64)U32_MAX * USEC_PER_SEC,
> > + rzg2l_gpt->rate_khz) * RZG2L_MAX_SCALE_FACTOR;
>
> This might loose precision (not entirely sure this is relevant, still more if you drop .max_val as
> suggested above).
Since the rate is 100MHz, in this case we don't lose any precision.
>
> > +
> > + /*
> > + * We need to keep the clock on, in case the bootloader has enabled the
> > + * PWM and is running during probe().
> > + */
> > + for (i = 0; i < RZG2L_MAX_PWM_CHANNELS; i++) {
> > + if (rzg2l_gpt_is_ch_enabled(rzg2l_gpt, i)) {
> > + u8 ch = RZG2L_GET_CH(i);
> > +
> > + rzg2l_gpt->bootloader_enabled_channels[i] = 1;
> > + rzg2l_gpt->enable_count[ch]++;
> > + pm_runtime_get_sync(dev);
> > + }
> > + }
> > +
> > + pm_runtime_put(dev);
> > +
> > + mutex_init(&rzg2l_gpt->lock);
> > + ret = devm_add_action_or_reset(dev, rzg2l_gpt_reset_assert, chip);
> > + if (ret < 0)
> > + return ret;
> > +
> > + chip->ops = &rzg2l_gpt_ops;
> > + ret = devm_pwmchip_add(dev, chip);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> > +
> > + return 0;
> > +
> > +err_pm_put:
> > + pm_runtime_put(dev);
>
> A guard (à la guard(pm_runtime_resume)(dev), similar to the guards for mutexes and spinlocks) would be
> elegant here and simplify error handling. (Only if you're motivated, I wouldn't make this a
> precondition for accepting your driver.)
We normally backport these drivers to CIP kernel(SLTSI) 5.10 and 6.1 once it hits mainline and then linux-rc series
From that angle, I would like to use mutexes and spinlocks, if it is ok for you.
Later I will send patch for using guards for pm_runtime_resume, mutexes and spinlocks which I cannot
backport to 5.10 and 6.1 kernel.
Please let me know is it ok for you?
[1] https://git.kernel.org/pub/scm/linux/kernel/git/cip/linux-cip.git/log/?h=linux-6.1.y-cip
Cheers
Biju
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-08-02 7:02 ` Biju Das
@ 2024-08-06 6:46 ` Uwe Kleine-König
2024-08-06 8:35 ` Biju Das
0 siblings, 1 reply; 12+ messages in thread
From: Uwe Kleine-König @ 2024-08-06 6:46 UTC (permalink / raw)
To: Biju Das
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Prabhakar Mahadev Lad, biju.das.au
[-- Attachment #1: Type: text/plain, Size: 10832 bytes --]
Hello Biju,
On Fri, Aug 02, 2024 at 07:02:19AM +0000, Biju Das wrote:
> > -----Original Message-----
> > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > Sent: Tuesday, July 30, 2024 8:54 PM
> > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> >
> > Hello,
> >
> > I'm a bit unlucky about this driver. I have the impression it is complicated and wonder if that is
> > necessary because the hardware is unusual or if we just have to spot some simplifications.
>
> I agree it is little bit complex driver. Once this driver is accepted, going forward, I need to support
> other drivers like (Counter , ADC triggering and POEG(Output disable) support).
>
> > I guess another problem is that the time between two consecutive reviews is long and I forget most
> > things I learned about the hardware from one to the other. While this is mostly my problem, the same
> > problem arises if the driver is touched later again. So I wonder if some more documentation is needed
> > about the relation between channels and outputs and subchannels. If the driver only supported one
> > output per channel, it could be considerably simpler (I think). But I guess that would be a
> > practically relevant restriction??
>
> Yes, one output per channel means, we cannot use POEG IP which is for short circuit protection in
> switching circuits. So, we need to use both IOs in the channel.
>
> I will add the below documentation to make it clear.
>
> * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
> * each HW channel have 2 IOs.
> * - Each IO is modelled as an independent PWM channel.
>
> Please let me know is it ok with respect to the initial driver?
looks fine.
> > Some simplifications spotted below.
> >
> > On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> > > RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit
> > > timer (GPT32E). It supports the following functions
> > > * 32 bits x 8 channels
> > > * Up-counting or down-counting (saw waves) or up/down-counting
> > > (triangle waves) for each counter.
> > > * Clock sources independently selectable for each channel
> > > * Two 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
> > > * Starting, stopping, clearing and up/down counters in response to input
> > > level comparison
> > > * Starting, clearing, stopping and up/down counters in response to a
> > > maximum of four external triggers
> > > * Output pin disable function by dead time error and detected
> > > short-circuits between output pins
> > > * A/D converter start triggers can be generated (GPT32E0 to GPT32E3)
> > > * Enables the noise filter for input capture and external trigger
> > > operation
> > >
> > > Add basic pwm support for RZ/G2L GPT driver by creating separate
> > > logical channels for each IOs.
> > >
> > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > ---
> > > v19->v20:
> > > * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> > > * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> > > * Added a comment in calculate_period_or_duty() related to overflow.
> > > * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> > > in probe(), apply() and remove() for simplification
> > > * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> > > [...]
> > > ---
> > > drivers/pwm/Kconfig | 11 +
> > > drivers/pwm/Makefile | 1 +
> > > drivers/pwm/pwm-rzg2l-gpt.c | 555
> > > ++++++++++++++++++++++++++++++++++++
> > > 3 files changed, 567 insertions(+)
> > > create mode 100644 drivers/pwm/pwm-rzg2l-gpt.c
> > >
> > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > > 00a543de8f82..3d398b308e3f 100644
> > > --- a/drivers/pwm/Kconfig
> > > +++ b/drivers/pwm/Kconfig
> > > @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> > > Generic PWM framework driver for the PWM controller found on
> > > Rockchip SoCs.
> > >
> > > +config PWM_RZG2L_GPT
> > > + tristate "Renesas RZ/G2L General PWM Timer support"
> > > + depends on ARCH_RZG2L || COMPILE_TEST
> > > + depends on HAS_IOMEM
> > > + help
> > > + This driver exposes the General PWM Timer controller found in Renesas
> > > + RZ/G2L like chips through the PWM API.
> > > +
> > > + To compile this driver as a module, choose M here: the module
> > > + will be called pwm-rzg2l-gpt.
> > > +
> > > config PWM_RZ_MTU3
> > > tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> > > depends on RZ_MTU3
> > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > > 6964ba45c795..fb9a2d9b9adb 100644
> > > --- a/drivers/pwm/Makefile
> > > +++ b/drivers/pwm/Makefile
> > > @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> > > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> > > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> > > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> > > +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> > > obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> > > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> > > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> > > diff --git a/drivers/pwm/pwm-rzg2l-gpt.c b/drivers/pwm/pwm-rzg2l-gpt.c
> > > new file mode 100644 index 000000000000..6005a689173e
> > > --- /dev/null
> > > +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> > > @@ -0,0 +1,555 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Renesas RZ/G2L General PWM Timer (GPT) driver
> > > + *
> > > + * Copyright (C) 2024 Renesas Electronics Corporation
> > > + *
> > > + * Hardware manual for this IP can be found here
> > > + *
> > > +https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-group-u
> > > +sers-manual-hardware-0?language=en
> > > + *
> > > + * 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.
> > > + * - When both channels are used, disabling the channel on one stops the
> > > + * other.
> > > + */
> > > +
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/io.h>
> > > +#include <linux/limits.h>
> > > +#include <linux/module.h>
> > > +#include <linux/of.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_runtime.h>
> > > +#include <linux/pwm.h>
> > > +#include <linux/reset.h>
> > > +#include <linux/time.h>
> > > +#include <linux/units.h>
> > > +
> > > +#define RZG2L_GTCR 0x2c
> > > +#define RZG2L_GTUDDTYC 0x30
> > > +#define RZG2L_GTIOR 0x34
> > > +#define RZG2L_GTBER 0x40
> > > +#define RZG2L_GTCNT 0x48
> > > +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> > > +#define RZG2L_GTPR 0x64
>
> These will be replaced as
>
> +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> +
> +#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))
> +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
I like this better, thanks.
> > > + */
> > > + tmp = (u64)val << (2 * prescale);
> > > + tmp *= USEC_PER_SEC;
> > > +
> > > + 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);
> > > + int rc;
> > > +
> > > + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > > + if (rc)
> > > + return rc;
> > > +
> > > + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> > > + if (state->enabled) {
> > > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > > + u8 prescale;
> > > + u32 val;
> > > +
> > > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > > + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
> >
> > Can it happen that prescale is > 5 here?
>
> Yes, if bootloader wrongly set it to 6 or 7. I will add a check in
> Probe and forcefully set to 5, if that the case. Is it ok?
>
> + if (prescale > 5) {
> + dev_warn(dev, "Invalid prescale %d > 5, force setting to 5", prescale);
> + /* Set prescale value of 5. */
> + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
> + FIELD_PREP(RZG2L_GTCR_TPCS, 5));
> + }
I wouldn't write back the 5 then. Just assume that the value read back
was 5. (Well unless the hardware behaves according to the normal formula
that applies for prescale ≤ 5, then it might make sense to continue with
the read value.)
> > > +err_pm_put:
> > > + pm_runtime_put(dev);
> >
> > A guard (à la guard(pm_runtime_resume)(dev), similar to the guards for mutexes and spinlocks) would be
> > elegant here and simplify error handling. (Only if you're motivated, I wouldn't make this a
> > precondition for accepting your driver.)
>
> We normally backport these drivers to CIP kernel(SLTSI) 5.10 and 6.1 once it hits mainline and then linux-rc series
> From that angle, I would like to use mutexes and spinlocks, if it is ok for you.
>
> Later I will send patch for using guards for pm_runtime_resume, mutexes and spinlocks which I cannot
> backport to 5.10 and 6.1 kernel.
>
> Please let me know is it ok for you?
sure.
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-08-06 6:46 ` Uwe Kleine-König
@ 2024-08-06 8:35 ` Biju Das
2024-08-06 15:12 ` Uwe Kleine-König
0 siblings, 1 reply; 12+ messages in thread
From: Biju Das @ 2024-08-06 8:35 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Prabhakar Mahadev Lad, biju.das.au
Hi Uwe,
Thanks for the feedback.
> -----Original Message-----
> From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> Sent: Tuesday, August 6, 2024 7:47 AM
> Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
>
> Hello Biju,
>
> On Fri, Aug 02, 2024 at 07:02:19AM +0000, Biju Das wrote:
> > > -----Original Message-----
> > > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > > Sent: Tuesday, July 30, 2024 8:54 PM
> > > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> > >
> > > Hello,
> > >
> > > I'm a bit unlucky about this driver. I have the impression it is
> > > complicated and wonder if that is necessary because the hardware is unusual or if we just have to
> spot some simplifications.
> >
> > I agree it is little bit complex driver. Once this driver is accepted,
> > going forward, I need to support other drivers like (Counter , ADC triggering and POEG(Output
> disable) support).
> >
> > > I guess another problem is that the time between two consecutive
> > > reviews is long and I forget most things I learned about the
> > > hardware from one to the other. While this is mostly my problem, the
> > > same problem arises if the driver is touched later again. So I
> > > wonder if some more documentation is needed about the relation
> > > between channels and outputs and subchannels. If the driver only supported one output per channel,
> it could be considerably simpler (I think). But I guess that would be a practically relevant
> restriction??
> >
> > Yes, one output per channel means, we cannot use POEG IP which is for
> > short circuit protection in switching circuits. So, we need to use both IOs in the channel.
> >
> > I will add the below documentation to make it clear.
> >
> > * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
> > * each HW channel have 2 IOs.
> > * - Each IO is modelled as an independent PWM channel.
> >
> > Please let me know is it ok with respect to the initial driver?
>
> looks fine.
>
> > > Some simplifications spotted below.
> > >
> > > On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> > > > RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit
> > > > timer (GPT32E). It supports the following functions
> > > > * 32 bits x 8 channels
> > > > * Up-counting or down-counting (saw waves) or up/down-counting
> > > > (triangle waves) for each counter.
> > > > * Clock sources independently selectable for each channel
> > > > * Two 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
> > > > * Starting, stopping, clearing and up/down counters in response to input
> > > > level comparison
> > > > * Starting, clearing, stopping and up/down counters in response to a
> > > > maximum of four external triggers
> > > > * Output pin disable function by dead time error and detected
> > > > short-circuits between output pins
> > > > * A/D converter start triggers can be generated (GPT32E0 to
> > > > GPT32E3)
> > > > * Enables the noise filter for input capture and external trigger
> > > > operation
> > > >
> > > > Add basic pwm support for RZ/G2L GPT driver by creating separate
> > > > logical channels for each IOs.
> > > >
> > > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > > ---
> > > > v19->v20:
> > > > * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> > > > * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> > > > * Added a comment in calculate_period_or_duty() related to overflow.
> > > > * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> > > > in probe(), apply() and remove() for simplification
> > > > * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> > > > [...]
> > > > ---
> > > > drivers/pwm/Kconfig | 11 +
> > > > drivers/pwm/Makefile | 1 +
> > > > drivers/pwm/pwm-rzg2l-gpt.c | 555
> > > > ++++++++++++++++++++++++++++++++++++
> > > > 3 files changed, 567 insertions(+) create mode 100644
> > > > drivers/pwm/pwm-rzg2l-gpt.c
> > > >
> > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > > > 00a543de8f82..3d398b308e3f 100644
> > > > --- a/drivers/pwm/Kconfig
> > > > +++ b/drivers/pwm/Kconfig
> > > > @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> > > > Generic PWM framework driver for the PWM controller found on
> > > > Rockchip SoCs.
> > > >
> > > > +config PWM_RZG2L_GPT
> > > > + tristate "Renesas RZ/G2L General PWM Timer support"
> > > > + depends on ARCH_RZG2L || COMPILE_TEST
> > > > + depends on HAS_IOMEM
> > > > + help
> > > > + This driver exposes the General PWM Timer controller found in Renesas
> > > > + RZ/G2L like chips through the PWM API.
> > > > +
> > > > + To compile this driver as a module, choose M here: the module
> > > > + will be called pwm-rzg2l-gpt.
> > > > +
> > > > config PWM_RZ_MTU3
> > > > tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> > > > depends on RZ_MTU3
> > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > > > 6964ba45c795..fb9a2d9b9adb 100644
> > > > --- a/drivers/pwm/Makefile
> > > > +++ b/drivers/pwm/Makefile
> > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> > > > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> > > > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> > > > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> > > > +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> > > > obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> > > > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> > > > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> > > > diff --git a/drivers/pwm/pwm-rzg2l-gpt.c
> > > > b/drivers/pwm/pwm-rzg2l-gpt.c new file mode 100644 index
> > > > 000000000000..6005a689173e
> > > > --- /dev/null
> > > > +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> > > > @@ -0,0 +1,555 @@
> > > > +// SPDX-License-Identifier: GPL-2.0
> > > > +/*
> > > > + * Renesas RZ/G2L General PWM Timer (GPT) driver
> > > > + *
> > > > + * Copyright (C) 2024 Renesas Electronics Corporation
> > > > + *
> > > > + * Hardware manual for this IP can be found here
> > > > + *
> > > > +https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-gro
> > > > +up-u sers-manual-hardware-0?language=en
> > > > + *
> > > > + * 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.
> > > > + * - When both channels are used, disabling the channel on one stops the
> > > > + * other.
> > > > + */
> > > > +
> > > > +#include <linux/bitfield.h>
> > > > +#include <linux/clk.h>
> > > > +#include <linux/io.h>
> > > > +#include <linux/limits.h>
> > > > +#include <linux/module.h>
> > > > +#include <linux/of.h>
> > > > +#include <linux/platform_device.h> #include <linux/pm_runtime.h>
> > > > +#include <linux/pwm.h> #include <linux/reset.h> #include
> > > > +<linux/time.h> #include <linux/units.h>
> > > > +
> > > > +#define RZG2L_GTCR 0x2c
> > > > +#define RZG2L_GTUDDTYC 0x30
> > > > +#define RZG2L_GTIOR 0x34
> > > > +#define RZG2L_GTBER 0x40
> > > > +#define RZG2L_GTCNT 0x48
> > > > +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> > > > +#define RZG2L_GTPR 0x64
> >
> > These will be replaced as
> >
> > +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> > +
> > +#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))
> > +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
>
> I like this better, thanks.
>
> > > > + */
> > > > + tmp = (u64)val << (2 * prescale);
> > > > + tmp *= USEC_PER_SEC;
> > > > +
> > > > + 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);
> > > > + int rc;
> > > > +
> > > > + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > > > + if (rc)
> > > > + return rc;
> > > > +
> > > > + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> > > > + if (state->enabled) {
> > > > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > > > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > > > + u8 prescale;
> > > > + u32 val;
> > > > +
> > > > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > > > + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
> > >
> > > Can it happen that prescale is > 5 here?
> >
> > Yes, if bootloader wrongly set it to 6 or 7. I will add a check in
> > Probe and forcefully set to 5, if that the case. Is it ok?
>
> >
> > + if (prescale > 5) {
> > + dev_warn(dev, "Invalid prescale %d > 5, force setting to 5",
> prescale);
> > + /* Set prescale value of 5. */
> > + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
> > + FIELD_PREP(RZG2L_GTCR_TPCS, 5));
> > + }
>
> I wouldn't write back the 5 then. Just assume that the value read back was 5. (Well unless the
> hardware behaves according to the normal formula that applies for prescale ≤ 5, then it might make
> sense to continue with the read value.)
OK, will just print the warning.
dev_warn(dev, "Invalid prescale set %d > 5, prescale);
>
> > > > +err_pm_put:
> > > > + pm_runtime_put(dev);
> > >
> > > A guard (à la guard(pm_runtime_resume)(dev), similar to the guards
> > > for mutexes and spinlocks) would be elegant here and simplify error
> > > handling. (Only if you're motivated, I wouldn't make this a
> > > precondition for accepting your driver.)
> >
> > We normally backport these drivers to CIP kernel(SLTSI) 5.10 and 6.1
> > once it hits mainline and then linux-rc series From that angle, I would like to use mutexes and
> spinlocks, if it is ok for you.
> >
> > Later I will send patch for using guards for pm_runtime_resume,
> > mutexes and spinlocks which I cannot backport to 5.10 and 6.1 kernel.
> >
> > Please let me know is it ok for you?
>
> sure.
Thanks,
Biju
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-08-06 8:35 ` Biju Das
@ 2024-08-06 15:12 ` Uwe Kleine-König
2024-08-06 15:47 ` Biju Das
0 siblings, 1 reply; 12+ messages in thread
From: Uwe Kleine-König @ 2024-08-06 15:12 UTC (permalink / raw)
To: Biju Das
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Prabhakar Mahadev Lad, biju.das.au
[-- Attachment #1: Type: text/plain, Size: 11450 bytes --]
Hello Biju,
On Tue, Aug 06, 2024 at 08:35:39AM +0000, Biju Das wrote:
> > -----Original Message-----
> > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > Sent: Tuesday, August 6, 2024 7:47 AM
> > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> >
> > Hello Biju,
> >
> > On Fri, Aug 02, 2024 at 07:02:19AM +0000, Biju Das wrote:
> > > > -----Original Message-----
> > > > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > > > Sent: Tuesday, July 30, 2024 8:54 PM
> > > > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> > > >
> > > > Hello,
> > > >
> > > > I'm a bit unlucky about this driver. I have the impression it is
> > > > complicated and wonder if that is necessary because the hardware is unusual or if we just have to
> > spot some simplifications.
> > >
> > > I agree it is little bit complex driver. Once this driver is accepted,
> > > going forward, I need to support other drivers like (Counter , ADC triggering and POEG(Output
> > disable) support).
> > >
> > > > I guess another problem is that the time between two consecutive
> > > > reviews is long and I forget most things I learned about the
> > > > hardware from one to the other. While this is mostly my problem, the
> > > > same problem arises if the driver is touched later again. So I
> > > > wonder if some more documentation is needed about the relation
> > > > between channels and outputs and subchannels. If the driver only supported one output per channel,
> > it could be considerably simpler (I think). But I guess that would be a practically relevant
> > restriction??
> > >
> > > Yes, one output per channel means, we cannot use POEG IP which is for
> > > short circuit protection in switching circuits. So, we need to use both IOs in the channel.
> > >
> > > I will add the below documentation to make it clear.
> > >
> > > * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
> > > * each HW channel have 2 IOs.
> > > * - Each IO is modelled as an independent PWM channel.
> > >
> > > Please let me know is it ok with respect to the initial driver?
> >
> > looks fine.
> >
> > > > Some simplifications spotted below.
> > > >
> > > > On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> > > > > RZ/G2L General PWM Timer (GPT) composed of 8 channels with 32-bit
> > > > > timer (GPT32E). It supports the following functions
> > > > > * 32 bits x 8 channels
> > > > > * Up-counting or down-counting (saw waves) or up/down-counting
> > > > > (triangle waves) for each counter.
> > > > > * Clock sources independently selectable for each channel
> > > > > * Two 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
> > > > > * Starting, stopping, clearing and up/down counters in response to input
> > > > > level comparison
> > > > > * Starting, clearing, stopping and up/down counters in response to a
> > > > > maximum of four external triggers
> > > > > * Output pin disable function by dead time error and detected
> > > > > short-circuits between output pins
> > > > > * A/D converter start triggers can be generated (GPT32E0 to
> > > > > GPT32E3)
> > > > > * Enables the noise filter for input capture and external trigger
> > > > > operation
> > > > >
> > > > > Add basic pwm support for RZ/G2L GPT driver by creating separate
> > > > > logical channels for each IOs.
> > > > >
> > > > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > > > ---
> > > > > v19->v20:
> > > > > * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> > > > > * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> > > > > * Added a comment in calculate_period_or_duty() related to overflow.
> > > > > * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> > > > > in probe(), apply() and remove() for simplification
> > > > > * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> > > > > [...]
> > > > > ---
> > > > > drivers/pwm/Kconfig | 11 +
> > > > > drivers/pwm/Makefile | 1 +
> > > > > drivers/pwm/pwm-rzg2l-gpt.c | 555
> > > > > ++++++++++++++++++++++++++++++++++++
> > > > > 3 files changed, 567 insertions(+) create mode 100644
> > > > > drivers/pwm/pwm-rzg2l-gpt.c
> > > > >
> > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > > > > 00a543de8f82..3d398b308e3f 100644
> > > > > --- a/drivers/pwm/Kconfig
> > > > > +++ b/drivers/pwm/Kconfig
> > > > > @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> > > > > Generic PWM framework driver for the PWM controller found on
> > > > > Rockchip SoCs.
> > > > >
> > > > > +config PWM_RZG2L_GPT
> > > > > + tristate "Renesas RZ/G2L General PWM Timer support"
> > > > > + depends on ARCH_RZG2L || COMPILE_TEST
> > > > > + depends on HAS_IOMEM
> > > > > + help
> > > > > + This driver exposes the General PWM Timer controller found in Renesas
> > > > > + RZ/G2L like chips through the PWM API.
> > > > > +
> > > > > + To compile this driver as a module, choose M here: the module
> > > > > + will be called pwm-rzg2l-gpt.
> > > > > +
> > > > > config PWM_RZ_MTU3
> > > > > tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> > > > > depends on RZ_MTU3
> > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > > > > 6964ba45c795..fb9a2d9b9adb 100644
> > > > > --- a/drivers/pwm/Makefile
> > > > > +++ b/drivers/pwm/Makefile
> > > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> > > > > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> > > > > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> > > > > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> > > > > +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> > > > > obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> > > > > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> > > > > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> > > > > diff --git a/drivers/pwm/pwm-rzg2l-gpt.c
> > > > > b/drivers/pwm/pwm-rzg2l-gpt.c new file mode 100644 index
> > > > > 000000000000..6005a689173e
> > > > > --- /dev/null
> > > > > +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> > > > > @@ -0,0 +1,555 @@
> > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > +/*
> > > > > + * Renesas RZ/G2L General PWM Timer (GPT) driver
> > > > > + *
> > > > > + * Copyright (C) 2024 Renesas Electronics Corporation
> > > > > + *
> > > > > + * Hardware manual for this IP can be found here
> > > > > + *
> > > > > +https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc-gro
> > > > > +up-u sers-manual-hardware-0?language=en
> > > > > + *
> > > > > + * 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.
> > > > > + * - When both channels are used, disabling the channel on one stops the
> > > > > + * other.
> > > > > + */
> > > > > +
> > > > > +#include <linux/bitfield.h>
> > > > > +#include <linux/clk.h>
> > > > > +#include <linux/io.h>
> > > > > +#include <linux/limits.h>
> > > > > +#include <linux/module.h>
> > > > > +#include <linux/of.h>
> > > > > +#include <linux/platform_device.h> #include <linux/pm_runtime.h>
> > > > > +#include <linux/pwm.h> #include <linux/reset.h> #include
> > > > > +<linux/time.h> #include <linux/units.h>
> > > > > +
> > > > > +#define RZG2L_GTCR 0x2c
> > > > > +#define RZG2L_GTUDDTYC 0x30
> > > > > +#define RZG2L_GTIOR 0x34
> > > > > +#define RZG2L_GTBER 0x40
> > > > > +#define RZG2L_GTCNT 0x48
> > > > > +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> > > > > +#define RZG2L_GTPR 0x64
> > >
> > > These will be replaced as
> > >
> > > +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> > > +
> > > +#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))
> > > +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
> >
> > I like this better, thanks.
> >
> > > > > + */
> > > > > + tmp = (u64)val << (2 * prescale);
> > > > > + tmp *= USEC_PER_SEC;
> > > > > +
> > > > > + 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);
> > > > > + int rc;
> > > > > +
> > > > > + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > > > > + if (rc)
> > > > > + return rc;
> > > > > +
> > > > > + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> > > > > + if (state->enabled) {
> > > > > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > > > > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > > > > + u8 prescale;
> > > > > + u32 val;
> > > > > +
> > > > > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > > > > + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
> > > >
> > > > Can it happen that prescale is > 5 here?
> > >
> > > Yes, if bootloader wrongly set it to 6 or 7. I will add a check in
> > > Probe and forcefully set to 5, if that the case. Is it ok?
> >
> > >
> > > + if (prescale > 5) {
> > > + dev_warn(dev, "Invalid prescale %d > 5, force setting to 5",
> > prescale);
> > > + /* Set prescale value of 5. */
> > > + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
> > > + FIELD_PREP(RZG2L_GTCR_TPCS, 5));
> > > + }
> >
> > I wouldn't write back the 5 then. Just assume that the value read back was 5. (Well unless the
> > hardware behaves according to the normal formula that applies for prescale ≤ 5, then it might make
> > sense to continue with the read value.)
>
> OK, will just print the warning.
>
> dev_warn(dev, "Invalid prescale set %d > 5, prescale);
No, please don't warn (or do it at most once per pwm_device).
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 12+ messages in thread
* RE: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
2024-08-06 15:12 ` Uwe Kleine-König
@ 2024-08-06 15:47 ` Biju Das
0 siblings, 0 replies; 12+ messages in thread
From: Biju Das @ 2024-08-06 15:47 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: Philipp Zabel, Geert Uytterhoeven, Fabrizio Castro, Magnus Damm,
linux-pwm@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
Prabhakar Mahadev Lad, biju.das.au
Hello Uwe,
> -----Original Message-----
> From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> Sent: Tuesday, August 6, 2024 4:13 PM
> Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
>
> Hello Biju,
>
> On Tue, Aug 06, 2024 at 08:35:39AM +0000, Biju Das wrote:
> > > -----Original Message-----
> > > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > > Sent: Tuesday, August 6, 2024 7:47 AM
> > > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> > >
> > > Hello Biju,
> > >
> > > On Fri, Aug 02, 2024 at 07:02:19AM +0000, Biju Das wrote:
> > > > > -----Original Message-----
> > > > > From: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
> > > > > Sent: Tuesday, July 30, 2024 8:54 PM
> > > > > Subject: Re: [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT
> > > > >
> > > > > Hello,
> > > > >
> > > > > I'm a bit unlucky about this driver. I have the impression it is
> > > > > complicated and wonder if that is necessary because the hardware
> > > > > is unusual or if we just have to
> > > spot some simplifications.
> > > >
> > > > I agree it is little bit complex driver. Once this driver is
> > > > accepted, going forward, I need to support other drivers like
> > > > (Counter , ADC triggering and POEG(Output
> > > disable) support).
> > > >
> > > > > I guess another problem is that the time between two consecutive
> > > > > reviews is long and I forget most things I learned about the
> > > > > hardware from one to the other. While this is mostly my problem,
> > > > > the same problem arises if the driver is touched later again. So
> > > > > I wonder if some more documentation is needed about the relation
> > > > > between channels and outputs and subchannels. If the driver only
> > > > > supported one output per channel,
> > > it could be considerably simpler (I think). But I guess that would
> > > be a practically relevant restriction??
> > > >
> > > > Yes, one output per channel means, we cannot use POEG IP which is
> > > > for short circuit protection in switching circuits. So, we need to use both IOs in the channel.
> > > >
> > > > I will add the below documentation to make it clear.
> > > >
> > > > * - General PWM Timer (GPT) has 8 HW channels for PWM operations and
> > > > * each HW channel have 2 IOs.
> > > > * - Each IO is modelled as an independent PWM channel.
> > > >
> > > > Please let me know is it ok with respect to the initial driver?
> > >
> > > looks fine.
> > >
> > > > > Some simplifications spotted below.
> > > > >
> > > > > On Fri, Jun 14, 2024 at 04:42:41PM +0100, Biju Das wrote:
> > > > > > RZ/G2L General PWM Timer (GPT) composed of 8 channels with
> > > > > > 32-bit timer (GPT32E). It supports the following functions
> > > > > > * 32 bits x 8 channels
> > > > > > * Up-counting or down-counting (saw waves) or up/down-counting
> > > > > > (triangle waves) for each counter.
> > > > > > * Clock sources independently selectable for each channel
> > > > > > * Two 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
> > > > > > * Starting, stopping, clearing and up/down counters in response to input
> > > > > > level comparison
> > > > > > * Starting, clearing, stopping and up/down counters in response to a
> > > > > > maximum of four external triggers
> > > > > > * Output pin disable function by dead time error and detected
> > > > > > short-circuits between output pins
> > > > > > * A/D converter start triggers can be generated (GPT32E0 to
> > > > > > GPT32E3)
> > > > > > * Enables the noise filter for input capture and external trigger
> > > > > > operation
> > > > > >
> > > > > > Add basic pwm support for RZ/G2L GPT driver by creating
> > > > > > separate logical channels for each IOs.
> > > > > >
> > > > > > Signed-off-by: Biju Das <biju.das.jz@bp.renesas.com>
> > > > > > ---
> > > > > > v19->v20:
> > > > > > * Added locks for rmw operations in rzg2l_gpt_{en,dis}able().
> > > > > > * Dropped decremeng enable_count based ch_en_bits in rzg2l_gpt_disable().
> > > > > > * Added a comment in calculate_period_or_duty() related to overflow.
> > > > > > * Replaced ch_en_bits->bootloader_enabled_channels and used this variable
> > > > > > in probe(), apply() and remove() for simplification
> > > > > > * Replaced pm_runtime_enable()->devm_pm_runtime_enable()
> > > > > > [...]
> > > > > > ---
> > > > > > drivers/pwm/Kconfig | 11 +
> > > > > > drivers/pwm/Makefile | 1 +
> > > > > > drivers/pwm/pwm-rzg2l-gpt.c | 555
> > > > > > ++++++++++++++++++++++++++++++++++++
> > > > > > 3 files changed, 567 insertions(+) create mode 100644
> > > > > > drivers/pwm/pwm-rzg2l-gpt.c
> > > > > >
> > > > > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > > > > > 00a543de8f82..3d398b308e3f 100644
> > > > > > --- a/drivers/pwm/Kconfig
> > > > > > +++ b/drivers/pwm/Kconfig
> > > > > > @@ -522,6 +522,17 @@ config PWM_ROCKCHIP
> > > > > > Generic PWM framework driver for the PWM controller found on
> > > > > > Rockchip SoCs.
> > > > > >
> > > > > > +config PWM_RZG2L_GPT
> > > > > > + tristate "Renesas RZ/G2L General PWM Timer support"
> > > > > > + depends on ARCH_RZG2L || COMPILE_TEST
> > > > > > + depends on HAS_IOMEM
> > > > > > + help
> > > > > > + This driver exposes the General PWM Timer controller found in Renesas
> > > > > > + RZ/G2L like chips through the PWM API.
> > > > > > +
> > > > > > + To compile this driver as a module, choose M here: the module
> > > > > > + will be called pwm-rzg2l-gpt.
> > > > > > +
> > > > > > config PWM_RZ_MTU3
> > > > > > tristate "Renesas RZ/G2L MTU3a PWM Timer support"
> > > > > > depends on RZ_MTU3
> > > > > > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > > > > > 6964ba45c795..fb9a2d9b9adb 100644
> > > > > > --- a/drivers/pwm/Makefile
> > > > > > +++ b/drivers/pwm/Makefile
> > > > > > @@ -47,6 +47,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
> > > > > > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o
> > > > > > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o
> > > > > > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o
> > > > > > +obj-$(CONFIG_PWM_RZG2L_GPT) += pwm-rzg2l-gpt.o
> > > > > > obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o
> > > > > > obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o
> > > > > > obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o
> > > > > > diff --git a/drivers/pwm/pwm-rzg2l-gpt.c
> > > > > > b/drivers/pwm/pwm-rzg2l-gpt.c new file mode 100644 index
> > > > > > 000000000000..6005a689173e
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/pwm/pwm-rzg2l-gpt.c
> > > > > > @@ -0,0 +1,555 @@
> > > > > > +// SPDX-License-Identifier: GPL-2.0
> > > > > > +/*
> > > > > > + * Renesas RZ/G2L General PWM Timer (GPT) driver
> > > > > > + *
> > > > > > + * Copyright (C) 2024 Renesas Electronics Corporation
> > > > > > + *
> > > > > > + * Hardware manual for this IP can be found here
> > > > > > + *
> > > > > > +https://www.renesas.com/eu/en/document/mah/rzg2l-group-rzg2lc
> > > > > > +-gro up-u sers-manual-hardware-0?language=en
> > > > > > + *
> > > > > > + * 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.
> > > > > > + * - When both channels are used, disabling the channel on one stops the
> > > > > > + * other.
> > > > > > + */
> > > > > > +
> > > > > > +#include <linux/bitfield.h>
> > > > > > +#include <linux/clk.h>
> > > > > > +#include <linux/io.h>
> > > > > > +#include <linux/limits.h>
> > > > > > +#include <linux/module.h>
> > > > > > +#include <linux/of.h>
> > > > > > +#include <linux/platform_device.h> #include
> > > > > > +<linux/pm_runtime.h> #include <linux/pwm.h> #include
> > > > > > +<linux/reset.h> #include <linux/time.h> #include
> > > > > > +<linux/units.h>
> > > > > > +
> > > > > > +#define RZG2L_GTCR 0x2c
> > > > > > +#define RZG2L_GTUDDTYC 0x30
> > > > > > +#define RZG2L_GTIOR 0x34
> > > > > > +#define RZG2L_GTBER 0x40
> > > > > > +#define RZG2L_GTCNT 0x48
> > > > > > +#define RZG2L_GTCCR(i) (0x4c + 4 * (i))
> > > > > > +#define RZG2L_GTPR 0x64
> > > >
> > > > These will be replaced as
> > > >
> > > > +#define RZG2L_GET_CH_OFFS(i) (0x100 * (i))
> > > > +
> > > > +#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))
> > > > +#define RZG2L_GTPR(ch) (0x64 + RZG2L_GET_CH_OFFS(ch))
> > >
> > > I like this better, thanks.
> > >
> > > > > > + */
> > > > > > + tmp = (u64)val << (2 * prescale);
> > > > > > + tmp *= USEC_PER_SEC;
> > > > > > +
> > > > > > + 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);
> > > > > > + int rc;
> > > > > > +
> > > > > > + rc = pm_runtime_resume_and_get(pwmchip_parent(chip));
> > > > > > + if (rc)
> > > > > > + return rc;
> > > > > > +
> > > > > > + state->enabled = rzg2l_gpt_is_ch_enabled(rzg2l_gpt, pwm->hwpwm);
> > > > > > + if (state->enabled) {
> > > > > > + u32 ch = RZG2L_GET_CH(pwm->hwpwm);
> > > > > > + u32 offs = RZG2L_GET_CH_OFFS(ch);
> > > > > > + u8 prescale;
> > > > > > + u32 val;
> > > > > > +
> > > > > > + val = rzg2l_gpt_read(rzg2l_gpt, offs + RZG2L_GTCR);
> > > > > > + prescale = FIELD_GET(RZG2L_GTCR_TPCS, val);
> > > > >
> > > > > Can it happen that prescale is > 5 here?
> > > >
> > > > Yes, if bootloader wrongly set it to 6 or 7. I will add a check in
> > > > Probe and forcefully set to 5, if that the case. Is it ok?
> > >
> > > >
> > > > + if (prescale > 5) {
> > > > + dev_warn(dev, "Invalid prescale %d
> > > > + > 5, force setting to 5",
> > > prescale);
> > > > + /* Set prescale value of 5. */
> > > > + rzg2l_gpt_modify(rzg2l_gpt, RZG2L_GTCR(ch), RZG2L_GTCR_TPCS,
> > > > + FIELD_PREP(RZG2L_GTCR_TPCS, 5));
> > > > + }
> > >
> > > I wouldn't write back the 5 then. Just assume that the value read
> > > back was 5. (Well unless the hardware behaves according to the
> > > normal formula that applies for prescale ≤ 5, then it might make
> > > sense to continue with the read value.)
> >
> > OK, will just print the warning.
> >
> > dev_warn(dev, "Invalid prescale set %d > 5, prescale);
>
> No, please don't warn (or do it at most once per pwm_device).
OK, Will change it to dev_warn_once(), so that it will print only once as it is
an unlikely case(bootloader setting wrong prescale values).
Cheers,
Biju
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2024-08-06 15:47 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-14 15:42 [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
2024-06-14 15:42 ` [PATCH v20 1/4] dt-bindings: pwm: Add RZ/G2L GPT binding Biju Das
2024-06-14 15:42 ` [PATCH v20 2/4] dt-bindings: pwm: rzg2l-gpt: Document renesas,poegs property Biju Das
2024-06-14 15:42 ` [PATCH v20 3/4] pwm: Add support for RZ/G2L GPT Biju Das
2024-07-30 19:54 ` Uwe Kleine-König
2024-08-02 7:02 ` Biju Das
2024-08-06 6:46 ` Uwe Kleine-König
2024-08-06 8:35 ` Biju Das
2024-08-06 15:12 ` Uwe Kleine-König
2024-08-06 15:47 ` Biju Das
2024-06-14 15:42 ` [PATCH v20 4/4] pwm: rzg2l-gpt: Add support for gpt linking with poeg Biju Das
2024-07-02 19:19 ` [PATCH v20 0/4] Add support for RZ/G2L GPT Biju Das
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox