linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Clemens Gruber <clemens.gruber@pqgruber.com>
To: linux-pwm@vger.kernel.org
Cc: Thierry Reding <thierry.reding@gmail.com>,
	u.kleine-koenig@pengutronix.de,
	Sven Van Asbroeck <TheSven73@gmail.com>,
	Lee Jones <lee.jones@linaro.org>,
	linux-kernel@vger.kernel.org,
	Mika Westerberg <mika.westerberg@linux.intel.com>,
	David Jander <david@protonic.nl>,
	Clemens Gruber <clemens.gruber@pqgruber.com>
Subject: [PATCH v5 7/7] pwm: pca9685: Restrict period change for prescaler users
Date: Tue, 15 Dec 2020 22:22:28 +0100	[thread overview]
Message-ID: <20201215212228.185517-7-clemens.gruber@pqgruber.com> (raw)
In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com>

Previously, the last used PWM channel could change the global prescale
setting, even if other channels were already in use.

Fix it by only allowing the first user of the prescaler to change the
global chip-wide prescale setting. If there is more than one channel in
use, the prescale settings resulting from the chosen periods must match.

PWMs that are disabled or have a duty cycle of 0% or 100% are not
considered to be using the prescaler as they have the full OFF or full
ON bits set. This also applies to channels used as GPIOs.

Signed-off-by: Clemens Gruber <clemens.gruber@pqgruber.com>
---
 drivers/pwm/pwm-pca9685.c | 51 +++++++++++++++++++++++++++++++++------
 1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c
index ff916980de49..438492d4aed4 100644
--- a/drivers/pwm/pwm-pca9685.c
+++ b/drivers/pwm/pwm-pca9685.c
@@ -23,11 +23,11 @@
 #include <linux/bitmap.h>
 
 /*
- * Because the PCA9685 has only one prescaler per chip, changing the period of
- * one channel affects the period of all 16 PWM outputs!
- * However, the ratio between each configured duty cycle and the chip-wide
- * period remains constant, because the OFF time is set in proportion to the
- * counter range.
+ * Because the PCA9685 has only one prescaler per chip, only the first channel
+ * that uses the prescaler is allowed to change the prescale register.
+ * PWM channels requested afterwards must use a period that results in the same
+ * prescale setting as the one set by the first requested channel, unless they
+ * use duty cycles of 0% or 100% (prescaler not used for full OFF/ON).
  */
 
 #define PCA9685_MODE1		0x00
@@ -80,6 +80,8 @@ struct pca9685 {
 	struct pwm_chip chip;
 	struct regmap *regmap;
 	bool staggered_outputs;
+	struct mutex prescaler_users_lock;
+	DECLARE_BITMAP(prescaler_users, PCA9685_MAXCHAN + 1);
 #if IS_ENABLED(CONFIG_GPIOLIB)
 	struct mutex lock;
 	struct gpio_chip gpio;
@@ -92,6 +94,18 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip)
 	return container_of(chip, struct pca9685, chip);
 }
 
