From patchwork Tue Dec 15 21:22:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416741 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=MytlSjHC; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWWP606Cz9sSf for ; Wed, 16 Dec 2020 08:25:21 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730063AbgLOVZE (ORCPT ); Tue, 15 Dec 2020 16:25:04 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54706 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729963AbgLOVYy (ORCPT ); Tue, 15 Dec 2020 16:24:54 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id D1272C727E0; Tue, 15 Dec 2020 22:24:08 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067449; bh=39FnCzOONg283DQeNJGA2qCcoPvQnuxcknvVuR3fPkw=; h=From:To:Cc:Subject:Date:From; b=MytlSjHCaqb1fyECimoPnVwD8PIwLnwDWeP7uWVqNkVio0QvQnzDT/PD/Edt3uwWn bauKq6frmwXwkRcM7OjPqxi4xbijlqnpqZc9LuyGFF5GrKsRNhtECUB8Jk8zDQnt0v 8X4zjnFek2EKo2lTa8m0UHrpcWczlYvJBGufCEx4= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 1/7] pwm: pca9685: Switch to atomic API Date: Tue, 15 Dec 2020 22:22:22 +0100 Message-Id: <20201215212228.185517-1-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org The switch to the atomic API goes hand in hand with a few fixes to previously experienced issues: - The duty cycle is no longer lost after disable/enable (previously the OFF registers were cleared in disable and the user was required to call config to restore the duty cycle settings) - If one sets a period resulting in the same prescale register value, the sleep and write to the register is now skipped Signed-off-by: Clemens Gruber --- Changes since v4: - Patches split up - Use a single set_duty function - Improve readability / new macros - Added a patch to restrict prescale changes to the first user Changes since v3: - Refactoring: Extracted common functions - Read prescale register value instead of caching it - Return all zeros and disabled for "all LEDs" channel state - Improved duty calculation / mapping to 0..4096 Changes since v2: - Always set default prescale value in probe - Simplified probe code - Inlined functions with one callsite Changes since v1: - Fixed a logic error - Impoved PM runtime handling and fixed !CONFIG_PM - Write default prescale reg value if invalid in probe - Reuse full_off/_on functions throughout driver - Use cached prescale value whenever possible drivers/pwm/pwm-pca9685.c | 253 +++++++++++++------------------------- 1 file changed, 87 insertions(+), 166 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 4a55dc18656c..1b5b5fb93b43 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -51,7 +51,6 @@ #define PCA9685_PRESCALE_MAX 0xFF /* => min. frequency of 24 Hz */ #define PCA9685_COUNTER_RANGE 4096 -#define PCA9685_DEFAULT_PERIOD 5000000 /* Default period_ns = 1/200 Hz */ #define PCA9685_OSC_CLOCK_MHZ 25 /* Internal oscillator with 25 MHz */ #define PCA9685_NUMREGS 0xFF @@ -71,10 +70,14 @@ #define LED_N_OFF_H(N) (PCA9685_LEDX_OFF_H + (4 * (N))) #define LED_N_OFF_L(N) (PCA9685_LEDX_OFF_L + (4 * (N))) +#define REG_ON_H(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_ON_H : LED_N_ON_H((C))) +#define REG_ON_L(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_ON_L : LED_N_ON_L((C))) +#define REG_OFF_H(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_OFF_H : LED_N_OFF_H((C))) +#define REG_OFF_L(C) ((C) >= PCA9685_MAXCHAN ? PCA9685_ALL_LED_OFF_L : LED_N_OFF_L((C))) + struct pca9685 { struct pwm_chip chip; struct regmap *regmap; - int period_ns; #if IS_ENABLED(CONFIG_GPIOLIB) struct mutex lock; struct gpio_chip gpio; @@ -87,6 +90,49 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) return container_of(chip, struct pca9685, chip); } +static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty) +{ + if (duty == 0) { + /* Set the full OFF bit, which has the highest precedence */ + regmap_write(pca->regmap, REG_OFF_H(channel), LED_FULL); + } else if (duty >= PCA9685_COUNTER_RANGE) { + /* Set the full ON bit and clear the full OFF bit */ + regmap_write(pca->regmap, REG_ON_H(channel), LED_FULL); + regmap_write(pca->regmap, REG_OFF_H(channel), 0); + } else { + /* Set OFF time (clears the full OFF bit) */ + regmap_write(pca->regmap, REG_OFF_L(channel), duty & 0xff); + regmap_write(pca->regmap, REG_OFF_H(channel), (duty >> 8) & 0xf); + /* Clear the full ON bit */ + regmap_write(pca->regmap, REG_ON_H(channel), 0); + } +} + +static unsigned int pca9685_pwm_get_duty(struct pca9685 *pca, int channel) +{ + unsigned int off_h, val; + + if (WARN_ON(channel >= PCA9685_MAXCHAN)) { + /* Hardware readout not supported for "all LEDs" channel */ + return 0; + } + + regmap_read(pca->regmap, LED_N_OFF_H(channel), &off_h); + if (off_h & LED_FULL) { + /* Full OFF bit is set */ + return 0; + } + + regmap_read(pca->regmap, LED_N_ON_H(channel), &val); + if (val & LED_FULL) { + /* Full ON bit is set */ + return PCA9685_COUNTER_RANGE; + } + + regmap_read(pca->regmap, LED_N_OFF_L(channel), &val); + return ((off_h & 0xf) << 8) | (val & 0xff); +} + #if IS_ENABLED(CONFIG_GPIOLIB) static bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, int pwm_idx) { @@ -138,34 +184,23 @@ static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset) static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) { struct pca9685 *pca = gpiochip_get_data(gpio); - struct pwm_device *pwm = &pca->chip.pwms[offset]; - unsigned int value; - regmap_read(pca->regmap, LED_N_ON_H(pwm->hwpwm), &value); - - return value & LED_FULL; + return pca9685_pwm_get_duty(pca, offset) != 0; } static void pca9685_pwm_gpio_set(struct gpio_chip *gpio, unsigned int offset, int value) { struct pca9685 *pca = gpiochip_get_data(gpio); - struct pwm_device *pwm = &pca->chip.pwms[offset]; - unsigned int on = value ? LED_FULL : 0; - - /* Clear both OFF registers */ - regmap_write(pca->regmap, LED_N_OFF_L(pwm->hwpwm), 0); - regmap_write(pca->regmap, LED_N_OFF_H(pwm->hwpwm), 0); - /* Set the full ON bit */ - regmap_write(pca->regmap, LED_N_ON_H(pwm->hwpwm), on); + pca9685_pwm_set_duty(pca, offset, value ? PCA9685_COUNTER_RANGE : 0); } static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) { struct pca9685 *pca = gpiochip_get_data(gpio); - pca9685_pwm_gpio_set(gpio, offset, 0); + pca9685_pwm_set_duty(pca, offset, 0); pm_runtime_put(pca->chip.dev); pca9685_pwm_clear_inuse(pca, offset); } @@ -246,167 +281,56 @@ static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable) } } -static int pca9685_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) +static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) { struct pca9685 *pca = to_pca(chip); - unsigned long long duty; - unsigned int reg; - int prescale; - - if (period_ns != pca->period_ns) { - prescale = DIV_ROUND_CLOSEST(PCA9685_OSC_CLOCK_MHZ * period_ns, - PCA9685_COUNTER_RANGE * 1000) - 1; - - if (prescale >= PCA9685_PRESCALE_MIN && - prescale <= PCA9685_PRESCALE_MAX) { - /* - * Putting the chip briefly into SLEEP mode - * at this point won't interfere with the - * pm_runtime framework, because the pm_runtime - * state is guaranteed active here. - */ - /* Put chip into sleep mode */ - pca9685_set_sleep_mode(pca, true); - - /* Change the chip-wide output frequency */ - regmap_write(pca->regmap, PCA9685_PRESCALE, prescale); - - /* Wake the chip up */ - pca9685_set_sleep_mode(pca, false); - - pca->period_ns = period_ns; - } else { - dev_err(chip->dev, - "prescaler not set: period out of bounds!\n"); - return -EINVAL; - } - } + unsigned long long duty, prescale; + unsigned int val = 0; - if (duty_ns < 1) { - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_H; - else - reg = LED_N_OFF_H(pwm->hwpwm); + if (state->polarity != PWM_POLARITY_NORMAL) + return -EOPNOTSUPP; - regmap_write(pca->regmap, reg, LED_FULL); - - return 0; + prescale = DIV_ROUND_CLOSEST_ULL(PCA9685_OSC_CLOCK_MHZ * state->period, + PCA9685_COUNTER_RANGE * 1000) - 1; + if (prescale < PCA9685_PRESCALE_MIN || prescale > PCA9685_PRESCALE_MAX) { + dev_err(chip->dev, "prescaler not set: period out of bounds!\n"); + return -EINVAL; } - if (duty_ns == period_ns) { - /* Clear both OFF registers */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_L; - else - reg = LED_N_OFF_L(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0x0); - - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_H; - else - reg = LED_N_OFF_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0x0); - - /* Set the full ON bit */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_ON_H; - else - reg = LED_N_ON_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, LED_FULL); + duty = PCA9685_COUNTER_RANGE * state->duty_cycle; + duty = DIV_ROUND_CLOSEST_ULL(duty, state->period); + if (!state->enabled || duty < 1) { + pca9685_pwm_set_duty(pca, pwm->hwpwm, 0); + return 0; + } else if (duty == PCA9685_COUNTER_RANGE) { + pca9685_pwm_set_duty(pca, pwm->hwpwm, duty); return 0; } - duty = PCA9685_COUNTER_RANGE * (unsigned long long)duty_ns; - duty = DIV_ROUND_UP_ULL(duty, period_ns); - - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_L; - else - reg = LED_N_OFF_L(pwm->hwpwm); - - regmap_write(pca->regmap, reg, (int)duty & 0xff); - - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_H; - else - reg = LED_N_OFF_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, ((int)duty >> 8) & 0xf); - - /* Clear the full ON bit, otherwise the set OFF time has no effect */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_ON_H; - else - reg = LED_N_ON_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0); - - return 0; -} - -static int pca9685_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct pca9685 *pca = to_pca(chip); - unsigned int reg; - - /* - * The PWM subsystem does not support a pre-delay. - * So, set the ON-timeout to 0 - */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_ON_L; - else - reg = LED_N_ON_L(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0); - - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_ON_H; - else - reg = LED_N_ON_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0); + regmap_read(pca->regmap, PCA9685_PRESCALE, &val); + if (prescale != val) { + /* + * Putting the chip briefly into SLEEP mode + * at this point won't interfere with the + * pm_runtime framework, because the pm_runtime + * state is guaranteed active here. + */ + /* Put chip into sleep mode */ + pca9685_set_sleep_mode(pca, true); - /* - * Clear the full-off bit. - * It has precedence over the others and must be off. - */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_H; - else - reg = LED_N_OFF_H(pwm->hwpwm); + /* Change the chip-wide output frequency */ + regmap_write(pca->regmap, PCA9685_PRESCALE, (int)prescale); - regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0); + /* Wake the chip up */ + pca9685_set_sleep_mode(pca, false); + } + pca9685_pwm_set_duty(pca, pwm->hwpwm, duty); return 0; } -static void pca9685_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct pca9685 *pca = to_pca(chip); - unsigned int reg; - - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_H; - else - reg = LED_N_OFF_H(pwm->hwpwm); - - regmap_write(pca->regmap, reg, LED_FULL); - - /* Clear the LED_OFF counter. */ - if (pwm->hwpwm >= PCA9685_MAXCHAN) - reg = PCA9685_ALL_LED_OFF_L; - else - reg = LED_N_OFF_L(pwm->hwpwm); - - regmap_write(pca->regmap, reg, 0x0); -} - static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); @@ -422,15 +346,13 @@ static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); - pca9685_pwm_disable(chip, pwm); + pca9685_pwm_set_duty(pca, pwm->hwpwm, 0); pm_runtime_put(chip->dev); pca9685_pwm_clear_inuse(pca, pwm->hwpwm); } static const struct pwm_ops pca9685_pwm_ops = { - .enable = pca9685_pwm_enable, - .disable = pca9685_pwm_disable, - .config = pca9685_pwm_config, + .apply = pca9685_pwm_apply, .request = pca9685_pwm_request, .free = pca9685_pwm_free, .owner = THIS_MODULE, @@ -461,7 +383,6 @@ static int pca9685_pwm_probe(struct i2c_client *client, ret); return ret; } - pca->period_ns = PCA9685_DEFAULT_PERIOD; i2c_set_clientdata(client, pca); From patchwork Tue Dec 15 21:22:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416742 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=B+r124iI; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWXx4w5Pz9sSf for ; Wed, 16 Dec 2020 08:26:41 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727952AbgLOVZ5 (ORCPT ); Tue, 15 Dec 2020 16:25:57 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54728 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728317AbgLOVZh (ORCPT ); Tue, 15 Dec 2020 16:25:37 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 4E628C727E5; Tue, 15 Dec 2020 22:24:54 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067494; bh=kHby63bueNno6SajQC6qRDHclc/CI0IKw4C1GFWWmvg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=B+r124iIe7HGprQKt/BK0Z2gwejiFPdn1lzj+3Tbi2T9vRUtzwSi9pgEZgQp+JK7g wDb4oYXIM0JAEZd6HYX4y46FouXEJnJlJrflAlKaA/EvnDdsnhl830a0WLYDMbZNat a9MatMMJHaSGwiADfQJtubeYbCVGlgooB+Ry9KnY= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 2/7] pwm: pca9685: Support hardware readout Date: Tue, 15 Dec 2020 22:22:23 +0100 Message-Id: <20201215212228.185517-2-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org Implements .get_state to read-out the current hardware state. The hardware readout may return slightly different values than those that were set in apply due to the limited range of possible prescale and counter register values. Also note that although the datasheet mentions 200 Hz as default frequency when using the internal 25 MHz oscillator, the calculated period from the default prescaler register setting of 30 is 5079040ns. Signed-off-by: Clemens Gruber --- drivers/pwm/pwm-pca9685.c | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 1b5b5fb93b43..b3398963c0ff 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -331,6 +331,46 @@ static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +static void pca9685_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pca9685 *pca = to_pca(chip); + unsigned long long duty; + unsigned int val; + + /* Calculate (chip-wide) period from prescale value */ + regmap_read(pca->regmap, PCA9685_PRESCALE, &val); + state->period = (PCA9685_COUNTER_RANGE * 1000 / PCA9685_OSC_CLOCK_MHZ) * + (val + 1); + + /* The (per-channel) polarity is fixed */ + state->polarity = PWM_POLARITY_NORMAL; + + if (pwm->hwpwm >= PCA9685_MAXCHAN) { + /* + * The "all LEDs" channel does not support HW readout + * Return 0 and disabled for backwards compatibility + */ + state->duty_cycle = 0; + state->enabled = false; + return; + } + + duty = pca9685_pwm_get_duty(pca, pwm->hwpwm); + + state->enabled = !!duty; + if (!state->enabled) { + state->duty_cycle = 0; + return; + } else if (duty == PCA9685_COUNTER_RANGE) { + state->duty_cycle = state->period; + return; + } + + duty *= state->period; + state->duty_cycle = duty / PCA9685_COUNTER_RANGE; +} + static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); @@ -353,6 +393,7 @@ static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) static const struct pwm_ops pca9685_pwm_ops = { .apply = pca9685_pwm_apply, + .get_state = pca9685_pwm_get_state, .request = pca9685_pwm_request, .free = pca9685_pwm_free, .owner = THIS_MODULE, From patchwork Tue Dec 15 21:22:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416744 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=mfAsdYFZ; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWbT6RYvz9sSf for ; Wed, 16 Dec 2020 08:28:53 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729887AbgLOV2t (ORCPT ); Tue, 15 Dec 2020 16:28:49 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54778 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728346AbgLOV2N (ORCPT ); Tue, 15 Dec 2020 16:28:13 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 8B414C727E0; Tue, 15 Dec 2020 22:27:28 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067648; bh=LxJvhwlUf9lRUgb/k0fnj/AV++LyinyiB1CxyKFrzr8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mfAsdYFZv58AHJqoWc1wfwsA49WCN3pPl1+pC+8TKjuj4URm5WPjb/sS2V0aYyiEA ZWFAoFWd/2CbpLWSoWLACE9Ez7D/VOeL7IicfMu83THFBoGzXXd3TZRExa0OmiNch1 /WbYz9y2OQvtdlDzUvFOeYkn+c+sGOU9X5x4zbL0= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 3/7] pwm: pca9685: Improve runtime PM behavior Date: Tue, 15 Dec 2020 22:22:24 +0100 Message-Id: <20201215212228.185517-3-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org The chip does not come out of POR in active state but in sleep state. To be sure (in case the bootloader woke it up) we force it to sleep in probe. On kernels without CONFIG_PM, we wake the chip in .probe and put it to sleep in .remove. Signed-off-by: Clemens Gruber --- drivers/pwm/pwm-pca9685.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index b3398963c0ff..7b14447f3c05 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -467,14 +467,19 @@ static int pca9685_pwm_probe(struct i2c_client *client, return ret; } - /* The chip comes out of power-up in the active state */ - pm_runtime_set_active(&client->dev); /* - * Enable will put the chip into suspend, which is what we - * want as all outputs are disabled at this point + * The chip comes out of power-up in the sleep state, + * but force it to sleep in case it was woken up before */ + pca9685_set_sleep_mode(pca, true); + pm_runtime_set_suspended(&client->dev); pm_runtime_enable(&client->dev); + if (!IS_ENABLED(CONFIG_PM)) { + /* Wake the chip up on non-PM environments */ + pca9685_set_sleep_mode(pca, false); + } + return 0; } @@ -486,7 +491,14 @@ static int pca9685_pwm_remove(struct i2c_client *client) ret = pwmchip_remove(&pca->chip); if (ret) return ret; + pm_runtime_disable(&client->dev); + + if (!IS_ENABLED(CONFIG_PM)) { + /* Put chip in sleep state on non-PM environments */ + pca9685_set_sleep_mode(pca, true); + } + return 0; } From patchwork Tue Dec 15 21:22:25 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416745 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=pS5ZwXvd; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWcv6v35z9sTK for ; Wed, 16 Dec 2020 08:30:07 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730074AbgLOV3S (ORCPT ); Tue, 15 Dec 2020 16:29:18 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54806 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728022AbgLOV3D (ORCPT ); Tue, 15 Dec 2020 16:29:03 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 6D531C727E5; Tue, 15 Dec 2020 22:28:18 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067698; bh=wke4oDo2nWqDUCqwQBkNnTYrwLML1pZG+thQsTK+htE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pS5ZwXvdZ0u5XeIB/Fpm9cElQh/heQDO9aUrF52eFiiaykP9r8o9+/g83sRsMNehB wUv0ZY7Iej+W7LIXCyC4n3/dnL/mYsD69QELvy1cWhxrmJ/er5K1uBHPAt54y/OM4X 0P5GnMUZMoe/P0zo4S/WVemX7pumJaxJKhjyZGLY= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 4/7] pwm: pca9685: Reset registers to POR state in probe Date: Tue, 15 Dec 2020 22:22:25 +0100 Message-Id: <20201215212228.185517-4-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org Reset the prescale and ON/OFF registers to their POR default state in the probe function. Otherwise, the PWMs could still be active after a watchdog reset and reboot, etc. Signed-off-by: Clemens Gruber --- drivers/pwm/pwm-pca9685.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 7b14447f3c05..38aadaf50996 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -47,6 +47,7 @@ #define PCA9685_ALL_LED_OFF_H 0xFD #define PCA9685_PRESCALE 0xFE +#define PCA9685_PRESCALE_DEF 0x1E /* => default frequency of ~200 Hz */ #define PCA9685_PRESCALE_MIN 0x03 /* => max. frequency of 1526 Hz */ #define PCA9685_PRESCALE_MAX 0xFF /* => min. frequency of 24 Hz */ @@ -446,9 +447,11 @@ static int pca9685_pwm_probe(struct i2c_client *client, reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3); regmap_write(pca->regmap, PCA9685_MODE1, reg); - /* Clear all "full off" bits */ + /* Reset ON/OFF registers to HW defaults (only full OFF bit is set) */ + regmap_write(pca->regmap, PCA9685_ALL_LED_ON_L, 0); + regmap_write(pca->regmap, PCA9685_ALL_LED_ON_H, 0); regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); - regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0); + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, LED_FULL); pca->chip.ops = &pca9685_pwm_ops; /* Add an extra channel for ALL_LED */ @@ -470,8 +473,10 @@ static int pca9685_pwm_probe(struct i2c_client *client, /* * The chip comes out of power-up in the sleep state, * but force it to sleep in case it was woken up before + * and set the default prescale value */ pca9685_set_sleep_mode(pca, true); + regmap_write(pca->regmap, PCA9685_PRESCALE, PCA9685_PRESCALE_DEF); pm_runtime_set_suspended(&client->dev); pm_runtime_enable(&client->dev); From patchwork Tue Dec 15 21:22:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416746 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=OYytZ3Oz; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWdG0m37z9sTK for ; Wed, 16 Dec 2020 08:30:26 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730141AbgLOV3s (ORCPT ); Tue, 15 Dec 2020 16:29:48 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54812 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728022AbgLOV3e (ORCPT ); Tue, 15 Dec 2020 16:29:34 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id D25C6C727E0; Tue, 15 Dec 2020 22:28:45 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067726; bh=VZT6wM9r2Vcydm8Etg5UEXrr3/TuRB6iASHSVNT69n0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OYytZ3Ozg0bwAs1c4x4HDReEKWcW8qeAAxnN9oR0wKYcOYviVx30pnk90aZoQG4os XoP5fJJBglXigKaX5zRyzE2syjtQ/63uNjzk6k1Nr0gOgY6bRJgoYgSWIeTAsXOcGf KPD8tUyY3A6IJkBcdWAYN1uo4bBFeuW1Pjq0zmqg= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 5/7] pwm: pca9685: Support staggered output ON times Date: Tue, 15 Dec 2020 22:22:26 +0100 Message-Id: <20201215212228.185517-5-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org The PCA9685 supports staggered LED output ON times to minimize current surges and reduce EMI. When this new option is enabled, the ON times of each channel are delayed by channel number x counter range / 16, which avoids asserting all enabled outputs at the same counter value while still maintaining the configured duty cycle of each output. Signed-off-by: Clemens Gruber --- drivers/pwm/pwm-pca9685.c | 62 +++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 38aadaf50996..ff916980de49 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -79,6 +79,7 @@ struct pca9685 { struct pwm_chip chip; struct regmap *regmap; + bool staggered_outputs; #if IS_ENABLED(CONFIG_GPIOLIB) struct mutex lock; struct gpio_chip gpio; @@ -93,45 +94,79 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty) { + unsigned int on, off; + if (duty == 0) { /* Set the full OFF bit, which has the highest precedence */ regmap_write(pca->regmap, REG_OFF_H(channel), LED_FULL); + return; } else if (duty >= PCA9685_COUNTER_RANGE) { /* Set the full ON bit and clear the full OFF bit */ regmap_write(pca->regmap, REG_ON_H(channel), LED_FULL); regmap_write(pca->regmap, REG_OFF_H(channel), 0); - } else { - /* Set OFF time (clears the full OFF bit) */ - regmap_write(pca->regmap, REG_OFF_L(channel), duty & 0xff); - regmap_write(pca->regmap, REG_OFF_H(channel), (duty >> 8) & 0xf); - /* Clear the full ON bit */ - regmap_write(pca->regmap, REG_ON_H(channel), 0); + return; + } + + if (pca->staggered_outputs) { + if (channel < PCA9685_MAXCHAN) { + /* + * To reduce EMI, the ON times of each channel are + * spread out evenly within the counter range, while + * still maintaining the configured duty cycle + */ + on = channel * PCA9685_COUNTER_RANGE / PCA9685_MAXCHAN; + off = (on + duty) % PCA9685_COUNTER_RANGE; + regmap_write(pca->regmap, REG_ON_L(channel), on & 0xff); + regmap_write(pca->regmap, REG_ON_H(channel), (on >> 8) & 0xf); + regmap_write(pca->regmap, REG_OFF_L(channel), off & 0xff); + regmap_write(pca->regmap, REG_OFF_H(channel), (off >> 8) & 0xf); + return; + } + /* No staggering possible if "all LEDs" channel is used */ + regmap_write(pca->regmap, PCA9685_ALL_LED_ON_L, 0); } + /* Set OFF time (clears the full OFF bit) */ + regmap_write(pca->regmap, REG_OFF_L(channel), duty & 0xff); + regmap_write(pca->regmap, REG_OFF_H(channel), (duty >> 8) & 0xf); + /* Clear the full ON bit */ + regmap_write(pca->regmap, REG_ON_H(channel), 0); } static unsigned int pca9685_pwm_get_duty(struct pca9685 *pca, int channel) { - unsigned int off_h, val; + unsigned int off, on, val; if (WARN_ON(channel >= PCA9685_MAXCHAN)) { /* Hardware readout not supported for "all LEDs" channel */ return 0; } - regmap_read(pca->regmap, LED_N_OFF_H(channel), &off_h); - if (off_h & LED_FULL) { + regmap_read(pca->regmap, LED_N_OFF_H(channel), &off); + if (off & LED_FULL) { /* Full OFF bit is set */ return 0; } - regmap_read(pca->regmap, LED_N_ON_H(channel), &val); - if (val & LED_FULL) { + regmap_read(pca->regmap, LED_N_ON_H(channel), &on); + if (on & LED_FULL) { /* Full ON bit is set */ return PCA9685_COUNTER_RANGE; } regmap_read(pca->regmap, LED_N_OFF_L(channel), &val); - return ((off_h & 0xf) << 8) | (val & 0xff); + off = ((off & 0xf) << 8) | (val & 0xff); + + if (pca->staggered_outputs) { + regmap_read(pca->regmap, LED_N_ON_L(channel), &val); + on = ((on & 0xf) << 8) | (val & 0xff); + + if (off >= on) + return off - on; + else + return off + PCA9685_COUNTER_RANGE - on; + } + + return off; } #if IS_ENABLED(CONFIG_GPIOLIB) @@ -442,6 +477,9 @@ static int pca9685_pwm_probe(struct i2c_client *client, regmap_write(pca->regmap, PCA9685_MODE2, reg); + pca->staggered_outputs = device_property_read_bool( + &client->dev, "nxp,staggered-outputs"); + /* Disable all LED ALLCALL and SUBx addresses to avoid bus collisions */ regmap_read(pca->regmap, PCA9685_MODE1, ®); reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3); From patchwork Tue Dec 15 21:22:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416747 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=N3+mnwXm; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWdG4Vtyz9sTX for ; Wed, 16 Dec 2020 08:30:26 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1730148AbgLOV3s (ORCPT ); Tue, 15 Dec 2020 16:29:48 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54830 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730147AbgLOV3s (ORCPT ); Tue, 15 Dec 2020 16:29:48 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id E8BA2C727E6; Tue, 15 Dec 2020 22:28:55 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067736; bh=SuRgUbmM3WsZ//QTtGwNoF7K88+t82tTtJm/pM9SDgQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N3+mnwXmomz5fPSVOTyVDgkesmWUhkNSAl59P00J2hsT3cxJX5nOx3iwc1aZUDTWw /iGFrJ9o5FHJ7CtpYk1i9+YmzW49+Rj02zJgrdQOliAfsVZcwYqsIgzd0GokYEUJaz tyrrLZyG3G1BtIA0qylK0nXn2KlKqqev4eQ61mwQ= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 6/7] dt-bindings: pwm: pca9685: Add nxp,staggered-outputs property Date: Tue, 15 Dec 2020 22:22:27 +0100 Message-Id: <20201215212228.185517-6-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org The pca9685 driver supports a new nxp,staggered-outputs property for reduced current surges and EMI. This adds documentation for the new DT property. Signed-off-by: Clemens Gruber --- Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt b/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt index f21b55c95738..fafe954369dc 100644 --- a/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt +++ b/Documentation/devicetree/bindings/pwm/nxp,pca9685-pwm.txt @@ -12,6 +12,8 @@ Optional properties: - invert (bool): boolean to enable inverted logic - open-drain (bool): boolean to configure outputs with open-drain structure; if omitted use totem-pole structure + - nxp,staggered-outputs (bool): boolean to enable staggered output ON times to + minimize current surges and EMI Example: From patchwork Tue Dec 15 21:22:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1416748 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org (client-ip=23.128.96.18; helo=vger.kernel.org; envelope-from=linux-pwm-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=quarantine dis=none) header.from=pqgruber.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=lO6qzAd9; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4CwWdq3KwFz9sSf for ; Wed, 16 Dec 2020 08:30:55 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729857AbgLOVac (ORCPT ); Tue, 15 Dec 2020 16:30:32 -0500 Received: from mail.pqgruber.com ([52.59.78.55]:54846 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1730222AbgLOVaE (ORCPT ); Tue, 15 Dec 2020 16:30:04 -0500 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id C8382C727E5; Tue, 15 Dec 2020 22:29:19 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1608067760; bh=mD2EIk+dKlXW5XQ3vfEC6pRh0s99jCzK0Z+HEmXXw3o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lO6qzAd9AQcL2kfqm4qZkMvwl3J87dUOg96jR2WHC+F21syY+dqMV/uCbeWgVByUS MnMeh1EGPm4/LxJqlxfSnmbuJuyjWzT+OFIHhmqYXblcWgZ3WrVNNg+VFfPOORu5IT 9QsMchEg5YRQg+ncEFpIk/tEfOa2Ec6nmdI9TaG8= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , u.kleine-koenig@pengutronix.de, Sven Van Asbroeck , Lee Jones , linux-kernel@vger.kernel.org, Mika Westerberg , David Jander , Clemens Gruber Subject: [PATCH v5 7/7] pwm: pca9685: Restrict period change for prescaler users Date: Tue, 15 Dec 2020 22:22:28 +0100 Message-Id: <20201215212228.185517-7-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201215212228.185517-1-clemens.gruber@pqgruber.com> References: <20201215212228.185517-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org 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 --- 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 /* - * 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, ®); if (device_property_read_bool(&client->dev, "invert"))