Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp3568788pxf; Mon, 29 Mar 2021 06:00:58 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzDLSoO9nJzqFv9qXXgkgrbCJ+XnJYa550rb36HB+++i2ZFZC5hfqxlhJr/0OTupEXA8kz0 X-Received: by 2002:a17:906:1986:: with SMTP id g6mr28122469ejd.533.1617022858006; Mon, 29 Mar 2021 06:00:58 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1617022857; cv=none; d=google.com; s=arc-20160816; b=HEseM0OLU656d+5NkM8nQXG4PdIPF3W84yLT9ZroYz8IINdWiBc2HVZZZRYXp+7TAZ iy8vPi7/vTEDig75gGmgmv3PdZYWIl3BCDPPuFeEAEl75sJY3+8kI826cY7yyMimd/N7 1kDUagRnuh2M1zLrZK/rQ0nLtDiomxXLvC2LRpuEfQhJYUA+1oDGIhzZ76CU1fi88Xxr SMQx7P8x5YlYyW3FyGGtlt0EKtDSOTBsUc0Uu18TJUNsgtlLX5qfiArnKxi3Z6ude6Yb XyxkR46j6Wcgac3rHX7pjKVRZXA0gydauRVExp0hpFEd8UQQTFIRNLYnke4kjRCEAM9L v45Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=3V0b4SCPnnWF6a37Pjdefw3jHvD9jBFTlXeOrslYX1M=; b=UY48jwO3hI0T0CvhzvJNm0NJMezl5Wz3A4e+L/nkF9GnfBKch4A9I3SQWkTBO8hKOV pSJttYXM/Ee8y1Pfj7iJxTutawj+eQSO/phmQmrxnjq7Tc0HHMedYNwe6Xpjf3u+4GAX 3ajCNjqTuHyGVm1z84SoYxfEUhCPzcMVFAKOCp18OeycgQ64YI4P900V//JpD/PmSyxf 64c8O1EyNzCZOXhgQ6vwYYiRb0selYBIDYH/GkAQYV0RUG2J5U1wNq4fuOOjIFHmBRq1 IPQ4k6vhmCW0qwmFS9JUYcsNKYfv3ihtl8fjqCH1DWXvJ3BmY97/2B/Vd0eso2M4o76z Aupg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@pqgruber.com header.s=mail header.b=t9e+JKGH; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=NONE dis=NONE) header.from=pqgruber.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id f12si12753716ejx.75.2021.03.29.06.00.35; Mon, 29 Mar 2021 06:00:57 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@pqgruber.com header.s=mail header.b=t9e+JKGH; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=NONE dis=NONE) header.from=pqgruber.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230488AbhC2M7e (ORCPT + 99 others); Mon, 29 Mar 2021 08:59:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53942 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231351AbhC2M66 (ORCPT ); Mon, 29 Mar 2021 08:58:58 -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 3AAF4C061574; Mon, 29 Mar 2021 05:58:58 -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 B7FFBC72850; Mon, 29 Mar 2021 14:58:56 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1617022737; bh=3V0b4SCPnnWF6a37Pjdefw3jHvD9jBFTlXeOrslYX1M=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=t9e+JKGHjn4id5yvgc+H/ooQfmz4YeWRLDC9cPjtK0XBbI27iHmYwSvx6Z0l2tgvK TKSgPNmSsePH6yzahM1G2bxSPCr+SBeX28wHWMPjsmVdPo8glaG7jzsgPKaXvW1iJz loY3D+U+vEqAD9yHRwkhmAA4HT9G3IUwYNNC5xf0= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Sven Van Asbroeck , u.kleine-koenig@pengutronix.de, linux-kernel@vger.kernel.org, Clemens Gruber Subject: [PATCH v6 4/7] pwm: pca9685: Support staggered output ON times Date: Mon, 29 Mar 2021 14:57:04 +0200 Message-Id: <20210329125707.182732-4-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210329125707.182732-1-clemens.gruber@pqgruber.com> References: <20210329125707.182732-1-clemens.gruber@pqgruber.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@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 --- Changes since v5: - Simplified staggered outputs special case in set/get_duty drivers/pwm/pwm-pca9685.c | 58 +++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 4d6684b90819..a61eafdd2335 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -78,6 +78,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,46 +94,70 @@ 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) { + 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 && 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; + } 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; + 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; } - val = 0; 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) + return off; + + /* Read ON register to calculate duty cycle of staggered output */ + val = 0; + regmap_read(pca->regmap, LED_N_ON_L(channel), &val); + on = ((on & 0xf) << 8) | (val & 0xff); + return (off - on) & (PCA9685_COUNTER_RANGE - 1); } #if IS_ENABLED(CONFIG_GPIOLIB) @@ -443,14 +468,19 @@ 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); 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 */ -- 2.31.1