From patchwork Fri May 7 13:18:42 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1475526 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; dkim=pass (1024-bit key; secure) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=DdVTD0Xy; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4Fc9yX6WkCz9t0T for ; Fri, 7 May 2021 23:19:16 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S236781AbhEGNUO (ORCPT ); Fri, 7 May 2021 09:20:14 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:37510 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235855AbhEGNUO (ORCPT ); Fri, 7 May 2021 09:20:14 -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 9781CC7C83F; Fri, 7 May 2021 15:19:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1620393553; bh=azGR4lOgRoYBnA2FhEhdNxlES2odxqcYq1pWiYYKz8A=; h=From:To:Cc:Subject:Date:From; b=DdVTD0Xy89ktmT8RTM/brYmPUPsiyVXxrAe44/8UlHo4ui53W22dKBtWb1AVl8png vUoEwXYGpWp6SjDc+EpXAWE+FhxRovs5DXS7tEFCkZE31E5QyGe/3FVAVDFHPf5JzI mClRWTCgk+6Yx/6kK0bymfEBo9n/Wj4lfuUSvBos= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH 1/4] pwm: core: Support new usage_power setting in PWM state Date: Fri, 7 May 2021 15:18:42 +0200 Message-Id: <20210507131845.37605-1-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org If usage_power is set, the PWM driver is only required to maintain the power output but has more freedom regarding signal form. If supported, the signal can be optimized, for example to improve EMI by phase shifting individual channels. Signed-off-by: Clemens Gruber --- Documentation/driver-api/pwm.rst | 4 ++++ drivers/pwm/core.c | 6 +++++- include/linux/pwm.h | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Documentation/driver-api/pwm.rst b/Documentation/driver-api/pwm.rst index a7ca4f58305a..750734a7f874 100644 --- a/Documentation/driver-api/pwm.rst +++ b/Documentation/driver-api/pwm.rst @@ -48,6 +48,10 @@ After being requested, a PWM has to be configured using:: This API controls both the PWM period/duty_cycle config and the enable/disable state. +There is also a usage_power setting: If set, the PWM driver is only required to +maintain the power output but has more freedom regarding signal form. +If supported by the driver, the signal can be optimized, for example to improve +EMI by phase shifting the individual channels of a chip. The pwm_config(), pwm_enable() and pwm_disable() functions are just wrappers around pwm_apply_state() and should not be used if the user wants to change diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index c4d5c0667137..4aa5a24cc6c5 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -554,7 +554,8 @@ int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state) if (state->period == pwm->state.period && state->duty_cycle == pwm->state.duty_cycle && state->polarity == pwm->state.polarity && - state->enabled == pwm->state.enabled) + state->enabled == pwm->state.enabled && + state->usage_power == pwm->state.usage_power) return 0; if (chip->ops->apply) { @@ -1259,6 +1260,9 @@ static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) seq_printf(s, " polarity: %s", state.polarity ? "inverse" : "normal"); + if (state.usage_power) + seq_puts(s, " usage_power"); + seq_puts(s, "\n"); } } diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 5bb90af4997e..5a73251d28e3 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -54,12 +54,17 @@ enum { * @duty_cycle: PWM duty cycle (in nanoseconds) * @polarity: PWM polarity * @enabled: PWM enabled status + * @usage_power: If set, the PWM driver is only required to maintain the power + * output but has more freedom regarding signal form. + * If supported, the signal can be optimized, for example to + * improve EMI by phase shifting individual channels. */ struct pwm_state { u64 period; u64 duty_cycle; enum pwm_polarity polarity; bool enabled; + bool usage_power; }; /** @@ -188,6 +193,7 @@ static inline void pwm_init_state(const struct pwm_device *pwm, state->period = args.period; state->polarity = args.polarity; state->duty_cycle = 0; + state->usage_power = false; } /** @@ -558,6 +564,7 @@ static inline void pwm_apply_args(struct pwm_device *pwm) state.enabled = false; state.polarity = pwm->args.polarity; state.period = pwm->args.period; + state.usage_power = false; pwm_apply_state(pwm, &state); } From patchwork Fri May 7 13:18:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1475527 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; dkim=pass (1024-bit key; secure) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=XaaNKUCl; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4Fc9yb5smZz9t0Y for ; Fri, 7 May 2021 23:19:19 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237216AbhEGNUR (ORCPT ); Fri, 7 May 2021 09:20:17 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:37516 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235855AbhEGNUR (ORCPT ); Fri, 7 May 2021 09:20:17 -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 370ECC5574D; Fri, 7 May 2021 15:19:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1620393556; bh=dIc+rdMHJw6NaQNk32/6rv1S08CEUMejD+3jh7ujaxg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XaaNKUClGtHUs5ghFcqIGD9h3Zdfj18YVDRw83O7Fi1nn9cI4rvJoRrV/NEuJWDms Zfmh+JvwZYpwKQ9GBbSrE0YZFtf6X1eSvh7pKwqO1WpA6PcOkyjRLg8MJ9I2dqGx/r ivKg3HjeJz/XIrc8cYerQInFZ3SEXTZ4I4bcBvfA= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH 2/4] pwm: pca9685: Support new usage_power setting in PWM state Date: Fri, 7 May 2021 15:18:43 +0200 Message-Id: <20210507131845.37605-2-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210507131845.37605-1-clemens.gruber@pqgruber.com> References: <20210507131845.37605-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@vger.kernel.org If usage_power is set, 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 --- drivers/pwm/pwm-pca9685.c | 61 +++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 7c9f174de64e..20dd579297e6 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -93,48 +93,77 @@ 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->state.usage_power && channel < PCA9685_MAXCHAN) { + /* + * If usage_power is set, 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->state.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 +470,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 */ From patchwork Fri May 7 13:18:44 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1475528 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; dkim=pass (1024-bit key; secure) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=pTpSgl1E; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4Fc9yl6wwTz9t0T for ; Fri, 7 May 2021 23:19:27 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237208AbhEGNU0 (ORCPT ); Fri, 7 May 2021 09:20:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41084 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S237226AbhEGNUV (ORCPT ); Fri, 7 May 2021 09:20:21 -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 41C8FC061763; Fri, 7 May 2021 06:19:21 -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 F2C7FC7C83F; Fri, 7 May 2021 15:19:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1620393559; bh=Mz1kswUI7YR9M9Gllqdljly4PtXQ/lQyl+G7Z4/fcJs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pTpSgl1ESnNaJw/w1V4nJbHcDkjO/K95l42z77SD4F6Ncyu0NTYg7rnKbyvZmAXKM n70SDPVqGPT6oa/jI4LKYqO/lVG1S9ezeK9nxVENtpa4LqXecw633RdP/5kGV7v6bx rKDvHcLHtp5yvM1f8mGvSpE5lM92WY5DuYuIuzSM= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH 3/4] pwm: pca9685: Restrict period change for enabled PWMs Date: Fri, 7 May 2021 15:18:44 +0200 Message-Id: <20210507131845.37605-3-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210507131845.37605-1-clemens.gruber@pqgruber.com> References: <20210507131845.37605-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 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 --- 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 20dd579297e6..f70162114197 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) { @@ -269,8 +286,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; @@ -314,8 +329,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; @@ -338,6 +353,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 @@ -360,6 +381,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) { @@ -401,6 +441,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; @@ -410,7 +458,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); } @@ -451,6 +503,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 Fri May 7 13:18:45 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Clemens Gruber X-Patchwork-Id: 1475529 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; dkim=pass (1024-bit key; secure) header.d=pqgruber.com header.i=@pqgruber.com header.a=rsa-sha256 header.s=mail header.b=Pus7vm2A; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by ozlabs.org (Postfix) with ESMTP id 4Fc9ym6xBpz9t0Y for ; Fri, 7 May 2021 23:19:28 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237229AbhEGNU1 (ORCPT ); Fri, 7 May 2021 09:20:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:41092 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233599AbhEGNUX (ORCPT ); Fri, 7 May 2021 09:20:23 -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 4A841C0613ED; Fri, 7 May 2021 06:19:22 -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 C4E31C5574D; Fri, 7 May 2021 15:19:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1620393561; bh=7AzMT1WQdpJLheDA7UHS/FHkM+zukIHX2EbLK0f9lC8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Pus7vm2A9zhnZvR7qq7173T3Rb/jgMRXYsii1xx0gh2rIe6B835W8Mn0nF/fZmdTh aq7Pc0NHG/2A4loVBjpi+9HwdWPvYCOmeItGCGya4/8tYFs2eOiqCfOKbFUW+nInwC SkMC6vz3QTsAU6Gg1O5qhEy/epfi6buT9C/MpdD0= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH 4/4] pwm: pca9685: Add error messages for failed regmap calls Date: Fri, 7 May 2021 15:18:45 +0200 Message-Id: <20210507131845.37605-4-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210507131845.37605-1-clemens.gruber@pqgruber.com> References: <20210507131845.37605-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-pwm@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 --- 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 f70162114197..42ed770b432c 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; } @@ -140,11 +164,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) @@ -157,25 +181,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->state.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; } @@ -321,8 +345,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); @@ -351,7 +382,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, @@ -369,7 +400,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); @@ -408,7 +439,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 @@ -505,7 +536,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; @@ -517,18 +550,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 */