Skip to content

Commit 3ad1f3a

Browse files
Uwe Kleine-Königthierryreding
authored andcommitted
pwm: Implement some checks for lowlevel drivers
There are some expectations which the callbacks provided by lowlevel drivers should fulfill. Implement checks that help driver authors to get these semantics right. As these have some overhead the checks can be disabled using a Kconfig setting. Signed-off-by: Uwe Kleine-König <[email protected]> Signed-off-by: Thierry Reding <[email protected]>
1 parent 2cb5cd9 commit 3ad1f3a

File tree

3 files changed

+140
-8
lines changed

3 files changed

+140
-8
lines changed

drivers/pwm/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ config PWM_SYSFS
3333
bool
3434
default y if SYSFS
3535

36+
config PWM_DEBUG
37+
bool "PWM lowlevel drivers additional checks and debug messages"
38+
depends on DEBUG_KERNEL
39+
help
40+
This option enables some additional checks to help lowlevel driver
41+
authors to get their callbacks implemented correctly.
42+
It is expected to introduce some runtime overhead and diagnostic
43+
output to the kernel log, so only enable while working on a driver.
44+
3645
config PWM_AB8500
3746
tristate "AB8500 PWM support"
3847
depends on AB8500_CORE && ARCH_U8500

drivers/pwm/core.c

Lines changed: 128 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
120120
if (pwm->chip->ops->get_state) {
121121
pwm->chip->ops->get_state(pwm->chip, pwm, &pwm->state);
122122
trace_pwm_get(pwm, &pwm->state);
123+
124+
if (IS_ENABLED(PWM_DEBUG))
125+
pwm->last = pwm->state;
123126
}
124127

125128
set_bit(PWMF_REQUESTED, &pwm->flags);
@@ -232,17 +235,28 @@ void *pwm_get_chip_data(struct pwm_device *pwm)
232235
}
233236
EXPORT_SYMBOL_GPL(pwm_get_chip_data);
234237

235-
static bool pwm_ops_check(const struct pwm_ops *ops)
238+
static bool pwm_ops_check(const struct pwm_chip *chip)
236239
{
240+
241+
const struct pwm_ops *ops = chip->ops;
242+
237243
/* driver supports legacy, non-atomic operation */
238-
if (ops->config && ops->enable && ops->disable)
239-
return true;
244+
if (ops->config && ops->enable && ops->disable) {
245+
if (IS_ENABLED(CONFIG_PWM_DEBUG))
246+
dev_warn(chip->dev,
247+
"Driver needs updating to atomic API\n");
240248

241-
/* driver supports atomic operation */
242-
if (ops->apply)
243249
return true;
250+
}
244251

245-
return false;
252+
if (!ops->apply)
253+
return false;
254+
255+
if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
256+
dev_warn(chip->dev,
257+
"Please implement the .get_state() callback\n");
258+
259+
return true;
246260
}
247261

