From patchwork Thu Apr 15 12:14:48 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 422869 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8A905C433B4 for ; Thu, 15 Apr 2021 12:15:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 4E99561153 for ; Thu, 15 Apr 2021 12:15:14 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232642AbhDOMPg (ORCPT ); Thu, 15 Apr 2021 08:15:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37282 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231590AbhDOMPf (ORCPT ); Thu, 15 Apr 2021 08:15:35 -0400 Received: from mail.pqgruber.com (mail.pqgruber.com [IPv6:2a05:d014:575:f70b:4f2c:8f1d:40c4:b13e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7216DC061574; Thu, 15 Apr 2021 05:15:12 -0700 (PDT) Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 895F3C725D8; Thu, 15 Apr 2021 14:15:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488910; bh=vlyINFcoDFx3dQaLmZtZQU4KgEy8A0TYubRr6L2GsjY=; h=From:To:Cc:Subject:Date:From; b=J26dTlJJlrH9PyrYvmZsdB9LRg17RmRODQPvG07q9vWDS4SQmUFjE+ZLDfVLf9k6n fy1TjvsZ5q7kJmGE4yWKbUU5M8oCwYGwHvzAeKcoG4rJNnCD6otPGKDAgWuMaTh/V+ EigS3B+Mk0gvnn/2DQ564WoMV9opM+Iqp+URUYq4= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 1/8] pwm: pca9685: Switch to atomic API Date: Thu, 15 Apr 2021 14:14:48 +0200 Message-Id: <20210415121455.39536-1-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@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 - Previously, only the full ON bit was toggled in GPIO mode (and full OFF cleared if set to high), which could result in both full OFF and full ON not being set and on=0, off=0, which is not allowed according to the datasheet - The OFF registers were reset to 0 in probe, which could lead to the forbidden on=0, off=0. Fixed by resetting to POR default (full OFF) Signed-off-by: Clemens Gruber Acked-by: Uwe Kleine-König --- Changes since v8: - Revert to DIV_ROUND_UP (keep math as-is) - Readability improvement for failed regmap_read Changes since v7: - Moved check for !state->enabled before prescaler configuration - Removed unnecessary cast - Use DIV_ROUND_DOWN in .apply Changes since v6: - Order of a comparison switched for improved readability Changes since v5: - Function documentation for set_duty - Variable initializations - Print warning if all LEDs channel - Changed EOPNOTSUPP to EINVAL - Improved error messages - Register reset corrections moved to this patch 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 | 261 +++++++++++++------------------------- 1 file changed, 91 insertions(+), 170 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 4a55dc18656c..ea19d72e5c34 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,53 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) return container_of(chip, struct pca9685, chip); } +/* Helper function to set the duty cycle ratio to duty/4096 (e.g. duty=2048 -> 50%) */ +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 = 0, val = 0; + + if (WARN_ON(channel >= PCA9685_MAXCHAN)) { + /* HW does not support reading state of "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; + } + + if (regmap_read(pca->regmap, LED_N_OFF_L(channel), &val)) { + /* Reset val to 0 in case reading LED_N_OFF_L failed */ + val = 0; + } + 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 +188,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 +285,52 @@ 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 -EINVAL; - 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, "pwm not changed: 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); - + if (!state->enabled) { + pca9685_pwm_set_duty(pca, pwm->hwpwm, 0); 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_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); - regmap_write(pca->regmap, reg, 0); + /* Change the chip-wide output frequency */ + regmap_write(pca->regmap, PCA9685_PRESCALE, prescale); - /* - * 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); - - regmap_update_bits(pca->regmap, reg, LED_FULL, 0x0); + /* Wake the chip up */ + pca9685_set_sleep_mode(pca, false); + } + duty = PCA9685_COUNTER_RANGE * state->duty_cycle; + duty = DIV_ROUND_UP_ULL(duty, state->period); + 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); @@ -484,9 +405,9 @@ 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 */ - regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, 0); - regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, 0); + /* Reset OFF registers to POR default */ + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, LED_FULL); + regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, LED_FULL); pca->chip.ops = &pca9685_pwm_ops; /* Add an extra channel for ALL_LED */ From patchwork Thu Apr 15 12:14:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 421872 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2AA4BC433ED for ; Thu, 15 Apr 2021 12:15:22 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id EA7C76124B for ; Thu, 15 Apr 2021 12:15:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232735AbhDOMPm (ORCPT ); Thu, 15 Apr 2021 08:15:42 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:34760 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231590AbhDOMPl (ORCPT ); Thu, 15 Apr 2021 08:15:41 -0400 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id A46EDC725D8; Thu, 15 Apr 2021 14:15:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488916; bh=06ZRhpbZxpxadONy0rq3gW+vmEz9TQ24K/L4VxJp+H4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qaALg2Q1xoIuc8ukGjU9iwMA4OVX01EGXckf1uyZ4frUa6uAwkHgVu4IBDjuZOmFV Dq/TfmycpYnu0rxwQ9v7frA1Ayncve67HMfHoNiy6FRllB3+fhwqpNCwO6VwJzGFOG hO3kI2mlmULtTnL7wRXFDoukIs72oKjFzBFxWL4s= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 2/8] pwm: pca9685: Support hardware readout Date: Thu, 15 Apr 2021 14:14:49 +0200 Message-Id: <20210415121455.39536-2-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Implement .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 Reviewed-by: Uwe Kleine-König --- Changes since v8: - As we left the math in apply as-is, use DIV_ROUND_DOWN in get_state Changes since v7: - Always return enabled=true for channels except "all LEDs" channel - Use DIV_ROUND_UP in .get_state Changes since v6: - Added a comment regarding the division (Suggested by Uwe) - Rebased drivers/pwm/pwm-pca9685.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index ea19d72e5c34..aa48c45bd294 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -331,6 +331,41 @@ 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 = 0; + + /* Calculate (chip-wide) period from prescale value */ + regmap_read(pca->regmap, PCA9685_PRESCALE, &val); + /* + * PCA9685_OSC_CLOCK_MHZ is 25, i.e. an integer divider of 1000. + * The following calculation is therefore only a multiplication + * and we are not losing precision. + */ + 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; + } + + state->enabled = true; + duty = pca9685_pwm_get_duty(pca, pwm->hwpwm); + state->duty_cycle = DIV_ROUND_DOWN_ULL(duty * state->period, PCA9685_COUNTER_RANGE); +} + static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); @@ -353,6 +388,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 Thu Apr 15 12:14:50 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 422868 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C2B85C433B4 for ; Thu, 15 Apr 2021 12:15:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8A34061153 for ; Thu, 15 Apr 2021 12:15:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232770AbhDOMPs (ORCPT ); Thu, 15 Apr 2021 08:15:48 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:34772 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232771AbhDOMPs (ORCPT ); Thu, 15 Apr 2021 08:15:48 -0400 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 9C870C725D8; Thu, 15 Apr 2021 14:15:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488923; bh=F3hXPjM9wtbrWoMAGiw2fyCzbJmA6VnJB+0OF7F65tY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AqQ3olZyjCQYfsjVpd3xIG9dQEy9l/9Ga/mspDkEtI66pSBDKLSCBh4g9QKLvvEvg rDkaCBTJxKzscs6vcy9Tk1KMVufSEA3ww8INe7IU4cCW00WF/UJuHmZiDLZynG5SWj cJRdkkdfI154aBaVFvNMULVRjpd1HxifhSSzRs3c= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 3/8] pwm: pca9685: Improve runtime PM behavior Date: Thu, 15 Apr 2021 14:14:50 +0200 Message-Id: <20210415121455.39536-3-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@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. If runtime PM is disabled, we instead wake the chip in .probe and put it to sleep in .remove. Signed-off-by: Clemens Gruber --- Changes since v8: - Rebased Changes since v7: - Handle sysfs power control as well and not just CONFIG_PM Changes since v6: - Improved !CONFIG_PM handling (wake it up without putting it to sleep first) drivers/pwm/pwm-pca9685.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index aa48c45bd294..1b013d03f5d8 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -462,14 +462,20 @@ 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 - */ pm_runtime_enable(&client->dev); + if (pm_runtime_enabled(&client->dev)) { + /* + * Although the chip comes out of power-up in the sleep state, + * we force it to sleep in case it was woken up before + */ + pca9685_set_sleep_mode(pca, true); + pm_runtime_set_suspended(&client->dev); + } else { + /* Wake the chip up if runtime PM is disabled */ + pca9685_set_sleep_mode(pca, false); + } + return 0; } @@ -481,7 +487,14 @@ static int pca9685_pwm_remove(struct i2c_client *client) ret = pwmchip_remove(&pca->chip); if (ret) return ret; + + if (!pm_runtime_enabled(&client->dev)) { + /* Put chip in sleep state if runtime PM is disabled */ + pca9685_set_sleep_mode(pca, true); + } + pm_runtime_disable(&client->dev); + return 0; } From patchwork Thu Apr 15 12:14:51 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 421871 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 90CC1C433ED for ; Thu, 15 Apr 2021 12:15:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 628146124B for ; Thu, 15 Apr 2021 12:15:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232798AbhDOMPz (ORCPT ); Thu, 15 Apr 2021 08:15:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37354 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232796AbhDOMPw (ORCPT ); Thu, 15 Apr 2021 08:15:52 -0400 Received: from mail.pqgruber.com (mail.pqgruber.com [IPv6:2a05:d014:575:f70b:4f2c:8f1d:40c4:b13e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id ABFA5C061574; Thu, 15 Apr 2021 05:15:29 -0700 (PDT) Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 0C362C725D8; Thu, 15 Apr 2021 14:15:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488928; bh=GlsSSVic5H44j5iEOaImBYJ/UJ6bO5xkDJrawlhvIZI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mlRaz6562tGiTImHsG2r9+X2/0S3ogOJED7lQ5HORi+GmvR76y3IekxPwcXVnCbyN zgxrygxD4+Oe2KIkaFpP3isElPstaCeCg5Ys0MW6CTZIhII4Nn3wXVCjDgd9EjKGNY KM3VqR7lv2yQHPYjIrx52Eusj2KL+5rJDYZ9Aj7U= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber , Rob Herring Subject: [PATCH v9 4/8] dt-bindings: pwm: Support new PWM_USAGE_POWER flag Date: Thu, 15 Apr 2021 14:14:51 +0200 Message-Id: <20210415121455.39536-4-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add the flag and corresponding documentation for PWM_USAGE_POWER. Cc: Rob Herring Signed-off-by: Clemens Gruber --- Documentation/devicetree/bindings/pwm/pwm.txt | 3 +++ include/dt-bindings/pwm/pwm.h | 1 + 2 files changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/pwm/pwm.txt b/Documentation/devicetree/bindings/pwm/pwm.txt index 084886bd721e..fe3a28f887c0 100644 --- a/Documentation/devicetree/bindings/pwm/pwm.txt +++ b/Documentation/devicetree/bindings/pwm/pwm.txt @@ -46,6 +46,9 @@ period in nanoseconds. Optionally, the pwm-specifier can encode a number of flags (defined in ) in a third cell: - PWM_POLARITY_INVERTED: invert the PWM signal polarity +- PWM_USAGE_POWER: Only care about the power output of the signal. This + allows drivers (if supported) to optimize the signals, for example to + improve EMI and reduce current spikes. Example with optional PWM specifier for inverse polarity diff --git a/include/dt-bindings/pwm/pwm.h b/include/dt-bindings/pwm/pwm.h index ab9a077e3c7d..0d5a8f0c0035 100644 --- a/include/dt-bindings/pwm/pwm.h +++ b/include/dt-bindings/pwm/pwm.h @@ -11,5 +11,6 @@ #define _DT_BINDINGS_PWM_PWM_H #define PWM_POLARITY_INVERTED (1 << 0) +#define PWM_USAGE_POWER (1 << 1) #endif From patchwork Thu Apr 15 12:14:52 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 422867 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E9F35C43461 for ; Thu, 15 Apr 2021 12:15:33 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id B3AAE613CD for ; Thu, 15 Apr 2021 12:15:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232816AbhDOMPz (ORCPT ); Thu, 15 Apr 2021 08:15:55 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37364 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232789AbhDOMPy (ORCPT ); Thu, 15 Apr 2021 08:15:54 -0400 Received: from mail.pqgruber.com (mail.pqgruber.com [IPv6:2a05:d014:575:f70b:4f2c:8f1d:40c4:b13e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DE8D7C061756; Thu, 15 Apr 2021 05:15:31 -0700 (PDT) Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 61DC3C725D8; Thu, 15 Apr 2021 14:15:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488930; bh=SxiB/wQTlRoG8/dxG1/RTDvR1hDyB+kFAQl39HISeJU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=xcOan3DDCz5Rq3XfIWpufXfrToPMMrhC90mGBQGWaL1xy1QiUmR092zwCzAPHbi6S Pk8zVPT06sMnqQ7H80pLkUCyrqMHYqj3dZEVneGgTC+yOszyFws2jebS44RZbt7TwG dQ5CFJGGkmT5ITLa+gYZXfaLyzONeIE8EoZKh3fE= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 5/8] pwm: core: Support new PWM_USAGE_POWER flag Date: Thu, 15 Apr 2021 14:14:52 +0200 Message-Id: <20210415121455.39536-5-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org If the flag PWM_USAGE_POWER is set on a channel, the PWM driver may optimize the signal as long as the power output is not changed. Depending on the specific driver, the optimization could for example improve EMI (if supported) by phase-shifting the individual channels. Signed-off-by: Clemens Gruber --- drivers/pwm/core.c | 9 +++++++-- include/linux/pwm.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index a8eff4b3ee36..56a9c739e1b2 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -153,9 +153,14 @@ of_pwm_xlate_with_flags(struct pwm_chip *pc, const struct of_phandle_args *args) pwm->args.period = args->args[1]; pwm->args.polarity = PWM_POLARITY_NORMAL; + pwm->args.usage_power = false; - if (args->args_count > 2 && args->args[2] & PWM_POLARITY_INVERTED) - pwm->args.polarity = PWM_POLARITY_INVERSED; + if (args->args_count > 2) { + if (args->args[2] & PWM_POLARITY_INVERTED) + pwm->args.polarity = PWM_POLARITY_INVERSED; + if (args->args[2] & PWM_USAGE_POWER) + pwm->args.usage_power = true; + } return pwm; } diff --git a/include/linux/pwm.h b/include/linux/pwm.h index e4d84d4db293..555e050e8bec 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -41,6 +41,7 @@ enum pwm_polarity { struct pwm_args { u64 period; enum pwm_polarity polarity; + bool usage_power; }; enum { From patchwork Thu Apr 15 12:14:53 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 421870 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 05619C43461 for ; Thu, 15 Apr 2021 12:15:40 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C05C0613B6 for ; Thu, 15 Apr 2021 12:15:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232841AbhDOMQA (ORCPT ); Thu, 15 Apr 2021 08:16:00 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:34792 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232836AbhDOMP7 (ORCPT ); Thu, 15 Apr 2021 08:15:59 -0400 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 3350AC725D8; Thu, 15 Apr 2021 14:15:34 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488934; bh=e7T/pjKrbxXXjLeiOJ7w1qh/Bu1UNFSyypxVNrneVpE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=G7gfngpQX5U4YbuX1gqZSXTum//xjmB6wQuP5s1Kz0MutmSXLebxtiVvIgBC6r3h3 TqBmAmI5dLO+q70tDJOKQ3rFxmSdXITYtVAvjcPYd+2gLdBkGlqXXPLGsrtknbfgKJ +C0eQNDshs9kJJinB5TB28VLymVKP7wONc626kf4= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 6/8] pwm: pca9685: Support new PWM_USAGE_POWER flag Date: Thu, 15 Apr 2021 14:14:53 +0200 Message-Id: <20210415121455.39536-6-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org If PWM_USAGE_POWER is set on a PWM, the pca9685 driver will phase shift the individual channels relative to their channel number. This improves EMI because the enabled channels no longer turn on at the same time, while still maintaining the configured duty cycle / power output. Signed-off-by: Clemens Gruber --- Changes since v8: - Improved readability of failed regmap_read - Rebased drivers/pwm/pwm-pca9685.c | 65 ++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 1b013d03f5d8..315a75db254d 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -93,48 +93,78 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) /* Helper function to set the duty cycle ratio to duty/4096 (e.g. duty=2048 -> 50%) */ static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty) { + struct pwm_device *pwm = &pca->chip.pwms[channel]; + 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 (pwm->args.usage_power && channel < PCA9685_MAXCHAN) { + /* + * If PWM_USAGE_POWER is set on a PWM, the pca9685 + * driver will phase shift the individual channels + * relative to their channel number. + * This improves EMI because the enabled channels no + * longer turn on at the same time, while still + * maintaining the configured duty cycle / power output. + */ + on = channel * PCA9685_COUNTER_RANGE / PCA9685_MAXCHAN; + } else + on = 0; + + off = (on + duty) % PCA9685_COUNTER_RANGE; + + /* Set ON time (clears full ON bit) */ + regmap_write(pca->regmap, REG_ON_L(channel), on & 0xff); + regmap_write(pca->regmap, REG_ON_H(channel), (on >> 8) & 0xf); + /* Set OFF time (clears full OFF bit) */ + regmap_write(pca->regmap, REG_OFF_L(channel), off & 0xff); + regmap_write(pca->regmap, REG_OFF_H(channel), (off >> 8) & 0xf); } static unsigned int pca9685_pwm_get_duty(struct pca9685 *pca, int channel) { - unsigned int off_h = 0, val = 0; + struct pwm_device *pwm = &pca->chip.pwms[channel]; + unsigned int off = 0, on = 0, val = 0; if (WARN_ON(channel >= PCA9685_MAXCHAN)) { /* HW does not support reading state of "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; } - if (regmap_read(pca->regmap, LED_N_OFF_L(channel), &val)) { - /* Reset val to 0 in case reading LED_N_OFF_L failed */ + regmap_read(pca->regmap, LED_N_OFF_L(channel), &val); + off = ((off & 0xf) << 8) | (val & 0xff); + if (!pwm->args.usage_power) + return off; + + /* Read ON register to calculate duty cycle of staggered output */ + if (regmap_read(pca->regmap, LED_N_ON_L(channel), &val)) { + /* Reset val to 0 in case reading LED_N_ON_L failed */ val = 0; } - return ((off_h & 0xf) << 8) | (val & 0xff); + on = ((on & 0xf) << 8) | (val & 0xff); + return (off - on) & (PCA9685_COUNTER_RANGE - 1); } #if IS_ENABLED(CONFIG_GPIOLIB) @@ -441,9 +471,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); - /* Reset OFF registers to POR default */ + /* Reset OFF/ON registers to POR default */ regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, LED_FULL); regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, LED_FULL); + regmap_write(pca->regmap, PCA9685_ALL_LED_ON_L, 0); + regmap_write(pca->regmap, PCA9685_ALL_LED_ON_H, 0); pca->chip.ops = &pca9685_pwm_ops; /* Add an extra channel for ALL_LED */ @@ -452,6 +484,9 @@ static int pca9685_pwm_probe(struct i2c_client *client, pca->chip.dev = &client->dev; pca->chip.base = -1; + pca->chip.of_xlate = of_pwm_xlate_with_flags; + pca->chip.of_pwm_n_cells = 3; + ret = pwmchip_add(&pca->chip); if (ret < 0) return ret; From patchwork Thu Apr 15 12:14:54 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 422866 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C2DC1C433ED for ; Thu, 15 Apr 2021 12:15:41 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 99A0761153 for ; Thu, 15 Apr 2021 12:15:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232836AbhDOMQD (ORCPT ); Thu, 15 Apr 2021 08:16:03 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:34802 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232849AbhDOMQC (ORCPT ); Thu, 15 Apr 2021 08:16:02 -0400 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 1ADE6C725D8; Thu, 15 Apr 2021 14:15:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488938; bh=latNAJWBsrIyPGaxdqAbBkbdTFqErqD4ENuPagtGkE8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WvZl4l7Z5X/WzE4FfchdHcj7U7LYbcbva20mvaN/F7scftuzZ2mL8kO7R/qDpxHNH zcDksPA3PE2cyis/lkUBauB0sLaeTwoUrNjtaXo8qt2eCEr52oEAfWCDKmPrN5MPRD 0oU8HZkJ/QCWPqNCcsri41cFI3QQL2FwZVaQjXVo= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 7/8] pwm: pca9685: Restrict period change for enabled PWMs Date: Thu, 15 Apr 2021 14:14:54 +0200 Message-Id: <20210415121455.39536-7-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Previously, the last used PWM channel could change the global prescale setting, even if other channels are already in use. Fix it by only allowing the first enabled PWM 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. GPIOs do not count as enabled PWMs as they are not using the prescaler and can't change it. Signed-off-by: Clemens Gruber Acked-by: Uwe Kleine-König --- Changes since v8: - Rebased Changes since v7: - As the HW readout always returns enabled, also set the pwms_enabled bit in request (except for the "all LEDs" channel) Changes since v6: - Only allow the first PWM that is enabled to change the prescaler, not the first one that uses the prescaler drivers/pwm/pwm-pca9685.c | 74 +++++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 315a75db254d..f3ff36381c5b 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 is enabled 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. + * GPIOs do not count as enabled PWMs as they are not using the prescaler. */ #define PCA9685_MODE1 0x00 @@ -78,8 +78,9 @@ struct pca9685 { struct pwm_chip chip; struct regmap *regmap; -#if IS_ENABLED(CONFIG_GPIOLIB) struct mutex lock; + DECLARE_BITMAP(pwms_enabled, PCA9685_MAXCHAN + 1); +#if IS_ENABLED(CONFIG_GPIOLIB) struct gpio_chip gpio; DECLARE_BITMAP(pwms_inuse, PCA9685_MAXCHAN + 1); #endif @@ -90,6 +91,22 @@ 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 lock mutex held */ +static bool pca9685_prescaler_can_change(struct pca9685 *pca, int channel) +{ + /* No PWM enabled: Change allowed */ + if (bitmap_empty(pca->pwms_enabled, PCA9685_MAXCHAN + 1)) + return true; + /* More than one PWM enabled: Change not allowed */ + if (bitmap_weight(pca->pwms_enabled, PCA9685_MAXCHAN + 1) > 1) + return false; + /* + * Only one PWM enabled: Change allowed if the PWM about to + * be changed is the one that is already enabled + */ + return test_bit(channel, pca->pwms_enabled); +} + /* Helper function to set the duty cycle ratio to duty/4096 (e.g. duty=2048 -> 50%) */ static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty) { @@ -270,8 +287,6 @@ static int pca9685_pwm_gpio_probe(struct pca9685 *pca) { struct device *dev = pca->chip.dev; - mutex_init(&pca->lock); - pca->gpio.label = dev_name(dev); pca->gpio.parent = dev; pca->gpio.request = pca9685_pwm_gpio_request; @@ -315,8 +330,8 @@ static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable) } } -static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, - const struct pwm_state *state) +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, prescale; @@ -339,6 +354,12 @@ static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, regmap_read(pca->regmap, PCA9685_PRESCALE, &val); if (prescale != val) { + if (!pca9685_prescaler_can_change(pca, pwm->hwpwm)) { + dev_err(chip->dev, + "pwm not changed: periods of enabled pwms must match!\n"); + return -EBUSY; + } + /* * Putting the chip briefly into SLEEP mode * at this point won't interfere with the @@ -361,6 +382,25 @@ static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +static int pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pca9685 *pca = to_pca(chip); + int ret; + + mutex_lock(&pca->lock); + ret = __pca9685_pwm_apply(chip, pwm, state); + if (ret == 0) { + if (state->enabled) + set_bit(pwm->hwpwm, pca->pwms_enabled); + else + clear_bit(pwm->hwpwm, pca->pwms_enabled); + } + mutex_unlock(&pca->lock); + + return ret; +} + static void pca9685_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { @@ -402,6 +442,14 @@ static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) if (pca9685_pwm_test_and_set_inuse(pca, pwm->hwpwm)) return -EBUSY; + + if (pwm->hwpwm < PCA9685_MAXCHAN) { + /* PWMs - except the "all LEDs" channel - default to enabled */ + mutex_lock(&pca->lock); + set_bit(pwm->hwpwm, pca->pwms_enabled); + mutex_unlock(&pca->lock); + } + pm_runtime_get_sync(chip->dev); return 0; @@ -411,7 +459,11 @@ static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); + mutex_lock(&pca->lock); pca9685_pwm_set_duty(pca, pwm->hwpwm, 0); + clear_bit(pwm->hwpwm, pca->pwms_enabled); + mutex_unlock(&pca->lock); + pm_runtime_put(chip->dev); pca9685_pwm_clear_inuse(pca, pwm->hwpwm); } @@ -452,6 +504,8 @@ static int pca9685_pwm_probe(struct i2c_client *client, i2c_set_clientdata(client, pca); + mutex_init(&pca->lock); + regmap_read(pca->regmap, PCA9685_MODE2, ®); if (device_property_read_bool(&client->dev, "invert")) From patchwork Thu Apr 15 12:14:55 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 421869 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-18.8 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_CR_TRAILER, INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5F7B8C433ED for ; Thu, 15 Apr 2021 12:15:48 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 269916137D for ; Thu, 15 Apr 2021 12:15:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232902AbhDOMQJ (ORCPT ); Thu, 15 Apr 2021 08:16:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37418 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232849AbhDOMQG (ORCPT ); Thu, 15 Apr 2021 08:16:06 -0400 Received: from mail.pqgruber.com (mail.pqgruber.com [IPv6:2a05:d014:575:f70b:4f2c:8f1d:40c4:b13e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 89786C061760; Thu, 15 Apr 2021 05:15:43 -0700 (PDT) Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 0D003C725D8; Thu, 15 Apr 2021 14:15:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1618488942; bh=YWHML3DuOzwgSktSARm58ZchDBxU953bun9zG3JNyD4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=namyFJEA/JD59ikB0boIuCpTDl4yd1zMiSJaxuf3veHCUeveFM2CZh6E+ZFqgy/6G NfMzE0cMfWLaFfCekgLixniO4+fQ+Kg0m0HekuXccwLIzosAsnWQjXe8CiOPC1r8/q 4GTjNhmXtgylXASNGe6Amy6JvSVNZZQLrCUiRGm8= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v9 8/8] pwm: pca9685: Add error messages for failed regmap calls Date: Thu, 15 Apr 2021 14:14:55 +0200 Message-Id: <20210415121455.39536-8-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210415121455.39536-1-clemens.gruber@pqgruber.com> References: <20210415121455.39536-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Regmap operations can fail if the underlying subsystem is not working properly (e.g. hogged I2C bus, etc.) As this is useful information for the user, print an error message if it happens. Let probe fail if the first regmap_read or the first regmap_write fails. Signed-off-by: Clemens Gruber Acked-by: Uwe Kleine-König --- Changes since v8: - Minor readability improvements - Rebased Changes since v7: - Use %pe instead of %d for error codes (Suggested by Uwe) drivers/pwm/pwm-pca9685.c | 83 ++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index f3ff36381c5b..e88e02118f25 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -107,6 +107,30 @@ static bool pca9685_prescaler_can_change(struct pca9685 *pca, int channel) return test_bit(channel, pca->pwms_enabled); } +static int pca9685_read_reg(struct pca9685 *pca, unsigned int reg, unsigned int *val) +{ + struct device *dev = pca->chip.dev; + int err; + + err = regmap_read(pca->regmap, reg, val); + if (err) + dev_err(dev, "regmap_read of register 0x%x failed: %pe\n", reg, ERR_PTR(err)); + + return err; +} + +static int pca9685_write_reg(struct pca9685 *pca, unsigned int reg, unsigned int val) +{ + struct device *dev = pca->chip.dev; + int err; + + err = regmap_write(pca->regmap, reg, val); + if (err) + dev_err(dev, "regmap_write to register 0x%x failed: %pe\n", reg, ERR_PTR(err)); + + return err; +} + /* Helper function to set the duty cycle ratio to duty/4096 (e.g. duty=2048 -> 50%) */ static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int duty) { @@ -115,12 +139,12 @@ static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int if (duty == 0) { /* Set the full OFF bit, which has the highest precedence */ - regmap_write(pca->regmap, REG_OFF_H(channel), LED_FULL); + pca9685_write_reg(pca, 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); + pca9685_write_reg(pca, REG_ON_H(channel), LED_FULL); + pca9685_write_reg(pca, REG_OFF_H(channel), 0); return; } @@ -141,11 +165,11 @@ static void pca9685_pwm_set_duty(struct pca9685 *pca, int channel, unsigned int off = (on + duty) % PCA9685_COUNTER_RANGE; /* Set ON time (clears full ON bit) */ - regmap_write(pca->regmap, REG_ON_L(channel), on & 0xff); - regmap_write(pca->regmap, REG_ON_H(channel), (on >> 8) & 0xf); + pca9685_write_reg(pca, REG_ON_L(channel), on & 0xff); + pca9685_write_reg(pca, REG_ON_H(channel), (on >> 8) & 0xf); /* Set OFF time (clears full OFF bit) */ - regmap_write(pca->regmap, REG_OFF_L(channel), off & 0xff); - regmap_write(pca->regmap, REG_OFF_H(channel), (off >> 8) & 0xf); + pca9685_write_reg(pca, REG_OFF_L(channel), off & 0xff); + pca9685_write_reg(pca, REG_OFF_H(channel), (off >> 8) & 0xf); } static unsigned int pca9685_pwm_get_duty(struct pca9685 *pca, int channel) @@ -158,25 +182,25 @@ static unsigned int pca9685_pwm_get_duty(struct pca9685 *pca, int channel) return 0; } - regmap_read(pca->regmap, LED_N_OFF_H(channel), &off); + pca9685_read_reg(pca, 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), &on); + pca9685_read_reg(pca, 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); + pca9685_read_reg(pca, LED_N_OFF_L(channel), &val); off = ((off & 0xf) << 8) | (val & 0xff); if (!pwm->args.usage_power) return off; /* Read ON register to calculate duty cycle of staggered output */ - if (regmap_read(pca->regmap, LED_N_ON_L(channel), &val)) { + if (pca9685_read_reg(pca, LED_N_ON_L(channel), &val)) { /* Reset val to 0 in case reading LED_N_ON_L failed */ val = 0; } @@ -322,8 +346,15 @@ static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca) static void pca9685_set_sleep_mode(struct pca9685 *pca, bool enable) { - regmap_update_bits(pca->regmap, PCA9685_MODE1, - MODE1_SLEEP, enable ? MODE1_SLEEP : 0); + struct device *dev = pca->chip.dev; + int err = regmap_update_bits(pca->regmap, PCA9685_MODE1, + MODE1_SLEEP, enable ? MODE1_SLEEP : 0); + if (err) { + dev_err(dev, "regmap_update_bits of register 0x%x failed: %pe\n", + PCA9685_MODE1, ERR_PTR(err)); + return; + } + if (!enable) { /* Wait 500us for the oscillator to be back up */ udelay(500); @@ -352,7 +383,7 @@ static int __pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } - regmap_read(pca->regmap, PCA9685_PRESCALE, &val); + pca9685_read_reg(pca, PCA9685_PRESCALE, &val); if (prescale != val) { if (!pca9685_prescaler_can_change(pca, pwm->hwpwm)) { dev_err(chip->dev, @@ -370,7 +401,7 @@ static int __pca9685_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, pca9685_set_sleep_mode(pca, true); /* Change the chip-wide output frequency */ - regmap_write(pca->regmap, PCA9685_PRESCALE, prescale); + pca9685_write_reg(pca, PCA9685_PRESCALE, prescale); /* Wake the chip up */ pca9685_set_sleep_mode(pca, false); @@ -409,7 +440,7 @@ static void pca9685_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, unsigned int val = 0; /* Calculate (chip-wide) period from prescale value */ - regmap_read(pca->regmap, PCA9685_PRESCALE, &val); + pca9685_read_reg(pca, PCA9685_PRESCALE, &val); /* * PCA9685_OSC_CLOCK_MHZ is 25, i.e. an integer divider of 1000. * The following calculation is therefore only a multiplication @@ -506,7 +537,9 @@ static int pca9685_pwm_probe(struct i2c_client *client, mutex_init(&pca->lock); - regmap_read(pca->regmap, PCA9685_MODE2, ®); + ret = pca9685_read_reg(pca, PCA9685_MODE2, ®); + if (ret) + return ret; if (device_property_read_bool(&client->dev, "invert")) reg |= MODE2_INVRT; @@ -518,18 +551,20 @@ static int pca9685_pwm_probe(struct i2c_client *client, else reg |= MODE2_OUTDRV; - regmap_write(pca->regmap, PCA9685_MODE2, reg); + ret = pca9685_write_reg(pca, PCA9685_MODE2, reg); + if (ret) + return ret; /* Disable all LED ALLCALL and SUBx addresses to avoid bus collisions */ - regmap_read(pca->regmap, PCA9685_MODE1, ®); + pca9685_read_reg(pca, PCA9685_MODE1, ®); reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3); - regmap_write(pca->regmap, PCA9685_MODE1, reg); + pca9685_write_reg(pca, PCA9685_MODE1, reg); /* Reset OFF/ON registers to POR default */ - regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_L, LED_FULL); - regmap_write(pca->regmap, PCA9685_ALL_LED_OFF_H, LED_FULL); - regmap_write(pca->regmap, PCA9685_ALL_LED_ON_L, 0); - regmap_write(pca->regmap, PCA9685_ALL_LED_ON_H, 0); + pca9685_write_reg(pca, PCA9685_ALL_LED_OFF_L, LED_FULL); + pca9685_write_reg(pca, PCA9685_ALL_LED_OFF_H, LED_FULL); + pca9685_write_reg(pca, PCA9685_ALL_LED_ON_L, 0); + pca9685_write_reg(pca, PCA9685_ALL_LED_ON_H, 0); pca->chip.ops = &pca9685_pwm_ops; /* Add an extra channel for ALL_LED */