diff options
-rw-r--r-- | drivers/pwm/core.c | 100 | ||||
-rw-r--r-- | include/linux/pwm.h | 13 |
2 files changed, 105 insertions, 8 deletions
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 6e752e148b98..5a095eb46b54 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -31,6 +31,24 @@ 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 void pwm_apply_debug(struct pwm_device *pwm, const struct pwm_state *state) { @@ -220,6 +238,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 +249,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,9 +278,16 @@ 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); @@ -334,8 +365,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); } @@ -368,6 +409,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; @@ -396,7 +445,9 @@ err_get_device: */ struct pwm_state state = { 0, }; - err = ops->get_state(chip, pwm, &state); + scoped_guard(pwmchip, chip) + err = ops->get_state(chip, pwm, &state); + trace_pwm_get(pwm, &state, err); if (!err) @@ -1020,6 +1071,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); @@ -1125,6 +1177,11 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) 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); @@ -1138,6 +1195,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) 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; @@ -1145,6 +1205,9 @@ int __pwmchip_add(struct pwm_chip *chip, struct module *owner) return 0; err_device_add: + scoped_guard(pwmchip, chip) + chip->operational = false; + if (IS_ENABLED(CONFIG_OF)) of_pwmchip_remove(chip); @@ -1164,11 +1227,27 @@ 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) { + 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); - scoped_guard(mutex, &pwm_lock) idr_remove(&pwm_chips, chip->id); + } device_del(&chip->dev); } @@ -1538,12 +1617,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; diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 8acd60b53f58..3ea73e075abe 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -275,6 +275,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 +293,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); }; |