+/* This function is supposed to be called with the prescaler_users_lock held */
+static inline bool pca9685_may_change_prescaler(struct pca9685 *pca, int channel)
+{
+	/*
+	 * A PWM channel may only change the prescaler if there are no users of
+	 * the prescaler yet or that same channel is the only one in use.
+	 */
+	return bitmap_empty(pca->prescaler_users, PCA9685_MAXCHAN + 1) ||
+		(bitmap_weight(pca->prescaler_users, PCA9685_MAXCHAN + 1) == 1 &&
+		 test_bit(channel, pca->prescaler_users));
+}
+
 static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty)
 {
 	unsigned int on, off;
@@ -337,16 +351,25 @@ static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	duty = PCA9685_COUNTER_RANGE * state->duty_cycle;
 	duty = DIV_ROUND_CLOSEST_ULL(duty, state->period);
 
+	mutex_lock(&pca->prescaler_users_lock);
+
 	if (!state->enabled || duty < 1) {
 		pca9685_pwm_set_duty(pca, pwm->hwpwm, 0);
-		return 0;
+		goto prescaler_unused;
 	} else if (duty == PCA9685_COUNTER_RANGE) {
 		pca9685_pwm_set_duty(pca, pwm->hwpwm, duty);
-		return 0;
+		goto prescaler_unused;
 	}
 
 	regmap_read(pca->regmap, PCA9685_PRESCALE, &val);
 	if (prescale != val) {
+		if (!pca9685_may_change_prescaler(pca, pwm->hwpwm)) {
+			mutex_unlock(&pca->prescaler_users_lock);
+			dev_err(chip->dev,
+				"prescaler not set: already in use with different setting!\n");
+			return -EBUSY;
+		}
+
 		/*
 		 * Putting the chip briefly into SLEEP mode
 		 * at this point won't interfere with the
@@ -364,6 +387,14 @@ static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	}
 
 	pca9685_pwm_set_duty(pca, pwm->hwpwm, duty);
+
+	set_bit(pwm->hwpwm, pca->prescaler_users);
+	mutex_unlock(&pca->prescaler_users_lock);
+	return 0;
+
+prescaler_unused:
+	clear_bit(pwm->hwpwm, pca->prescaler_users);
+	mutex_unlock(&pca->prescaler_users_lock);
 	return 0;
 }
 
@@ -422,7 +453,11 @@ static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
 {
 	struct pca9685 *pca = to_pca(chip);
 
+	mutex_lock(&pca->prescaler_users_lock);
+	clear_bit(pwm->hwpwm, pca->prescaler_users);
 	pca9685_pwm_set_duty(pca, pwm->hwpwm, 0);
+	mutex_unlock(&pca->prescaler_users_lock);
+
 	pm_runtime_put(chip->dev);
 	pca9685_pwm_clear_inuse(pca, pwm->hwpwm);
 }
@@ -463,6 +498,8 @@ static int pca9685_pwm_probe(struct i2c_client *client,
 
 	i2c_set_clientdata(client, pca);
 
+	mutex_init(&pca->prescaler_users_lock);
+
 	regmap_read(pca->regmap, PCA9685_MODE2, &reg);
 
 	if (device_property_read_bool(&client->dev, "invert"))
-- 
2.29.2


  parent reply	other threads:[~2020-12-15 21:30 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-15 21:22 [PATCH v5 1/7] pwm: pca9685: Switch to atomic API Clemens Gruber
2020-12-15 21:22 ` [PATCH v5 2/7] pwm: pca9685: Support hardware readout Clemens Gruber
2020-12-15 21:22 ` [PATCH v5 3/7] pwm: pca9685: Improve runtime PM behavior Clemens Gruber
2020-12-15 21:22 ` [PATCH v5 4/7] pwm: pca9685: Reset registers to POR state in probe Clemens Gruber
2020-12-17  4:02   ` Sven Van Asbroeck
2020-12-17 17:45     ` Clemens Gruber
2021-03-01 21:46   ` Uwe Kleine-König
2021-03-04 13:16     ` Clemens Gruber
2020-12-15 21:22 ` [PATCH v5 5/7] pwm: pca9685: Support staggered output ON times Clemens Gruber
2020-12-17  4:02   ` Sven Van Asbroeck
2020-12-17 17:50     ` Clemens Gruber
2020-12-15 21:22 ` [PATCH v5 6/7] dt-bindings: pwm: pca9685: Add nxp,staggered-outputs property Clemens Gruber
2020-12-15 21:22 ` Clemens Gruber [this message]
2020-12-17  4:03   ` [PATCH v5 7/7] pwm: pca9685: Restrict period change for prescaler users Sven Van Asbroeck
2020-12-17 18:07     ` Clemens Gruber
2020-12-17 18:17       ` Sven Van Asbroeck
2020-12-17  3:58 ` [PATCH v5 1/7] pwm: pca9685: Switch to atomic API Sven Van Asbroeck
2020-12-17 16:48   ` Clemens Gruber
2020-12-17 17:10     ` Sven Van Asbroeck
2021-03-01 21:41       ` Uwe Kleine-König
2021-03-04 13:10         ` Clemens Gruber
2021-03-22  7:58       ` Thierry Reding
2021-03-27 15:54         ` Clemens Gruber
2021-03-01 21:44 ` Uwe Kleine-König
2021-03-04 13:12   ` Clemens Gruber

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201215212228.185517-7-clemens.gruber@pqgruber.com \
    --to=clemens.gruber@pqgruber.com \
    --cc=TheSven73@gmail.com \
    --cc=david@protonic.nl \
    --cc=lee.jones@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=mika.westerberg@linux.intel.com \
    --cc=thierry.reding@gmail.com \
    --cc=u.kleine-koenig@pengutronix.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).