summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-11-20 12:34:22 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2024-11-20 12:34:22 -0800
commit7d7560666515855f67df6b0a78fecf2007d35dcc (patch)
tree974816b54382b5390578caca2818849159773171
parentf2ef39727a229ddd127e3538b58f4f5bdc5eeea6 (diff)
parentb2eaa1170e45dc18eb09dcc9abafbe9a7502e960 (diff)
Merge tag 'pwm/for-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux
Pull pwm updates from Uwe Kleine-König: "This contains a new abstraction for PWM waveforms that is more expressive that the legacy one. Compared to the old abstraction it contains a duty_offset member instead of polarity. This new abstraction is already used in an ADC driver merged into the iio tree. The new API requires changes to the lowlevel drivers. For now there are two drivers that are converted to the new API (axi-pwmgen and stm32). Converted drivers continue to work with the old API. Drivers not yet converted only work with the older API. Otherwise it's the usual collection of fixes, cleanups and dt doc updates. This time around thanks go to Andy Shevchenko, Clark Wang, Conor Dooley, David Lechner, Dimitri Fedrau, Frank Li, Jun Li, Kelvin Zhang, Krzysztof Kozlowski, Nuno Sa, Shen Lichuan and Trevor Gamblin for code contributions, testing and review" * tag 'pwm/for-6.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/ukleinek/linux: pwm: Assume a disabled PWM to emit a constant inactive output pwm: core: export pwm_get_state_hw() pwm: core: use device_match_name() instead of strcmp(dev_name(... dt-bindings: pwm: adi,axi-pwmgen: Increase #pwm-cells to 3 pwm: imx27: Use clk_bulk_*() API to simplify clock handling pwm: imx27: Workaround of the pwm output bug when decrease the duty cycle pwm: axi-pwmgen: Enable FORCE_ALIGN by default pwm: axi-pwmgen: Rename 0x10 register dt-bindings: pwm: amlogic: Document C3 PWM pwm: axi-pwmgen: Create a dedicated function for getting driver data from a chip pwm: atmel-tcb: Use min() macro pwm: stm32: Fix error checking for a regmap_read() call pwm: Add kernel doc for members added to pwm_ops recently pwm: Reorder symbols in core.c pwm: stm32: Implementation of the waveform callbacks pwm: axi-pwmgen: Implementation of the waveform callbacks pwm: Add tracing for waveform callbacks pwm: Provide new consumer API functions for waveforms pwm: New abstraction for PWM waveforms pwm: Add more locking
-rw-r--r--Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml4
-rw-r--r--Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml1
-rw-r--r--drivers/pwm/core.c885
-rw-r--r--drivers/pwm/pwm-atmel-tcb.c4
-rw-r--r--drivers/pwm/pwm-axi-pwmgen.c179
-rw-r--r--drivers/pwm/pwm-imx27.c161
-rw-r--r--drivers/pwm/pwm-stm32.c612
-rw-r--r--include/linux/pwm.h66
-rw-r--r--include/trace/events/pwm.h134
9 files changed, 1561 insertions, 485 deletions
diff --git a/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml b/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
index ec6115d3796b..aa35209f74cf 100644
--- a/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
+++ b/Documentation/devicetree/bindings/pwm/adi,axi-pwmgen.yaml
@@ -27,7 +27,7 @@ properties:
maxItems: 1
"#pwm-cells":
- const: 2
+ const: 3
clocks:
maxItems: 1
@@ -44,5 +44,5 @@ examples:
compatible = "adi,axi-pwmgen-2.00.a";
reg = <0x44b00000 0x1000>;
clocks = <&spi_clk>;
- #pwm-cells = <2>;
+ #pwm-cells = <3>;
};
diff --git a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
index e021cf59421a..cc3ebd4deeb6 100644
--- a/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
+++ b/Documentation/devicetree/bindings/pwm/pwm-amlogic.yaml
@@ -39,6 +39,7 @@ properties:
- amlogic,meson-s4-pwm
- items:
- enum:
+ - amlogic,c3-pwm
- amlogic,meson-a1-pwm
- const: amlogic,meson-s4-pwm
- items:
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 6e752e148b98..9c733877e98e 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -31,6 +31,397 @@ static DEFINE_MUTEX(pwm_lock);
static DEFINE_IDR(pwm_chips);
+static void pwmchip_lock(struct pwm_chip *chip)
+{
+ if (chip->atomic)
+ spin_lock(&chip->atomic_lock);
+ else
+ mutex_lock(&chip->nonatomic_lock);
+}
+
+static void pwmchip_unlock(struct pwm_chip *chip)
+{
+ if (chip->atomic)
+ spin_unlock(&chip->atomic_lock);
+ else
+ mutex_unlock(&chip->nonatomic_lock);
+}
+
+DEFINE_GUARD(pwmchip, struct pwm_chip *, pwmchip_lock(_T), pwmchip_unlock(_T))
+
+static bool pwm_wf_valid(const struct pwm_waveform *wf)
+{
+ /*
+ * For now restrict waveforms to period_length_ns <= S64_MAX to provide
+ * some space for future extensions. One possibility is to simplify
+ * representing waveforms with inverted polarity using negative values
+ * somehow.
+ */
+ if (wf->period_length_ns > S64_MAX)
+ return false;
+
+ if (wf->duty_length_ns > wf->period_length_ns)
+ return false;
+
+ /*
+ * .duty_offset_ns is supposed to be smaller than .period_length_ns, apart
+ * from the corner case .duty_offset_ns == 0 && .period_length_ns == 0.
+ */
+ if (wf->duty_offset_ns && wf->duty_offset_ns >= wf->period_length_ns)
+ return false;
+
+ return true;
+}
+
+static void pwm_wf2state(const struct pwm_waveform *wf, struct pwm_state *state)
+{
+ if (wf->period_length_ns) {
+ if (wf->duty_length_ns + wf->duty_offset_ns < wf->period_length_ns)
+ *state = (struct pwm_state){
+ .enabled = true,
+ .polarity = PWM_POLARITY_NORMAL,
+ .period = wf->period_length_ns,
+ .duty_cycle = wf->duty_length_ns,
+ };
+ else
+ *state = (struct pwm_state){
+ .enabled = true,
+ .polarity = PWM_POLARITY_INVERSED,
+ .period = wf->period_length_ns,
+ .duty_cycle = wf->period_length_ns - wf->duty_length_ns,
+ };
+ } else {
+ *state = (struct pwm_state){
+ .enabled = false,
+ };
+ }
+}
+
+static void pwm_state2wf(const struct pwm_state *state, struct pwm_waveform *wf)
+{
+ if (state->enabled) {
+ if (state->polarity == PWM_POLARITY_NORMAL)
+ *wf = (struct pwm_waveform){
+ .period_length_ns = state->period,
+ .duty_length_ns = state->duty_cycle,
+ .duty_offset_ns = 0,
+ };
+ else
+ *wf = (struct pwm_waveform){
+ .period_length_ns = state->period,
+ .duty_length_ns = state->period - state->duty_cycle,
+ .duty_offset_ns = state->duty_cycle,
+ };
+ } else {
+ *wf = (struct pwm_waveform){
+ .period_length_ns = 0,
+ };
+ }
+}
+
+static int pwmwfcmp(const struct pwm_waveform *a, const struct pwm_waveform *b)
+{
+ if (a->period_length_ns > b->period_length_ns)
+ return 1;
+
+ if (a->period_length_ns < b->period_length_ns)
+ return -1;
+
+ if (a->duty_length_ns > b->duty_length_ns)
+ return 1;
+
+ if (a->duty_length_ns < b->duty_length_ns)
+ return -1;
+
+ if (a->duty_offset_ns > b->duty_offset_ns)
+ return 1;
+
+ if (a->duty_offset_ns < b->duty_offset_ns)
+ return -1;
+
+ return 0;
+}
+
+static bool pwm_check_rounding(const struct pwm_waveform *wf,
+ const struct pwm_waveform *wf_rounded)
+{
+ if (!wf->period_length_ns)
+ return true;
+
+ if (wf->period_length_ns < wf_rounded->period_length_ns)
+ return false;
+
+ if (wf->duty_length_ns < wf_rounded->duty_length_ns)
+ return false;
+
+ if (wf->duty_offset_ns < wf_rounded->duty_offset_ns)
+ return false;
+
+ return true;
+}
+
+static int __pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_waveform *wf, void *wfhw)
+{
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw);
+ trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret);
+
+ return ret;
+}
+
+static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *wfhw, struct pwm_waveform *wf)
+{
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf);
+ trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret);
+
+ return ret;
+}
+
+static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw)
+{
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ ret = ops->read_waveform(chip, pwm, wfhw);
+ trace_pwm_read_waveform(pwm, wfhw, ret);
+
+ return ret;
+}
+
+static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw)
+{
+ const struct pwm_ops *ops = chip->ops;
+ int ret;
+
+ ret = ops->write_waveform(chip, pwm, wfhw);
+ trace_pwm_write_waveform(pwm, wfhw, ret);
+
+ return ret;
+}
+
+#define WFHWSIZE 20
+
+/**
+ * pwm_round_waveform_might_sleep - Query hardware capabilities
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @wf: waveform to round and output parameter
+ *
+ * Typically a given waveform cannot be implemented exactly by hardware, e.g.
+ * because hardware only supports coarse period resolution or no duty_offset.
+ * This function returns the actually implemented waveform if you pass wf to
+ * pwm_set_waveform_might_sleep now.
+ *
+ * Note however that the world doesn't stop turning when you call it, so when
+ * doing
+ *
+ * pwm_round_waveform_might_sleep(mypwm, &wf);
+ * pwm_set_waveform_might_sleep(mypwm, &wf, true);
+ *
+ * the latter might fail, e.g. because an input clock changed its rate between
+ * these two calls and the waveform determined by
+ * pwm_round_waveform_might_sleep() cannot be implemented any more.
+ *
+ * Returns 0 on success, 1 if there is no valid hardware configuration matching
+ * the input waveform under the PWM rounding rules or a negative errno.
+ */
+int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ struct pwm_waveform wf_req = *wf;
+ char wfhw[WFHWSIZE];
+ int ret_tohw, ret_fromhw;
+
+ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+
+ if (!pwm_wf_valid(wf))
+ return -EINVAL;
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, wfhw);
+ if (ret_tohw < 0)
+ return ret_tohw;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw > 1)
+ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_tohw: requested %llu/%llu [+%llu], return value %d\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
+
+ ret_fromhw = __pwm_round_waveform_fromhw(chip, pwm, wfhw, wf);
+ if (ret_fromhw < 0)
+ return ret_fromhw;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_fromhw > 0)
+ dev_err(&chip->dev, "Unexpected return value from __pwm_round_waveform_fromhw: requested %llu/%llu [+%llu], return value %d\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw);
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) &&
+ ret_tohw == 0 && !pwm_check_rounding(&wf_req, wf))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns,
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
+
+ return ret_tohw;
+}
+EXPORT_SYMBOL_GPL(pwm_round_waveform_might_sleep);
+
+/**
+ * pwm_get_waveform_might_sleep - Query hardware about current configuration
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @wf: output parameter
+ *
+ * Stores the current configuration of the PWM in @wf. Note this is the
+ * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform.
+ */
+int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ char wfhw[WFHWSIZE];
+ int err;
+
+ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ err = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
+
+ return __pwm_round_waveform_fromhw(chip, pwm, &wfhw, wf);
+}
+EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep);
+
+/* Called with the pwmchip lock held */
+static int __pwm_set_waveform(struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ bool exact)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ char wfhw[WFHWSIZE];
+ struct pwm_waveform wf_rounded;
+ int err;
+
+ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+
+ if (!pwm_wf_valid(wf))
+ return -EINVAL;
+
+ err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw);
+ if (err)
+ return err;
+
+ if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) {
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
+ if (err)
+ return err;
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
+
+ if (exact && pwmwfcmp(wf, &wf_rounded)) {
+ dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
+
+ return 1;
+ }
+ }
+
+ err = __pwm_write_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
+
+ /* update .state */
+ pwm_wf2state(wf, &pwm->state);
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ops->read_waveform && wf->period_length_ns) {
+ struct pwm_waveform wf_set;
+
+ err = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (err)
+ /* maybe ignore? */
+ return err;
+
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_set);
+ if (err)
+ /* maybe ignore? */
+ return err;
+
+ if (pwmwfcmp(&wf_set, &wf_rounded) != 0)
+ dev_err(&chip->dev,
+ "Unexpected setting: requested %llu/%llu [+%llu], expected %llu/%llu [+%llu], set %llu/%llu [+%llu]\n",
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns,
+ wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns);
+ }
+ return 0;
+}
+
+/**
+ * pwm_set_waveform_might_sleep - Apply a new waveform
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @wf: The waveform to apply
+ * @exact: If true no rounding is allowed
+ *
+ * Typically a requested waveform cannot be implemented exactly, e.g. because
+ * you requested .period_length_ns = 100 ns, but the hardware can only set
+ * periods that are a multiple of 8.5 ns. With that hardware passing exact =
+ * true results in pwm_set_waveform_might_sleep() failing and returning 1. If
+ * exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger
+ * than the requested value).
+ * Note that even with exact = true, some rounding by less than 1 is
+ * possible/needed. In the above example requesting .period_length_ns = 94 and
+ * exact = true, you get the hardware configured with period = 93.5 ns.
+ */
+int pwm_set_waveform_might_sleep(struct pwm_device *pwm,
+ const struct pwm_waveform *wf, bool exact)
+{
+ struct pwm_chip *chip = pwm->chip;
+ int err;
+
+ 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();
+ err = __pwm_set_waveform(pwm, wf, exact);
+ non_block_end();
+ } else {
+ err = __pwm_set_waveform(pwm, wf, exact);
+ }
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep);
+
static void pwm_apply_debug(struct pwm_device *pwm,
const struct pwm_state *state)
{
@@ -75,7 +466,7 @@ static void pwm_apply_debug(struct pwm_device *pwm,
state->duty_cycle < state->period)
dev_warn(pwmchip_parent(chip), ".apply ignored .polarity\n");
- if (state->enabled &&
+ if (state->enabled && s2.enabled &&
last->polarity == state->polarity &&
last->period > s2.period &&
last->period <= state->period)
@@ -83,7 +474,11 @@ static void pwm_apply_debug(struct pwm_device *pwm,
".apply didn't pick the best available period (requested: %llu, applied: %llu, possible: %llu)\n",
state->period, s2.period, last->period);
- if (state->enabled && state->period < s2.period)
+ /*
+ * Rounding period up is fine only if duty_cycle is 0 then, because a
+ * flat line doesn't have a characteristic period.
+ */
+ if (state->enabled && s2.enabled && state->period < s2.period && s2.duty_cycle)
dev_warn(pwmchip_parent(chip),
".apply is supposed to round down period (requested: %llu, applied: %llu)\n",
state->period, s2.period);
@@ -99,7 +494,7 @@ static void pwm_apply_debug(struct pwm_device *pwm,
s2.duty_cycle, s2.period,
last->duty_cycle, last->period);
- if (state->enabled && state->duty_cycle < s2.duty_cycle)
+ if (state->enabled && s2.enabled && state->duty_cycle < s2.duty_cycle)
dev_warn(pwmchip_parent(chip),
".apply is supposed to round down duty_cycle (requested: %llu/%llu, applied: %llu/%llu)\n",
state->duty_cycle, state->period,
@@ -164,6 +559,7 @@ static bool pwm_state_valid(const struct pwm_state *state)
static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
{
struct pwm_chip *chip;
+ const struct pwm_ops *ops;
int err;
if (!pwm || !state)
@@ -187,6 +583,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
}
chip = pwm->chip;
+ ops = chip->ops;
if (state->period == pwm->state.period &&
state->duty_cycle == pwm->state.duty_cycle &&
@@ -195,18 +592,69 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
state->usage_power == pwm->state.usage_power)
return 0;
- err = chip->ops->apply(chip, pwm, state);
- trace_pwm_apply(pwm, state, err);
- if (err)
- return err;
+ if (ops->write_waveform) {
+ struct pwm_waveform wf;
+ char wfhw[WFHWSIZE];
- pwm->state = *state;
+ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
- /*
- * only do this after pwm->state was applied as some
- * implementations of .get_state depend on this
- */
- pwm_apply_debug(pwm, state);
+ pwm_state2wf(state, &wf);
+
+ /*
+ * The rounding is wrong here for states with inverted polarity.
+ * While .apply() rounds down duty_cycle (which represents the
+ * time from the start of the period to the inner edge),
+ * .round_waveform_tohw() rounds down the time the PWM is high.
+ * Can be fixed if the need arises, until reported otherwise
+ * let's assume that consumers don't care.
+ */
+
+ err = __pwm_round_waveform_tohw(chip, pwm, &wf, &wfhw);
+ if (err) {
+ if (err > 0)
+ /*
+ * This signals an invalid request, typically
+ * the requested period (or duty_offset) is
+ * smaller than possible with the hardware.
+ */
+ return -EINVAL;
+
+ return err;
+ }
+
+ if (IS_ENABLED(CONFIG_PWM_DEBUG)) {
+ struct pwm_waveform wf_rounded;
+
+ err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded);
+ if (err)
+ return err;
+
+ if (!pwm_check_rounding(&wf, &wf_rounded))
+ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n",
+ wf.duty_length_ns, wf.period_length_ns, wf.duty_offset_ns,
+ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns);
+ }
+
+ err = __pwm_write_waveform(chip, pwm, &wfhw);
+ if (err)
+ return err;
+
+ pwm->state = *state;
+
+ } else {
+ err = ops->apply(chip, pwm, state);
+ trace_pwm_apply(pwm, state, err);
+ if (err)
+ return err;
+
+ pwm->state = *state;
+
+ /*
+ * only do this after pwm->state was applied as some
+ * implementations of .get_state() depend on this
+ */
+ pwm_apply_debug(pwm, state);
+ }
return 0;
}
@@ -220,6 +668,7 @@ static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state)
int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
{
int err;
+ struct pwm_chip *chip = pwm->chip;
/*
* Some lowlevel driver's implementations of .apply() make use of
@@ -230,7 +679,12 @@ int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state)
*/
might_sleep();
- if (IS_ENABLED(CONFIG_PWM_DEBUG) && pwm->chip->atomic) {
+ 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.
@@ -254,14 +708,70 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep);
*/
int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state)
{
- WARN_ONCE(!pwm->chip->atomic,
+ struct pwm_chip *chip = pwm->chip;
+
+ WARN_ONCE(!chip->atomic,
"sleeping PWM driver used in atomic context\n");
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
return __pwm_apply(pwm, state);
}
EXPORT_SYMBOL_GPL(pwm_apply_atomic);
/**
+ * pwm_get_state_hw() - get the current PWM state from hardware
+ * @pwm: PWM device
+ * @state: state to fill with the current PWM state
+ *
+ * Similar to pwm_get_state() but reads the current PWM state from hardware
+ * instead of the requested state.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ * Context: May sleep.
+ */
+int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
+{
+ struct pwm_chip *chip = pwm->chip;
+ const struct pwm_ops *ops = chip->ops;
+ int ret = -EOPNOTSUPP;
+
+ might_sleep();
+
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
+ if (ops->read_waveform) {
+ char wfhw[WFHWSIZE];
+ struct pwm_waveform wf;
+
+ BUG_ON(WFHWSIZE < ops->sizeof_wfhw);
+
+ ret = __pwm_read_waveform(chip, pwm, &wfhw);
+ if (ret)
+ return ret;
+
+ ret = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf);
+ if (ret)
+ return ret;
+
+ pwm_wf2state(&wf, state);
+
+ } else if (ops->get_state) {
+ ret = ops->get_state(chip, pwm, state);
+ trace_pwm_get(pwm, state, ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pwm_get_state_hw);
+
+/**
* pwm_adjust_config() - adjust the current PWM config to the PWM arguments
* @pwm: PWM device
*
@@ -334,8 +844,18 @@ static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
if (!ops->capture)
return -ENOSYS;
+ /*
+ * Holding the pwm_lock is probably not needed. If you use pwm_capture()
+ * and you're interested to speed it up, please convince yourself it's
+ * really not needed, test and then suggest a patch on the mailing list.
+ */
guard(mutex)(&pwm_lock);
+ guard(pwmchip)(chip);
+
+ if (!chip->operational)
+ return -ENODEV;
+
return ops->capture(chip, pwm, result, timeout);
}
@@ -350,9 +870,7 @@ static struct pwm_chip *pwmchip_find_by_name(const char *name)
guard(mutex)(&pwm_lock);
idr_for_each_entry_ul(&pwm_chips, chip, tmp, id) {
- const char *chip_name = dev_name(pwmchip_parent(chip));
-
- if (chip_name && strcmp(chip_name, name) == 0)
+ if (device_match_name(pwmchip_parent(chip), name))
return chip;
}
@@ -368,6 +886,14 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
if (test_bit(PWMF_REQUESTED, &pwm->flags))
return -EBUSY;
+ /*
+ * This function is called while holding pwm_lock. As .operational only
+ * changes while holding this lock, checking it here without holding the
+ * chip lock is fine.
+ */
+ if (!chip->operational)
+ return -ENODEV;
+
if (!try_module_get(chip->owner))
return -ENODEV;
@@ -386,7 +912,7 @@ err_get_device:
}
}
- if (ops->get_state) {
+ if (ops->read_waveform || ops->get_state) {
/*
* Zero-initialize state because most drivers are unaware of
* .usage_power. The other members of state are supposed to be
@@ -396,9 +922,7 @@ err_get_device:
*/
struct pwm_state state = { 0, };
- err = ops->get_state(chip, pwm, &state);
- trace_pwm_get(pwm, &state, err);
-
+ err = pwm_get_state_hw(pwm, &state);
if (!err)
pwm->state = state;
@@ -1020,6 +1544,7 @@ struct pwm_chip *pwmchip_alloc(struct device *parent, unsigned int npwm, size_t
chip->npwm = npwm;
chip->uses_pwmchip_alloc = true;
+ chip->operational = false;
pwmchip_dev = &chip->dev;
device_initialize(pwmchip_dev);
@@ -1084,114 +1609,27 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
{
const struct pwm_ops *ops = chip->ops;
- if (!ops->apply)
- return false;
-
- if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
- dev_warn(pwmchip_parent(chip),
- "Please implement the .get_state() callback\n");
-
- return true;
-}
-
-/**
- * __pwmchip_add() - register a new PWM chip
- * @chip: the PWM chip to add
- * @owner: reference to the module providing the chip.
- *
- * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the
- * pwmchip_add wrapper to do this right.
- *
- * Returns: 0 on success or a negative error code on failure.
- */
-int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
-{
- int ret;
-
- if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm)
- return -EINVAL;
-
- /*
- * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc,
- * otherwise the embedded struct device might disappear too early
- * resulting in memory corruption.
- * Catch drivers that were not converted appropriately.
- */
- if (!chip->uses_pwmchip_alloc)
- return -EINVAL;
-
- if (!pwm_ops_check(chip))
- return -EINVAL;
-
- chip->owner = owner;
-
- guard(mutex)(&pwm_lock);
-
- ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
- if (ret < 0)
- return ret;
-
- chip->id = ret;
-
- dev_set_name(&chip->dev, "pwmchip%u", chip->id);
-
- if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_add(chip);
-
- ret = device_add(&chip->dev);
- if (ret)
- goto err_device_add;
-
- return 0;
-
-err_device_add:
- if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_remove(chip);
-
- idr_remove(&pwm_chips, chip->id);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(__pwmchip_add);
-
-/**
- * pwmchip_remove() - remove a PWM chip
- * @chip: the PWM chip to remove
- *
- * Removes a PWM chip.
- */
-void pwmchip_remove(struct pwm_chip *chip)
-{
- pwmchip_sysfs_unexport(chip);
-
- if (IS_ENABLED(CONFIG_OF))
- of_pwmchip_remove(chip);
-
- scoped_guard(mutex, &pwm_lock)
- idr_remove(&pwm_chips, chip->id);
-
- device_del(&chip->dev);
-}
-EXPORT_SYMBOL_GPL(pwmchip_remove);
-
-static void devm_pwmchip_remove(void *data)
-{
- struct pwm_chip *chip = data;
-
- pwmchip_remove(chip);
-}
+ if (ops->write_waveform) {
+ if (!ops->round_waveform_tohw ||
+ !ops->round_waveform_fromhw ||
+ !ops->write_waveform)
+ return false;
-int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner)
-{
- int ret;
+ if (WFHWSIZE < ops->sizeof_wfhw) {
+ dev_warn(pwmchip_parent(chip), "WFHWSIZE < %zu\n", ops->sizeof_wfhw);
+ return false;
+ }
+ } else {
+ if (!ops->apply)
+ return false;
- ret = __pwmchip_add(chip, owner);
- if (ret)
- return ret;
+ if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
+ dev_warn(pwmchip_parent(chip),
+ "Please implement the .get_state() callback\n");
+ }
- return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip);
+ return true;
}
-EXPORT_SYMBOL_GPL(__devm_pwmchip_add);
static struct device_link *pwm_device_link_add(struct device *dev,
struct pwm_device *pwm)
@@ -1371,36 +1809,6 @@ static DEFINE_MUTEX(pwm_lookup_lock);
static LIST_HEAD(pwm_lookup_list);
/**
- * pwm_add_table() - register PWM device consumers
- * @table: array of consumers to register
- * @num: number of consumers in table
- */
-void pwm_add_table(struct pwm_lookup *table, size_t num)
-{
- guard(mutex)(&pwm_lookup_lock);
-
- while (num--) {
- list_add_tail(&table->list, &pwm_lookup_list);
- table++;
- }
-}
-
-/**
- * pwm_remove_table() - unregister PWM device consumers
- * @table: array of consumers to unregister
- * @num: number of consumers in table
- */
-void pwm_remove_table(struct pwm_lookup *table, size_t num)
-{
- guard(mutex)(&pwm_lookup_lock);
-
- while (num--) {
- list_del(&table->list);
- table++;
- }
-}
-
-/**
* pwm_get() - look up and request a PWM device
* @dev: device for PWM consumer
* @con_id: consumer name
@@ -1538,12 +1946,17 @@ void pwm_put(struct pwm_device *pwm)
guard(mutex)(&pwm_lock);
- if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ /*
+ * Trigger a warning if a consumer called pwm_put() twice.
+ * If the chip isn't operational, PWMF_REQUESTED was already cleared in
+ * pwmchip_remove(). So don't warn in this case.
+ */
+ if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
pr_warn("PWM device already freed\n");
return;
}
- if (chip->ops->free)
+ if (chip->operational && chip->ops->free)
pwm->chip->ops->free(pwm->chip, pwm);
pwm->label = NULL;
@@ -1621,6 +2034,162 @@ struct pwm_device *devm_fwnode_pwm_get(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get);
+/**
+ * __pwmchip_add() - register a new PWM chip
+ * @chip: the PWM chip to add
+ * @owner: reference to the module providing the chip.
+ *
+ * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the
+ * pwmchip_add wrapper to do this right.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int __pwmchip_add(struct pwm_chip *chip, struct module *owner)
+{
+ int ret;
+
+ if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm)
+ return -EINVAL;
+
+ /*
+ * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc,
+ * otherwise the embedded struct device might disappear too early
+ * resulting in memory corruption.
+ * Catch drivers that were not converted appropriately.
+ */
+ if (!chip->uses_pwmchip_alloc)
+ return -EINVAL;
+
+ if (!pwm_ops_check(chip))
+ return -EINVAL;
+
+ chip->owner = owner;
+
+ if (chip->atomic)
+ spin_lock_init(&chip->atomic_lock);
+ else
+ mutex_init(&chip->nonatomic_lock);
+
+ guard(mutex)(&pwm_lock);
+
+ ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ chip->id = ret;
+
+ dev_set_name(&chip->dev, "pwmchip%u", chip->id);
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_add(chip);
+
+ scoped_guard(pwmchip, chip)
+ chip->operational = true;
+
+ ret = device_add(&chip->dev);
+ if (ret)
+ goto err_device_add;
+
+ return 0;
+
+err_device_add:
+ scoped_guard(pwmchip, chip)
+ chip->operational = false;
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_remove(chip);
+
+ idr_remove(&pwm_chips, chip->id);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(__pwmchip_add);
+
+/**
+ * pwmchip_remove() - remove a PWM chip
+ * @chip: the PWM chip to remove
+ *
+ * Removes a PWM chip.
+ */
+void pwmchip_remove(struct pwm_chip *chip)
+{
+ pwmchip_sysfs_unexport(chip);
+
+ scoped_guard(mutex, &pwm_lock) {
+ unsigned int i;
+
+ scoped_guard(pwmchip, chip)
+ chip->operational = false;
+
+ for (i = 0; i < chip->npwm; ++i) {
+ struct pwm_device *pwm = &chip->pwms[i];
+
+ if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
+ dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i);
+ if (pwm->chip->ops->free)
+ pwm->chip->ops->free(pwm->chip, pwm);
+ }
+ }
+
+ if (IS_ENABLED(CONFIG_OF))
+ of_pwmchip_remove(chip);
+
+ idr_remove(&pwm_chips, chip->id);
+ }
+
+ device_del(&chip->dev);
+}
+EXPORT_SYMBOL_GPL(pwmchip_remove);
+
+static void devm_pwmchip_remove(void *data)
+{
+ struct pwm_chip *chip = data;
+
+ pwmchip_remove(chip);
+}
+
+int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner)
+{
+ int ret;
+
+ ret = __pwmchip_add(chip, owner);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip);
+}
+EXPORT_SYMBOL_GPL(__devm_pwmchip_add);
+
+/**
+ * pwm_add_table() - register PWM device consumers
+ * @table: array of consumers to register
+ * @num: number of consumers in table
+ */
+void pwm_add_table(struct pwm_lookup *table, size_t num)
+{
+ guard(mutex)(&pwm_lookup_lock);
+
+ while (num--) {
+ list_add_tail(&table->list, &pwm_lookup_list);
+ table++;
+ }
+}
+
+/**
+ * pwm_remove_table() - unregister PWM device consumers
+ * @table: array of consumers to unregister
+ * @num: number of consumers in table
+ */
+void pwm_remove_table(struct pwm_lookup *table, size_t num)
+{
+ guard(mutex)(&pwm_lookup_lock);
+
+ while (num--) {
+ list_del(&table->list);
+ table++;
+ }
+}
+
static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s)
{
unsigned int i;
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c
index 5ee4254d1e48..f9ff78ba122d 100644
--- a/drivers/pwm/pwm-atmel-tcb.c
+++ b/drivers/pwm/pwm-atmel-tcb.c
@@ -342,8 +342,8 @@ static int atmel_tcb_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}
- period = state->period < INT_MAX ? state->period : INT_MAX;
- duty_cycle = state->duty_cycle < INT_MAX ? state->duty_cycle : INT_MAX;
+ period = min(state->period, INT_MAX);
+ duty_cycle = min(state->duty_cycle, INT_MAX);
ret = atmel_tcb_pwm_config(chip, pwm, duty_cycle, period);
if (ret)
diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c
index b5477659ba18..4259a0db9ff4 100644
--- a/drivers/pwm/pwm-axi-pwmgen.c
+++ b/drivers/pwm/pwm-axi-pwmgen.c
@@ -9,7 +9,7 @@
*
* Limitations:
* - The writes to registers for period and duty are shadowed until
- * LOAD_CONFIG is written to AXI_PWMGEN_REG_CONFIG, at which point
+ * LOAD_CONFIG is written to AXI_PWMGEN_REG_RSTN, at which point
* they take effect.
* - Writing LOAD_CONFIG also has the effect of re-synchronizing all
* enabled channels, which could cause glitching on other channels. It
@@ -23,6 +23,7 @@
#include <linux/err.h>
#include <linux/fpga/adi-axi-common.h>
#include <linux/io.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
@@ -32,14 +33,16 @@
#define AXI_PWMGEN_REG_ID 0x04
#define AXI_PWMGEN_REG_SCRATCHPAD 0x08
#define AXI_PWMGEN_REG_CORE_MAGIC 0x0C
-#define AXI_PWMGEN_REG_CONFIG 0x10
+#define AXI_PWMGEN_REG_RSTN 0x10
+#define AXI_PWMGEN_REG_RSTN_LOAD_CONFIG BIT(1)
+#define AXI_PWMGEN_REG_RSTN_RESET BIT(0)
#define AXI_PWMGEN_REG_NPWM 0x14
+#define AXI_PWMGEN_REG_CONFIG 0x18
+#define AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN BIT(1)
#define AXI_PWMGEN_CHX_PERIOD(ch) (0x40 + (4 * (ch)))
#define AXI_PWMGEN_CHX_DUTY(ch) (0x80 + (4 * (ch)))
#define AXI_PWMGEN_CHX_OFFSET(ch) (0xC0 + (4 * (ch)))
#define AXI_PWMGEN_REG_CORE_MAGIC_VAL 0x601A3471 /* Identification number to test during setup */
-#define AXI_PWMGEN_LOAD_CONFIG BIT(1)
-#define AXI_PWMGEN_REG_CONFIG_RESET BIT(0)
struct axi_pwmgen_ddata {
struct regmap *regmap;
@@ -53,81 +56,147 @@ static const struct regmap_config axi_pwmgen_regmap_config = {
.max_register = 0xFC,
};
-static int axi_pwmgen_apply(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
+/* This represents a hardware configuration for one channel */
+struct axi_pwmgen_waveform {
+ u32 period_cnt;
+ u32 duty_cycle_cnt;
+ u32 duty_offset_cnt;
+};
+
+static struct axi_pwmgen_ddata *axi_pwmgen_ddata_from_chip(struct pwm_chip *chip)
{
- struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
- unsigned int ch = pwm->hwpwm;
- struct regmap *regmap = ddata->regmap;
- u64 period_cnt, duty_cnt;
- int ret;
+ return pwmchip_get_drvdata(chip);
+}
- if (state->polarity != PWM_POLARITY_NORMAL)
- return -EINVAL;
+static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct axi_pwmgen_waveform){
+ .period_cnt = 0,
+ .duty_cycle_cnt = 0,
+ .duty_offset_cnt = 0,
+ };
+ } else {
+ /* With ddata->clk_rate_hz < NSEC_PER_SEC this won't overflow. */
+ wfhw->period_cnt = min_t(u64,
+ mul_u64_u32_div(wf->period_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+
+ if (wfhw->period_cnt == 0) {
+ /*
+ * The specified period is too short for the hardware.
+ * Let's round .duty_cycle down to 0 to get a (somewhat)
+ * valid result.
+ */
+ wfhw->period_cnt = 1;
+ wfhw->duty_cycle_cnt = 0;
+ wfhw->duty_offset_cnt = 0;
+ } else {
+ wfhw->duty_cycle_cnt = min_t(u64,
+ mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+ wfhw->duty_offset_cnt = min_t(u64,
+ mul_u64_u32_div(wf->duty_offset_ns, ddata->clk_rate_hz, NSEC_PER_SEC),
+ U32_MAX);
+ }
+ }
- if (state->enabled) {
- period_cnt = mul_u64_u64_div_u64(state->period, ddata->clk_rate_hz, NSEC_PER_SEC);
- if (period_cnt > UINT_MAX)
- period_cnt = UINT_MAX;
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> PERIOD: %08x, DUTY: %08x, OFFSET: %08x\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt);
- if (period_cnt == 0)
- return -EINVAL;
+ return 0;
+}
- ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), period_cnt);
- if (ret)
- return ret;
+static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *_wfhw, struct pwm_waveform *wf)
+{
+ const struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
- duty_cnt = mul_u64_u64_div_u64(state->duty_cycle, ddata->clk_rate_hz, NSEC_PER_SEC);
- if (duty_cnt > UINT_MAX)
- duty_cnt = UINT_MAX;
+ wf->period_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->period_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
- ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), duty_cnt);
- if (ret)
- return ret;
- } else {
- ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), 0);
- if (ret)
- return ret;
+ wf->duty_length_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_cycle_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
- ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), 0);
- if (ret)
- return ret;
- }
+ wf->duty_offset_ns = DIV64_U64_ROUND_UP((u64)wfhw->duty_offset_cnt * NSEC_PER_SEC,
+ ddata->clk_rate_hz);
- return regmap_write(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_LOAD_CONFIG);
+ return 0;
}
-static int axi_pwmgen_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
- struct pwm_state *state)
+static int axi_pwmgen_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
{
- struct axi_pwmgen_ddata *ddata = pwmchip_get_drvdata(chip);
+ const struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
struct regmap *regmap = ddata->regmap;
unsigned int ch = pwm->hwpwm;
- u32 cnt;
int ret;
- ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &cnt);
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_PERIOD(ch), wfhw->period_cnt);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_DUTY(ch), wfhw->duty_cycle_cnt);
if (ret)
return ret;
- state->enabled = cnt != 0;
+ ret = regmap_write(regmap, AXI_PWMGEN_CHX_OFFSET(ch), wfhw->duty_offset_cnt);
+ if (ret)
+ return ret;
+
+ return regmap_write(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_LOAD_CONFIG);
+}
+
+static int axi_pwmgen_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct axi_pwmgen_waveform *wfhw = _wfhw;
+ struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+ struct regmap *regmap = ddata->regmap;
+ unsigned int ch = pwm->hwpwm;
+ int ret;
- state->period = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_PERIOD(ch), &wfhw->period_cnt);
+ if (ret)
+ return ret;
- ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &cnt);
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_DUTY(ch), &wfhw->duty_cycle_cnt);
if (ret)
return ret;
- state->duty_cycle = DIV_ROUND_UP_ULL((u64)cnt * NSEC_PER_SEC, ddata->clk_rate_hz);
+ ret = regmap_read(regmap, AXI_PWMGEN_CHX_OFFSET(ch), &wfhw->duty_offset_cnt);
+ if (ret)
+ return ret;
- state->polarity = PWM_POLARITY_NORMAL;
+ if (wfhw->duty_cycle_cnt > wfhw->period_cnt)
+ wfhw->duty_cycle_cnt = wfhw->period_cnt;
+
+ /* XXX: is this the actual behaviour of the hardware? */
+ if (wfhw->duty_offset_cnt >= wfhw->period_cnt) {
+ wfhw->duty_cycle_cnt = 0;
+ wfhw->duty_offset_cnt = 0;
+ }
return 0;
}
static const struct pwm_ops axi_pwmgen_pwm_ops = {
- .apply = axi_pwmgen_apply,
- .get_state = axi_pwmgen_get_state,
+ .sizeof_wfhw = sizeof(struct axi_pwmgen_waveform),
+ .round_waveform_tohw = axi_pwmgen_round_waveform_tohw,
+ .round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw,
+ .read_waveform = axi_pwmgen_read_waveform,
+ .write_waveform = axi_pwmgen_write_waveform,
};
static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
@@ -156,7 +225,17 @@ static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
}
/* Enable the core */
- ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_RESET);
+ ret = regmap_clear_bits(regmap, AXI_PWMGEN_REG_RSTN, AXI_PWMGEN_REG_RSTN_RESET);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable force align so that changes to PWM period and duty cycle take
+ * effect immediately. Otherwise, the effect of the change is delayed
+ * until the period of all channels run out, which can be long after the
+ * apply function returns.
+ */
+ ret = regmap_set_bits(regmap, AXI_PWMGEN_REG_CONFIG, AXI_PWMGEN_REG_CONFIG_FORCE_ALIGN);
if (ret)
return ret;
diff --git a/drivers/pwm/pwm-imx27.c b/drivers/pwm/pwm-imx27.c
index 9e2bbf5b4a8c..3d34cdc4a3a5 100644
--- a/drivers/pwm/pwm-imx27.c
+++ b/drivers/pwm/pwm-imx27.c
@@ -26,6 +26,7 @@
#define MX3_PWMSR 0x04 /* PWM Status Register */
#define MX3_PWMSAR 0x0C /* PWM Sample Register */
#define MX3_PWMPR 0x10 /* PWM Period Register */
+#define MX3_PWMCNR 0x14 /* PWM Counter Register */
#define MX3_PWMCR_FWM GENMASK(27, 26)
#define MX3_PWMCR_STOPEN BIT(25)
@@ -79,9 +80,12 @@
/* PWMPR register value of 0xffff has the same effect as 0xfffe */
#define MX3_PWMPR_MAX 0xfffe
+static const char * const pwm_imx27_clks[] = {"ipg", "per"};
+#define PWM_IMX27_PER 1
+
struct pwm_imx27_chip {
- struct clk *clk_ipg;
- struct clk *clk_per;
+ struct clk_bulk_data clks[ARRAY_SIZE(pwm_imx27_clks)];
+ int clks_cnt;
void __iomem *mmio_base;
/*
@@ -97,29 +101,6 @@ static inline struct pwm_imx27_chip *to_pwm_imx27_chip(struct pwm_chip *chip)
return pwmchip_get_drvdata(chip);
}
-static int pwm_imx27_clk_prepare_enable(struct pwm_imx27_chip *imx)
-{
- int ret;
-
- ret = clk_prepare_enable(imx->clk_ipg);
- if (ret)
- return ret;
-
- ret = clk_prepare_enable(imx->clk_per);
- if (ret) {
- clk_disable_unprepare(imx->clk_ipg);
- return ret;
- }
-
- return 0;
-}
-
-static void pwm_imx27_clk_disable_unprepare(struct pwm_imx27_chip *imx)
-{
- clk_disable_unprepare(imx->clk_per);
- clk_disable_unprepare(imx->clk_ipg);
-}
-
static int pwm_imx27_get_state(struct pwm_chip *chip,
struct pwm_device *pwm, struct pwm_state *state)
{
@@ -128,7 +109,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip,
u64 tmp;
int ret;
- ret = pwm_imx27_clk_prepare_enable(imx);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret < 0)
return ret;
@@ -151,7 +132,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip,
}
prescaler = MX3_PWMCR_PRESCALER_GET(val);
- pwm_clk = clk_get_rate(imx->clk_per);
+ pwm_clk = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
val = readl(imx->mmio_base + MX3_PWMPR);
period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
@@ -171,7 +152,7 @@ static int pwm_imx27_get_state(struct pwm_chip *chip,
tmp = NSEC_PER_SEC * (u64)(val) * prescaler;
state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk);
- pwm_imx27_clk_disable_unprepare(imx);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
return 0;
}
@@ -219,14 +200,16 @@ static void pwm_imx27_wait_fifo_slot(struct pwm_chip *chip,
static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
- unsigned long period_cycles, duty_cycles, prescale;
+ unsigned long period_cycles, duty_cycles, prescale, period_us, tmp;
struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
unsigned long long c;
unsigned long long clkrate;
+ unsigned long flags;
+ int val;
int ret;
u32 cr;
- clkrate = clk_get_rate(imx->clk_per);
+ clkrate = clk_get_rate(imx->clks[PWM_IMX27_PER].clk);
c = clkrate * state->period;
do_div(c, NSEC_PER_SEC);
@@ -256,14 +239,105 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
if (pwm->state.enabled) {
pwm_imx27_wait_fifo_slot(chip, pwm);
} else {
- ret = pwm_imx27_clk_prepare_enable(imx);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret)
return ret;
pwm_imx27_sw_reset(chip);
}
- writel(duty_cycles, imx->mmio_base + MX3_PWMSAR);
+ val = readl(imx->mmio_base + MX3_PWMPR);
+ val = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
+ cr = readl(imx->mmio_base + MX3_PWMCR);
+ tmp = NSEC_PER_SEC * (u64)(val + 2) * MX3_PWMCR_PRESCALER_GET(cr);
+ tmp = DIV_ROUND_UP_ULL(tmp, clkrate);
+ period_us = DIV_ROUND_UP_ULL(tmp, 1000);
+
+ /*
+ * ERR051198:
+ * PWM: PWM output may not function correctly if the FIFO is empty when
+ * a new SAR value is programmed
+ *
+ * Description:
+ * When the PWM FIFO is empty, a new value programmed to the PWM Sample
+ * register (PWM_PWMSAR) will be directly applied even if the current
+ * timer period has not expired.
+ *
+ * If the new SAMPLE value programmed in the PWM_PWMSAR register is
+ * less than the previous value, and the PWM counter register
+ * (PWM_PWMCNR) that contains the current COUNT value is greater than
+ * the new programmed SAMPLE value, the current period will not flip
+ * the level. This may result in an output pulse with a duty cycle of
+ * 100%.
+ *
+ * Consider a change from
+ * ________
+ * / \______/
+ * ^ * ^
+ * to
+ * ____
+ * / \__________/
+ * ^ ^
+ * At the time marked by *, the new write value will be directly applied
+ * to SAR even the current period is not over if FIFO is empty.
+ *
+ * ________ ____________________
+ * / \______/ \__________/
+ * ^ ^ * ^ ^
+ * |<-- old SAR -->| |<-- new SAR -->|
+ *
+ * That is the output is active for a whole period.
+ *
+ * Workaround:
+ * Check new SAR less than old SAR and current counter is in errata
+ * windows, write extra old SAR into FIFO and new SAR will effect at
+ * next period.
+ *
+ * Sometime period is quite long, such as over 1 second. If add old SAR
+ * into FIFO unconditional, new SAR have to wait for next period. It
+ * may be too long.
+ *
+ * Turn off the interrupt to ensure that not IRQ and schedule happen
+ * during above operations. If any irq and schedule happen, counter
+ * in PWM will be out of data and take wrong action.
+ *
+ * Add a safety margin 1.5us because it needs some time to complete
+ * IO write.
+ *
+ * Use writel_relaxed() to minimize the interval between two writes to
+ * the SAR register to increase the fastest PWM frequency supported.
+ *
+ * When the PWM period is longer than 2us(or <500kHz), this workaround
+ * can solve this problem. No software workaround is available if PWM
+ * period is shorter than IO write. Just try best to fill old data
+ * into FIFO.
+ */
+ c = clkrate * 1500;
+ do_div(c, NSEC_PER_SEC);
+
+ local_irq_save(flags);
+ val = FIELD_GET(MX3_PWMSR_FIFOAV, readl_relaxed(imx->mmio_base + MX3_PWMSR));
+
+ if (duty_cycles < imx->duty_cycle && (cr & MX3_PWMCR_EN)) {
+ if (period_us < 2) { /* 2us = 500 kHz */
+ /* Best effort attempt to fix up >500 kHz case */
+ udelay(3 * period_us);
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ } else if (val < MX3_PWMSR_FIFOAV_2WORDS) {
+ val = readl_relaxed(imx->mmio_base + MX3_PWMCNR);
+ /*
+ * If counter is close to period, controller may roll over when
+ * next IO write.
+ */
+ if ((val + c >= duty_cycles && val < imx->duty_cycle) ||
+ val + c >= period_cycles)
+ writel_relaxed(imx->duty_cycle, imx->mmio_base + MX3_PWMSAR);
+ }
+ }
+ writel_relaxed(duty_cycles, imx->mmio_base + MX3_PWMSAR);
+ local_irq_restore(flags);
+
writel(period_cycles, imx->mmio_base + MX3_PWMPR);
/*
@@ -287,7 +361,7 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
writel(cr, imx->mmio_base + MX3_PWMCR);
if (!state->enabled)
- pwm_imx27_clk_disable_unprepare(imx);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
return 0;
}
@@ -309,21 +383,22 @@ static int pwm_imx27_probe(struct platform_device *pdev)
struct pwm_imx27_chip *imx;
int ret;
u32 pwmcr;
+ int i;
chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*imx));
if (IS_ERR(chip))
return PTR_ERR(chip);
imx = to_pwm_imx27_chip(chip);
- imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
- if (IS_ERR(imx->clk_ipg))
- return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_ipg),
- "getting ipg clock failed\n");
+ imx->clks_cnt = ARRAY_SIZE(pwm_imx27_clks);
+ for (i = 0; i < imx->clks_cnt; ++i)
+ imx->clks[i].id = pwm_imx27_clks[i];
+
+ ret = devm_clk_bulk_get(&pdev->dev, imx->clks_cnt, imx->clks);
- imx->clk_per = devm_clk_get(&pdev->dev, "per");
- if (IS_ERR(imx->clk_per))
- return dev_err_probe(&pdev->dev, PTR_ERR(imx->clk_per),
- "failed to get peripheral clock\n");
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "getting clocks failed\n");
chip->ops = &pwm_imx27_ops;
@@ -331,14 +406,14 @@ static int pwm_imx27_probe(struct platform_device *pdev)
if (IS_ERR(imx->mmio_base))
return PTR_ERR(imx->mmio_base);
- ret = pwm_imx27_clk_prepare_enable(imx);
+ ret = clk_bulk_prepare_enable(imx->clks_cnt, imx->clks);
if (ret)
return ret;
/* keep clks on if pwm is running */
pwmcr = readl(imx->mmio_base + MX3_PWMCR);
if (!(pwmcr & MX3_PWMCR_EN))
- pwm_imx27_clk_disable_unprepare(imx);
+ clk_bulk_disable_unprepare(imx->clks_cnt, imx->clks);
return devm_pwmchip_add(&pdev->dev, chip);
}
diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c
index eb24054f9729..b889e64522c3 100644
--- a/drivers/pwm/pwm-stm32.c
+++ b/drivers/pwm/pwm-stm32.c
@@ -51,6 +51,391 @@ static u32 active_channels(struct stm32_pwm *dev)
return ccer & TIM_CCER_CCXE;
}
+struct stm32_pwm_waveform {
+ u32 ccer;
+ u32 psc;
+ u32 arr;
+ u32 ccr;
+};
+
+static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const struct pwm_waveform *wf,
+ void *_wfhw)
+{
+ struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ unsigned long rate;
+ u64 ccr, duty;
+ int ret;
+
+ if (wf->period_length_ns == 0) {
+ *wfhw = (struct stm32_pwm_waveform){
+ .ccer = 0,
+ };
+
+ return 0;
+ }
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ wfhw->ccer = TIM_CCER_CCxE(ch + 1);
+ if (priv->have_complementary_output)
+ wfhw->ccer = TIM_CCER_CCxNE(ch + 1);
+
+ rate = clk_get_rate(priv->clk);
+
+ if (active_channels(priv) & ~(1 << ch * 4)) {
+ u64 arr;
+
+ /*
+ * Other channels are already enabled, so the configured PSC and
+ * ARR must be used for this channel, too.
+ */
+ ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr);
+ if (ret)
+ goto out;
+
+ /*
+ * calculate the best value for ARR for the given PSC, refuse if
+ * the resulting period gets bigger than the requested one.
+ */
+ arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ if (arr <= wfhw->arr) {
+ /*
+ * requested period is small than the currently
+ * configured and unchangable period, report back the smallest
+ * possible period, i.e. the current state; Initialize
+ * ccr to anything valid.
+ */
+ wfhw->ccr = 0;
+ ret = 1;
+ goto out;
+ }
+
+ } else {
+ /*
+ * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so
+ * the calculations here won't overflow.
+ * First we need to find the minimal value for prescaler such that
+ *
+ * period_ns * clkrate
+ * ------------------------------ < max_arr + 1
+ * NSEC_PER_SEC * (prescaler + 1)
+ *
+ * This equation is equivalent to
+ *
+ * period_ns * clkrate
+ * ---------------------------- < prescaler + 1
+ * NSEC_PER_SEC * (max_arr + 1)
+ *
+ * Using integer division and knowing that the right hand side is
+ * integer, this is further equivalent to
+ *
+ * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler
+ */
+ u64 psc = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1));
+ u64 arr;
+
+ wfhw->psc = min_t(u64, psc, MAX_TIM_PSC);
+
+ arr = mul_u64_u64_div_u64(wf->period_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ if (!arr) {
+ /*
+ * requested period is too small, report back the smallest
+ * possible period, i.e. ARR = 0. The only valid CCR
+ * value is then zero, too.
+ */
+ wfhw->arr = 0;
+ wfhw->ccr = 0;
+ ret = 1;
+ goto out;
+ }
+
+ /*
+ * ARR is limited intentionally to values less than
+ * priv->max_arr to allow 100% duty cycle.
+ */
+ wfhw->arr = min_t(u64, arr, priv->max_arr) - 1;
+ }
+
+ duty = mul_u64_u64_div_u64(wf->duty_length_ns, rate,
+ (u64)NSEC_PER_SEC * (wfhw->psc + 1));
+ duty = min_t(u64, duty, wfhw->arr + 1);
+
+ if (wf->duty_length_ns && wf->duty_offset_ns &&
+ wf->duty_length_ns + wf->duty_offset_ns >= wf->period_length_ns) {
+ wfhw->ccer |= TIM_CCER_CCxP(ch + 1);
+ if (priv->have_complementary_output)
+ wfhw->ccer |= TIM_CCER_CCxNP(ch + 1);
+
+ ccr = wfhw->arr + 1 - duty;
+ } else {
+ ccr = duty;
+ }
+
+ wfhw->ccr = min_t(u64, ccr, wfhw->arr + 1);
+
+ dev_dbg(&chip->dev, "pwm#%u: %lld/%lld [+%lld] @%lu -> CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x\n",
+ pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns,
+ rate, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr);
+
+out:
+ clk_disable(priv->clk);
+
+ return ret;
+}
+
+/*
+ * This should be moved to lib/math/div64.c. Currently there are some changes
+ * pending to mul_u64_u64_div_u64. Uwe will care for that when the dust settles.
+ */
+static u64 stm32_pwm_mul_u64_u64_div_u64_roundup(u64 a, u64 b, u64 c)
+{
+ u64 res = mul_u64_u64_div_u64(a, b, c);
+ /* Those multiplications might overflow but it doesn't matter */
+ u64 rem = a * b - c * res;
+
+ if (rem)
+ res += 1;
+
+ return res;
+}
+
+static int stm32_pwm_round_waveform_fromhw(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw,
+ struct pwm_waveform *wf)
+{
+ const struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ unsigned long rate = clk_get_rate(priv->clk);
+ u64 ccr_ns;
+
+ /* The result doesn't overflow for rate >= 15259 */
+ wf->period_length_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1),
+ NSEC_PER_SEC, rate);
+
+ ccr_ns = stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * wfhw->ccr,
+ NSEC_PER_SEC, rate);
+
+ if (wfhw->ccer & TIM_CCER_CCxP(ch + 1)) {
+ wf->duty_length_ns =
+ stm32_pwm_mul_u64_u64_div_u64_roundup(((u64)wfhw->psc + 1) * (wfhw->arr + 1 - wfhw->ccr),
+ NSEC_PER_SEC, rate);
+
+ wf->duty_offset_ns = ccr_ns;
+ } else {
+ wf->duty_length_ns = ccr_ns;
+ wf->duty_offset_ns = 0;
+ }
+
+ dev_dbg(&chip->dev, "pwm#%u: CCER: %08x, PSC: %08x, ARR: %08x, CCR: %08x @%lu -> %lld/%lld [+%lld]\n",
+ pwm->hwpwm, wfhw->ccer, wfhw->psc, wfhw->arr, wfhw->ccr, rate,
+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns);
+
+ } else {
+ *wf = (struct pwm_waveform){
+ .period_length_ns = 0,
+ };
+ }
+
+ return 0;
+}
+
+static int stm32_pwm_read_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ void *_wfhw)
+{
+ struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &wfhw->ccer);
+ if (ret)
+ goto out;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ ret = regmap_read(priv->regmap, TIM_PSC, &wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &wfhw->arr);
+ if (ret)
+ goto out;
+
+ if (wfhw->arr == U32_MAX)
+ wfhw->arr -= 1;
+
+ ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &wfhw->ccr);
+ if (ret)
+ goto out;
+
+ if (wfhw->ccr > wfhw->arr + 1)
+ wfhw->ccr = wfhw->arr + 1;
+ }
+
+out:
+ clk_disable(priv->clk);
+
+ return ret;
+}
+
+static int stm32_pwm_write_waveform(struct pwm_chip *chip,
+ struct pwm_device *pwm,
+ const void *_wfhw)
+{
+ const struct stm32_pwm_waveform *wfhw = _wfhw;
+ struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
+ unsigned int ch = pwm->hwpwm;
+ int ret;
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ return ret;
+
+ if (wfhw->ccer & TIM_CCER_CCxE(ch + 1)) {
+ u32 ccer, mask;
+ unsigned int shift;
+ u32 ccmr;
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
+ if (ret)
+ goto out;
+
+ /* If there are other channels enabled, don't update PSC and ARR */
+ if (ccer & ~TIM_CCER_CCxE(ch + 1) & TIM_CCER_CCXE) {
+ u32 psc, arr;
+
+ ret = regmap_read(priv->regmap, TIM_PSC, &psc);
+ if (ret)
+ goto out;
+
+ if (psc != wfhw->psc) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = regmap_read(priv->regmap, TIM_ARR, &arr);
+ if (ret)
+ goto out;
+
+ if (arr != wfhw->arr) {
+ ret = -EBUSY;
+ goto out;
+ }
+ } else {
+ ret = regmap_write(priv->regmap, TIM_PSC, wfhw->psc);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->regmap, TIM_ARR, wfhw->arr);
+ if (ret)
+ goto out;
+
+ ret = regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE);
+ if (ret)
+ goto out;
+
+ }
+
+ /* set polarity */
+ mask = TIM_CCER_CCxP(ch + 1) | TIM_CCER_CCxNP(ch + 1);
+ ret = regmap_update_bits(priv->regmap, TIM_CCER, mask, wfhw->ccer);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(priv->regmap, TIM_CCRx(ch + 1), wfhw->ccr);
+ if (ret)
+ goto out;
+
+ /* Configure output mode */
+ shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
+ ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
+ mask = CCMR_CHANNEL_MASK << shift;
+
+ if (ch < 2)
+ ret = regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
+ else
+ ret = regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
+ if (ret)
+ goto out;
+
+ ret = regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE);
+ if (ret)
+ goto out;
+
+ if (!(ccer & TIM_CCER_CCxE(ch + 1))) {
+ mask = TIM_CCER_CCxE(ch + 1) | TIM_CCER_CCxNE(ch + 1);
+
+ ret = clk_enable(priv->clk);
+ if (ret)
+ goto out;
+
+ ccer = (ccer & ~mask) | (wfhw->ccer & mask);
+ regmap_write(priv->regmap, TIM_CCER, ccer);
+
+ /* Make sure that registers are updated */
+ regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG);
+
+ /* Enable controller */
+ regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
+ }
+
+ } else {
+ /* disable channel */
+ u32 mask, ccer;
+
+ mask = TIM_CCER_CCxE(ch + 1);
+ if (priv->have_complementary_output)
+ mask |= TIM_CCER_CCxNE(ch + 1);
+
+ ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
+ if (ret)
+ goto out;
+
+ if (ccer & mask) {
+ ccer = ccer & ~mask;
+
+ ret = regmap_write(priv->regmap, TIM_CCER, ccer);
+ if (ret)
+ goto out;
+
+ if (!(ccer & TIM_CCER_CCXE)) {
+ /* When all channels are disabled, we can disable the controller */
+ ret = regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
+ if (ret)
+ goto out;
+ }
+
+ clk_disable(priv->clk);
+ }
+ }
+
+out:
+ clk_disable(priv->clk);
+
+ return ret;
+}
+
#define TIM_CCER_CC12P (TIM_CCER_CC1P | TIM_CCER_CC2P)
#define TIM_CCER_CC12E (TIM_CCER_CC1E | TIM_CCER_CC2E)
#define TIM_CCER_CC34P (TIM_CCER_CC3P | TIM_CCER_CC4P)
@@ -308,228 +693,13 @@ unlock:
return ret;
}
-static int stm32_pwm_config(struct stm32_pwm *priv, unsigned int ch,
- u64 duty_ns, u64 period_ns)
-{
- unsigned long long prd, dty;
- unsigned long long prescaler;
- u32 ccmr, mask, shift;
-
- /*
- * .probe() asserted that clk_get_rate() is not bigger than 1 GHz, so
- * the calculations here won't overflow.
- * First we need to find the minimal value for prescaler such that
- *
- * period_ns * clkrate
- * ------------------------------ < max_arr + 1
- * NSEC_PER_SEC * (prescaler + 1)
- *
- * This equation is equivalent to
- *
- * period_ns * clkrate
- * ---------------------------- < prescaler + 1
- * NSEC_PER_SEC * (max_arr + 1)
- *
- * Using integer division and knowing that the right hand side is
- * integer, this is further equivalent to
- *
- * (period_ns * clkrate) // (NSEC_PER_SEC * (max_arr + 1)) ≤ prescaler
- */
-
- prescaler = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk),
- (u64)NSEC_PER_SEC * ((u64)priv->max_arr + 1));
- if (prescaler > MAX_TIM_PSC)
- return -EINVAL;
-
- prd = mul_u64_u64_div_u64(period_ns, clk_get_rate(priv->clk),
- (u64)NSEC_PER_SEC * (prescaler + 1));
- if (!prd)
- return -EINVAL;
-
- /*
- * All channels share the same prescaler and counter so when two
- * channels are active at the same time we can't change them
- */
- if (active_channels(priv) & ~(1 << ch * 4)) {
- u32 psc, arr;
-
- regmap_read(priv->regmap, TIM_PSC, &psc);
- regmap_read(priv->regmap, TIM_ARR, &arr);
-
- if ((psc != prescaler) || (arr != prd - 1))
- return -EBUSY;
- }
-
- regmap_write(priv->regmap, TIM_PSC, prescaler);
- regmap_write(priv->regmap, TIM_ARR, prd - 1);
- regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_ARPE);
-
- /* Calculate the duty cycles */
- dty = mul_u64_u64_div_u64(duty_ns, clk_get_rate(priv->clk),
- (u64)NSEC_PER_SEC * (prescaler + 1));
-
- regmap_write(priv->regmap, TIM_CCRx(ch + 1), dty);
-
- /* Configure output mode */
- shift = (ch & 0x1) * CCMR_CHANNEL_SHIFT;
- ccmr = (TIM_CCMR_PE | TIM_CCMR_M1) << shift;
- mask = CCMR_CHANNEL_MASK << shift;
-
- if (ch < 2)
- regmap_update_bits(priv->regmap, TIM_CCMR1, mask, ccmr);
- else
- regmap_update_bits(priv->regmap, TIM_CCMR2, mask, ccmr);
-
- regmap_set_bits(priv->regmap, TIM_BDTR, TIM_BDTR_MOE);
-
- return 0;
-}
-
-static int stm32_pwm_set_polarity(struct stm32_pwm *priv, unsigned int ch,
- enum pwm_polarity polarity)
-{
- u32 mask;
-
- mask = TIM_CCER_CCxP(ch + 1);
- if (priv->have_complementary_output)
- mask |= TIM_CCER_CCxNP(ch + 1);
-
- regmap_update_bits(priv->regmap, TIM_CCER, mask,
- polarity == PWM_POLARITY_NORMAL ? 0 : mask);
-
- return 0;
-}
-
-static int stm32_pwm_enable(struct stm32_pwm *priv, unsigned int ch)
-{
- u32 mask;
- int ret;
-
- ret = clk_enable(priv->clk);
- if (ret)
- return ret;
-
- /* Enable channel */
- mask = TIM_CCER_CCxE(ch + 1);
- if (priv->have_complementary_output)
- mask |= TIM_CCER_CCxNE(ch + 1);
-
- regmap_set_bits(priv->regmap, TIM_CCER, mask);
-
- /* Make sure that registers are updated */
- regmap_set_bits(priv->regmap, TIM_EGR, TIM_EGR_UG);
-
- /* Enable controller */
- regmap_set_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
-
- return 0;
-}
-
-static void stm32_pwm_disable(struct stm32_pwm *priv, unsigned int ch)
-{
- u32 mask;
-
- /* Disable channel */
- mask = TIM_CCER_CCxE(ch + 1);
- if (priv->have_complementary_output)
- mask |= TIM_CCER_CCxNE(ch + 1);
-
- regmap_clear_bits(priv->regmap, TIM_CCER, mask);
-
- /* When all channels are disabled, we can disable the controller */
- if (!active_channels(priv))
- regmap_clear_bits(priv->regmap, TIM_CR1, TIM_CR1_CEN);
-
- clk_disable(priv->clk);
-}
-
-static int stm32_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
-{
- bool enabled;
- struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
- int ret;
-
- enabled = pwm->state.enabled;
-
- if (!state->enabled) {
- if (enabled)
- stm32_pwm_disable(priv, pwm->hwpwm);
- return 0;
- }
-
- if (state->polarity != pwm->state.polarity)
- stm32_pwm_set_polarity(priv, pwm->hwpwm, state->polarity);
-
- ret = stm32_pwm_config(priv, pwm->hwpwm,
- state->duty_cycle, state->period);
- if (ret)
- return ret;
-
- if (!enabled && state->enabled)
- ret = stm32_pwm_enable(priv, pwm->hwpwm);
-
- return ret;
-}
-
-static int stm32_pwm_apply_locked(struct pwm_chip *chip, struct pwm_device *pwm,
- const struct pwm_state *state)
-{
- struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
- int ret;
-
- /* protect common prescaler for all active channels */
- mutex_lock(&priv->lock);
- ret = stm32_pwm_apply(chip, pwm, state);
- mutex_unlock(&priv->lock);
-
- return ret;
-}
-
-static int stm32_pwm_get_state(struct pwm_chip *chip,
- struct pwm_device *pwm, struct pwm_state *state)
-{
- struct stm32_pwm *priv = to_stm32_pwm_dev(chip);
- int ch = pwm->hwpwm;
- unsigned long rate;
- u32 ccer, psc, arr, ccr;
- u64 dty, prd;
- int ret;
-
- mutex_lock(&priv->lock);
-
- ret = regmap_read(priv->regmap, TIM_CCER, &ccer);
- if (ret)
- goto out;
-
- state->enabled = ccer & TIM_CCER_CCxE(ch + 1);
- state->polarity = (ccer & TIM_CCER_CCxP(ch + 1)) ?
- PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
- ret = regmap_read(priv->regmap, TIM_PSC, &psc);
- if (ret)
- goto out;
- ret = regmap_read(priv->regmap, TIM_ARR, &arr);
- if (ret)
- goto out;
- ret = regmap_read(priv->regmap, TIM_CCRx(ch + 1), &ccr);
- if (ret)
- goto out;
-
- rate = clk_get_rate(priv->clk);
-
- prd = (u64)NSEC_PER_SEC * (psc + 1) * (arr + 1);
- state->period = DIV_ROUND_UP_ULL(prd, rate);
- dty = (u64)NSEC_PER_SEC * (psc + 1) * ccr;
- state->duty_cycle = DIV_ROUND_UP_ULL(dty, rate);
-
-out:
- mutex_unlock(&priv->lock);
- return ret;
-}
-
static const struct pwm_ops stm32pwm_ops = {
- .apply = stm32_pwm_apply_locked,
- .get_state = stm32_pwm_get_state,
+ .sizeof_wfhw = sizeof(struct stm32_pwm_waveform),
+ .round_waveform_tohw = stm32_pwm_round_waveform_tohw,
+ .round_waveform_fromhw = stm32_pwm_round_waveform_fromhw,
+ .read_waveform = stm32_pwm_read_waveform,
+ .write_waveform = stm32_pwm_write_waveform,
+
.capture = IS_ENABLED(CONFIG_DMA_ENGINE) ? stm32_pwm_capture : NULL,
};
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index 8acd60b53f58..78827f312407 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -49,6 +49,31 @@ enum {
PWMF_EXPORTED = 1,
};
+/**
+ * struct pwm_waveform - description of a PWM waveform
+ * @period_length_ns: PWM period
+ * @duty_length_ns: PWM duty cycle
+ * @duty_offset_ns: offset of the rising edge from the period's start
+ *
+ * This is a representation of a PWM waveform alternative to struct pwm_state
+ * below. It's more expressive than struct pwm_state as it contains a
+ * duty_offset_ns and so can represent offsets other than zero (with .polarity =
+ * PWM_POLARITY_NORMAL) and period - duty_cycle (.polarity =
+ * PWM_POLARITY_INVERSED).
+ *
+ * Note there is no explicit bool for enabled. A "disabled" PWM is represented
+ * by .period_length_ns = 0. Note further that the behaviour of a "disabled" PWM
+ * is undefined. Depending on the hardware's capabilities it might drive the
+ * active or inactive level, go high-z or even continue to toggle.
+ *
+ * The unit for all three members is nanoseconds.
+ */
+struct pwm_waveform {
+ u64 period_length_ns;
+ u64 duty_length_ns;
+ u64 duty_offset_ns;
+};
+
/*
* struct pwm_state - state of a PWM channel
* @period: PWM period (in nanoseconds)
@@ -251,6 +276,11 @@ struct pwm_capture {
* @request: optional hook for requesting a PWM
* @free: optional hook for freeing a PWM
* @capture: capture and report PWM signal
+ * @sizeof_wfhw: size (in bytes) of driver specific waveform presentation
+ * @round_waveform_tohw: convert a struct pwm_waveform to driver specific presentation
+ * @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
* @apply: atomically apply a new PWM config
* @get_state: get the current PWM state.
*/
@@ -259,6 +289,17 @@ struct pwm_ops {
void (*free)(struct pwm_chip *chip, struct pwm_device *pwm);
int (*capture)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_capture *result, unsigned long timeout);
+
+ size_t sizeof_wfhw;
+ int (*round_waveform_tohw)(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_waveform *wf, void *wfhw);
+ int (*round_waveform_fromhw)(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *wfhw, struct pwm_waveform *wf);
+ int (*read_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
+ void *wfhw);
+ int (*write_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
+ const void *wfhw);
+
int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state);
int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -275,6 +316,9 @@ struct pwm_ops {
* @of_xlate: request a PWM device given a device tree PWM specifier
* @atomic: can the driver's ->apply() be called in atomic context
* @uses_pwmchip_alloc: signals if pwmchip_allow was used to allocate this chip
+ * @operational: signals if the chip can be used (or is already deregistered)
+ * @nonatomic_lock: mutex for nonatomic chips
+ * @atomic_lock: mutex for atomic chips
* @pwms: array of PWM devices allocated by the framework
*/
struct pwm_chip {
@@ -290,6 +334,16 @@ struct pwm_chip {
/* only used internally by the PWM framework */
bool uses_pwmchip_alloc;
+ bool operational;
+ union {
+ /*
+ * depending on the chip being atomic or not either the mutex or
+ * the spinlock is used. It protects .operational and
+ * synchronizes the callbacks in .ops
+ */
+ struct mutex nonatomic_lock;
+ spinlock_t atomic_lock;
+ };
struct pwm_device pwms[] __counted_by(npwm);
};
@@ -309,9 +363,14 @@ static inline void pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
}
#if IS_ENABLED(CONFIG_PWM)
-/* PWM user APIs */
+
+/* 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_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);
+int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state);
int pwm_adjust_config(struct pwm_device *pwm);
/**
@@ -436,6 +495,11 @@ static inline int pwm_apply_atomic(struct pwm_device *pwm,
return -EOPNOTSUPP;
}
+static inline int pwm_get_state_hw(struct pwm_device *pwm, struct pwm_state *state)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int pwm_adjust_config(struct pwm_device *pwm)
{
return -EOPNOTSUPP;
diff --git a/include/trace/events/pwm.h b/include/trace/events/pwm.h
index 8022701c446d..8ba898fd335c 100644
--- a/include/trace/events/pwm.h
+++ b/include/trace/events/pwm.h
@@ -8,15 +8,135 @@
#include <linux/pwm.h>
#include <linux/tracepoint.h>
+#define TP_PROTO_pwm(args...) \
+ TP_PROTO(struct pwm_device *pwm, args)
+
+#define TP_ARGS_pwm(args...) \
+ TP_ARGS(pwm, args)
+
+#define TP_STRUCT__entry_pwm(args...) \
+ TP_STRUCT__entry( \
+ __field(unsigned int, chipid) \
+ __field(unsigned int, hwpwm) \
+ args)
+
+#define TP_fast_assign_pwm(args...) \
+ TP_fast_assign( \
+ __entry->chipid = pwm->chip->id; \
+ __entry->hwpwm = pwm->hwpwm; \
+ args)
+
+#define TP_printk_pwm(fmt, args...) \
+ TP_printk("pwmchip%u.%u: " fmt, __entry->chipid, __entry->hwpwm, args)
+
+#define __field_pwmwf(wf) \
+ __field(u64, wf ## _period_length_ns) \
+ __field(u64, wf ## _duty_length_ns) \
+ __field(u64, wf ## _duty_offset_ns) \
+
+#define fast_assign_pwmwf(wf) \
+ __entry->wf ## _period_length_ns = wf->period_length_ns; \
+ __entry->wf ## _duty_length_ns = wf->duty_length_ns; \
+ __entry->wf ## _duty_offset_ns = wf->duty_offset_ns
+
+#define printk_pwmwf_format(wf) \
+ "%lld/%lld [+%lld]"
+
+#define printk_pwmwf_formatargs(wf) \
+ __entry->wf ## _duty_length_ns, __entry->wf ## _period_length_ns, __entry->wf ## _duty_offset_ns
+
+TRACE_EVENT(pwm_round_waveform_tohw,
+
+ TP_PROTO_pwm(const struct pwm_waveform *wf, void *wfhw, int err),
+
+ TP_ARGS_pwm(wf, wfhw, err),
+
+ TP_STRUCT__entry_pwm(
+ __field_pwmwf(wf)
+ __field(void *, wfhw)
+ __field(int, err)
+ ),
+
+ TP_fast_assign_pwm(
+ fast_assign_pwmwf(wf);
+ __entry->wfhw = wfhw;
+ __entry->err = err;
+ ),
+
+ TP_printk_pwm(printk_pwmwf_format(wf) " > %p err=%d",
+ printk_pwmwf_formatargs(wf), __entry->wfhw, __entry->err)
+);
+
+TRACE_EVENT(pwm_round_waveform_fromhw,
+
+ TP_PROTO_pwm(const void *wfhw, struct pwm_waveform *wf, int err),
+
+ TP_ARGS_pwm(wfhw, wf, err),
+
+ TP_STRUCT__entry_pwm(
+ __field(const void *, wfhw)
+ __field_pwmwf(wf)
+ __field(int, err)
+ ),
+
+ TP_fast_assign_pwm(
+ __entry->wfhw = wfhw;
+ fast_assign_pwmwf(wf);
+ __entry->err = err;
+ ),
+
+ TP_printk_pwm("%p > " printk_pwmwf_format(wf) " err=%d",
+ __entry->wfhw, printk_pwmwf_formatargs(wf), __entry->err)
+);
+
+TRACE_EVENT(pwm_read_waveform,
+
+ TP_PROTO_pwm(void *wfhw, int err),
+
+ TP_ARGS_pwm(wfhw, err),
+
+ TP_STRUCT__entry_pwm(
+ __field(void *, wfhw)
+ __field(int, err)
+ ),
+
+ TP_fast_assign_pwm(
+ __entry->wfhw = wfhw;
+ __entry->err = err;
+ ),
+
+ TP_printk_pwm("%p err=%d",
+ __entry->wfhw, __entry->err)
+);
+
+TRACE_EVENT(pwm_write_waveform,
+
+ TP_PROTO_pwm(const void *wfhw, int err),
+
+ TP_ARGS_pwm(wfhw, err),
+
+ TP_STRUCT__entry_pwm(
+ __field(const void *, wfhw)
+ __field(int, err)
+ ),
+
+ TP_fast_assign_pwm(
+ __entry->wfhw = wfhw;
+ __entry->err = err;
+ ),
+
+ TP_printk_pwm("%p err=%d",
+ __entry->wfhw, __entry->err)
+);
+
+
DECLARE_EVENT_CLASS(pwm,
TP_PROTO(struct pwm_device *pwm, const struct pwm_state *state, int err),
TP_ARGS(pwm, state, err),
- TP_STRUCT__entry(
- __field(unsigned int, chipid)
- __field(unsigned int, hwpwm)
+ TP_STRUCT__entry_pwm(
__field(u64, period)
__field(u64, duty_cycle)
__field(enum pwm_polarity, polarity)
@@ -24,9 +144,7 @@ DECLARE_EVENT_CLASS(pwm,
__field(int, err)
),
- TP_fast_assign(
- __entry->chipid = pwm->chip->id;
- __entry->hwpwm = pwm->hwpwm;
+ TP_fast_assign_pwm(
__entry->period = state->period;
__entry->duty_cycle = state->duty_cycle;
__entry->polarity = state->polarity;
@@ -34,8 +152,8 @@ DECLARE_EVENT_CLASS(pwm,
__entry->err = err;
),
- TP_printk("pwmchip%u.%u: period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d",
- __entry->chipid, __entry->hwpwm, __entry->period, __entry->duty_cycle,
+ TP_printk_pwm("period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d",
+ __entry->period, __entry->duty_cycle,
__entry->polarity, __entry->enabled, __entry->err)
);