[PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep()

From: Nicolae-Daniel Deaconescu

Date: Fri Nov 06 2026 - 11:11:16 EST


A consumer that must meet minimum times, or place an edge relative to the
cycles of a clock a PWM gates, can't do that with the waveform API alone.
Rounding functions round down, so a minimum needs margin. Margin size is
the PWM step, known only to the provider.

Let the provider report the rate of the clock its timing derives from, and
the clock cycles per step. A consumer then works in whole steps, converts
to nanoseconds rounding up, and gets that waveform back from the hardware.
Without this, a consumer reads the rate from a clock it gets on its own.
That describes a relationship the hardware doesn't have, and breaks if the
PWM divides its clock.

Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@xxxxxxxxxx>
---
drivers/pwm/core.c | 65 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm.h | 34 ++++++++++++++++++++++++
2 files changed, 99 insertions(+)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 2a050ff4608b..c8f9a53af770 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -14,6 +14,7 @@
#include <linux/of.h>
#include <linux/pwm.h>
#include <linux/list.h>
+#include <linux/math64.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
@@ -325,6 +326,67 @@ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf
}
EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep);

+/**
+ * pwm_get_waveform_step_might_sleep - Query the granularity of the waveforms
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @step: output parameter
+ *
+ * Stores in @step the clock rate the timing of @pwm is derived from and how
+ * many cycles of that clock one step lasts. Period, duty length and duty
+ * offset are multiples of that step, so a waveform that is asked for in whole
+ * steps, and is within the range of the hardware, is not changed by rounding.
+ * Compute the length in nanoseconds of n steps
+ * as n * @step->clk_cycles_per_step * NSEC_PER_SEC / @step->clk_rate_Hz and
+ * round it up, because pwm_round_waveform_might_sleep() and
+ * pwm_set_waveform_might_sleep() round down.
+ *
+ * The values can change when a clock changes its rate, so they are only valid
+ * as long as the provider's input clocks are not reconfigured.
+ *
+ * Context: May sleep.
+ * Returns: 0 on success, -EOPNOTSUPP if the provider doesn't report it or a
+ * negative errno
+ */
+int pwm_get_waveform_step_might_sleep(struct pwm_device *pwm,
+ struct pwm_waveform_step *step)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ might_sleep();
+
+ if (!pwmchip_supports_waveform(chip) || !ops->get_waveform_step)
+ return -EOPNOTSUPP;
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
+ /*
+ * Catch any drivers that have been marked as atomic but
+ * that will sleep anyway.
+ */
+ non_block_start();
+ ret = ops->get_waveform_step(chip, pwm, step);
+ non_block_end();
+ } else {
+ ret = ops->get_waveform_step(chip, pwm, step);
+ }
+ if (ret)
+ return ret;
+
+ if (!step->clk_rate_Hz ||
+ step->clk_rate_Hz > mul_u32_u32(step->clk_cycles_per_step, NSEC_PER_SEC))
+ return -EINVAL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pwm_get_waveform_step_might_sleep);
+
/* Called with the pwmchip lock held */
static int __pwm_set_waveform(struct pwm_device *pwm,
const struct pwm_waveform *wf,
@@ -1710,6 +1772,9 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
if (!ops->apply)
return false;

+ if (ops->get_waveform_step)
+ return false;
+
if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
dev_warn(pwmchip_parent(chip),
"Please implement the .get_state() callback\n");
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index b11ae91723f8..c52f4c9153c2 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -76,6 +76,34 @@ struct pwm_waveform {
u64 duty_offset_ns;
};

+/**
+ * struct pwm_waveform_step - granularity of the waveforms a PWM can generate
+ * @clk_rate_Hz: rate of the clock the PWM's timing is derived from
+ * @clk_cycles_per_step: number of those clock cycles one step lasts
+ *
+ * The period, the duty length and the duty offset of a waveform are multiples
+ * of one step, which lasts @clk_cycles_per_step / @clk_rate_Hz seconds. A PWM
+ * that divides its input clock in hardware has @clk_cycles_per_step > 1. A
+ * waveform that is asked for in whole steps, and is within the range of the
+ * hardware, is not changed by rounding.
+ *
+ * The step is the same for every waveform and for the period, the duty length
+ * and the duty offset, so a provider whose granularity depends on the
+ * requested period (for example through a prescaler) can't describe itself
+ * with it. One step also has to last at least 1 ns, otherwise rounding a
+ * length in nanoseconds up can add a whole step.
+ *
+ * A consumer that needs a minimum time, or that has to place an edge relative
+ * to the cycles of the clock a PWM channel gates, can use this to ask for
+ * the next possible value instead of one that is rounded down, and to compute
+ * the frequency a rounded period results in without the error of the
+ * nanosecond resolution of struct pwm_waveform.
+ */
+struct pwm_waveform_step {
+ unsigned long clk_rate_Hz;
+ unsigned int clk_cycles_per_step;
+};
+
/*
* struct pwm_state - state of a PWM channel
* @period: PWM period (in nanoseconds)
@@ -287,6 +315,9 @@ struct pwm_capture {
* @round_waveform_fromhw: convert a driver specific waveform presentation to struct pwm_waveform
* @read_waveform: read driver specific waveform presentation from hardware
* @write_waveform: write driver specific waveform presentation to hardware
+ * @get_waveform_step: optional, report the granularity of the waveforms. Called
+ * with the chip locked, so it must not sleep on an atomic chip and should
+ * report a cached clock rate instead of calling clk_get_rate()
* @apply: atomically apply a new PWM config
* @get_state: get the current PWM state.
*/
@@ -305,6 +336,8 @@ struct pwm_ops {
void *wfhw);
int (*write_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
const void *wfhw);
+ int (*get_waveform_step)(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_waveform_step *step);

int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state);
@@ -394,6 +427,7 @@ static inline void pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
/* PWM consumer APIs */
int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
+int pwm_get_waveform_step_might_sleep(struct pwm_device *pwm, struct pwm_waveform_step *step);
int pwm_set_waveform_might_sleep(struct pwm_device *pwm, const struct pwm_waveform *wf, bool exact);
int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
--
2.56.0