From nobody Sun Sep 27 00:40:21 2026 Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) (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 A5C9F2D7BF for ; Fri, 28 Aug 2026 02:07:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787882854; cv=none; b=KhTYeaetP+H8sDMCpcGQTu//wNe0zJCFqlPamgFG8WdD58ZGt0yxrnSYYKRjJLpBPf07Od2k1oWjd8OkUpny42pCTqlGEzTHt1JFxB00BF651Ptgi7u2svFHHc7QsNzYyBBNoFdlQivttEhNmYeRyDpTxIJYJ24C+C0GpdlhvWc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787882854; c=relaxed/simple; bh=jQeszfyCJomcExvHKytY2Ovxm3FNHZMnkqnNU5UTBTk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=hYvbGAizeAu+nPn+T6EJ0BcEXv14Us+E2f2eGZcV26s4qhaFX/DuHBVeZzqpObN6x0DXonqX+XUEg34ibsJTB3zGHym4tBP4TCuxvpvW5NuRPa56Y50P6F+mCacj6u2eaRa9AV/vQWfITUaMVPJUQIcGFEL5ROggIxl54SsNdUM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com; spf=pass smtp.mailfrom=oss.qualcomm.com; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b=hAj/x+jD; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=imtP5F2T; arc=none smtp.client-ip=205.220.168.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=oss.qualcomm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=qualcomm.com header.i=@qualcomm.com header.b="hAj/x+jD"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="imtP5F2T" Received: from pps.filterd (m0279866.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 67S21mVb974474 for ; Fri, 28 Aug 2026 02:07:31 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h= cc:content-transfer-encoding:content-type:date:from:message-id :mime-version:subject:to; s=qcppdkim1; bh=U2W7usSLOkQrYBFd/rybpv Iuwsk/lZ9gf+4UZ0R1Ctw=; b=hAj/x+jDywP+xyFT860j1c2+K6LlGGzgsCMoNO 1ZhkVyUF8j+73xsx1ENXtmw9ln6nxX9MTOgisB9mAdPzmjxJ5RprjcWsupByxzls pISOMwXIU6oxUnFTOSzWBph9gIfGJiJZJdQfoZV8iRZGLuC13a6TgOplPN41BuUV 3hyNR2Hwjkx6FmmxZi0Xdov2zkqrKz4oi4qrqfaPjwBFRBj72rKJuFFLT45+aikf ICo8JGAOBNz8hQCaygbSG3jiMTZkwwktpeHbR+XteupPdaQeGiJM1WmHjjlTwRLL 0KeJ5MFAvNadIU9LuZ4f4TYbDmpzkpivk41WCXiJSETBB4nw== Received: from mail-oa1-f71.google.com (mail-oa1-f71.google.com [209.85.160.71]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4game7upmr-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Fri, 28 Aug 2026 02:07:28 +0000 (GMT) Received: by mail-oa1-f71.google.com with SMTP id 586e51a60fabf-463ddb3b4f7so740537fac.0 for ; Thu, 27 Aug 2026 19:07:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1787882786; x=1788487586; darn=vger.kernel.org; h=cc:to:message-id:content-transfer-encoding:content-type :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=U2W7usSLOkQrYBFd/rybpvIuwsk/lZ9gf+4UZ0R1Ctw=; b=imtP5F2T/b1/stgU7JjiSOGo09RoW3XL8naAjWNf7MafObRSlmedvaoAQpTyiD6I1L /UKz+kl/aiTwELFxSJKWyHxu/vjNZvnOFH3C6lpqAlKbDPPZExO35Fcx2yX6qsQOY1nf PJpsI//jQzOgq8KdDrPVbk5ZluUHoH/gPqRfP7NSf7K+L/B8vlh0WtE43GAYGBTwLW/W DXRdC8UlOHvox7ZPJgP2ShWKuH6PlsXIE9OiobySz/oernCcQwEzgj86yggMWuk2Udte 5KRrlY4eAz9whMOKEv2oFY5KcVZ0XkU7adkv4F2s62vb14P3MTT4g9mDPwXNPv/bJVTI 4EdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787882786; x=1788487586; h=cc:to:message-id:content-transfer-encoding:content-type :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=U2W7usSLOkQrYBFd/rybpvIuwsk/lZ9gf+4UZ0R1Ctw=; b=fchKjfG2ONqB87kPwS+yqkMGyZUsfGJILF8rfyop8ElsqtaABo/Jrs2g2g8kHBFPqu wUd0NdA78hAS/11WFzZ8bAMs/v0uVsIiaKhcy+8rRCgjYBNL68vLusMpsIhftd3Ss1XX 4AbaKk4BCpFxV+/O6Vz6RnVpXin7iBTFBPyGJW9if4YxsXmOg4pqo/2gz4DSXVa8JBsd LsKbsEhKoJKutk/+OJ8M/RkOBbVGc4ejel/ugfoyB4q9zFmIfBQ2uO3VZoOhD2IueVpg Hb6U+/1Q1qMFO2/Tgr2RTa6w42itgeaRoMAUKhYkH/QsRtDUY/1lEdCA6zkcnMeujz0A 8juw== X-Forwarded-Encrypted: i=1; AHgh+RrfrfWe1rPH8m9iMlIRfFQ2h6km2BiPQRHHU2cb3Ea7np09Ndh1uOxlelmWCVv/G39x/rr0Nt311A40l8A=@vger.kernel.org X-Gm-Message-State: AFuF++lYNMxiSLX2jTctY6IBonlb3BKOqk7q53LhRhFTRp3bVBdTiPO7 VQbr59ePd+NT9ODKEkd8X0cK2Tat9QPbx2YRtI82swgRdKWT4Nh111eGqctJ8sPPp+BsW61EZcT 5zaVJD5j1auz00FdnuCkNRjtxVuaYeWbLj/yCRP/1AsrXVk0ggnWdr7L4VsKO1U1TLw0= X-Gm-Gg: AR+sD13HOScMi9+b41a3JBD8KTG4wd9ccWTl1OWRETwO0+M0SMmh6ObAju+z110sEg4 aVLtz5YA9VIxetUA30ZC1uLKafl3dIrJ8iDL/gfP9O0U9CkvUFoqqzZBbXtDOSMNr2Itf9hyDmB lTsb3678WuidWzvRS1RP4tLozv1kD7Ksel0IwjsvASJmfKwEhg5wK0BFlZc8L6FYmNK+yiMY4gf 0EvdeND0eJJs6zetFS8syEcrzNncbzfloZKQ0ApIfR5piMwBt2L599HeZPAsUbsBcd1dbk2ThKp jkUoXDxonMf5Auii/pGEH/spIHN2/UQfCgkupENFigN37Pm9BtCixV1bH7Rlfs8peqFhnICUFq9 OXSmZJU009zectfN0CjvvhPWOjGBKF0YlmFoxpCZJ9qiLEww2xQeA X-Received: by 2002:a05:6820:f112:b0:6b1:7251:4210 with SMTP id 006d021491bc7-6b1c6753cb4mr2384811eaf.22.1787882786441; Thu, 27 Aug 2026 19:06:26 -0700 (PDT) X-Received: by 2002:a05:6820:f112:b0:6b1:7251:4210 with SMTP id 006d021491bc7-6b1c6753cb4mr2384744eaf.22.1787882785921; Thu, 27 Aug 2026 19:06:25 -0700 (PDT) Received: from hu-fenglinw-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3286f80a0a5sm613290eec.11.2026.08.27.19.06.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 27 Aug 2026 19:06:25 -0700 (PDT) From: Fenglin Wu Date: Thu, 27 Aug 2026 19:06:17 -0700 Subject: [PATCH v3] leds: rgb: leds-qcom-lpg: Add PWM frequency mode support 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 Message-Id: <20260827-lpg-pwm-fm-support-v3-1-88ae5208c57b@oss.qualcomm.com> X-B4-Tracking: v=1; b=H4sIABjtkGoC/22Qy2rDMBBFf8VoXQW9LFte5T9KF3qMHBW/ItluS /C/V3YC2ZgBwYWrc+fOAyWIARJqigeKsIYUxiEL/lEge9NDCzi4rBEjTJKK1bibWjz99Nj3OC3 TNMYZl55oIbl03FmUP04RfPg9oJ9fT50W8w123kkvR4T7ktPmp+0d1hRHlCTlERVbg+3YjREb3 enBAs5oXDPBpRRKG8+bVe5IoxNkZ9+HuSlopUrvmDNQC/DCccWNVJoYprnhFfU1JcZBifb1biH NY/w7TrDSY5tXW3XWdqU4Ty5rK6aVYvQ6pnS5L7rb0y/5Oagre5NqIk5JLJOMFY4SQQGcPiFt2 /YPkMeySKQBAAA= X-Change-ID: 20260728-lpg-pwm-fm-support-5f0a4636d3dc To: linux-arm-msm@vger.kernel.org, Lee Jones , Pavel Machek Cc: David Collins , Subbaraman Narayanamurthy , Kamal Wadhwa , linux-leds@vger.kernel.org, linux-kernel@vger.kernel.org, Fenglin Wu X-Mailer: b4 0.16-dev-17187 X-Developer-Signature: v=1; a=ed25519-sha256; t=1787882784; l=12891; i=fenglin.wu@oss.qualcomm.com; s=20260324; h=from:subject:message-id; bh=jQeszfyCJomcExvHKytY2Ovxm3FNHZMnkqnNU5UTBTk=; b=TVDAkewAMhUypIIZ2DF3QSycdCE7nxavnFDCCYJZnROIEp+rKVfmIGaG2FlaUwwknMWSvYd8I FO9wW9a+oneC9+sxfzLGeFaOlMVtRrJxKWK64p66ysJlrTCfYj5jfA/ X-Developer-Key: i=fenglin.wu@oss.qualcomm.com; a=ed25519; pk=hJdt3E7o54lql+miD2GaxwF74cDyhgNwMbmFOZ46bRU= X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwODI4MDAxNSBTYWx0ZWRfX0AR+G3jo09TG HO7fR3iPROOget72BV/0hlaBuVmSrm3MjWB1TYiUEfhxoLMMyivt8zPKgjTcfSgvV+TR50CBQbM +qou6KOLMNwBxCnr9iNBDinQBsr4DgQIFk157XC0nITr02EPzWlFQG5cG6ksfuE8lXiFVX5EpKE 9jT4s4JfyADNmk+SnTkPDsl2wE3ofP4J/nG8ohssExZ8HqJKAsz72iLv6AOAFg54FS06RptK5qq ChLPnJTEC88Ia4z+SzF4RUiW+aT6EcbyJN7aipenGPOQc75MZISfcASAY9ubmSj/1tDAvmS18bY 8qNhfXN0kavR7UgWfn2eiP3m5+3Lo22OMLkzEazvxLQdNywXUKHYsgV/wPR/KsKC84VRciMG5yR BXGUb+anrT+Abwe+ClKiOYDSyaFqGZfOKEJekAOdfDNLnJnoLtruUgwu9vr8ukNfXAJpZORUKNf fWd8rJe1eXvu4WuWLnw== X-Proofpoint-GUID: 9UKJF6kRZn4CvuYZ8tqPHeVFqqCyPMOO X-Authority-Analysis: v=2.4 cv=Yrc/gYYX c=1 sm=1 tr=0 ts=6a90ed60 cx=c_pps a=CWtnpBpaoqyeOyNyJ5EW7Q==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=Sv0fKeRqtYgA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=YMgV9FUhrdKAYTUUvYB2:22 a=VwQbUJbxAAAA:8 a=EUspDBNiAAAA:8 a=bC-a23v3AAAA:8 a=lUA4vhS5x62BsxejJRMA:9 a=QEXdDO2ut3YA:10 a=vh23qwtRXIYOdz9xvnmn:22 a=FO4_E8m0qiDe52t0p3_H:22 X-Proofpoint-ORIG-GUID: 9UKJF6kRZn4CvuYZ8tqPHeVFqqCyPMOO X-Proofpoint-Spam-Info: AW1haW4tMjYwODI4MDAxNSBTYWx0ZWRfX0oMoSDBchY9D GHnAf8ieAxeTyiSw1oyE311ptMe2XuGU5TDygNZTb3KF/v2iHutkjIyL4rBjhEnz/JGRb9aI+WH /HLEZiISxPgWO6jV8qjAdzlpDgunVtc= X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.293,Aquarius:18.0.1176,Hydra:6.1.134,FMLib:17.12.100.49 definitions=2026-08-28_01,2026-08-27_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 clxscore=1015 spamscore=0 priorityscore=1501 adultscore=0 impostorscore=0 bulkscore=0 suspectscore=0 lowpriorityscore=0 malwarescore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2608280015 Certain PWM channels on a PMIC (e.g. PM8350C PWM4) support a Frequency Mode (FM) that can generate waveforms with more frequency points than the standard LPG PWM mode. The trade-off is that the duty cycle can only be fixed at 50%. Add the FM support. When the PWM channel is requested to set a duty cycle to exactly 50%, use FM mode by default as it provides a finer-grained frequency resolution in that case. Also, guard mutex lock at the top of lpg_pwm_get_state(), to make the register read atomic against the register write in lpg_pwm_apply(). Signed-off-by: Fenglin Wu --- Dependency: This change was made based on the LED color balance fix change which is under review and has not yet been merged: https://lore.kernel.org/linux-arm-msm/20260716-lpg-rgb-color-balance-fix-v6= -1-b49d51528f61@oss.qualcomm.com/ This change should be applied on top of that one. --- Changes in v3: - Fixed Sashiko review comments: 1. Instead of using an array with pre-rounded values in lpg_clk_period_ns= [], calculate the clk period by using the lpg_clk_rates[] to improve accur= acy. 2. Move the subtype check in lpg_init_channels() so the check in lpg_calc= _freq_fm() could be removed along with the error message, also change the message to a debug level when calculating the period in FM failed. 3. Check if the requsted period is out of the frequency mode support range at the beginning of lpg_calc_freq_fm(). 4. Update PWM_FM_ENABLE bit based on the channel enabling status in lpg_apply_frequency_mode(). 5. Add mutex gaurd in lpg_pwm_get_state() to protect the register read from the register write in lpg_pwm_apply(). - Link to v2: https://patch.msgid.link/20260804-lpg-pwm-fm-support-v2-1-bc4= d1041eeda@oss.qualcomm.com Changes in v2: - When assigning lsb/best_lsb value to period_actual, cast it to u64 1st th= en add 1. - Link to v1: https://patch.msgid.link/20260729-lpg-pwm-fm-support-v1-1-16d= 3c72a9921@oss.qualcomm.com --- drivers/leds/rgb/leds-qcom-lpg.c | 203 +++++++++++++++++++++++++++++++++++= +--- 1 file changed, 187 insertions(+), 16 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-= lpg.c index 24b1f570f524..2907ddb3a568 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -21,6 +21,8 @@ #define LPG_SUBTYPE_PWM 0xb #define LPG_SUBTYPE_HI_RES_PWM 0xc #define LPG_SUBTYPE_LPG_LITE 0x11 +#define PWM_STATUS1_REG 0x08 +#define PWM_FM_PRESENT BIT(0) #define LPG_PATTERN_CONFIG_REG 0x40 #define LPG_SIZE_CLK_REG 0x41 #define PWM_CLK_SELECT_MASK GENMASK(1, 0) @@ -42,6 +44,10 @@ #define PWM_SEC_ACCESS_REG 0xd0 #define PWM_DTEST_REG(x) (0xe2 + (x) - 1) =20 +/* offset 0x50: PWM_FM_MODE_REG on LPG_SUBTYPE_PWM */ +#define PWM_FM_MODE_REG 0x50 +#define PWM_FM_ENABLE BIT(7) + #define SDAM_REG_PBS_SEQ_EN 0x42 #define SDAM_PBS_TRIG_SET 0xe5 #define SDAM_PBS_TRIG_CLR 0xe6 @@ -110,6 +116,8 @@ struct lpg_data; * @ramp_hi_pause_ms: pause (in milliseconds) after iterating over pattern * @pattern_lo_idx: start index of associated pattern * @pattern_hi_idx: last index of associated pattern + * @fm_capable: hardware supports Frequency Mode + * @use_fm: set the period using Frequency Mode */ struct lpg_channel { struct lpg *lpg; @@ -146,6 +154,9 @@ struct lpg_channel { =20 unsigned int pattern_lo_idx; unsigned int pattern_hi_idx; + + bool fm_capable; + bool use_fm; }; =20 /** @@ -238,11 +249,13 @@ struct lpg { * @sdam_offset: Channel offset in LPG SDAM * @base: base address for PWM channel registers * @triled_mask: bitmask for controlling this channel in TRILED + * @fm_capable: channel hardware supports Frequency Mode */ struct lpg_channel_data { unsigned int sdam_offset; unsigned int base; u8 triled_mask; + bool fm_capable; }; =20 /** @@ -435,6 +448,101 @@ static const unsigned int lpg_pre_divs[] =3D {1, 3, 5= , 6}; static const unsigned int lpg_pwm_resolution[] =3D {6, 9}; static const unsigned int lpg_pwm_resolution_hi_res[] =3D {8, 9, 10, 11, 1= 2, 13, 14, 15}; =20 +static int lpg_calc_freq_fm(struct lpg_channel *chan, uint64_t period_ns) +{ + unsigned int best_clk =3D 0, best_exp =3D 0, best_lsb =3D 0; + unsigned int clk, exp, lsb; + unsigned int clk_len; + u64 lsb_tmp, period_actual; + u64 curr_err, last_err; + u64 min_err =3D U64_MAX; + u64 max_period_ns; + bool found =3D false; + + clk_len =3D ARRAY_SIZE(lpg_clk_rates); + + /* Check if the requested period exceeds what freuqency mode can support = */ + max_period_ns =3D div64_u64((u64)NSEC_PER_SEC * (U8_MAX + 1) * (1 << (LPG= _MAX_M + 1)), + lpg_clk_rates[1]); + if (period_ns > max_period_ns) + return -EINVAL; + + /* + * Formula (rearranged to solve for pwm_value_lsb): + * + * period_ns * clk_rate + * pwm_value_lsb =3D ------------------------------ - 1 + * 2 * (2^pwm_exp) * NSEC_PER_SEC + * + * For each (clk, exp) combination, calculate pwm_value_lsb and then + * use it to calculate the actual period. Store the combination that + * yields the closest match to the desired period. + * + */ + + for (clk =3D 1; clk < clk_len; clk++) { + last_err =3D U64_MAX; + + for (exp =3D 0; exp <=3D LPG_MAX_M; exp++) { + /* Calculate pwm_value_lsb for this (clk, exp) pair */ + lsb_tmp =3D div64_u64(period_ns * lpg_clk_rates[clk], NSEC_PER_SEC); + lsb_tmp >>=3D (exp + 1); + + if (lsb_tmp =3D=3D 0 || lsb_tmp - 1 > U8_MAX) + continue; + + lsb =3D lsb_tmp - 1; + + period_actual =3D (u64)(lsb) + 1; + period_actual <<=3D (exp + 1); + period_actual =3D div64_u64(period_actual * NSEC_PER_SEC, + lpg_clk_rates[clk]); + + curr_err =3D period_ns - period_actual; + if (curr_err < min_err) { + min_err =3D curr_err; + best_clk =3D clk; + best_exp =3D exp; + best_lsb =3D lsb; + found =3D true; + } + + if (curr_err > last_err) + break; + + last_err =3D curr_err; + } + } + + if (!found) { + dev_dbg(chan->lpg->dev, + "FM: Cannot generate period %llu ns\n", period_ns); + return -EINVAL; + } + + chan->clk_sel =3D best_clk; + chan->pre_div_exp =3D best_exp; + chan->pwm_value =3D best_lsb; + + /* pre-div and pwm-resolution don't impact frequency mode */ + chan->pre_div_sel =3D 0; + chan->pwm_resolution_sel =3D 0; + + /* Calculate actual period for reference */ + period_actual =3D (u64)(best_lsb) + 1; + period_actual <<=3D (best_exp + 1); + period_actual =3D div64_u64(period_actual * NSEC_PER_SEC, + lpg_clk_rates[best_clk]); + chan->period =3D period_actual; + + dev_dbg(chan->lpg->dev, + "Frequency mode: period=3D%llu ns -> clk=3D%u Hz (idx=3D%u), exp=3D%u, l= sb=3D%u (actual=3D%llu ns, err=3D%llu ns)\n", + period_ns, lpg_clk_rates[best_clk], best_clk, best_exp, best_lsb, + period_actual, min_err); + + return 0; +} + static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) { unsigned int i, pwm_resolution_count, best_pwm_resolution_sel =3D 0; @@ -802,11 +910,26 @@ static void lpg_apply_dtest(struct lpg_channel *chan) chan->dtest_value); } =20 +static void lpg_apply_frequency_mode(struct lpg_channel *chan) +{ + struct lpg *lpg =3D chan->lpg; + bool use_fm; + + if (!chan->fm_capable) + return; + + use_fm =3D chan->use_fm && chan->enabled; + + regmap_update_bits(lpg->map, chan->base + PWM_FM_MODE_REG, + PWM_FM_ENABLE, use_fm ? PWM_FM_ENABLE : 0); +} + static void lpg_apply(struct lpg_channel *chan) { lpg_disable_glitch(chan); lpg_apply_freq(chan); lpg_apply_pwm_value(chan); + lpg_apply_frequency_mode(chan); lpg_apply_control(chan); lpg_apply_sync(chan); if (chan->lpg->lpg_chan_sdam) @@ -1318,28 +1441,42 @@ static int lpg_pwm_apply(struct pwm_chip *chip, str= uct pwm_device *pwm, { struct lpg *lpg =3D lpg_pwm_from_chip(chip); struct lpg_channel *chan =3D &lpg->channels[pwm->hwpwm]; + bool use_fm =3D false; int ret =3D 0; =20 if (state->polarity !=3D PWM_POLARITY_NORMAL) return -EINVAL; =20 - mutex_lock(&lpg->lock); + guard(mutex)(&lpg->lock); =20 if (state->enabled) { - ret =3D lpg_calc_freq(chan, state->period); - if (ret < 0) - goto out_unlock; + use_fm =3D chan->fm_capable && (state->duty_cycle =3D=3D state->period /= 2); =20 - lpg_calc_duty(chan, state->duty_cycle); + if (use_fm) { + ret =3D lpg_calc_freq_fm(chan, state->period); + /* + * fallback to use the standard mode if frequency + * mode couldn't satisfy the requested period + */ + if (ret < 0) + use_fm =3D false; + } + + if (!use_fm) { + ret =3D lpg_calc_freq(chan, state->period); + if (ret < 0) + return ret; + + lpg_calc_duty(chan, state->duty_cycle); + } } + + chan->use_fm =3D use_fm; chan->enabled =3D state->enabled; =20 lpg_apply(chan); =20 -out_unlock: - mutex_unlock(&lpg->lock); - - return ret; + return 0; } =20 static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, @@ -1347,20 +1484,24 @@ static int lpg_pwm_get_state(struct pwm_chip *chip,= struct pwm_device *pwm, { struct lpg *lpg =3D lpg_pwm_from_chip(chip); struct lpg_channel *chan =3D &lpg->channels[pwm->hwpwm]; + bool fm_active =3D false; unsigned int resolution; unsigned int pre_div; + unsigned int clk_idx; unsigned int refclk; unsigned int val; unsigned int m; u16 pwm_value; int ret; =20 + guard(mutex)(&lpg->lock); + ret =3D regmap_read(lpg->map, chan->base + LPG_SIZE_CLK_REG, &val); if (ret) return ret; =20 if (chan->subtype =3D=3D LPG_SUBTYPE_HI_RES_PWM) { - unsigned int clk_idx =3D FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val); + clk_idx =3D FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val); =20 if (clk_idx >=3D ARRAY_SIZE(lpg_clk_rates_hi_res)) return -EINVAL; @@ -1368,7 +1509,8 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, s= truct pwm_device *pwm, refclk =3D lpg_clk_rates_hi_res[clk_idx]; resolution =3D lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK,= val)]; } else { - refclk =3D lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)]; + clk_idx =3D FIELD_GET(PWM_CLK_SELECT_MASK, val); + refclk =3D lpg_clk_rates[clk_idx]; resolution =3D lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)]; } =20 @@ -1384,9 +1526,29 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, = struct pwm_device *pwm, if (ret) return ret; =20 - state->period =3D DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * ((1 << resolution= ) - 1) * - pre_div * (1 << m), refclk); - state->duty_cycle =3D DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * p= re_div * (1 << m), refclk); + if (chan->fm_capable) { + ret =3D regmap_read(lpg->map, chan->base + PWM_FM_MODE_REG, &val); + if (ret) + return ret; + + fm_active =3D !!(val & PWM_FM_ENABLE); + } + + if (fm_active) { + /* + * FM period formula: + * period =3D 2 * (pwm_value_lsb + 1) * (2^exp) * NSEC_PER_SEC / refc= lk + */ + pwm_value &=3D 0xff; + state->period =3D DIV_ROUND_UP_ULL((u64)2 * (pwm_value + 1) * (1 << m) * + NSEC_PER_SEC, refclk); + state->duty_cycle =3D state->period / 2; + } else { + state->period =3D DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * + ((1 << resolution) - 1) * pre_div * (1 << m), refclk); + state->duty_cycle =3D DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * + pwm_value * pre_div * (1 << m), refclk); + } } else { state->period =3D 0; state->duty_cycle =3D 0; @@ -1560,7 +1722,8 @@ static int lpg_init_channels(struct lpg *lpg) { const struct lpg_data *data =3D lpg->data; struct lpg_channel *chan; - int i; + unsigned int status; + int ret, i; =20 for (i =3D 0; i < data->num_channels; i++) { chan =3D &lpg->channels[i]; @@ -1572,6 +1735,14 @@ static int lpg_init_channels(struct lpg *lpg) chan->sdam_offset =3D data->channels[i].sdam_offset; =20 regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype); + + if (chan->subtype =3D=3D LPG_SUBTYPE_PWM && data->channels[i].fm_capable= ) { + ret =3D regmap_read(lpg->map, chan->base + PWM_STATUS1_REG, &status); + if (ret < 0) + return ret; + + chan->fm_capable =3D !!(status & PWM_FM_PRESENT); + } } =20 return 0; @@ -1895,7 +2066,7 @@ static const struct lpg_data pm8350c_pwm_data =3D { { .base =3D 0xe800, .triled_mask =3D BIT(7), .sdam_offset =3D 0x48 }, { .base =3D 0xe900, .triled_mask =3D BIT(6), .sdam_offset =3D 0x56 }, { .base =3D 0xea00, .triled_mask =3D BIT(5), .sdam_offset =3D 0x64 }, - { .base =3D 0xeb00 }, + { .base =3D 0xeb00, .fm_capable =3D true }, }, }; =20 --- base-commit: 1795fd2dbe84ef4d393b69a0b2a3b371f810bde5 change-id: 20260728-lpg-pwm-fm-support-5f0a4636d3dc prerequisite-change-id: 20260605-lpg-rgb-color-balance-fix-82436649abf3:v6 prerequisite-patch-id: 931a69abc7b07f7f5b5603e8159c092d9a54703f Best regards, -- =20 Fenglin Wu