[PATCH v5 6/7] pwm: tegra: Add support for Tegra264

From: Mikko Perttunen

Date: Thu May 28 2026 - 22:54:13 EST


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@xxxxxxxxxx>
Signed-off-by: Yi-Wei Wang <yiweiw@xxxxxxxxxx>
Signed-off-by: Mikko Perttunen <mperttunen@xxxxxxxxxx>
---
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@xxxxxxxxxxxxxx>
*
- * 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