From nobody Fri Oct 3 08:53:16 2025 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F86525743E for ; Thu, 2 Oct 2025 16:17:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759421869; cv=none; b=sewo490USEjHJFRvuj0OS6H8BV6LUalOdyN4cZ731SNH8iekncLC3a9DWmzsRgntRA9uVsBYfX0g9DGPvKdzIBcVSRhhByGb9JTWMyN0ZAhBKzy7BEQkNiezQABpeL2l9cck5BQWiSP2GZcOI7MhwgM4n28xIzyAoX64htyDsss= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1759421869; c=relaxed/simple; bh=icokVYbdLQks88tTLw4ipYlurwp9cy9RRjOd1bzoanI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SE+Sdj1+ive70lMYKhtsLGO9CVk5nXKEbIDFnT4uSOYYdomrIjlmK4rpWQY6rA2n1MFQS5ovoA8j2DJ5GYeF+WnYbtdV4eKQAY9xr07jcDj2VJXVIX2uD8bEuP+N0TVY72klPAVfvc2/Yr3Poyu3wa/IYv+FRYj5NkdXiXTdy6s= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=P+1Up+Fo; arc=none smtp.client-ip=209.85.128.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="P+1Up+Fo" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-46e4473d7f6so7742575e9.1 for ; Thu, 02 Oct 2025 09:17:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1759421863; x=1760026663; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=5JlE3VQEkX+NZzFUWR7UbDiukVP0x9CAeaLJi6PxvNs=; b=P+1Up+Fo20we/Ax4Ut+6vcdGe3d22jr1Xwyi4KjaqvDYdfjEOUd09ncznl6rW7oEUb etnV3QIB9vSXdNVcWh02DDZVwB6q35rbwRQhtxBLmXE5eu7ooEfIYZwgMD44v1DN7M9b Uqih3TcpwfWIJRSb3zGF7LqqAxGAMEZ26TVj5b4ylX1F5ikBwdgva83x6nSfawBZvlNO OjNRcmd1DcTjf3jJUgVj1Pa5e+INajxXWPoFWK2JrL4hjeEK/EsQJCqh/yno716ckxxV SyVp8W7prLgJXsTsPAdUe0DfW298wK1Qj5xN7f1kcR1l1OOfbF8pdUPJaqNuCyfVlMht OyPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1759421863; x=1760026663; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=5JlE3VQEkX+NZzFUWR7UbDiukVP0x9CAeaLJi6PxvNs=; b=WzMIGd90Hszy5CguyVgIdSGdbQKUhcPbRYTGAg16zOkuqyy3SjYKE/DCfta4o3LwV9 FcQz8UxuBHoBIpEogPP+cORIl3rd3DtSgs5J5Gs826wFKqZ2dGVXqAQZaA1hvfVDRw5U wujUqBhyVOH2lG5QXN9kUTDrMNCSpg0Hk5ROYHux6bWnmBWelu4lvhrBVnbYGBlv92xF huP10rUWZzZtG96PGECKrKongme/Dee9UL56CXiIyY9JkMPbLqHWYXd0jwzKogNmOK1y UiQo4EZfDEn8jKJnp0Md6xKK4uAgR/5D+5msGskUSfy8begpXVJAUWK+q8IYQooUUgRf Yc3Q== X-Forwarded-Encrypted: i=1; AJvYcCV8w5XuNq8p6HLarkXxgPtT6QuO8Qfy8OXESG02FHYKFG7izL2dKh6RXqfQg5xSdC62ykRYc8HDNsA/s5E=@vger.kernel.org X-Gm-Message-State: AOJu0YyA1cnC3RlYzRCU3RUf4KA04XSdXpuLEiiaFpT/GcEdIlaf6YiL YTn28JlsGRDZbivw6jxaMBWDKGCzvHHaaOW+v4pU2bFyawAfAKnVL3Ik X-Gm-Gg: ASbGncsghNVegE+LsUbCeihhI7BNIwDWvLibzcu2wtE9w/GESIqGkU1ais+dqIZzwev fQEpLKaqmmjZX7zqAm9r7X0TP2dYzh/K+ixAD346wD1+jzW+ySp8OpC5cdmCdiRXlNBXQXbaI2u 7kYV/o//yaRbG0HKumCW/OJ1aFwCgFMtQFiaM9FSUmO4M2IP3fs2k4Gtk7ssqR5uw95faSayFmW LeZTFz5t/E+UEdfBrKZ7zAw08F03jIwKWGDY0okLkl7wY+WC27zKXNMsRUZBimGegnhb/aA/YWL GIEnK8+BvndcWUq1iQtB6F0781VFAvjHelVQd8tM38hRw1c7CAlefiZYdm7dDVukh9jhmfnK/TS vlrKRgXfdRuS/aZIWmJ32PDKsp8Y9B6r1WKVXhiTcMklNwMonJp7gYMhI4kKtG8V4Qhox9xOSly 2WsHVh X-Google-Smtp-Source: AGHT+IEBhv9DgYZ7L9pUdQgAW3cBVEpRN3yqd8pwzgLtLqLF86430wr4u493P7SA0mJ4XERbij0yvg== X-Received: by 2002:a05:600c:c4ab:b0:46e:4abd:6515 with SMTP id 5b1f17b1804b1-46e612be3b4mr50335555e9.21.1759421862677; Thu, 02 Oct 2025 09:17:42 -0700 (PDT) Received: from iku.example.org ([2a06:5906:61b:2d00:607d:d8e6:591c:c858]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-46e5b5e4922sm58605515e9.1.2025.10.02.09.17.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Oct 2025 09:17:41 -0700 (PDT) From: Prabhakar X-Google-Original-From: Prabhakar To: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , Maarten Lankhorst , Maxime Ripard , Tomi Valkeinen , Thomas Zimmermann , David Airlie , Simona Vetter , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Geert Uytterhoeven , Michael Turquette , Stephen Boyd , Biju Das , Magnus Damm Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-renesas-soc@vger.kernel.org, linux-clk@vger.kernel.org, Prabhakar , Fabrizio Castro , Tommaso Merciai , Lad Prabhakar Subject: [PATCH v9 2/6] clk: renesas: rzv2h-cpg: Add support for DSI clocks Date: Thu, 2 Oct 2025 17:17:24 +0100 Message-ID: <20251002161728.186024-3-prabhakar.mahadev-lad.rj@bp.renesas.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251002161728.186024-1-prabhakar.mahadev-lad.rj@bp.renesas.com> References: <20251002161728.186024-1-prabhakar.mahadev-lad.rj@bp.renesas.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Lad Prabhakar Add support for PLLDSI and its post-dividers in the RZ/V2H CPG driver and export helper APIs for use by the DSI driver. Introduce per-PLL-DSI state in the CPG private structure and provide a set of helper functions that find valid PLL parameter combinations for a requested frequency. The new helpers are rzv2h_get_pll_pars(), rzv2h_get_pll_div_pars(), rzv2h_get_pll_divs_pars() and rzv2h_get_pll_dtable_pars() and they are exported in the "RZV2H_CPG" namespace for use by other consumers (notably the DSI driver). These helpers perform iterative searches over PLL parameters (M, K, P, S) and optional post-dividers and return the best match (or an exact match when possible). Move PLL/CLK related limits and parameter types into the shared include (include/linux/clk/renesas.h) by adding struct rzv2h_pll_limits, struct rzv2h_pll_pars and struct rzv2h_pll_div_pars plus the RZV2H_CPG_PLL_DSI_LIMITS() helper macro to define DSI PLL limits. This change centralises the PLLDSI algorithms so the CPG and DSI drivers compute PLL parameters consistently and allows the DSI driver to accurately request rates and program its PLL. Co-developed-by: Fabrizio Castro Signed-off-by: Fabrizio Castro Signed-off-by: Lad Prabhakar --- v8->v9: - Dropped `renesas-rzv2h-cpg-pll.h` header and merged into `renesas.h` - Exported the symbols for PLL calculation apis - Updated commit message - Dropped reviewed-by tags due to above changes v7->v8: - Dropped rzv2h_get_pll_dsi_info() helper and opencoded instead. - Dropped is_plldsi parameter from rzv2h_cpg_pll_clk_register() v6->v7: - Made struct rzv2h_pll_limits more modular also added Ffout limits - Made the alogirithm modular and also added apis based on the needs for lvds and dpi v5->v6: - Renamed CPG_PLL_STBY_SSCGEN_WEN to CPG_PLL_STBY_SSC_EN_WEN - Updated CPG_PLL_CLK1_DIV_K, CPG_PLL_CLK1_DIV_M, and CPG_PLL_CLK1_DIV_P macros to use GENMASK - Updated req->rate in rzv2h_cpg_plldsi_div_determine_rate() - Dropped the cast in rzv2h_cpg_plldsi_div_set_rate() - Dropped rzv2h_cpg_plldsi_round_rate() and implemented rzv2h_cpg_plldsi_determine_rate() instead - Made use of FIELD_PREP() - Moved CPG_CSDIV1 macro in patch 2/4 - Dropped two_pow_s in rzv2h_dsi_get_pll_parameters_values() - Used mul_u32_u32() while calculating output_m and output_k_range - Used div_s64() instead of div64_s64() while calculating pll_k - Used mul_u32_u32() while calculating fvco and fvco checks - Rounded the final output using DIV_U64_ROUND_CLOSEST() v4->v5: - No changes v3->v4: - Corrected parameter name in rzv2h_dsi_get_pll_parameters_values() description freq_millihz v2->v3: - Update the commit message to clarify the purpose of `renesas-rzv2h-dsi.h` header - Used mul_u32_u32() in rzv2h_cpg_plldsi_div_determine_rate() - Replaced *_mhz to *_millihz for clarity - Updated u64->u32 for fvco limits - Initialized the members in declaration order for RZV2H_CPG_PLL_DSI_LIMITS() macro - Used clk_div_mask() in rzv2h_cpg_plldsi_div_recalc_rate() - Replaced `unsigned long long` with u64 - Dropped rzv2h_cpg_plldsi_clk_recalc_rate() and reused rzv2h_cpg_pll_clk_recalc_rate() instead - In rzv2h_cpg_plldsi_div_set_rate() followed the same style of RMW-operation as done in the other functions - Renamed rzv2h_cpg_plldsi_set_rate() to rzv2h_cpg_pll_set_rate() - Dropped rzv2h_cpg_plldsi_clk_register() and reused rzv2h_cpg_pll_clk_register() instead - Added a gaurd in renesas-rzv2h-dsi.h header v1->v2: - No changes --- drivers/clk/renesas/rzv2h-cpg.c | 560 +++++++++++++++++++++++++++++++- drivers/clk/renesas/rzv2h-cpg.h | 19 +- include/linux/clk/renesas.h | 136 ++++++++ 3 files changed, 706 insertions(+), 9 deletions(-) diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cp= g.c index ff688dc88ba3..cbf484c3345f 100644 --- a/drivers/clk/renesas/rzv2h-cpg.c +++ b/drivers/clk/renesas/rzv2h-cpg.c @@ -14,9 +14,14 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include #include #include #include @@ -26,6 +31,7 @@ #include #include #include +#include =20 #include =20 @@ -47,13 +53,15 @@ =20 #define CPG_PLL_STBY(x) ((x)) #define CPG_PLL_STBY_RESETB BIT(0) +#define CPG_PLL_STBY_SSC_EN BIT(2) #define CPG_PLL_STBY_RESETB_WEN BIT(16) +#define CPG_PLL_STBY_SSC_EN_WEN BIT(18) #define CPG_PLL_CLK1(x) ((x) + 0x004) -#define CPG_PLL_CLK1_KDIV(x) ((s16)FIELD_GET(GENMASK(31, 16), (x))) -#define CPG_PLL_CLK1_MDIV(x) FIELD_GET(GENMASK(15, 6), (x)) -#define CPG_PLL_CLK1_PDIV(x) FIELD_GET(GENMASK(5, 0), (x)) +#define CPG_PLL_CLK1_KDIV GENMASK(31, 16) +#define CPG_PLL_CLK1_MDIV GENMASK(15, 6) +#define CPG_PLL_CLK1_PDIV GENMASK(5, 0) #define CPG_PLL_CLK2(x) ((x) + 0x008) -#define CPG_PLL_CLK2_SDIV(x) FIELD_GET(GENMASK(2, 0), (x)) +#define CPG_PLL_CLK2_SDIV GENMASK(2, 0) #define CPG_PLL_MON(x) ((x) + 0x010) #define CPG_PLL_MON_RESETB BIT(0) #define CPG_PLL_MON_LOCK BIT(4) @@ -65,6 +73,22 @@ =20 #define CPG_CLKSTATUS0 (0x700) =20 +/* On RZ/G3E SoC we have two DSI PLLs */ +#define MAX_CPG_DSI_PLL 2 + +/** + * struct rzv2h_pll_dsi_info - PLL DSI information, holds the limits and p= arameters + * + * @pll_dsi_limits: PLL DSI parameters limits + * @pll_dsi_parameters: Calculated PLL DSI parameters + * @req_pll_dsi_rate: Requested PLL DSI rate + */ +struct rzv2h_pll_dsi_info { + const struct rzv2h_pll_limits *pll_dsi_limits; + struct rzv2h_pll_div_pars pll_dsi_parameters; + unsigned long req_pll_dsi_rate; +}; + /** * struct rzv2h_cpg_priv - Clock Pulse Generator Private Data * @@ -80,6 +104,7 @@ * @ff_mod_status_ops: Fixed Factor Module Status Clock operations * @mstop_count: Array of mstop values * @rcdev: Reset controller entity + * @pll_dsi_info: Array of PLL DSI information, holds the limits and param= eters */ struct rzv2h_cpg_priv { struct device *dev; @@ -98,6 +123,8 @@ struct rzv2h_cpg_priv { atomic_t *mstop_count; =20 struct reset_controller_dev rcdev; + + struct rzv2h_pll_dsi_info pll_dsi_info[MAX_CPG_DSI_PLL]; }; =20 #define rcdev_to_priv(x) container_of(x, struct rzv2h_cpg_priv, rcdev) @@ -168,6 +195,508 @@ struct rzv2h_ff_mod_status_clk { #define to_rzv2h_ff_mod_status_clk(_hw) \ container_of(_hw, struct rzv2h_ff_mod_status_clk, fix.hw) =20 +/** + * struct rzv2h_plldsi_div_clk - PLL DSI DDIV clock + * + * @dtable: divider table + * @priv: CPG private data + * @hw: divider clk + * @ddiv: divider configuration + */ +struct rzv2h_plldsi_div_clk { + const struct clk_div_table *dtable; + struct rzv2h_cpg_priv *priv; + struct clk_hw hw; + struct ddiv ddiv; +}; + +#define to_plldsi_div_clk(_hw) \ + container_of(_hw, struct rzv2h_plldsi_div_clk, hw) + +#define RZ_V2H_OSC_CLK_IN_MEGA (24 * MEGA) +#define RZV2H_MAX_DIV_TABLES (16) + +/** + * rzv2h_get_pll_pars - Finds the best combination of PLL parameters + * for a given frequency. + * + * @limits: Pointer to the structure containing the limits for the PLL par= ameters + * @pars: Pointer to the structure where the best calculated PLL parameter= s values + * will be stored + * @freq_millihz: Target output frequency in millihertz + * + * This function calculates the best set of PLL parameters (M, K, P, S) to= achieve + * the desired frequency. + * There is no direct formula to calculate the PLL parameters, as it's an = open + * system of equations, therefore this function uses an iterative approach= to + * determine the best solution. The best solution is one that minimizes th= e error + * (desired frequency - actual frequency). + * + * Return: true if a valid set of parameters values is found, false otherw= ise. + */ +bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_pars *pars, u64 freq_millihz) +{ + u64 fout_min_millihz =3D mul_u32_u32(limits->fout.min, MILLI); + u64 fout_max_millihz =3D mul_u32_u32(limits->fout.max, MILLI); + struct rzv2h_pll_pars p, best; + + if (freq_millihz > fout_max_millihz || + freq_millihz < fout_min_millihz) + return false; + + /* Initialize best error to maximum possible value */ + best.error_millihz =3D S64_MAX; + + for (p.p =3D limits->p.min; p.p <=3D limits->p.max; p.p++) { + u32 fref =3D RZ_V2H_OSC_CLK_IN_MEGA / p.p; + u16 divider; + + for (divider =3D 1 << limits->s.min, p.s =3D limits->s.min; + p.s <=3D limits->s.max; p.s++, divider <<=3D 1) { + for (p.m =3D limits->m.min; p.m <=3D limits->m.max; p.m++) { + u64 output_m, output_k_range; + s64 pll_k, output_k; + u64 fvco, output; + + /* + * The frequency generated by the PLL + divider + * is calculated as follows: + * + * With: + * Freq =3D Ffout =3D Ffvco / 2^(pll_s) + * Ffvco =3D (pll_m + (pll_k / 65536)) * Ffref + * Ffref =3D 24MHz / pll_p + * + * Freq can also be rewritten as: + * Freq =3D Ffvco / 2^(pll_s) + * =3D ((pll_m + (pll_k / 65536)) * Ffref) / 2^(pll_s) + * =3D (pll_m * Ffref) / 2^(pll_s) + ((pll_k / 65536) * Ffref) / = 2^(pll_s) + * =3D output_m + output_k + * + * Every parameter has been determined at this + * point, but pll_k. + * + * Considering that: + * limits->k.min <=3D pll_k <=3D limits->k.max + * Then: + * -0.5 <=3D (pll_k / 65536) < 0.5 + * Therefore: + * -Ffref / (2 * 2^(pll_s)) <=3D output_k < Ffref / (2 * 2^(pll_s)) + */ + + /* Compute output M component (in mHz) */ + output_m =3D DIV_ROUND_CLOSEST_ULL(mul_u32_u32(p.m, fref) * MILLI, + divider); + /* Compute range for output K (in mHz) */ + output_k_range =3D DIV_ROUND_CLOSEST_ULL(mul_u32_u32(fref, MILLI), + 2 * divider); + /* + * No point in continuing if we can't achieve + * the desired frequency + */ + if (freq_millihz < (output_m - output_k_range) || + freq_millihz >=3D (output_m + output_k_range)) { + continue; + } + + /* + * Compute the K component + * + * Since: + * Freq =3D output_m + output_k + * Then: + * output_k =3D Freq - output_m + * =3D ((pll_k / 65536) * Ffref) / 2^(pll_s) + * Therefore: + * pll_k =3D (output_k * 65536 * 2^(pll_s)) / Ffref + */ + output_k =3D freq_millihz - output_m; + pll_k =3D div_s64(output_k * 65536ULL * divider, + fref); + pll_k =3D DIV_S64_ROUND_CLOSEST(pll_k, MILLI); + + /* Validate K value within allowed limits */ + if (pll_k < limits->k.min || + pll_k > limits->k.max) + continue; + + p.k =3D pll_k; + + /* Compute (Ffvco * 65536) */ + fvco =3D mul_u32_u32(p.m * 65536 + p.k, fref); + if (fvco < mul_u32_u32(limits->fvco.min, 65536) || + fvco > mul_u32_u32(limits->fvco.max, 65536)) + continue; + + /* PLL_M component of (output * 65536 * PLL_P) */ + output =3D mul_u32_u32(p.m * 65536, RZ_V2H_OSC_CLK_IN_MEGA); + /* PLL_K component of (output * 65536 * PLL_P) */ + output +=3D p.k * RZ_V2H_OSC_CLK_IN_MEGA; + /* Make it in mHz */ + output *=3D MILLI; + output =3D DIV_U64_ROUND_CLOSEST(output, 65536 * p.p * divider); + + /* Check output frequency against limits */ + if (output < fout_min_millihz || + output > fout_max_millihz) + continue; + + p.error_millihz =3D freq_millihz - output; + p.freq_millihz =3D output; + + /* If an exact match is found, return immediately */ + if (p.error_millihz =3D=3D 0) { + *pars =3D p; + return true; + } + + /* Update best match if error is smaller */ + if (abs(best.error_millihz) > abs(p.error_millihz)) + best =3D p; + } + } + } + + /* If no valid parameters were found, return false */ + if (best.error_millihz =3D=3D S64_MAX) + return false; + + *pars =3D best; + return true; +} +EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_pars, "RZV2H_CPG"); + +/* + * rzv2h_get_pll_div_pars - Finds the best combination of PLL parameters + * and divider value for a given frequency. + * + * @limits: Pointer to the structure containing the limits for the PLL par= ameters + * @pars: Pointer to the structure where the best calculated PLL parameter= s and + * divider values will be stored + * @divider: Divider value to be applied to the PLL output + * @freq_millihz: Target output frequency in millihertz + * + * This function calculates the best set of PLL parameters (M, K, P, S) wh= ere + * the divider value is already known. See rzv2h_get_pll_pars() for more d= etails + * on how the PLL parameters are calculated. + */ +bool rzv2h_get_pll_div_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, u8 divider, + u64 freq_millihz) +{ + if (!rzv2h_get_pll_pars(limits, &pars->pll, freq_millihz * divider)) + return false; + + pars->div.divider_value =3D divider; + pars->div.freq_millihz =3D DIV_U64_ROUND_CLOSEST(pars->pll.freq_millihz, = divider); + pars->div.error_millihz =3D freq_millihz - pars->div.freq_millihz; + + return true; +} +EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_div_pars, "RZV2H_CPG"); + +/* + * rzv2h_get_pll_divs_pars - Finds the best combination of PLL parameters + * and divider value for a given frequency. + * + * @limits: Pointer to the structure containing the limits for the PLL par= ameters + * @pars: Pointer to the structure where the best calculated PLL parameter= s and + * divider values will be stored + * @table: Pointer to the array of valid divider values + * @table_size: Size of the divider values array + * @freq_millihz: Target output frequency in millihertz + * + * This function calculates the best set of PLL parameters (M, K, P, S) an= d divider + * value to achieve the desired frequency. See rzv2h_get_pll_pars() for mo= re details + * on how the PLL parameters are calculated. + * + * freq_millihz is the desired frequency generated by the PLL followed by a + * a gear. + */ +bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const u8 *table, u8 table_size, u64 freq_millihz) +{ + struct rzv2h_pll_div_pars p, best; + + best.div.error_millihz =3D S64_MAX; + p.div.error_millihz =3D S64_MAX; + for (unsigned int i =3D 0; i < table_size; i++) { + if (!rzv2h_get_pll_div_pars(limits, &p, table[i], freq_millihz)) + continue; + + if (p.div.error_millihz =3D=3D 0) { + *pars =3D p; + return true; + } + + if (abs(best.div.error_millihz) > abs(p.div.error_millihz)) + best =3D p; + } + + if (best.div.error_millihz =3D=3D S64_MAX) + return false; + + *pars =3D best; + return true; +} +EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_divs_pars, "RZV2H_CPG"); + +/* + * rzv2h_get_pll_dtable_pars - Finds the best combination of PLL parameters + * and divider value for a given frequency using a divider table. + * + * @limits: Pointer to the structure containing the limits for the PLL par= ameters + * @pars: Pointer to the structure where the best calculated PLL parameter= s and + * divider values will be stored + * @dtable: Pointer to the array of valid divider values + * @freq_millihz: Target output frequency in millihertz + * + * See rzv2h_get_pll_divs_pars() for more details on how the PLL + * parameters and divider values are calculated. + */ +bool rzv2h_get_pll_dtable_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const struct clk_div_table *dtable, u64 freq_millihz) +{ + const struct clk_div_table *div =3D dtable; + u8 table[RZV2H_MAX_DIV_TABLES] =3D { 0 }; + unsigned int i =3D 0; + + for (; div->div; div++) { + if (i >=3D RZV2H_MAX_DIV_TABLES) + return false; + table[i++] =3D div->div; + } + + return rzv2h_get_pll_divs_pars(limits, pars, table, i, freq_millihz); +} +EXPORT_SYMBOL_NS_GPL(rzv2h_get_pll_dtable_pars, "RZV2H_CPG"); + +static unsigned long rzv2h_cpg_plldsi_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct rzv2h_plldsi_div_clk *dsi_div =3D to_plldsi_div_clk(hw); + struct rzv2h_cpg_priv *priv =3D dsi_div->priv; + struct ddiv ddiv =3D dsi_div->ddiv; + u32 div; + + div =3D readl(priv->base + ddiv.offset); + div >>=3D ddiv.shift; + div &=3D clk_div_mask(ddiv.width); + div =3D dsi_div->dtable[div].div; + + return DIV_ROUND_CLOSEST_ULL(parent_rate, div); +} + +static int rzv2h_cpg_plldsi_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct rzv2h_plldsi_div_clk *dsi_div =3D to_plldsi_div_clk(hw); + struct pll_clk *pll_clk =3D to_pll(clk_hw_get_parent(hw)); + struct rzv2h_cpg_priv *priv =3D dsi_div->priv; + struct rzv2h_pll_div_pars *dsi_params; + struct rzv2h_pll_dsi_info *dsi_info; + u64 rate_millihz; + + dsi_info =3D &priv->pll_dsi_info[pll_clk->pll.instance]; + dsi_params =3D &dsi_info->pll_dsi_parameters; + + rate_millihz =3D mul_u32_u32(req->rate, MILLI); + if (rate_millihz =3D=3D dsi_params->div.error_millihz + dsi_params->div.f= req_millihz) + goto exit_determine_rate; + + if (!rzv2h_get_pll_dtable_pars(dsi_info->pll_dsi_limits, dsi_params, dsi_= div->dtable, + rate_millihz)) { + dev_err(priv->dev, + "failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + +exit_determine_rate: + req->rate =3D DIV_ROUND_CLOSEST_ULL(dsi_params->div.freq_millihz, MILLI); + req->best_parent_rate =3D req->rate * dsi_params->div.divider_value; + dsi_info->req_pll_dsi_rate =3D req->best_parent_rate; + + return 0; +} + +static int rzv2h_cpg_plldsi_div_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct rzv2h_plldsi_div_clk *dsi_div =3D to_plldsi_div_clk(hw); + struct pll_clk *pll_clk =3D to_pll(clk_hw_get_parent(hw)); + struct rzv2h_cpg_priv *priv =3D dsi_div->priv; + struct rzv2h_pll_div_pars *dsi_params; + struct rzv2h_pll_dsi_info *dsi_info; + struct ddiv ddiv =3D dsi_div->ddiv; + const struct clk_div_table *clkt; + bool divider_found =3D false; + u32 val, shift; + + dsi_info =3D &priv->pll_dsi_info[pll_clk->pll.instance]; + dsi_params =3D &dsi_info->pll_dsi_parameters; + + for (clkt =3D dsi_div->dtable; clkt->div; clkt++) { + if (clkt->div =3D=3D dsi_params->div.divider_value) { + divider_found =3D true; + break; + } + } + + if (!divider_found) + return -EINVAL; + + shift =3D ddiv.shift; + val =3D readl(priv->base + ddiv.offset) | DDIV_DIVCTL_WEN(shift); + val &=3D ~(clk_div_mask(ddiv.width) << shift); + val |=3D clkt->val << shift; + writel(val, priv->base + ddiv.offset); + + return 0; +} + +static const struct clk_ops rzv2h_cpg_plldsi_div_ops =3D { + .recalc_rate =3D rzv2h_cpg_plldsi_div_recalc_rate, + .determine_rate =3D rzv2h_cpg_plldsi_div_determine_rate, + .set_rate =3D rzv2h_cpg_plldsi_div_set_rate, +}; + +static struct clk * __init +rzv2h_cpg_plldsi_div_clk_register(const struct cpg_core_clk *core, + struct rzv2h_cpg_priv *priv) +{ + struct rzv2h_plldsi_div_clk *clk_hw_data; + struct clk **clks =3D priv->clks; + struct clk_init_data init; + const struct clk *parent; + const char *parent_name; + struct clk_hw *clk_hw; + int ret; + + parent =3D clks[core->parent]; + if (IS_ERR(parent)) + return ERR_CAST(parent); + + clk_hw_data =3D devm_kzalloc(priv->dev, sizeof(*clk_hw_data), GFP_KERNEL); + if (!clk_hw_data) + return ERR_PTR(-ENOMEM); + + clk_hw_data->priv =3D priv; + clk_hw_data->ddiv =3D core->cfg.ddiv; + clk_hw_data->dtable =3D core->dtable; + + parent_name =3D __clk_get_name(parent); + init.name =3D core->name; + init.ops =3D &rzv2h_cpg_plldsi_div_ops; + init.flags =3D core->flag; + init.parent_names =3D &parent_name; + init.num_parents =3D 1; + + clk_hw =3D &clk_hw_data->hw; + clk_hw->init =3D &init; + + ret =3D devm_clk_hw_register(priv->dev, clk_hw); + if (ret) + return ERR_PTR(ret); + + return clk_hw->clk; +} + +static int rzv2h_cpg_plldsi_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct pll_clk *pll_clk =3D to_pll(hw); + struct rzv2h_cpg_priv *priv =3D pll_clk->priv; + struct rzv2h_pll_dsi_info *dsi_info; + u64 rate_millihz; + + dsi_info =3D &priv->pll_dsi_info[pll_clk->pll.instance]; + /* check if the divider has already invoked the algorithm */ + if (req->rate =3D=3D dsi_info->req_pll_dsi_rate) + return 0; + + /* If the req->rate doesn't match we do the calculation assuming there is= no divider */ + rate_millihz =3D mul_u32_u32(req->rate, MILLI); + if (!rzv2h_get_pll_pars(dsi_info->pll_dsi_limits, + &dsi_info->pll_dsi_parameters.pll, rate_millihz)) { + dev_err(priv->dev, + "failed to determine rate for req->rate: %lu\n", + req->rate); + return -EINVAL; + } + + req->rate =3D DIV_ROUND_CLOSEST_ULL(dsi_info->pll_dsi_parameters.pll.freq= _millihz, MILLI); + dsi_info->req_pll_dsi_rate =3D req->rate; + + return 0; +} + +static int rzv2h_cpg_pll_set_rate(struct pll_clk *pll_clk, + struct rzv2h_pll_pars *params, + bool ssc_disable) +{ + struct rzv2h_cpg_priv *priv =3D pll_clk->priv; + u16 offset =3D pll_clk->pll.offset; + u32 val; + int ret; + + /* Put PLL into standby mode */ + writel(CPG_PLL_STBY_RESETB_WEN, priv->base + CPG_PLL_STBY(offset)); + ret =3D readl_poll_timeout_atomic(priv->base + CPG_PLL_MON(offset), + val, !(val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + dev_err(priv->dev, "Failed to put PLLDSI into standby mode"); + return ret; + } + + /* Output clock setting 1 */ + writel(FIELD_PREP(CPG_PLL_CLK1_KDIV, (u16)params->k) | + FIELD_PREP(CPG_PLL_CLK1_MDIV, params->m) | + FIELD_PREP(CPG_PLL_CLK1_PDIV, params->p), + priv->base + CPG_PLL_CLK1(offset)); + + /* Output clock setting 2 */ + val =3D readl(priv->base + CPG_PLL_CLK2(offset)); + writel((val & ~CPG_PLL_CLK2_SDIV) | FIELD_PREP(CPG_PLL_CLK2_SDIV, params-= >s), + priv->base + CPG_PLL_CLK2(offset)); + + /* Put PLL to normal mode */ + if (ssc_disable) + val =3D CPG_PLL_STBY_SSC_EN_WEN; + else + val =3D CPG_PLL_STBY_SSC_EN_WEN | CPG_PLL_STBY_SSC_EN; + writel(val | CPG_PLL_STBY_RESETB_WEN | CPG_PLL_STBY_RESETB, + priv->base + CPG_PLL_STBY(offset)); + + /* PLL normal mode transition, output clock stability check */ + ret =3D readl_poll_timeout_atomic(priv->base + CPG_PLL_MON(offset), + val, (val & CPG_PLL_MON_LOCK), + 100, 2000); + if (ret) { + dev_err(priv->dev, "Failed to put PLLDSI into normal mode"); + return ret; + } + + return 0; +} + +static int rzv2h_cpg_plldsi_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct pll_clk *pll_clk =3D to_pll(hw); + struct rzv2h_pll_dsi_info *dsi_info; + struct rzv2h_cpg_priv *priv =3D pll_clk->priv; + + dsi_info =3D &priv->pll_dsi_info[pll_clk->pll.instance]; + + return rzv2h_cpg_pll_set_rate(pll_clk, &dsi_info->pll_dsi_parameters.pll,= true); +} + static int rzv2h_cpg_pll_clk_is_enabled(struct clk_hw *hw) { struct pll_clk *pll_clk =3D to_pll(hw); @@ -231,12 +760,19 @@ static unsigned long rzv2h_cpg_pll_clk_recalc_rate(st= ruct clk_hw *hw, clk1 =3D readl(priv->base + CPG_PLL_CLK1(pll.offset)); clk2 =3D readl(priv->base + CPG_PLL_CLK2(pll.offset)); =20 - rate =3D mul_u64_u32_shr(parent_rate, (CPG_PLL_CLK1_MDIV(clk1) << 16) + - CPG_PLL_CLK1_KDIV(clk1), 16 + CPG_PLL_CLK2_SDIV(clk2)); + rate =3D mul_u64_u32_shr(parent_rate, (FIELD_GET(CPG_PLL_CLK1_MDIV, clk1)= << 16) + + (s16)FIELD_GET(CPG_PLL_CLK1_KDIV, clk1), + 16 + FIELD_GET(CPG_PLL_CLK2_SDIV, clk2)); =20 - return DIV_ROUND_CLOSEST_ULL(rate, CPG_PLL_CLK1_PDIV(clk1)); + return DIV_ROUND_CLOSEST_ULL(rate, FIELD_GET(CPG_PLL_CLK1_PDIV, clk1)); } =20 +static const struct clk_ops rzv2h_cpg_plldsi_ops =3D { + .recalc_rate =3D rzv2h_cpg_pll_clk_recalc_rate, + .determine_rate =3D rzv2h_cpg_plldsi_determine_rate, + .set_rate =3D rzv2h_cpg_plldsi_set_rate, +}; + static const struct clk_ops rzv2h_cpg_pll_ops =3D { .is_enabled =3D rzv2h_cpg_pll_clk_is_enabled, .enable =3D rzv2h_cpg_pll_clk_enable, @@ -263,6 +799,10 @@ rzv2h_cpg_pll_clk_register(const struct cpg_core_clk *= core, if (!pll_clk) return ERR_PTR(-ENOMEM); =20 + if (core->type =3D=3D CLK_TYPE_PLLDSI) + priv->pll_dsi_info[core->cfg.pll.instance].pll_dsi_limits =3D + core->cfg.pll.limits; + parent_name =3D __clk_get_name(parent); init.name =3D core->name; init.ops =3D ops; @@ -587,6 +1127,12 @@ rzv2h_cpg_register_core_clk(const struct cpg_core_clk= *core, case CLK_TYPE_SMUX: clk =3D rzv2h_cpg_mux_clk_register(core, priv); break; + case CLK_TYPE_PLLDSI: + clk =3D rzv2h_cpg_pll_clk_register(core, priv, &rzv2h_cpg_plldsi_ops); + break; + case CLK_TYPE_PLLDSI_DIV: + clk =3D rzv2h_cpg_plldsi_div_clk_register(core, priv); + break; default: goto fail; } diff --git a/drivers/clk/renesas/rzv2h-cpg.h b/drivers/clk/renesas/rzv2h-cp= g.h index e2053049c299..637803bc1e89 100644 --- a/drivers/clk/renesas/rzv2h-cpg.h +++ b/drivers/clk/renesas/rzv2h-cpg.h @@ -22,15 +22,20 @@ struct pll { unsigned int offset:9; unsigned int has_clkn:1; unsigned int instance:2; + const struct rzv2h_pll_limits *limits; }; =20 -#define PLL_PACK(_offset, _has_clkn, _instance) \ +#define PLL_PACK_LIMITS(_offset, _has_clkn, _instance, _limits) \ ((struct pll){ \ .offset =3D _offset, \ .has_clkn =3D _has_clkn, \ - .instance =3D _instance \ + .instance =3D _instance, \ + .limits =3D _limits \ }) =20 +#define PLL_PACK(_offset, _has_clkn, _instance) \ + PLL_PACK_LIMITS(_offset, _has_clkn, _instance, NULL) + #define PLLCA55 PLL_PACK(0x60, 1, 0) #define PLLGPU PLL_PACK(0x120, 1, 0) =20 @@ -191,6 +196,8 @@ enum clk_types { CLK_TYPE_PLL, CLK_TYPE_DDIV, /* Dynamic Switching Divider */ CLK_TYPE_SMUX, /* Static Mux */ + CLK_TYPE_PLLDSI, /* PLLDSI */ + CLK_TYPE_PLLDSI_DIV, /* PLLDSI divider */ }; =20 #define DEF_TYPE(_name, _id, _type...) \ @@ -221,6 +228,14 @@ enum clk_types { .num_parents =3D ARRAY_SIZE(_parent_names), \ .flag =3D CLK_SET_RATE_PARENT, \ .mux_flags =3D CLK_MUX_HIWORD_MASK) +#define DEF_PLLDSI(_name, _id, _parent, _pll_packed) \ + DEF_TYPE(_name, _id, CLK_TYPE_PLLDSI, .parent =3D _parent, .cfg.pll =3D _= pll_packed) +#define DEF_PLLDSI_DIV(_name, _id, _parent, _ddiv_packed, _dtable) \ + DEF_TYPE(_name, _id, CLK_TYPE_PLLDSI_DIV, \ + .cfg.ddiv =3D _ddiv_packed, \ + .dtable =3D _dtable, \ + .parent =3D _parent, \ + .flag =3D CLK_SET_RATE_PARENT) =20 /** * struct rzv2h_mod_clk - Module Clocks definitions diff --git a/include/linux/clk/renesas.h b/include/linux/clk/renesas.h index 0ebbe2f0b45e..57bf6c41d2ae 100644 --- a/include/linux/clk/renesas.h +++ b/include/linux/clk/renesas.h @@ -10,7 +10,9 @@ #ifndef __LINUX_CLK_RENESAS_H_ #define __LINUX_CLK_RENESAS_H_ =20 +#include #include +#include =20 struct device; struct device_node; @@ -32,4 +34,138 @@ void cpg_mssr_detach_dev(struct generic_pm_domain *unus= ed, struct device *dev); #define cpg_mssr_attach_dev NULL #define cpg_mssr_detach_dev NULL #endif + +/** + * struct rzv2h_pll_limits - PLL parameter constraints + * + * This structure defines the minimum and maximum allowed values for + * various parameters used to configure a PLL. These limits ensure + * the PLL operates within valid and stable ranges. + * + * @fout: Output frequency range (in MHz) + * @fout.min: Minimum allowed output frequency + * @fout.max: Maximum allowed output frequency + * + * @fvco: PLL oscillation frequency range (in MHz) + * @fvco.min: Minimum allowed VCO frequency + * @fvco.max: Maximum allowed VCO frequency + * + * @m: Main-divider range + * @m.min: Minimum main-divider value + * @m.max: Maximum main-divider value + * + * @p: Pre-divider range + * @p.min: Minimum pre-divider value + * @p.max: Maximum pre-divider value + * + * @s: Divider range + * @s.min: Minimum divider value + * @s.max: Maximum divider value + * + * @k: Delta-sigma modulator range (signed) + * @k.min: Minimum delta-sigma value + * @k.max: Maximum delta-sigma value + */ +struct rzv2h_pll_limits { + struct { + u32 min; + u32 max; + } fout; + + struct { + u32 min; + u32 max; + } fvco; + + struct { + u16 min; + u16 max; + } m; + + struct { + u8 min; + u8 max; + } p; + + struct { + u8 min; + u8 max; + } s; + + struct { + s16 min; + s16 max; + } k; +}; + +/** + * struct rzv2h_pll_pars - PLL configuration parameters + * + * This structure contains the configuration parameters for the + * Phase-Locked Loop (PLL), used to achieve a specific output frequency. + * + * @m: Main divider value + * @p: Pre-divider value + * @s: Output divider value + * @k: Delta-sigma modulation value + * @freq_millihz: Calculated PLL output frequency in millihertz + * @error_millihz: Frequency error from target in millihertz (signed) + */ +struct rzv2h_pll_pars { + u16 m; + u8 p; + u8 s; + s16 k; + u64 freq_millihz; + s64 error_millihz; +}; + +/** + * struct rzv2h_pll_div_pars - PLL parameters with post-divider + * + * This structure is used for PLLs that include an additional post-divider + * stage after the main PLL block. It contains both the PLL configuration + * parameters and the resulting frequency/error values after the divider. + * + * @pll: Main PLL configuration parameters (see struct rzv2h_pll_pars) + * + * @div: Post-divider configuration and result + * @div.divider_value: Divider applied to the PLL output + * @div.freq_millihz: Output frequency after divider in millihertz + * @div.error_millihz: Frequency error from target in millihertz (signed) + */ +struct rzv2h_pll_div_pars { + struct rzv2h_pll_pars pll; + struct { + u8 divider_value; + u64 freq_millihz; + s64 error_millihz; + } div; +}; + +#define RZV2H_CPG_PLL_DSI_LIMITS(name) \ + static const struct rzv2h_pll_limits (name) =3D { \ + .fout =3D { .min =3D 25 * MEGA, .max =3D 375 * MEGA }, \ + .fvco =3D { .min =3D 1600 * MEGA, .max =3D 3200 * MEGA }, \ + .m =3D { .min =3D 64, .max =3D 533 }, \ + .p =3D { .min =3D 1, .max =3D 4 }, \ + .s =3D { .min =3D 0, .max =3D 6 }, \ + .k =3D { .min =3D -32768, .max =3D 32767 }, \ + } \ + +bool rzv2h_get_pll_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_pars *pars, u64 freq_millihz); + +bool rzv2h_get_pll_div_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, u8 divider, + u64 freq_millihz); + +bool rzv2h_get_pll_divs_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const u8 *table, u8 table_size, u64 freq_millihz); + +bool rzv2h_get_pll_dtable_pars(const struct rzv2h_pll_limits *limits, + struct rzv2h_pll_div_pars *pars, + const struct clk_div_table *dtable, u64 freq_millihz); + #endif --=20 2.51.0