mirror of
https://github.com/coolsnowwolf/lede.git
synced 2025-05-01 13:50:54 +08:00
238 lines
6.5 KiB
Diff
238 lines
6.5 KiB
Diff
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
|
|
---
|
|
drivers/pwm/core.c | 95 +++++++++++++++++++++++++++++++++++++++++----
|
|
include/linux/pwm.h | 13 +++++++
|
|
2 files changed, 100 insertions(+), 8 deletions(-)
|
|
|
|
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
|
|
index 6e752e148b98..b97e2ea0691d 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);
|
|
@@ -336,6 +367,11 @@ static int pwm_capture(struct pwm_device *pwm, struct pwm_capture *result,
|
|
|
|
guard(mutex)(&pwm_lock);
|
|
|
|
+ guard(pwmchip)(chip);
|
|
+
|
|
+ if (!chip->operational)
|
|
+ return -ENODEV;
|
|
+
|
|
return ops->capture(chip, pwm, result, timeout);
|
|
}
|
|
|
|
@@ -368,6 +404,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 +440,9 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
|
|
*/
|
|
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 +1066,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 +1172,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 +1190,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 +1200,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 +1222,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_alert(&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 +1612,17 @@ void pwm_put(struct pwm_device *pwm)
|
|
|
|
guard(mutex)(&pwm_lock);
|
|
|
|
- if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) {
|
|
+ /*
|
|
+ * If the chip isn't operational, PWMF_REQUESTED was already cleared. So
|
|
+ * don't warn in this case. This can only happen if a consumer called
|
|
+ * pwm_put() twice.
|
|
+ */
|
|
+ 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..464054a45e57 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 calls to the .ops->apply and .ops->get_state()
|
|
+ */
|
|
+ struct mutex nonatomic_lock;
|
|
+ struct spinlock atomic_lock;
|
|
+ };
|
|
struct pwm_device pwms[] __counted_by(npwm);
|
|
};
|
|
|
|
--
|
|
2.43.0
|