mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
To: Jonathan Cameron <jic23@kernel.org>
Cc: "Uwe Kleine-König" <ukleinek@kernel.org>,
	"Conor Dooley" <conor+dt@kernel.org>,
	"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
	"Rob Herring" <robh@kernel.org>,
	"David Lechner" <dlechner@baylibre.com>,
	"Nuno Sá" <nuno.sa@analog.com>,
	"Andy Shevchenko" <andy@kernel.org>,
	"Michael Hennerich" <michael.hennerich@analog.com>,
	"Trevor Gamblin" <tgamblin@baylibre.com>,
	"Joshua Crofts" <crofts@kernel.org>,
	"Sergiu Cuciurean" <sergiu.cuciurean@analog.com>,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-pwm@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	"Nicolae-Daniel Deaconescu"
	<Nicolae-daniel.Deaconescu@analog.com>
Subject: [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep()
Date: Wed, 4 Nov 2026 15:21:39 +0200	[thread overview]
Message-ID: <20261104132143.215581-2-Nicolae-daniel.Deaconescu@analog.com> (raw)
In-Reply-To: <20261104132143.215581-1-Nicolae-daniel.Deaconescu@analog.com>

A consumer that has to meet minimum times, or place an edge relative to
the cycles of a clock that a PWM gates, cannot do that with the waveform
API alone. The rounding functions round down, so a minimum has to be
requested with some margin, and the size of that margin is the step of
the PWM, which only the provider knows.

A provider can report the rate of the clock its timing is derived from
and how many cycles of that clock one step lasts. A consumer can then
work in whole steps, convert them to nanoseconds rounding up, and get
that waveform back from the hardware. Without this, a consumer has to
read the rate from a clock it gets on its own, which describes
a relationship the hardware doesn't have, and is wrong if
PWM divides the clock.

Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
 drivers/pwm/core.c  | 63 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm.h | 32 +++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 2a050ff4608b..6184f17497f0 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -325,6 +325,66 @@ 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 asked for in whole steps
+ * 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.
+ *
+ * Returns: 0 on success, -EOPNOTSUPP if the provider doesn't report it or a
+ * negative errno
+ * Context: May sleep.
+ */
+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;
+
+	if (!pwmchip_supports_waveform(chip) || !ops->get_waveform_step)
+		return -EOPNOTSUPP;
+
+	might_sleep();
+
+	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_cycles_per_step ||
+	    step->clk_rate_hz > (u64)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 +1770,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..f6e943607542 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -76,6 +76,32 @@ 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.
+ *
+ * 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 +313,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 +334,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 +425,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.34.1


  reply	other threads:[~2026-11-04 13:22 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` Nicolae-Daniel Deaconescu [this message]
2026-11-04 19:51   ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Andy Shevchenko
2026-11-06 16:11     ` Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 2/5] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 3/5] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
2026-11-05  0:23   ` Conor Dooley
2026-11-06 16:11     ` Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-04 20:22   ` Andy Shevchenko
2026-11-06 16:10     ` Nicolae-Daniel Deaconescu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261104132143.215581-2-Nicolae-daniel.Deaconescu@analog.com \
    --to=nicolae-daniel.deaconescu@analog.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=crofts@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=michael.hennerich@analog.com \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    --cc=sergiu.cuciurean@analog.com \
    --cc=tgamblin@baylibre.com \
    --cc=ukleinek@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®