* [PATCH v5 0/7] Tegra264 PWM support
@ 2026-05-29 2:47 Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 1/7] dt-bindings: pwm: Document Tegra264 controller Mikko Perttunen
` (6 more replies)
0 siblings, 7 replies; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Thierry Reding,
Mikko Perttunen, Yi-Wei Wang
Hello,
this adds support for the PWM controller on Tegra264. The controller
is similar to previous generations, but the register fields are
widened, the depth is made configurable, and the enable bit moves
to a different spot.
This series adds only basic support with fixed depth -- configurable
depth will come later.
Patch 1 adds device tree bindings for Tegra264 PWM (compatible
string).
Patch 2 prefixes driver-local macros and static helpers with
tegra_/TEGRA_ to make their scoping clear.
Patches 3 to 6 contain the PWM driver changes for Tegra264.
Patch 7 adds device tree nodes for the PWM controllers on Tegra264.
Thanks,
Mikko
---
Changes in v5:
- New patch: prefix driver-local macros and static helpers with
tegra_/TEGRA_ (suggested by Uwe)
- Squashed the enable_reg parameterization into the "Add support for
Tegra264" patch
- Rename pwm_readl()/pwm_writel() parameter `dev` to `pwm` to match the
rest of the driver
- Drop an extra blank line in tegra_pwm_enable()
- Reflow the code comment text in "Add support for Tegra264" to
fit within 80 columns
- Dropped duty_width field since it was unused.
- Rebased on latest -next.
- Link to v4: https://lore.kernel.org/r/20260331-t264-pwm-v4-0-c041659677cf@nvidia.com
Changes in v4:
- Use ULONG_MAX rather than S64_MAX to avoid overflow on 32-bit platforms
- Link to v3: https://lore.kernel.org/r/20260330-t264-pwm-v3-0-5714427d5976@nvidia.com
Changes in v3:
- Fixed device tree binding patch.
- Picked up trailers.
- Link to v2: https://lore.kernel.org/r/20260325-t264-pwm-v2-0-998d885984b3@nvidia.com
Changes in v2:
- Added device tree binding and Tegra264 device tree patches by Thierry.
- Link to v1: https://lore.kernel.org/r/20260323-t264-pwm-v1-0-4c4ff743050f@nvidia.com
---
Mikko Perttunen (4):
pwm: tegra: Prefix driver-local macros and functions
pwm: tegra: Modify read/write accessors for multi-register channel
pwm: tegra: Parametrize duty and scale field widths
pwm: tegra: Add support for Tegra264
Thierry Reding (2):
dt-bindings: pwm: Document Tegra264 controller
arm64: tegra: Add PWM controllers on Tegra264
Yi-Wei Wang (1):
pwm: tegra: Avoid hard-coded max clock frequency
.../bindings/pwm/nvidia,tegra20-pwm.yaml | 1 +
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 72 ++++++++++
drivers/pwm/pwm-tegra.c | 155 ++++++++++++++-------
3 files changed, 176 insertions(+), 52 deletions(-)
---
base-commit: f7af91adc230aa99e23330ecf85bc9badd9780ad
change-id: 20260303-t264-pwm-57e10d039df1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 1/7] dt-bindings: pwm: Document Tegra264 controller
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 2/7] pwm: tegra: Prefix driver-local macros and functions Mikko Perttunen
` (5 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Thierry Reding,
Mikko Perttunen
From: Thierry Reding <treding@nvidia.com>
Add a new compatible string for the PWM controller found on Tegra264.
The controller is similar to earlier generations but not compatible
with them.
Signed-off-by: Thierry Reding <treding@nvidia.com>
[mperttunen: Drop extra Tegra194 compatible string]
Acked-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.yaml b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.yaml
index 41cea4979132..cb2f36e7b5d6 100644
--- a/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.yaml
+++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra20-pwm.yaml
@@ -16,6 +16,7 @@ properties:
- enum:
- nvidia,tegra20-pwm
- nvidia,tegra186-pwm
+ - nvidia,tegra264-pwm
- items:
- enum:
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 2/7] pwm: tegra: Prefix driver-local macros and functions
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 1/7] dt-bindings: pwm: Document Tegra264 controller Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency Mikko Perttunen
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Mikko Perttunen
Prefix driver-local defines and functions with tegra_/TEGRA_ to clearly
distinguish them from any general PWM related symbols.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/pwm/pwm-tegra.c | 54 ++++++++++++++++++++++++-------------------------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index 172063b51d44..f8c5495a56a6 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -51,11 +51,11 @@
#include <soc/tegra/common.h>
-#define PWM_ENABLE (1 << 31)
-#define PWM_DUTY_WIDTH 8
-#define PWM_DUTY_SHIFT 16
-#define PWM_SCALE_WIDTH 13
-#define PWM_SCALE_SHIFT 0
+#define TEGRA_PWM_ENABLE (1 << 31)
+#define TEGRA_PWM_DUTY_WIDTH 8
+#define TEGRA_PWM_DUTY_SHIFT 16
+#define TEGRA_PWM_SCALE_WIDTH 13
+#define TEGRA_PWM_SCALE_SHIFT 0
struct tegra_pwm_soc {
unsigned int num_channels;
@@ -81,12 +81,12 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
return pwmchip_get_drvdata(chip);
}
-static inline u32 pwm_readl(struct tegra_pwm_chip *pc, unsigned int offset)
+static inline u32 tegra_pwm_readl(struct tegra_pwm_chip *pc, unsigned int offset)
{
return readl(pc->regs + (offset << 4));
}
-static inline void pwm_writel(struct tegra_pwm_chip *pc, unsigned int offset, u32 value)
+static inline void tegra_pwm_writel(struct tegra_pwm_chip *pc, unsigned int offset, u32 value)
{
writel(value, pc->regs + (offset << 4));
}
@@ -102,22 +102,22 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/*
* Convert from duty_ns / period_ns to a fixed number of duty ticks
- * per (1 << PWM_DUTY_WIDTH) cycles and make sure to round to the
+ * per (1 << TEGRA_PWM_DUTY_WIDTH) cycles and make sure to round to the
* nearest integer during division.
*/
- c *= (1 << PWM_DUTY_WIDTH);
+ c *= (1 << TEGRA_PWM_DUTY_WIDTH);
c = DIV_ROUND_CLOSEST_ULL(c, period_ns);
- val = (u32)c << PWM_DUTY_SHIFT;
+ val = (u32)c << TEGRA_PWM_DUTY_SHIFT;
/*
- * min period = max clock limit >> PWM_DUTY_WIDTH
+ * min period = max clock limit >> TEGRA_PWM_DUTY_WIDTH
*/
if (period_ns < pc->min_period_ns)
return -EINVAL;
/*
- * Compute the prescaler value for which (1 << PWM_DUTY_WIDTH)
+ * Compute the prescaler value for which (1 << TEGRA_PWM_DUTY_WIDTH)
* cycles at the PWM clock rate will take period_ns nanoseconds.
*
* num_channels: If single instance of PWM controller has multiple
@@ -131,7 +131,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
if (pc->soc->num_channels == 1) {
/*
- * Rate is multiplied with 2^PWM_DUTY_WIDTH so that it matches
+ * Rate is multiplied with 2^TEGRA_PWM_DUTY_WIDTH so that it matches
* with the maximum possible rate that the controller can
* provide. Any further lower value can be derived by setting
* PFM bits[0:12].
@@ -141,7 +141,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* source clock rate as required_clk_rate, PWM controller will
* be able to configure the requested period.
*/
- required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC << PWM_DUTY_WIDTH,
+ required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC << TEGRA_PWM_DUTY_WIDTH,
period_ns);
if (required_clk_rate > clk_round_rate(pc->clk, required_clk_rate))
@@ -163,9 +163,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
pc->clk_rate = clk_get_rate(pc->clk);
}
- /* Consider precision in PWM_SCALE_WIDTH rate calculation */
+ /* Consider precision in TEGRA_PWM_SCALE_WIDTH rate calculation */
rate = mul_u64_u64_div_u64(pc->clk_rate, period_ns,
- (u64)NSEC_PER_SEC << PWM_DUTY_WIDTH);
+ (u64)NSEC_PER_SEC << TEGRA_PWM_DUTY_WIDTH);
/*
* Since the actual PWM divider is the register's frequency divider
@@ -181,10 +181,10 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* Make sure that the rate will fit in the register's frequency
* divider field.
*/
- if (rate >> PWM_SCALE_WIDTH)
+ if (rate >> TEGRA_PWM_SCALE_WIDTH)
return -EINVAL;
- val |= rate << PWM_SCALE_SHIFT;
+ val |= rate << TEGRA_PWM_SCALE_SHIFT;
/*
* If the PWM channel is disabled, make sure to turn on the clock
@@ -195,9 +195,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
if (err)
return err;
} else
- val |= PWM_ENABLE;
+ val |= TEGRA_PWM_ENABLE;
- pwm_writel(pc, pwm->hwpwm, val);
+ tegra_pwm_writel(pc, pwm->hwpwm, val);
/*
* If the PWM is not enabled, turn the clock off again to save power.
@@ -218,9 +218,9 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
if (rc)
return rc;
- val = pwm_readl(pc, pwm->hwpwm);
- val |= PWM_ENABLE;
- pwm_writel(pc, pwm->hwpwm, val);
+ val = tegra_pwm_readl(pc, pwm->hwpwm);
+ val |= TEGRA_PWM_ENABLE;
+ tegra_pwm_writel(pc, pwm->hwpwm, val);
return 0;
}
@@ -230,9 +230,9 @@ static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
u32 val;
- val = pwm_readl(pc, pwm->hwpwm);
- val &= ~PWM_ENABLE;
- pwm_writel(pc, pwm->hwpwm, val);
+ val = tegra_pwm_readl(pc, pwm->hwpwm);
+ val &= ~TEGRA_PWM_ENABLE;
+ tegra_pwm_writel(pc, pwm->hwpwm, val);
pm_runtime_put_sync(pwmchip_parent(chip));
}
@@ -318,7 +318,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
/* Set minimum limit of PWM period for the IP */
pc->min_period_ns =
- (NSEC_PER_SEC / (pc->soc->max_frequency >> PWM_DUTY_WIDTH)) + 1;
+ (NSEC_PER_SEC / (pc->soc->max_frequency >> TEGRA_PWM_DUTY_WIDTH)) + 1;
pc->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
if (IS_ERR(pc->rst)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 1/7] dt-bindings: pwm: Document Tegra264 controller Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 2/7] pwm: tegra: Prefix driver-local macros and functions Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 3:28 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 4/7] pwm: tegra: Modify read/write accessors for multi-register channel Mikko Perttunen
` (3 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Yi-Wei Wang,
Thierry Reding, Mikko Perttunen
From: Yi-Wei Wang <yiweiw@nvidia.com>
The clock driving the Tegra PWM IP can be sourced from different parent
clocks. Hence, let dev_pm_opp_set_rate() set the max clock rate based
upon the current parent clock that can be specified via device-tree.
After this, the Tegra194 SoC data becomes redundant, so get rid of it.
Signed-off-by: Yi-Wei Wang <yiweiw@nvidia.com>
Reviewed-by: Thierry Reding <treding@nvidia.com>
Co-developed-by: Mikko Perttunen <mperttunen@nvidia.com>
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/pwm/pwm-tegra.c | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index f8c5495a56a6..303e2720034d 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -59,9 +59,6 @@
struct tegra_pwm_soc {
unsigned int num_channels;
-
- /* Maximum IP frequency for given SoCs */
- unsigned long max_frequency;
};
struct tegra_pwm_chip {
@@ -303,7 +300,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
return ret;
/* Set maximum frequency of the IP */
- ret = dev_pm_opp_set_rate(&pdev->dev, pc->soc->max_frequency);
+ ret = dev_pm_opp_set_rate(&pdev->dev, ULONG_MAX);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret);
goto put_pm;
@@ -318,7 +315,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
/* Set minimum limit of PWM period for the IP */
pc->min_period_ns =
- (NSEC_PER_SEC / (pc->soc->max_frequency >> TEGRA_PWM_DUTY_WIDTH)) + 1;
+ (NSEC_PER_SEC / (pc->clk_rate >> TEGRA_PWM_DUTY_WIDTH)) + 1;
pc->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
if (IS_ERR(pc->rst)) {
@@ -397,23 +394,16 @@ static int __maybe_unused tegra_pwm_runtime_resume(struct device *dev)
static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
- .max_frequency = 48000000UL,
};
static const struct tegra_pwm_soc tegra186_pwm_soc = {
.num_channels = 1,
- .max_frequency = 102000000UL,
-};
-
-static const struct tegra_pwm_soc tegra194_pwm_soc = {
- .num_channels = 1,
- .max_frequency = 408000000UL,
};
static const struct of_device_id tegra_pwm_of_match[] = {
{ .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
{ .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
- { .compatible = "nvidia,tegra194-pwm", .data = &tegra194_pwm_soc },
+ { .compatible = "nvidia,tegra194-pwm", .data = &tegra186_pwm_soc },
{ }
};
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 4/7] pwm: tegra: Modify read/write accessors for multi-register channel
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
` (2 preceding siblings ...)
2026-05-29 2:47 ` [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths Mikko Perttunen
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Mikko Perttunen
On Tegra264, each PWM instance has two registers (per channel, of which
there is one). Update the tegra_pwm_readl/tegra_pwm_writel helper
functions to take channel (as struct pwm_device *) and offset
separately.
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/pwm/pwm-tegra.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index 303e2720034d..c82d537436a0 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -57,6 +57,8 @@
#define TEGRA_PWM_SCALE_WIDTH 13
#define TEGRA_PWM_SCALE_SHIFT 0
+#define TEGRA_PWM_CSR_0 0
+
struct tegra_pwm_soc {
unsigned int num_channels;
};
@@ -78,14 +80,18 @@ static inline struct tegra_pwm_chip *to_tegra_pwm_chip(struct pwm_chip *chip)
return pwmchip_get_drvdata(chip);
}
-static inline u32 tegra_pwm_readl(struct tegra_pwm_chip *pc, unsigned int offset)
+static inline u32 tegra_pwm_readl(struct pwm_device *pwm, unsigned int offset)
{
- return readl(pc->regs + (offset << 4));
+ struct tegra_pwm_chip *chip = to_tegra_pwm_chip(pwm->chip);
+
+ return readl(chip->regs + (pwm->hwpwm * 16) + offset);
}
-static inline void tegra_pwm_writel(struct tegra_pwm_chip *pc, unsigned int offset, u32 value)
+static inline void tegra_pwm_writel(struct pwm_device *pwm, unsigned int offset, u32 value)
{
- writel(value, pc->regs + (offset << 4));
+ struct tegra_pwm_chip *chip = to_tegra_pwm_chip(pwm->chip);
+
+ writel(value, chip->regs + (pwm->hwpwm * 16) + offset);
}
static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -194,7 +200,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
} else
val |= TEGRA_PWM_ENABLE;
- tegra_pwm_writel(pc, pwm->hwpwm, val);
+ tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
/*
* If the PWM is not enabled, turn the clock off again to save power.
@@ -207,7 +213,6 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
int rc = 0;
u32 val;
@@ -215,21 +220,20 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
if (rc)
return rc;
- val = tegra_pwm_readl(pc, pwm->hwpwm);
+ val = tegra_pwm_readl(pwm, TEGRA_PWM_CSR_0);
val |= TEGRA_PWM_ENABLE;
- tegra_pwm_writel(pc, pwm->hwpwm, val);
+ tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
return 0;
}
static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
- struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
u32 val;
- val = tegra_pwm_readl(pc, pwm->hwpwm);
+ val = tegra_pwm_readl(pwm, TEGRA_PWM_CSR_0);
val &= ~TEGRA_PWM_ENABLE;
- tegra_pwm_writel(pc, pwm->hwpwm, val);
+ tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
pm_runtime_put_sync(pwmchip_parent(chip));
}
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
` (3 preceding siblings ...)
2026-05-29 2:47 ` [PATCH v5 4/7] pwm: tegra: Modify read/write accessors for multi-register channel Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 4:05 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 6/7] pwm: tegra: Add support for Tegra264 Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 7/7] arm64: tegra: Add PWM controllers on Tegra264 Mikko Perttunen
6 siblings, 1 reply; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Yi-Wei Wang,
Mikko Perttunen
Tegra264 has wider fields for the duty and scale register fields.
Parameterize the scale field width. The depth value becomes
disconnected from the duty field width, so define it separately
and remove the duty field width definition.
Co-developed-by: Yi-Wei Wang <yiweiw@nvidia.com>
Signed-off-by: Yi-Wei Wang <yiweiw@nvidia.com>
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/pwm/pwm-tegra.c | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index c82d537436a0..7c7b884d4436 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -52,15 +52,17 @@
#include <soc/tegra/common.h>
#define TEGRA_PWM_ENABLE (1 << 31)
-#define TEGRA_PWM_DUTY_WIDTH 8
#define TEGRA_PWM_DUTY_SHIFT 16
-#define TEGRA_PWM_SCALE_WIDTH 13
#define TEGRA_PWM_SCALE_SHIFT 0
#define TEGRA_PWM_CSR_0 0
+#define TEGRA_PWM_DEPTH 256
+
struct tegra_pwm_soc {
unsigned int num_channels;
+
+ unsigned int scale_width;
};
struct tegra_pwm_chip {
@@ -105,22 +107,22 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/*
* Convert from duty_ns / period_ns to a fixed number of duty ticks
- * per (1 << TEGRA_PWM_DUTY_WIDTH) cycles and make sure to round to the
+ * per TEGRA_PWM_DEPTH cycles and make sure to round to the
* nearest integer during division.
*/
- c *= (1 << TEGRA_PWM_DUTY_WIDTH);
+ c *= TEGRA_PWM_DEPTH;
c = DIV_ROUND_CLOSEST_ULL(c, period_ns);
val = (u32)c << TEGRA_PWM_DUTY_SHIFT;
/*
- * min period = max clock limit >> TEGRA_PWM_DUTY_WIDTH
+ * min period = max clock limit / TEGRA_PWM_DEPTH
*/
if (period_ns < pc->min_period_ns)
return -EINVAL;
/*
- * Compute the prescaler value for which (1 << TEGRA_PWM_DUTY_WIDTH)
+ * Compute the prescaler value for which TEGRA_PWM_DEPTH
* cycles at the PWM clock rate will take period_ns nanoseconds.
*
* num_channels: If single instance of PWM controller has multiple
@@ -134,7 +136,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
*/
if (pc->soc->num_channels == 1) {
/*
- * Rate is multiplied with 2^TEGRA_PWM_DUTY_WIDTH so that it matches
+ * Rate is multiplied with TEGRA_PWM_DEPTH so that it matches
* with the maximum possible rate that the controller can
* provide. Any further lower value can be derived by setting
* PFM bits[0:12].
@@ -144,7 +146,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* source clock rate as required_clk_rate, PWM controller will
* be able to configure the requested period.
*/
- required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC << TEGRA_PWM_DUTY_WIDTH,
+ required_clk_rate = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * TEGRA_PWM_DEPTH,
period_ns);
if (required_clk_rate > clk_round_rate(pc->clk, required_clk_rate))
@@ -166,9 +168,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
pc->clk_rate = clk_get_rate(pc->clk);
}
- /* Consider precision in TEGRA_PWM_SCALE_WIDTH rate calculation */
+ /* Consider precision in scale_width rate calculation */
rate = mul_u64_u64_div_u64(pc->clk_rate, period_ns,
- (u64)NSEC_PER_SEC << TEGRA_PWM_DUTY_WIDTH);
+ (u64)NSEC_PER_SEC * TEGRA_PWM_DEPTH);
/*
* Since the actual PWM divider is the register's frequency divider
@@ -184,7 +186,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
* Make sure that the rate will fit in the register's frequency
* divider field.
*/
- if (rate >> TEGRA_PWM_SCALE_WIDTH)
+ if (rate >> pc->soc->scale_width)
return -EINVAL;
val |= rate << TEGRA_PWM_SCALE_SHIFT;
@@ -319,7 +321,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
/* Set minimum limit of PWM period for the IP */
pc->min_period_ns =
- (NSEC_PER_SEC / (pc->clk_rate >> TEGRA_PWM_DUTY_WIDTH)) + 1;
+ (NSEC_PER_SEC / (pc->clk_rate / TEGRA_PWM_DEPTH)) + 1;
pc->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
if (IS_ERR(pc->rst)) {
@@ -398,10 +400,12 @@ static int __maybe_unused tegra_pwm_runtime_resume(struct device *dev)
static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
+ .scale_width = 13,
};
static const struct tegra_pwm_soc tegra186_pwm_soc = {
.num_channels = 1,
+ .scale_width = 13,
};
static const struct of_device_id tegra_pwm_of_match[] = {
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 6/7] pwm: tegra: Add support for Tegra264
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
` (4 preceding siblings ...)
2026-05-29 2:47 ` [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
2026-05-29 4:30 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 7/7] arm64: tegra: Add PWM controllers on Tegra264 Mikko Perttunen
6 siblings, 1 reply; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Yi-Wei Wang,
Mikko Perttunen
Tegra264 changes the register layout to accommodate wider fields
for duty and scale, and adds configurable depth which will be
supported in a later patch. The enable bit also moves from CSR_0
to a separate CSR_1 register.
To support the new enable register location, introduce an
enable_reg field in struct tegra_pwm_soc that identifies which
register contains the PWM_ENABLE bit. tegra_pwm_enable() and
tegra_pwm_disable() read/write this field accordingly, and
tegra_pwm_config() skips OR-ing PWM_ENABLE into its CSR_0 write
on SoCs where the enable bit is not in CSR_0.
Update the top comment to describe the register layout in more
detail.
Co-developed-by: Yi-Wei Wang <yiweiw@nvidia.com>
Signed-off-by: Yi-Wei Wang <yiweiw@nvidia.com>
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
drivers/pwm/pwm-tegra.c | 91 ++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 72 insertions(+), 19 deletions(-)
diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
index 7c7b884d4436..50e72139cbc3 100644
--- a/drivers/pwm/pwm-tegra.c
+++ b/drivers/pwm/pwm-tegra.c
@@ -7,22 +7,61 @@
* Copyright (c) 2010-2020, NVIDIA Corporation.
* Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de>
*
- * Overview of Tegra Pulse Width Modulator Register:
- * 1. 13-bit: Frequency division (SCALE)
- * 2. 8-bit : Pulse division (DUTY)
- * 3. 1-bit : Enable bit
+ * Overview of Tegra Pulse Width Modulator Register
+ * CSR_0 of Tegra20, Tegra186, and Tegra194:
+ * +-------+-------+-----------------------------------------------------------+
+ * | Bit | Field | Description |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 31 | ENB | Enable Pulse width modulator. |
+ * | | | 0 = DISABLE, 1 = ENABLE. |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 30:16 | PWM_0 | Pulse width that needs to be programmed. |
+ * | | | 0 = Always low. |
+ * | | | 1 = 1 / 256 pulse high. |
+ * | | | 2 = 2 / 256 pulse high. |
+ * | | | N = N / 256 pulse high. |
+ * | | | Only 8 bits are usable [23:16]. |
+ * | | | Bit[24] can be programmed to 1 to achieve 100% duty |
+ * | | | cycle. In this case the other bits [23:16] are set to |
+ * | | | don’t care. |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 12:0 | PFM_0 | Frequency divider that needs to be programmed, also known |
+ * | | | as SCALE. Division by (1 + PFM_0). |
+ * +-------+-------+-----------------------------------------------------------+
*
- * The PWM clock frequency is divided by 256 before subdividing it based
- * on the programmable frequency division value to generate the required
- * frequency for PWM output. The maximum output frequency that can be
- * achieved is (max rate of source clock) / 256.
- * e.g. if source clock rate is 408 MHz, maximum output frequency can be:
- * 408 MHz/256 = 1.6 MHz.
- * This 1.6 MHz frequency can further be divided using SCALE value in PWM.
+ * CSR_0 of Tegra264:
+ * +-------+-------+-----------------------------------------------------------+
+ * | Bit | Field | Description |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 31:16 | PWM_0 | Pulse width that needs to be programmed. |
+ * | | | 0 = Always low. |
+ * | | | 1 = 1 / (1 + CSR_1.DEPTH) pulse high. |
+ * | | | 2 = 2 / (1 + CSR_1.DEPTH) pulse high. |
+ * | | | N = N / (1 + CSR_1.DEPTH) pulse high. |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 15:0 | PFM_0 | Frequency divider that needs to be programmed, also known |
+ * | | | as SCALE. Division by (1 + PFM_0). |
+ * +-------+-------+-----------------------------------------------------------+
+ *
+ * CSR_1 of Tegra264:
+ * +-------+-------+-----------------------------------------------------------+
+ * | Bit | Field | Description |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 31 | ENB | Enable Pulse width modulator. |
+ * | | | 0 = DISABLE, 1 = ENABLE. |
+ * +-------+-------+-----------------------------------------------------------+
+ * | 30:15 | DEPTH | Depth for pulse width modulator. This controls the pulse |
+ * | | | time generated. Division by (1 + CSR_1.DEPTH). |
+ * +-------+-------+-----------------------------------------------------------+
*
- * PWM pulse width: 8 bits are usable [23:16] for varying pulse width.
- * To achieve 100% duty cycle, program Bit [24] of this register to
- * 1’b1. In which case the other bits [23:16] are set to don't care.
+ * The PWM clock frequency is divided by DEPTH = (1 + CSR_1.DEPTH) before
+ * subdividing it based on the programmable frequency division value to
+ * generate the required frequency for PWM output. DEPTH is fixed to 256
+ * before Tegra264. The maximum output frequency that can be achieved is
+ * (max rate of source clock) / DEPTH.
+ * e.g. if source clock rate is 408 MHz, and DEPTH = 256, maximum output
+ * frequency can be: 408 MHz / 256 ~= 1.6 MHz.
+ * This 1.6 MHz frequency can further be divided using SCALE value in PWM.
*
* Limitations:
* - When PWM is disabled, the output is driven to inactive.
@@ -56,11 +95,13 @@
#define TEGRA_PWM_SCALE_SHIFT 0
#define TEGRA_PWM_CSR_0 0
+#define TEGRA_PWM_CSR_1 4
#define TEGRA_PWM_DEPTH 256
struct tegra_pwm_soc {
unsigned int num_channels;
+ unsigned int enable_reg;
unsigned int scale_width;
};
@@ -199,8 +240,9 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
err = pm_runtime_resume_and_get(pwmchip_parent(chip));
if (err)
return err;
- } else
+ } else if (pc->soc->enable_reg == TEGRA_PWM_CSR_0) {
val |= TEGRA_PWM_ENABLE;
+ }
tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
@@ -215,6 +257,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
+ struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
int rc = 0;
u32 val;
@@ -222,20 +265,21 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
if (rc)
return rc;
- val = tegra_pwm_readl(pwm, TEGRA_PWM_CSR_0);
+ val = tegra_pwm_readl(pwm, pc->soc->enable_reg);
val |= TEGRA_PWM_ENABLE;
- tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
+ tegra_pwm_writel(pwm, pc->soc->enable_reg, val);
return 0;
}
static void tegra_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
+ struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
u32 val;
- val = tegra_pwm_readl(pwm, TEGRA_PWM_CSR_0);
+ val = tegra_pwm_readl(pwm, pc->soc->enable_reg);
val &= ~TEGRA_PWM_ENABLE;
- tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
+ tegra_pwm_writel(pwm, pc->soc->enable_reg, val);
pm_runtime_put_sync(pwmchip_parent(chip));
}
@@ -400,18 +444,27 @@ static int __maybe_unused tegra_pwm_runtime_resume(struct device *dev)
static const struct tegra_pwm_soc tegra20_pwm_soc = {
.num_channels = 4,
+ .enable_reg = TEGRA_PWM_CSR_0,
.scale_width = 13,
};
static const struct tegra_pwm_soc tegra186_pwm_soc = {
.num_channels = 1,
+ .enable_reg = TEGRA_PWM_CSR_0,
.scale_width = 13,
};
+static const struct tegra_pwm_soc tegra264_pwm_soc = {
+ .num_channels = 1,
+ .enable_reg = TEGRA_PWM_CSR_1,
+ .scale_width = 16,
+};
+
static const struct of_device_id tegra_pwm_of_match[] = {
{ .compatible = "nvidia,tegra20-pwm", .data = &tegra20_pwm_soc },
{ .compatible = "nvidia,tegra186-pwm", .data = &tegra186_pwm_soc },
{ .compatible = "nvidia,tegra194-pwm", .data = &tegra186_pwm_soc },
+ { .compatible = "nvidia,tegra264-pwm", .data = &tegra264_pwm_soc },
{ }
};
MODULE_DEVICE_TABLE(of, tegra_pwm_of_match);
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 7/7] arm64: tegra: Add PWM controllers on Tegra264
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
` (5 preceding siblings ...)
2026-05-29 2:47 ` [PATCH v5 6/7] pwm: tegra: Add support for Tegra264 Mikko Perttunen
@ 2026-05-29 2:47 ` Mikko Perttunen
6 siblings, 0 replies; 11+ messages in thread
From: Mikko Perttunen @ 2026-05-29 2:47 UTC (permalink / raw)
To: Thierry Reding, Uwe Kleine-König, Jonathan Hunter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley
Cc: linux-pwm, linux-tegra, linux-kernel, devicetree, Thierry Reding,
Mikko Perttunen
From: Thierry Reding <treding@nvidia.com>
Tegra264 has a number of PWM controllers that are similar but
incompatible with those found on earlier chips.
Signed-off-by: Thierry Reding <treding@nvidia.com>
[mperttunen: Adjust commit message]
Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com>
---
arch/arm64/boot/dts/nvidia/tegra264.dtsi | 72 ++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/arch/arm64/boot/dts/nvidia/tegra264.dtsi b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
index 2d8e7e37830f..d08adb60ade4 100644
--- a/arch/arm64/boot/dts/nvidia/tegra264.dtsi
+++ b/arch/arm64/boot/dts/nvidia/tegra264.dtsi
@@ -3336,6 +3336,18 @@ i2c3: i2c@c610000 {
status = "disabled";
};
+ pwm4: pwm@c6a0000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc6a0000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM4>;
+ resets = <&bpmp TEGRA264_RESET_PWM4>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
pmc: pmc@c800000 {
compatible = "nvidia,tegra264-pmc";
reg = <0x0 0x0c800000 0x0 0x100000>,
@@ -3631,6 +3643,66 @@ i2c16: i2c@c430000 {
status = "disabled";
};
+ pwm2: pwm@c5e0000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc5e0000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM2>;
+ resets = <&bpmp TEGRA264_RESET_PWM2>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
+ pwm3: pwm@c5f0000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc5f0000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM3>;
+ resets = <&bpmp TEGRA264_RESET_PWM3>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
+ pwm5: pwm@c600000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc600000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM5>;
+ resets = <&bpmp TEGRA264_RESET_PWM5>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
+ pwm9: pwm@c610000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc610000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM9>;
+ resets = <&bpmp TEGRA264_RESET_PWM9>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
+ pwm10: pwm@c620000 {
+ compatible = "nvidia,tegra264-pwm";
+ reg = <0x0 0xc620000 0x0 0x10000>;
+ status = "disabled";
+
+ clocks = <&bpmp TEGRA264_CLK_PWM10>;
+ resets = <&bpmp TEGRA264_RESET_PWM10>;
+ reset-names = "pwm";
+
+ #pwm-cells = <2>;
+ };
+
i2c0: i2c@c630000 {
compatible = "nvidia,tegra264-i2c";
reg = <0x00 0x0c630000 0x0 0x10000>;
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency
2026-05-29 2:47 ` [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency Mikko Perttunen
@ 2026-05-29 3:28 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-05-29 3:28 UTC (permalink / raw)
To: Mikko Perttunen; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Potential division by zero during probe due to unvalidated dynamically fetched clock rate.
- [High] Logic error in `dev_pm_opp_set_rate()` usage causes probe failure on devices with OPP tables.
--
commit 74520499a078af31d704c76e29fb2e41b43c4ecc
Author: Yi-Wei Wang <yiweiw@nvidia.com>
pwm: tegra: Avoid hard-coded max clock frequency
This commit changes the Tegra PWM driver to dynamically fetch the max
clock rate from the current parent clock using dev_pm_opp_set_rate()
and removes the hard-coded maximum frequencies from the SoC data.
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index f8c5495a56a66..303e2720034d2 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
[ ... ]
> @@ -303,7 +300,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
> return ret;
>
> /* Set maximum frequency of the IP */
> - ret = dev_pm_opp_set_rate(&pdev->dev, pc->soc->max_frequency);
> + ret = dev_pm_opp_set_rate(&pdev->dev, ULONG_MAX);
[Severity: High]
Does setting the target frequency to ULONG_MAX work safely with OPP tables?
dev_pm_opp_set_rate() will round ULONG_MAX to the clock driver's absolute
maximum frequency. If a platform (such as Tegra30) has an OPP table where
the highest defined OPP is lower than this absolute maximum,
dev_pm_opp_set_rate() will fail to find a matching OPP and return -ERANGE.
Could this failure inadvertently abort the driver probe on platforms with
such OPP tables?
> if (ret < 0) {
> dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret);
> goto put_pm;
> @@ -318,7 +315,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
>
> /* Set minimum limit of PWM period for the IP */
> pc->min_period_ns =
> - (NSEC_PER_SEC / (pc->soc->max_frequency >> TEGRA_PWM_DUTY_WIDTH)) + 1;
> + (NSEC_PER_SEC / (pc->clk_rate >> TEGRA_PWM_DUTY_WIDTH)) + 1;
[Severity: High]
Is it possible for pc->clk_rate to be less than 256 here?
If clk_get_rate() returns 0 (such as when the clock is unconfigured) or a
value less than 1 << TEGRA_PWM_DUTY_WIDTH, the right shift will evaluate to
0.
Could this lead to a division by zero panic during device probe?
>
> pc->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
> if (IS_ERR(pc->rst)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260529-t264-pwm-v5-0-7bf9e405a96a@nvidia.com?part=3
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths
2026-05-29 2:47 ` [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths Mikko Perttunen
@ 2026-05-29 4:05 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-05-29 4:05 UTC (permalink / raw)
To: Mikko Perttunen; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Division by zero kernel panic in `tegra_pwm_probe` if the retrieved clock rate is less than `TEGRA_PWM_DEPTH` (256).
- [High] Integer truncation of 64-bit PWM state values to 32-bit signed integers in `tegra_pwm_config` leading to a division by zero kernel panic.
--
commit 0c0ba991124cb1c177969041379fb6a95cd2a4c9
Author: Mikko Perttunen <mperttunen@nvidia.com>
pwm: tegra: Parametrize duty and scale field widths
This patch parametrizes the scale field width for the Tegra PWM driver to
accommodate wider fields in Tegra264. It separates the depth value from the
duty field width and removes the duty field width definition.
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index c82d537436a0f..7c7b884d44363 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
[ ... ]
> @@ -105,22 +107,22 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
>
> /*
> * Convert from duty_ns / period_ns to a fixed number of duty ticks
> - * per (1 << TEGRA_PWM_DUTY_WIDTH) cycles and make sure to round to the
> + * per TEGRA_PWM_DEPTH cycles and make sure to round to the
> * nearest integer during division.
> */
> - c *= (1 << TEGRA_PWM_DUTY_WIDTH);
> + c *= TEGRA_PWM_DEPTH;
> c = DIV_ROUND_CLOSEST_ULL(c, period_ns);
[Severity: High]
This is a pre-existing issue, but could this division result in a kernel
panic if period_ns becomes zero?
The core PWM framework provides 64-bit unsigned integers for the duty cycle
and period. When tegra_pwm_apply() passes these to tegra_pwm_config(), they
are cast to 32-bit signed integers (int duty_ns, int period_ns).
If a user sets a period of 4294967296 ns (2^32 ns, roughly 4.29 seconds) via
sysfs, the 64-bit value gets truncated to period_ns = 0.
Since the DIV_ROUND_CLOSEST_ULL() division occurs before any bounds checking
against min_period_ns, does this allow an unprivileged user with sysfs access
to trigger a division by zero exception and panic the kernel?
[ ... ]
> @@ -319,7 +321,7 @@ static int tegra_pwm_probe(struct platform_device *pdev)
>
> /* Set minimum limit of PWM period for the IP */
> pc->min_period_ns =
> - (NSEC_PER_SEC / (pc->clk_rate >> TEGRA_PWM_DUTY_WIDTH)) + 1;
> + (NSEC_PER_SEC / (pc->clk_rate / TEGRA_PWM_DEPTH)) + 1;
[Severity: High]
This isn't a bug introduced by this patch, but can this lead to a division
by zero exception during probe?
If clk_get_rate() returns a rate less than TEGRA_PWM_DEPTH (256) or fails
and returns 0, the denominator (pc->clk_rate / TEGRA_PWM_DEPTH) evaluates
to 0.
Will the subsequent NSEC_PER_SEC / 0 operation cause an immediate kernel panic
during boot? Should the driver validate pc->clk_rate and return an error if
it is too low?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260529-t264-pwm-v5-0-7bf9e405a96a@nvidia.com?part=5
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 6/7] pwm: tegra: Add support for Tegra264
2026-05-29 2:47 ` [PATCH v5 6/7] pwm: tegra: Add support for Tegra264 Mikko Perttunen
@ 2026-05-29 4:30 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-05-29 4:30 UTC (permalink / raw)
To: Mikko Perttunen; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The driver assumes a fixed PWM depth of 256 but fails to initialize the new configurable DEPTH register field on Tegra264, risking incorrect PWM frequency and duty cycle.
--
commit a215267521de0fbfc0019ea87d4d0d3a5d3250ea
Author: Mikko Perttunen <mperttunen@nvidia.com>
pwm: tegra: Add support for Tegra264
This patch adds support for the Tegra264 PWM controller. It introduces
an enable_reg field to handle the relocated PWM_ENABLE bit in the new
CSR_1 register for Tegra264 devices.
> diff --git a/drivers/pwm/pwm-tegra.c b/drivers/pwm/pwm-tegra.c
> index 7c7b884d44363..50e72139cbc34 100644
> --- a/drivers/pwm/pwm-tegra.c
> +++ b/drivers/pwm/pwm-tegra.c
[ ... ]
> @@ -215,6 +257,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
>
> static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> {
> + struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
> int rc = 0;
> u32 val;
>
> @@ -222,20 +265,21 @@ static int tegra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> if (rc)
> return rc;
>
> - val = tegra_pwm_readl(pwm, TEGRA_PWM_CSR_0);
> + val = tegra_pwm_readl(pwm, pc->soc->enable_reg);
> val |= TEGRA_PWM_ENABLE;
> - tegra_pwm_writel(pwm, TEGRA_PWM_CSR_0, val);
> + tegra_pwm_writel(pwm, pc->soc->enable_reg, val);
[Severity: High]
Could this cause a regression where the PWM output is left in a broken state
if the hardware resets the DEPTH field to 0?
The commit message notes that configurable depth will be supported in a
later patch, and tegra_pwm_config() continues to use a hardcoded
TEGRA_PWM_DEPTH of 256 for duty cycle and clock rate calculations.
Since this read-modify-write only updates the ENB bit, if the CSR_1.DEPTH
field defaults to 0 after a device reset, wouldn't the hardware divide by 1
instead of 256?
This seems like it would cause the PWM to operate at 256x the intended
frequency, and any calculated duty cycle value would exceed the hardware's
maximum depth. Should CSR_1.DEPTH be explicitly initialized to 255 here to
safely match the software's TEGRA_PWM_DEPTH assumption?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260529-t264-pwm-v5-0-7bf9e405a96a@nvidia.com?part=6
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-05-29 4:30 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29 2:47 [PATCH v5 0/7] Tegra264 PWM support Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 1/7] dt-bindings: pwm: Document Tegra264 controller Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 2/7] pwm: tegra: Prefix driver-local macros and functions Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 3/7] pwm: tegra: Avoid hard-coded max clock frequency Mikko Perttunen
2026-05-29 3:28 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 4/7] pwm: tegra: Modify read/write accessors for multi-register channel Mikko Perttunen
2026-05-29 2:47 ` [PATCH v5 5/7] pwm: tegra: Parametrize duty and scale field widths Mikko Perttunen
2026-05-29 4:05 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 6/7] pwm: tegra: Add support for Tegra264 Mikko Perttunen
2026-05-29 4:30 ` sashiko-bot
2026-05-29 2:47 ` [PATCH v5 7/7] arm64: tegra: Add PWM controllers on Tegra264 Mikko Perttunen
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox