From nobody Fri Oct 2 04:46:28 2026 Received: from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com [205.220.180.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 3140F3D6463 for ; Wed, 5 Aug 2026 06:07:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=205.220.180.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785910053; cv=none; b=V0o+HYtSzvsZXnPmLVBlLmWmc/z6N6BeDbsG0Jp+q2baOZ6beE2V7CFWRTqVY3zQ1K5c51brKsyP64s7tN4dexmWg1UV7TKwzYLRJK+w8qPgGTOV9RJV2Syde48gQm4+p/d8XJXX1yYXWfGje4bOW6r5KoGe7LIG76onsLBkEWc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785910053; c=relaxed/simple; bh=v5A35AsIX1FU9PnlCaxEuScG//lY3QWfODA5E4NAydQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=bem+XbkBIgb6B5egMJxwwj5e6duqAKDVupJj5Nbft1/qILNK8jgaa8tZF3bpEJw1Hr4JcfaWffdgsAzyseGJ3FhEzSbPutjUmJ5FAFdDPRCW2TH8qk6LH5Z361vmrVJ5lppL0ccCi4Awn7RG/Bsrqwa1+IH+FaNaCMttXqpFrvo= 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=UA4aliFH; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b=ZCA37vZm; arc=none smtp.client-ip=205.220.180.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="UA4aliFH"; dkim=pass (2048-bit key) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com header.b="ZCA37vZm" Received: from pps.filterd (m0279873.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 6755l8ri1960870 for ; Wed, 5 Aug 2026 06: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=P20Xb1E7kJMyaj3ABhnnsY mgv+Ve8QuEQi4ULh8hl84=; b=UA4aliFHNojrNRQcRdzQ8JqdAmbWz0DNuWcn/n 3+5dcarVFUNXSVNTBLMk4O/OmH6znvrVntLpucAVCLfAY7Uy1mxyCOI/nxqLuQXR h+U/V3hIh4gAzVBKaqTUHvqzuTsDBOoMLOLA8gyys5e7Bt9eLgkspSQUWTWgpMrR JZfRCiI8AV3hK0WLu6hhtIiGMjbfeGXrG/z8/IydywYMgvN6CFNf6AWpLSe34ouI cg5Lq9pSA2/07LuZ0IyLGHW/uBGWkHvWVZZsPVrbDtBmGqCc9rV9h038OQkIQQcq 5tQU0WHrwyDd4PYtmi+44uihSyNeZnVHuv0BJ/62Pt1s52xA== Received: from mail-pg1-f198.google.com (mail-pg1-f198.google.com [209.85.215.198]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4fujwskbn8-1 (version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT) for ; Wed, 05 Aug 2026 06:07:30 +0000 (GMT) Received: by mail-pg1-f198.google.com with SMTP id 41be03b00d2f7-cb48f41343aso1417199a12.1 for ; Tue, 04 Aug 2026 23:07:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=oss.qualcomm.com; s=google; t=1785910050; x=1786514850; 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=P20Xb1E7kJMyaj3ABhnnsYmgv+Ve8QuEQi4ULh8hl84=; b=ZCA37vZmkAa0iJGNuMOZDDnpOAG0gQkkyxXAxwGmHGLzTqLLVB+VONU2VqV1BKHzml 9pZGmb7usksULNXT8xEx+2hIpRotECNm1N1TeO8HGu8B2+XhFd0EDG4Yd4uf17MGxaj2 4SwUs8MJZZ/qAR82ucMGNmHEEjlSo32lgul+hiTimlVJIanhtZLTqAXEH6BxvPqB9nEB irCgIgxs54ynSqPpnDhXgyCJGqKkfVnvFaOHI1qmwry1RflxhPbhQ7cD91QIy8Iex6LM MPf/o2Fnqx8AU9Herwe1TVsNwMlETSfHKkIwV5zAH3F/ijv3YX065TCVK02qNzwD9ON9 x8Mg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785910050; x=1786514850; 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=P20Xb1E7kJMyaj3ABhnnsYmgv+Ve8QuEQi4ULh8hl84=; b=aF6CQlhw7vX+cdM/wgVld7a4YSwZKWD3Vd5hcIy+lcctV1QPXgKBW0LCLdt5igtZDo JpdxuFWJQn0P09zLw9OLDiFRZL34EZoYKjxp1cpuVhmWmupM3EFVk9zQvd67VrWEE8OP IpfY3KSNWC5cxvtblhaXBesFPPmP1RFvwQOgzcDmpgrBm9cyXSLd1zoco2VUDlNVby9t UEIyK33I+3vLLqpCZhkMUPDOOi2vTBZEu+ep0gqEMbXA13Ta6LHeybnbm4S3Gfrrxzvt iQBchanHeknovU5PEZa+RRlcdEQvueSj4UFtCM3Jhv/xbWaRulk5UviRWQeE0gcXeHDV aHyg== X-Forwarded-Encrypted: i=1; AHgh+RqGXhtxXU9KDbkIMc2dSYbdwM9qghjWn64G3uf5aabbe1XRxlg7J2D41q50hBi1+t9L7m88hifkO0kYQGI=@vger.kernel.org X-Gm-Message-State: AOJu0YyZFhAEwzsRzc3NSpBIP8hUb0OLfiCSBiJduJppo0o1IUEGdhO0 6OlDDFuJAOn7obPG/iuJqHnGolPYv98jdrcdkRCSVIxkylutpHZZnmTsc57L5h8gmHJUiYeNo2T bkbDps8NuJba0Hv0tHAd61dZG19f03H+fNgtHCcdjxf9wDQJKwyXzX9U0I3EtqzC0YgA= X-Gm-Gg: AR+sD10q2M+saFY7HC7SXLKYXrAQqXMH3NbdayWctb7d4h7O3S0irDKw/rUJM6qJhV2 hokFY1UeI8WU26ChntbEEF9WwxbAH6ek2YnPAb+6zd4RbNJCXKkqiFXFUUhiRi1ItdGB0MdlHsx V3Hw9CjEU6seNe31qM1kDB3J+64PywSRrrWvriX9L3PV71RPJeRYQFYqegQicV4h0hNVDM0iSwk dJWqeiiz3xeBv6ir5FMNuogm2GOfPsrPzxCPxdrLVYoyHfJp2XRuH6HHIdUByD+HMzgxiyiXPoG hUzkFL7fLfWGhA9NRjl+xkrlcyjPTfHieSA+n0UiB363dWfHYBBhwWXH3vVUMBMPY0xaO5T0fKW QRF5aHw7zjNJ2gdCANBC1q8jDsRH4OPiSZcC/0MydikXI+OpsfN1o X-Received: by 2002:a05:6a21:6e4a:b0:3b4:a0c0:6cb7 with SMTP id adf61e73a8af0-3cb859365c2mr5077727637.11.1785910049592; Tue, 04 Aug 2026 23:07:29 -0700 (PDT) X-Received: by 2002:a05:6a21:6e4a:b0:3b4:a0c0:6cb7 with SMTP id adf61e73a8af0-3cb859365c2mr5077624637.11.1785910048647; Tue, 04 Aug 2026 23:07:28 -0700 (PDT) Received: from hu-fenglinw-lv.qualcomm.com (Global_NAT1.qualcomm.com. [129.46.96.20]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-cbe7092b0ddsm735861a12.30.2026.08.04.23.07.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 23:07:28 -0700 (PDT) From: Fenglin Wu Date: Tue, 04 Aug 2026 23:07:26 -0700 Subject: [PATCH v2] 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: <20260804-lpg-pwm-fm-support-v2-1-bc4d1041eeda@oss.qualcomm.com> X-B4-Tracking: v=1; b=H4sIAB3TcmoC/22QzW6DMBCEXwX5XEf+AYM59T2qHPyzJq4AOzbQV hHvXkMi9VKtZGmk8Xw7+0AZkoeM+uqBEmw++zAXwd4qZG5qHgB7WzRihAnSsg6PccDxa8Juwnm NMaQFN46oWnBhuTWofIwJnP8+Qz+uT51X/QlmOZJejgT3tdCWp+0PVtgHSpDmRKVBYxPGkLBWo 5oN4BKNO1ZzIWqptOP9Jo5IrTIU5zT5pa9oKxtnmdXQ1eBqyyXXQiqimeKat9R1lGgLDTrWu/m 8hPRznmCj5zavtvK/thvFZUpZ0zIlJaPvIefLfVXjQb+UB133ff8Fi9buf1oBAAA= 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=1785910047; l=11834; i=fenglin.wu@oss.qualcomm.com; s=20260324; h=from:subject:message-id; bh=v5A35AsIX1FU9PnlCaxEuScG//lY3QWfODA5E4NAydQ=; b=ibviDp7swkrVgIvu07f5kv6ICqf75PzjRVaXUBWWdtv8hKtpK5lrGke6scHtFBiYVKQkoD4TJ i2xeLjuR5vnCy6Ye4VqJGe/f1sWWKBdD4PvDx40AVg2vdJ+6s8SpxDb X-Developer-Key: i=fenglin.wu@oss.qualcomm.com; a=ed25519; pk=hJdt3E7o54lql+miD2GaxwF74cDyhgNwMbmFOZ46bRU= X-Proofpoint-GUID: TyBLErkmn-DswwJdAaPMiLYVDm2fCaRX X-Proofpoint-ORIG-GUID: TyBLErkmn-DswwJdAaPMiLYVDm2fCaRX X-Proofpoint-Spam-Info: AW1haW4tMjYwODA1MDA0NSBTYWx0ZWRfX2D5jUHNk+r1i WS1I0ZcZ4hF3aK22YRgYv7sYui+LEuQ0hKn1pGVyn0p4X4IhdZNi8AtckT4DoYv/hdT34vzplmL oXSNdOKI/V3UCiHTVqsOSXmf0NuuL2w= X-Authority-Analysis: v=2.4 cv=Lu6iDHdc c=1 sm=1 tr=0 ts=6a72d322 cx=c_pps a=Qgeoaf8Lrialg5Z894R3/Q==:117 a=ouPCqIW2jiPt+lZRy3xVPw==:17 a=IkcTkHD0fZMA:10 a=Sv0fKeRqtYgA:10 a=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22 a=rJkE3RaqiGZ5pbrm-msn:22 a=VwQbUJbxAAAA:8 a=EUspDBNiAAAA:8 a=bC-a23v3AAAA:8 a=KiLQHq0dUJNUFEj8lKYA:9 a=QEXdDO2ut3YA:10 a=x9snwWr2DeNwDh03kgHS:22 a=FO4_E8m0qiDe52t0p3_H:22 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwODA1MDA0NSBTYWx0ZWRfX50GU6n115sjV 93h7/HwMVDDJFKqEQd6rd55d707KH8aLjmbvT05GbtuO+aLB+gpnKT9I6HChWXERZK4+c0Ok3/u y3mAPP78Ns0bBVTGZaZMLW0g0Vt6Ss1PctLyAL6lYg3HhdsbXj6DAXnL7SyKOLTkzJmHV5YniHd nCt7NIkrHf1y/WdZWGUulGDjMW9w8yAx7voPBF/lRJJnLprS+65QFxH++o/h52V27WG2ex8Otu1 GxLo925RcS/P46L0Ok7EjzCnWtIuPS8qCSmiIPKLsRzTw3EyTKCheFAjLi7uHGUXQy3SQT7y++y NjUEFV3kfPqWtRAzxQmzNsLy9Is3hlPOeBBxbcokodU6qGHS3o5jIHQu3OFFa8vYxIH2i+62Geu UVTcJP4cAlDDPg+6oTwJbdm9WNATr0ydYhpNFpZJWfxl6JvHsSNJHxPtwYN4rCK88x1fu/8FSbD cH0kxT60NcOVXKXbnbQ== 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-05_02,2026-08-04_02,2025-10-01_01 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 phishscore=0 adultscore=0 impostorscore=0 malwarescore=0 spamscore=0 priorityscore=1501 clxscore=1015 bulkscore=0 lowpriorityscore=0 suspectscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound adjust=0 reason=mlx scancount=1 engine=8.22.0-2606150000 definitions=main-2608050045 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. 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 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 | 199 +++++++++++++++++++++++++++++++++++= ---- 1 file changed, 183 insertions(+), 16 deletions(-) diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-= lpg.c index 24b1f570f524..ab5b64b17afb 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,9 @@ #define PWM_SEC_ACCESS_REG 0xd0 #define PWM_DTEST_REG(x) (0xe2 + (x) - 1) =20 +#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 +115,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 +153,9 @@ struct lpg_channel { =20 unsigned int pattern_lo_idx; unsigned int pattern_hi_idx; + + bool fm_capable; + bool use_fm; }; =20 /** @@ -238,11 +248,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 /** @@ -431,10 +443,105 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned in= t mask) =20 static const unsigned int lpg_clk_rates[] =3D {0, 1024, 32768, 19200000}; static const unsigned int lpg_clk_rates_hi_res[] =3D {0, 1024, 32768, 1920= 0000, 76800000}; +static const unsigned int lpg_clk_period_ns[] =3D {0, 976562, 30517, 52}; 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) +{ + const unsigned int *clk_rate_arr, *clk_period_arr; + 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; + bool found =3D false; + + if (chan->subtype !=3D LPG_SUBTYPE_PWM) { + dev_err(chan->lpg->dev, "Only SUBTYPE_PWM support frequency mode\n"); + return -EOPNOTSUPP; + } + + clk_rate_arr =3D lpg_clk_rates; + clk_len =3D ARRAY_SIZE(lpg_clk_rates); + clk_period_arr =3D lpg_clk_period_ns; + + /* + * Formula (rearranged to solve for pwm_value_lsb): + * + * period_ns + * pwm_value_lsb =3D ------------------------------- - 1 + * 2 * (2^pwm_exp) * clk_period_ns + * + * 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 period_ns; + lsb_tmp =3D div64_u64(lsb_tmp, clk_period_arr[clk]); + 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 clk_period_arr[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_err(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; + + 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 clk_period_arr[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, clk_rate_arr[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 +909,23 @@ 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; + + if (!chan->fm_capable) + return; + + regmap_write(lpg->map, chan->base + PWM_FM_MODE_REG, + chan->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 +1437,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,8 +1480,10 @@ 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; @@ -1360,7 +1495,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, s= truct pwm_device *pwm, 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 +1503,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 +1520,32 @@ 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) { + if (clk_idx >=3D ARRAY_SIZE(lpg_clk_period_ns)) + return -EINVAL; + + /* + * FM period formula: + * period =3D 2 * (pwm_value_lsb + 1) * (2^exp) * clk_period_ns + */ + pwm_value &=3D 0xff; + state->period =3D (u64)2 * (pwm_value + 1) * (1 << m) * + lpg_clk_period_ns[clk_idx]; + 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 +1719,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 +1732,13 @@ 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 (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 +2062,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