248262
/**
@@ -266,7 +280,7 @@ int pwmchip_add_with_polarity(struct pwm_chip *chip,
266280
if (!chip || !chip->dev || !chip->ops || !chip->npwm)
267281
return -EINVAL;
268282

269-
if (!pwm_ops_check(chip->ops))
283+
if (!pwm_ops_check(chip))
270284
return -EINVAL;
271285

272286
mutex_lock(&pwm_lock);
@@ -450,6 +464,107 @@ void pwm_free(struct pwm_device *pwm)
450464
}
451465
EXPORT_SYMBOL_GPL(pwm_free);
452466

467+
void pwm_apply_state_debug(struct pwm_device *pwm,
468+
const struct pwm_state *state)
469+
{
470+
struct pwm_state *last = &pwm->last;
471+
struct pwm_chip *chip = pwm->chip;
472+
struct pwm_state s1, s2;
473+
int err;
474+
475+
if (!IS_ENABLED(CONFIG_PWM_DEBUG))
476+
return;
477+
478+
/* No reasonable diagnosis possible without .get_state() */
479+
if (!chip->ops->get_state)
480+
return;
481+
482+
/*
483+
* *state was just applied. Read out the hardware state and do some
484+
* checks.
485+
*/
486+
487+
chip->ops->get_state(chip, pwm, &s1);
488+
trace_pwm_get(pwm, &s1);
489+
490+
/*
491+
* The lowlevel driver either ignored .polarity (which is a bug) or as
492+
* best effort inverted .polarity and fixed .duty_cycle respectively.
493+
* Undo this inversion and fixup for further tests.
494+
*/
495+
if (s1.enabled && s1.polarity != state->polarity) {
496+
s2.polarity = state->polarity;
497+
s2.duty_cycle = s1.period - s1.duty_cycle;
498+
s2.period = s1.period;
499+
s2.enabled = s1.enabled;
500+
} else {
501+
s2 = s1;
502+
}
503+
504+
if (s2.polarity != state->polarity &&
505+
state->duty_cycle < state->period)
506+
dev_warn(chip->dev, ".apply ignored .polarity\n");
507+
508+
if (state->enabled &&
509+
last->polarity == state->polarity &&
510+
last->period > s2.period &&
511+
last->period <= state->period)
512+
dev_warn(chip->dev,
513+
".apply didn't pick the best available period (requested: %u, applied: %u, possible: %u)\n",
514+
state->period, s2.period, last->period);
515+
516+
if (state->enabled && state->period < s2.period)
517+
dev_warn(chip->dev,
518+
".apply is supposed to round down period (requested: %u, applied: %u)\n",
519+
state->period, s2.period);
520+
521+
if (state->enabled &&
522+
last->polarity == state->polarity &&
523+
last->period == s2.period &&
524+
last->duty_cycle > s2.duty_cycle &&
525+
last->duty_cycle <= state->duty_cycle)
526+
dev_warn(chip->dev,
527+
".apply didn't pick the best available duty cycle (requested: %u/%u, applied: %u/%u, possible: %u/%u)\n",
528+
state->duty_cycle, state->period,
529+
s2.duty_cycle, s2.period,
530+
last->duty_cycle, last->period);
531+
532+
if (state->enabled && state->duty_cycle < s2.duty_cycle)
533+
dev_warn(chip->dev,
534+
".apply is supposed to round down duty_cycle (requested: %u/%u, applied: %u/%u)\n",
535+
state->duty_cycle, state->period,
536+
s2.duty_cycle, s2.period);
537+
538+
if (!state->enabled && s2.enabled && s2.duty_cycle > 0)
539+
dev_warn(chip->dev,
540+
"requested disabled, but yielded enabled with duty > 0");
541+
542+
/* reapply the state that the driver reported being configured. */
543+
err = chip->ops->apply(chip, pwm, &s1);
544+
if (err) {
545+
*last = s1;
546+
dev_err(chip->dev, "failed to reapply current setting\n");
547+
return;
548+
}
549+
550+
trace_pwm_apply(pwm, &s1);
551+
552+
chip->ops->get_state(chip, pwm, last);
553+
trace_pwm_get(pwm, last);
554+
555+
/* reapplication of the current state should give an exact match */
556+
if (s1.enabled != last->enabled ||
557+
s1.polarity != last->polarity ||
558+
(s1.enabled && s1.period != last->period) ||
559+
(s1.enabled && s1.duty_cycle != last->duty_cycle)) {
560+
dev_err(chip->dev,
561+
".apply is not idempotent (ena=%d pol=%d %u/%u) -> (ena=%d pol=%d %u/%u)\n",
562+
s1.enabled, s1.polarity, s1.duty_cycle, s1.period,
563+
last->enabled, last->polarity, last->duty_cycle,
564+
last->period);
565+
}
566+
}
567+
453568
/**
454569
* pwm_apply_state() - atomically apply a new state to a PWM device
455570
* @pwm: PWM device
@@ -480,6 +595,12 @@ int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state)
480595
trace_pwm_apply(pwm, state);
481596

482597
pwm->state = *state;
598+
599+
/*
600+
* only do this after pwm->state was applied as some
601+
* implementations of .get_state depend on this
602+
*/
603+
pwm_apply_state_debug(pwm, state);
483604
} else {
484605
/*
485606
* FIXME: restore the initial state in case of error.

include/linux/pwm.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ struct pwm_state {
7171
* @chip: PWM chip providing this PWM device
7272
* @chip_data: chip-private data associated with the PWM device
7373
* @args: PWM arguments
74-
* @state: curent PWM channel state
74+
* @state: last applied state
75+
* @last: last implemented state (for PWM_DEBUG)
7576
*/
7677
struct pwm_device {
7778
const char *label;
@@ -83,6 +84,7 @@ struct pwm_device {
8384

8485
struct pwm_args args;
8586
struct pwm_state state;
87+
struct pwm_state last;
8688
};
8789

8890
/**

0 commit comments

Comments
 (0)