From nobody Wed Dec 17 21:00:33 2025 Received: from mx07-00178001.pphosted.com (mx07-00178001.pphosted.com [185.132.182.106]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 82AB22D1922; Tue, 29 Apr 2025 12:54:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.132.182.106 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745931278; cv=none; b=iwBikyzK7VNvPaWM7+5MJw6eB1hrwJA9p0dESZmkOpTODHcsQ0vLYohibVW3DOw1fqSddpLeqAai+R2kY3CQtYe3tTAxk78zqA0WU4qMt4X4cG0C8ndmlBbU7x3l6CapwrQKr6j5sEAsgOVJXo0sWOdXkHdYCcUUNm0gHztRsKM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745931278; c=relaxed/simple; bh=sUoBXPHJCCp1rAAgoisQ7SogUzqw+lsJWMiJP7sF01o=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=hj86/9R59vY+kYgdPluUBAOLtYIBW77KactWLwkTJRDjtJRtsFQlZOlFjvMF3G4JLnTOuZ91bBYt1phzsQ2ohuJIxF0UQFjfPOlBvyIluQKHWaI5QC20lqKOTTfvoNRwW4rNjsDVRX04F0pW6etd96slm3gqlm8I20uNHT8lO1E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foss.st.com; spf=pass smtp.mailfrom=foss.st.com; dkim=pass (2048-bit key) header.d=foss.st.com header.i=@foss.st.com header.b=gpgoSvPe; arc=none smtp.client-ip=185.132.182.106 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=foss.st.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=foss.st.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=foss.st.com header.i=@foss.st.com header.b="gpgoSvPe" Received: from pps.filterd (m0288072.ppops.net [127.0.0.1]) by mx07-00178001.pphosted.com (8.18.1.2/8.18.1.2) with ESMTP id 53TB98ZF013482; Tue, 29 Apr 2025 14:54:21 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=foss.st.com; h= cc:content-transfer-encoding:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to; s=selector1; bh= QAvR8bHc7xRGQ12NxG7wqG8ta/oQ0TQJr7gEjuwlswM=; b=gpgoSvPemAGq7hwu LXd1p1LJrw48X1vgcZfrVdt6FuTYgV4yCeFlkTNgTsCmnupRfTE3CD1vpz2gKO5X ef+gcBhjS6Qq1u8HRCerkACbYWwPGKMTVkhxI9upO6AcgxdxHr9Eujumynak9+vC TOzizTyXYfpypk8T46fiBpbzAjAqmKLY/bS97ubPn4oXUF+sJWRczG2j5xGnffeb a70nZiMRbVRN7y/znCoN/u9ixfHsZRY+RF0jVoIonvMzy7O7KJNL7b/+iIt2ZAs6 f0vwvDVtV3Li7yipAjoDPnahoKSaSMRZg3WMrf22H2JCnuFIsmOPqc7eQYrM0W1V 5zvXTA== Received: from beta.dmz-ap.st.com (beta.dmz-ap.st.com [138.198.100.35]) by mx07-00178001.pphosted.com (PPS) with ESMTPS id 468mwm3pda-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Tue, 29 Apr 2025 14:54:21 +0200 (MEST) Received: from euls16034.sgp.st.com (euls16034.sgp.st.com [10.75.44.20]) by beta.dmz-ap.st.com (STMicroelectronics) with ESMTP id 630454004D; Tue, 29 Apr 2025 14:53:12 +0200 (CEST) Received: from Webmail-eu.st.com (eqndag1node4.st.com [10.75.129.133]) by euls16034.sgp.st.com (STMicroelectronics) with ESMTP id 648E6A7751D; Tue, 29 Apr 2025 14:51:49 +0200 (CEST) Received: from SAFDAG1NODE1.st.com (10.75.90.17) by EQNDAG1NODE4.st.com (10.75.129.133) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.39; Tue, 29 Apr 2025 14:51:49 +0200 Received: from localhost (10.252.5.160) by SAFDAG1NODE1.st.com (10.75.90.17) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.39; Tue, 29 Apr 2025 14:51:48 +0200 From: Fabrice Gasnier To: , , , CC: , , , , , , , , , , , , , , , Subject: [PATCH v6 4/7] pwm: stm32-lp: add support for stm32mp25 Date: Tue, 29 Apr 2025 14:51:30 +0200 Message-ID: <20250429125133.1574167-5-fabrice.gasnier@foss.st.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250429125133.1574167-1-fabrice.gasnier@foss.st.com> References: <20250429125133.1574167-1-fabrice.gasnier@foss.st.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: EQNCAS1NODE4.st.com (10.75.129.82) To SAFDAG1NODE1.st.com (10.75.90.17) X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1099,Hydra:6.0.736,FMLib:17.12.80.40 definitions=2025-04-29_04,2025-04-24_02,2025-02-21_01 Add support for STM32MP25 SoC. A new compatible has been added to the dt-bindings. It represents handle new features, registers and bits diversity. It isn't used currently in the driver, as matching is done by retrieving MFD parent data. New dedicated capture/compare channels has been added: e.g. a new compare register for channel 2. Some controls (polarity / cc channel enable) are handled in CCMR register on this new variant (instead of wavepol bit). So, Low-power timer can now have up to two PWM outputs. Use device data from the MFD parent to configure the number of PWM channels e.g. 'npwm'. Update current get_state() and apply() ops to support either: - one PWM channel (as on older revision, or LPTIM5 on STM32MP25) - two PWM channels (e.g. LPTIM1/2/3/4 on STM32MP25 that has the full feature set) Introduce new routines to manage common prescaler, reload register and global enable bit. Acked-by: Uwe Kleine-K=C3=B6nig Signed-off-by: Fabrice Gasnier --- Changes in V5: - Add Uwe's Acked-by Changes in V2: - rely on fallback compatible as no specific .data is associated to the driver. Matching is achieved by using MFD parent data. - renamed registers/bits defintions --- drivers/pwm/pwm-stm32-lp.c | 219 ++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 26 deletions(-) diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c index 5832dce8ed9d..4789eafb8bac 100644 --- a/drivers/pwm/pwm-stm32-lp.c +++ b/drivers/pwm/pwm-stm32-lp.c @@ -20,6 +20,7 @@ struct stm32_pwm_lp { struct clk *clk; struct regmap *regmap; + unsigned int num_cc_chans; }; =20 static inline struct stm32_pwm_lp *to_stm32_pwm_lp(struct pwm_chip *chip) @@ -30,13 +31,101 @@ static inline struct stm32_pwm_lp *to_stm32_pwm_lp(str= uct pwm_chip *chip) /* STM32 Low-Power Timer is preceded by a configurable power-of-2 prescale= r */ #define STM32_LPTIM_MAX_PRESCALER 128 =20 +static int stm32_pwm_lp_update_allowed(struct stm32_pwm_lp *priv, int chan= nel) +{ + int ret; + u32 ccmr1; + unsigned long ccmr; + + /* Only one PWM on this LPTIMER: enable, prescaler and reload value can b= e changed */ + if (!priv->num_cc_chans) + return true; + + ret =3D regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (ret) + return ret; + ccmr =3D ccmr1 & (STM32_LPTIM_CC1E | STM32_LPTIM_CC2E); + + /* More than one channel enabled: enable, prescaler or ARR value can't be= changed */ + if (bitmap_weight(&ccmr, sizeof(u32) * BITS_PER_BYTE) > 1) + return false; + + /* + * Only one channel is enabled (or none): check status on the other chann= el, to + * report if enable, prescaler or ARR value can be changed. + */ + if (channel) + return !(ccmr1 & STM32_LPTIM_CC1E); + else + return !(ccmr1 & STM32_LPTIM_CC2E); +} + +static int stm32_pwm_lp_compare_channel_apply(struct stm32_pwm_lp *priv, i= nt channel, + bool enable, enum pwm_polarity polarity) +{ + u32 ccmr1, val, mask; + bool reenable; + int ret; + + /* No dedicated CC channel: nothing to do */ + if (!priv->num_cc_chans) + return 0; + + ret =3D regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (ret) + return ret; + + if (channel) { + /* Must disable CC channel (CCxE) to modify polarity (CCxP), then re-ena= ble */ + reenable =3D (enable && FIELD_GET(STM32_LPTIM_CC2E, ccmr1)) && + (polarity !=3D FIELD_GET(STM32_LPTIM_CC2P, ccmr1)); + + mask =3D STM32_LPTIM_CC2SEL | STM32_LPTIM_CC2E | STM32_LPTIM_CC2P; + val =3D FIELD_PREP(STM32_LPTIM_CC2P, polarity); + val |=3D FIELD_PREP(STM32_LPTIM_CC2E, enable); + } else { + reenable =3D (enable && FIELD_GET(STM32_LPTIM_CC1E, ccmr1)) && + (polarity !=3D FIELD_GET(STM32_LPTIM_CC1P, ccmr1)); + + mask =3D STM32_LPTIM_CC1SEL | STM32_LPTIM_CC1E | STM32_LPTIM_CC1P; + val =3D FIELD_PREP(STM32_LPTIM_CC1P, polarity); + val |=3D FIELD_PREP(STM32_LPTIM_CC1E, enable); + } + + if (reenable) { + u32 cfgr, presc; + unsigned long rate; + unsigned int delay_us; + + ret =3D regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1, + channel ? STM32_LPTIM_CC2E : STM32_LPTIM_CC1E, 0); + if (ret) + return ret; + /* + * After a write to the LPTIM_CCMRx register, a new write operation can = only be + * performed after a delay of at least (PRESC =C3=97 3) clock cycles + */ + ret =3D regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); + if (ret) + return ret; + presc =3D FIELD_GET(STM32_LPTIM_PRESC, cfgr); + rate =3D clk_get_rate(priv->clk) >> presc; + if (!rate) + return -EINVAL; + delay_us =3D 3 * DIV_ROUND_UP(USEC_PER_SEC, rate); + usleep_range(delay_us, delay_us * 2); + } + + return regmap_update_bits(priv->regmap, STM32_LPTIM_CCMR1, mask, val); +} + static int stm32_pwm_lp_apply(struct pwm_chip *chip, struct pwm_device *pw= m, const struct pwm_state *state) { struct stm32_pwm_lp *priv =3D to_stm32_pwm_lp(chip); unsigned long long prd, div, dty; struct pwm_state cstate; - u32 val, mask, cfgr, presc =3D 0; + u32 arr, val, mask, cfgr, presc =3D 0; bool reenable; int ret; =20 @@ -45,10 +134,28 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, s= truct pwm_device *pwm, =20 if (!state->enabled) { if (cstate.enabled) { - /* Disable LP timer */ - ret =3D regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + /* Disable CC channel if any */ + ret =3D stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, false, + state->polarity); if (ret) return ret; + ret =3D regmap_write(priv->regmap, pwm->hwpwm ? + STM32_LPTIM_CCR2 : STM32_LPTIM_CMP, 0); + if (ret) + return ret; + + /* Check if the timer can be disabled */ + ret =3D stm32_pwm_lp_update_allowed(priv, pwm->hwpwm); + if (ret < 0) + return ret; + + if (ret) { + /* Disable LP timer */ + ret =3D regmap_write(priv->regmap, STM32_LPTIM_CR, 0); + if (ret) + return ret; + } + /* disable clock to PWM counter */ clk_disable(priv->clk); } @@ -79,6 +186,23 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, st= ruct pwm_device *pwm, dty =3D prd * state->duty_cycle; do_div(dty, state->period); =20 + ret =3D regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); + if (ret) + return ret; + + /* + * When there are several channels, they share the same prescaler and rel= oad value. + * Check if this can be changed, or the values are the same for all chann= els. + */ + if (!stm32_pwm_lp_update_allowed(priv, pwm->hwpwm)) { + ret =3D regmap_read(priv->regmap, STM32_LPTIM_ARR, &arr); + if (ret) + return ret; + + if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) !=3D presc) || (arr !=3D prd - 1= )) + return -EBUSY; + } + if (!cstate.enabled) { /* enable clock to drive PWM counter */ ret =3D clk_enable(priv->clk); @@ -86,15 +210,20 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, s= truct pwm_device *pwm, return ret; } =20 - ret =3D regmap_read(priv->regmap, STM32_LPTIM_CFGR, &cfgr); - if (ret) - goto err; - if ((FIELD_GET(STM32_LPTIM_PRESC, cfgr) !=3D presc) || - (FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) !=3D state->polarity)) { + ((FIELD_GET(STM32_LPTIM_WAVPOL, cfgr) !=3D state->polarity) && !priv-= >num_cc_chans)) { val =3D FIELD_PREP(STM32_LPTIM_PRESC, presc); - val |=3D FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity); - mask =3D STM32_LPTIM_PRESC | STM32_LPTIM_WAVPOL; + mask =3D STM32_LPTIM_PRESC; + + if (!priv->num_cc_chans) { + /* + * WAVPOL bit is only available when no capature compare channel is use= d, + * e.g. on LPTIMER instances that have only one output channel. CCMR1 is + * used otherwise. + */ + val |=3D FIELD_PREP(STM32_LPTIM_WAVPOL, state->polarity); + mask |=3D STM32_LPTIM_WAVPOL; + } =20 /* Must disable LP timer to modify CFGR */ reenable =3D true; @@ -120,20 +249,27 @@ static int stm32_pwm_lp_apply(struct pwm_chip *chip, = struct pwm_device *pwm, if (ret) goto err; =20 - ret =3D regmap_write(priv->regmap, STM32_LPTIM_CMP, prd - (1 + dty)); + /* Write CMP/CCRx register and ensure it's been properly written */ + ret =3D regmap_write(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_= LPTIM_CMP, + prd - (1 + dty)); if (ret) goto err; =20 - /* ensure CMP & ARR registers are properly written */ - ret =3D regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, + /* ensure ARR and CMP/CCRx registers are properly written */ + ret =3D regmap_read_poll_timeout(priv->regmap, STM32_LPTIM_ISR, val, pwm-= >hwpwm ? + (val & STM32_LPTIM_CMP2_ARROK) =3D=3D STM32_LPTIM_CMP2_ARROK : (val & STM32_LPTIM_CMPOK_ARROK) =3D=3D STM32_LPTIM_CMPOK_ARROK, 100, 1000); if (ret) { dev_err(pwmchip_parent(chip), "ARR/CMP registers write issue\n"); goto err; } - ret =3D regmap_write(priv->regmap, STM32_LPTIM_ICR, - STM32_LPTIM_CMPOKCF_ARROKCF); + ret =3D regmap_write(priv->regmap, STM32_LPTIM_ICR, pwm->hwpwm ? + STM32_LPTIM_CMP2OKCF_ARROKCF : STM32_LPTIM_CMPOKCF_ARROKCF); + if (ret) + goto err; + + ret =3D stm32_pwm_lp_compare_channel_apply(priv, pwm->hwpwm, true, state-= >polarity); if (ret) goto err; =20 @@ -161,11 +297,22 @@ static int stm32_pwm_lp_get_state(struct pwm_chip *ch= ip, { struct stm32_pwm_lp *priv =3D to_stm32_pwm_lp(chip); unsigned long rate =3D clk_get_rate(priv->clk); - u32 val, presc, prd; + u32 val, presc, prd, ccmr1; + bool enabled; u64 tmp; =20 regmap_read(priv->regmap, STM32_LPTIM_CR, &val); - state->enabled =3D !!FIELD_GET(STM32_LPTIM_ENABLE, val); + enabled =3D !!FIELD_GET(STM32_LPTIM_ENABLE, val); + if (priv->num_cc_chans) { + /* There's a CC chan, need to also check if it's enabled */ + regmap_read(priv->regmap, STM32_LPTIM_CCMR1, &ccmr1); + if (pwm->hwpwm) + enabled &=3D !!FIELD_GET(STM32_LPTIM_CC2E, ccmr1); + else + enabled &=3D !!FIELD_GET(STM32_LPTIM_CC1E, ccmr1); + } + state->enabled =3D enabled; + /* Keep PWM counter clock refcount in sync with PWM initial state */ if (state->enabled) { int ret =3D clk_enable(priv->clk); @@ -176,14 +323,21 @@ static int stm32_pwm_lp_get_state(struct pwm_chip *ch= ip, =20 regmap_read(priv->regmap, STM32_LPTIM_CFGR, &val); presc =3D FIELD_GET(STM32_LPTIM_PRESC, val); - state->polarity =3D FIELD_GET(STM32_LPTIM_WAVPOL, val); + if (priv->num_cc_chans) { + if (pwm->hwpwm) + state->polarity =3D FIELD_GET(STM32_LPTIM_CC2P, ccmr1); + else + state->polarity =3D FIELD_GET(STM32_LPTIM_CC1P, ccmr1); + } else { + state->polarity =3D FIELD_GET(STM32_LPTIM_WAVPOL, val); + } =20 regmap_read(priv->regmap, STM32_LPTIM_ARR, &prd); tmp =3D prd + 1; tmp =3D (tmp << presc) * NSEC_PER_SEC; state->period =3D DIV_ROUND_CLOSEST_ULL(tmp, rate); =20 - regmap_read(priv->regmap, STM32_LPTIM_CMP, &val); + regmap_read(priv->regmap, pwm->hwpwm ? STM32_LPTIM_CCR2 : STM32_LPTIM_CMP= , &val); tmp =3D prd - val; tmp =3D (tmp << presc) * NSEC_PER_SEC; state->duty_cycle =3D DIV_ROUND_CLOSEST_ULL(tmp, rate); @@ -201,15 +355,25 @@ static int stm32_pwm_lp_probe(struct platform_device = *pdev) struct stm32_lptimer *ddata =3D dev_get_drvdata(pdev->dev.parent); struct stm32_pwm_lp *priv; struct pwm_chip *chip; + unsigned int npwm; int ret; =20 - chip =3D devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*priv)); + if (!ddata->num_cc_chans) { + /* No dedicated CC channel, so there's only one PWM channel */ + npwm =3D 1; + } else { + /* There are dedicated CC channels, each with one PWM output */ + npwm =3D ddata->num_cc_chans; + } + + chip =3D devm_pwmchip_alloc(&pdev->dev, npwm, sizeof(*priv)); if (IS_ERR(chip)) return PTR_ERR(chip); priv =3D to_stm32_pwm_lp(chip); =20 priv->regmap =3D ddata->regmap; priv->clk =3D ddata->clk; + priv->num_cc_chans =3D ddata->num_cc_chans; chip->ops =3D &stm32_pwm_lp_ops; =20 ret =3D devm_pwmchip_add(&pdev->dev, chip); @@ -225,12 +389,15 @@ static int stm32_pwm_lp_suspend(struct device *dev) { struct pwm_chip *chip =3D dev_get_drvdata(dev); struct pwm_state state; - - pwm_get_state(&chip->pwms[0], &state); - if (state.enabled) { - dev_err(dev, "The consumer didn't stop us (%s)\n", - chip->pwms[0].label); - return -EBUSY; + unsigned int i; + + for (i =3D 0; i < chip->npwm; i++) { + pwm_get_state(&chip->pwms[i], &state); + if (state.enabled) { + dev_err(dev, "The consumer didn't stop us (%s)\n", + chip->pwms[i].label); + return -EBUSY; + } } =20 return pinctrl_pm_select_sleep_state(dev); --=20 2.25.1