diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-20 12:34:22 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-11-20 12:34:22 -0800 |
commit | 7d7560666515855f67df6b0a78fecf2007d35dcc (patch) | |
tree | 974816b54382b5390578caca2818849159773171 /drivers/pwm/core.c | |
parent | f2ef39727a229ddd127e3538b58f4f5bdc5eeea6 (diff) | |
parent | b2eaa1170e45dc18eb09dcc9abafbe9a7502e960 (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
Diffstat (limited to 'drivers/pwm/core.c')
-rw-r--r-- | drivers/pwm/core.c | 885 |
1 files changed, 727 insertions, 158 deletions
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; |