Netdev List
 help / color / mirror / Atom feed
From: Ali Rouhi <arouhi@sitime.com>
To: "jiri@resnulli.us" <jiri@resnulli.us>
Cc: "vadim.fedorenko@linux.dev" <vadim.fedorenko@linux.dev>,
	"arkadiusz.kubalewski@intel.com" <arkadiusz.kubalewski@intel.com>,
	"ivecera@redhat.com" <ivecera@redhat.com>,
	"robh@kernel.org" <robh@kernel.org>,
	"krzk+dt@kernel.org" <krzk+dt@kernel.org>,
	"conor+dt@kernel.org" <conor+dt@kernel.org>,
	"cjubran@nvidia.com" <cjubran@nvidia.com>,
	"pabeni@redhat.com" <pabeni@redhat.com>,
	"Oleg.Zadorozhnyi@devoxsoftware.com"
	<Oleg.Zadorozhnyi@devoxsoftware.com>,
	"prabhakar.mahadev-lad.rj@bp.renesas.com"
	<prabhakar.mahadev-lad.rj@bp.renesas.com>,
	"dev@kael-k.io" <dev@kael-k.io>,
	"devicetree@vger.kernel.org" <devicetree@vger.kernel.org>,
	"netdev@vger.kernel.org" <netdev@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	Ali Rouhi <arouhi@sitime.com>
Subject: [PATCH v10 14/14] dpll: sit9531x: allow the device tree to override two board facts
Date: Mon, 21 Sep 2026 20:11:19 +0000	[thread overview]
Message-ID: <20260921201108.42676-15-arouhi@sitime.com> (raw)
In-Reply-To: <20260921201108.42676-1-arouhi@sitime.com>

From: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>

Two things the driver reads from the chip can be wrong on a board, and
neither has anywhere else to come from.

The VCO frequency is derived from the feedback divider, which is exact
while the loop runs but not while a PLL sits in free-run with a divider
the configuration never programmed; a board that knows its own VCO can
state it.  The output-to-PLL routing is read from the output map
registers, which describe what the loaded configuration did -- and a board
whose outputs are fanned out differently from what those registers imply
can state that too.

Both are optional.  Absent the properties the driver behaves exactly as
before, deriving one and reading the other.

Signed-off-by: Oleg Zadorozhnyi <Oleg.Zadorozhnyi@devoxsoftware.com>
Assisted-by: Claude:claude-4-opus [chat]
Signed-off-by: Ali Rouhi <arouhi@sitime.com>
---

Notes:
    Changes in v10:
    Checked the device-tree array lengths against the part: the VCO
    override needs an entry per PLL, and an output map shorter than the
    variant's output count is ignored rather than read as unmapped
    outputs.
    
    Updated the accessor's kernel-doc, which no longer described the
    override path.

 drivers/dpll/sit9531x/core.c | 171 ++++++++++++++++++++++++++++++++++-
 drivers/dpll/sit9531x/core.h |   1 +
 2 files changed, 167 insertions(+), 5 deletions(-)

diff --git a/drivers/dpll/sit9531x/core.c b/drivers/dpll/sit9531x/core.c
index e8d47999f1a9..5316e6bcb338 100644
--- a/drivers/dpll/sit9531x/core.c
+++ b/drivers/dpll/sit9531x/core.c
@@ -1695,12 +1695,15 @@ int sit9531x_pll_ffo_ppt(struct sit9531x_dev *sitdev, u8 pll_idx, s64 *ffo)
 }
 
 /*
- * sit9531x_get_fvco - read VCO frequency from chip's DIVN registers
+ * sit9531x_get_fvco - the VCO frequency the driver works from
  *
- * Fvco = Fref * DIVN, where DIVN comes from sit9531x_divn_static() and
- * Fref = xtal_freq << doubler.  DIVN is the steady-state Fvco/Fref
- * target programmed by the NVM blob and is authoritative in both
- * free-run and sync modes.
+ * A board that describes the VCO through "sitime,pll-fvco" is taken at
+ * its word and nothing is read.  Otherwise Fvco = Fref * DIVN, where
+ * DIVN comes from sit9531x_divn_static() and Fref = xtal_freq <<
+ * doubler.  DIVN is the steady-state Fvco/Fref target programmed by the
+ * NVM blob and is authoritative in both free-run and sync modes; the
+ * result is clamped to the band the PLL runs in, so every caller divides
+ * the same number.
  *
  * Return: 0 with *fvco set on success, -ENODATA when DIVN is not
  * programmed (dormant PLL), or the register access error.  A bus
@@ -3238,6 +3241,27 @@ static int sit9531x_out_state_fetch(struct sit9531x_dev *sitdev, u8 index)
 
 	sitdev->out[index].state_stale = false;
 
+	/*
+	 * DT board-config override: the per-PLL OUTPUT_ENABLE bitmaps
+	 * (0x27/0x28) do not unambiguously express output->PLL routing on
+	 * every config (overlaps, and some outputs routed outside that
+	 * path).  When the board supplies an explicit map, trust it.
+	 */
+	if (sitdev->out_pll_map_valid) {
+		u8 m = sitdev->out_pll_map[index];
+
+		if (m < SIT9531X_NUM_PLLS) {
+			out->pll_idx = m;
+			out->routed = true;
+			out->enabled = !muted;
+		} else {
+			out->pll_idx = 0;
+			out->routed = false;
+			out->enabled = false;
+		}
+		return 0;
+	}
+
 	/*
 	 * The OUT_MAP_LO/HI bitmaps are indexed by the physical slot the
 	 * output occupies on the chip, not by the driver's logical output
@@ -4223,6 +4247,126 @@ static u64 sit9531x_derive_clock_id(struct sit9531x_dev *sitdev)
 	return clkid;
 }
 
+/*
+ * Does an Fvco fall in the band the given PLL runs in?
+ *
+ * The two bands are disjoint, and which one applies is fixed per PLL, so a
+ * single envelope from the bottom of the low band to the top of the high
+ * one would accept both the ~1 GHz gap between them and a rate belonging to
+ * the other PLL's band.
+ */
+static bool sit9531x_fvco_in_band(u8 pll_idx, u64 fvco)
+{
+	if (pll_idx == 1 || pll_idx == 3)
+		return fvco >= SIT9531X_FVCO_HIGHBAND_MIN &&
+		       fvco <= SIT9531X_FVCO_HIGHBAND_MAX;
+
+	return fvco >= SIT9531X_FVCO_LOWBAND_MIN &&
+	       fvco <= SIT9531X_FVCO_LOWBAND_MAX;
+}
+
+/*
+ * Board-config overrides for fixed efuse/blob routing the chip registers do
+ * not describe unambiguously.  Absent properties leave pll_fvco[] zeroed
+ * (derive from DIVN) and out_pll_map_valid false (use the OUT_MAP registers).
+ */
+static void sit9531x_parse_board_config(struct sit9531x_dev *sitdev)
+{
+	u32 map[SIT9531X_MAX_OUTPUTS];
+	int n, i, rc;
+
+	if (device_property_present(sitdev->dev, "sitime,pll-fvco")) {
+		/*
+		 * A fixed-count read rejects a short array but accepts a
+		 * long one and drops the surplus, so the count is checked
+		 * here: the property describes four PLLs and an array of
+		 * any other length describes something else.
+		 */
+		rc = device_property_count_u64(sitdev->dev,
+					       "sitime,pll-fvco");
+		if (rc != SIT9531X_NUM_PLLS) {
+			dev_warn(sitdev->dev,
+				 "sitime,pll-fvco needs %d entries, ignoring\n",
+				 SIT9531X_NUM_PLLS);
+			memset(sitdev->pll_fvco, 0, sizeof(sitdev->pll_fvco));
+			goto out_map;
+		}
+
+		rc = device_property_read_u64_array(sitdev->dev,
+						    "sitime,pll-fvco",
+						    sitdev->pll_fvco,
+						    SIT9531X_NUM_PLLS);
+		if (rc) {
+			dev_warn(sitdev->dev,
+				 "invalid sitime,pll-fvco (%d), ignoring\n",
+				 rc);
+			memset(sitdev->pll_fvco, 0, sizeof(sitdev->pll_fvco));
+		}
+
+		/*
+		 * The override is used verbatim by the divider math, so an
+		 * implausible value (units typo, wrong cell count worked
+		 * around with zeros) must not silently misprogram DIVO.
+		 * Anything outside both VCO bands is dropped with a warning
+		 * rather than trusted.
+		 */
+		for (i = 0; i < SIT9531X_NUM_PLLS; i++) {
+			u64 f = sitdev->pll_fvco[i];
+
+			if (f && !sit9531x_fvco_in_band(i, f)) {
+				dev_warn(sitdev->dev,
+					 "PLL%c Fvco override %llu Hz is outside the band that PLL runs in, ignoring\n",
+					 'A' + i, f);
+				sitdev->pll_fvco[i] = 0;
+			}
+		}
+	}
+
+out_map:
+	if (!device_property_present(sitdev->dev, "sitime,output-pll-map"))
+		return;
+
+	/*
+	 * Any 1..MAX_OUTPUTS length is accepted so the 8-output SiT95317 need
+	 * not pad to 12; variant detection has not run yet and entries past
+	 * the detected num_outputs are never indexed.  Trailing entries of a
+	 * short map must read as unmapped rather than 0 (== PLLA), which
+	 * would mark unrouted outputs active in sit9531x_out_state_fetch().
+	 */
+	memset(sitdev->out_pll_map, SIT9531X_OUT_PLL_UNMAPPED,
+	       sizeof(sitdev->out_pll_map));
+
+	n = device_property_count_u32(sitdev->dev, "sitime,output-pll-map");
+	sitdev->out_pll_map_count = (n > 0) ? n : 0;
+	if (n <= 0 || n > SIT9531X_MAX_OUTPUTS ||
+	    device_property_read_u32_array(sitdev->dev, "sitime,output-pll-map",
+					   map, n)) {
+		dev_warn(sitdev->dev,
+			 "invalid sitime,output-pll-map, ignoring\n");
+		return;
+	}
+
+	/*
+	 * The binding allows only 0-3 and 255 per entry.  A stray value
+	 * would silently unroute an output (m >= SIT9531X_NUM_PLLS reads
+	 * as unmapped in sit9531x_out_state_fetch()), so reject the whole
+	 * property loudly instead.
+	 */
+	for (i = 0; i < n; i++) {
+		if (map[i] >= SIT9531X_NUM_PLLS &&
+		    map[i] != SIT9531X_OUT_PLL_UNMAPPED) {
+			dev_warn(sitdev->dev,
+				 "sitime,output-pll-map entry %d is %u (must be 0-3 or 255), ignoring map\n",
+				 i, map[i]);
+			return;
+		}
+	}
+
+	for (i = 0; i < n; i++)
+		sitdev->out_pll_map[i] = map[i];
+	sitdev->out_pll_map_valid = true;
+}
+
 int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
 {
 	struct clk *xtal_clk;
@@ -4267,6 +4411,8 @@ int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
 	if (sitdev->reset_gpio)
 		fsleep(10000);	/* internal boot after release */
 
+	sit9531x_parse_board_config(sitdev);
+
 	rc = sit9531x_read_variant_id(sitdev, &variant_id);
 	if (rc)
 		return rc;
@@ -4277,6 +4423,21 @@ int sit9531x_dev_probe(struct sit9531x_dev *sitdev)
 				     "Unknown variant ID: 0x%02x\n",
 				     variant_id);
 
+	/*
+	 * The map is parsed before the variant is known, so its length can
+	 * only be checked against the part here.  A map that stops short of
+	 * the outputs this variant has leaves the rest reading as unmapped,
+	 * which would drop real output pins; fall back to the routing the
+	 * registers describe instead.
+	 */
+	if (sitdev->out_pll_map_valid &&
+	    sitdev->out_pll_map_count < sitdev->info->num_outputs) {
+		dev_warn(sitdev->dev,
+			 "sitime,output-pll-map has %u of %u outputs, ignoring map\n",
+			 sitdev->out_pll_map_count, sitdev->info->num_outputs);
+		sitdev->out_pll_map_valid = false;
+	}
+
 	sitdev->clock_id = sit9531x_derive_clock_id(sitdev);
 	sitdev->intsync_src = -1;
 
diff --git a/drivers/dpll/sit9531x/core.h b/drivers/dpll/sit9531x/core.h
index 1fc14eabb621..31a5f49c0253 100644
--- a/drivers/dpll/sit9531x/core.h
+++ b/drivers/dpll/sit9531x/core.h
@@ -224,6 +224,7 @@ struct sit9531x_dev {
 	u64			pll_fvco[SIT9531X_NUM_PLLS];
 	u8			out_pll_map[SIT9531X_MAX_OUTPUTS];
 	bool			out_pll_map_valid;
+	u8			out_pll_map_count;
 
 	/* Inter-PLL synchronization state */
 	s8			intsync_src;
-- 
2.43.0


  parent reply	other threads:[~2026-09-21 20:11 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-21 20:11 [PATCH v10 00/14] dpll: add SiTime SiT9531x DPLL clock driver Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 01/14] dt-bindings: vendor-prefixes: add SiTime Corporation Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 02/14] dt-bindings: dpll: add SiTime SiT95316 clock generator Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-30 23:33     ` Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 03/14] dpll: add basic SiTime SiT9531x support Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-30 23:33     ` Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 04/14] dpll: sit9531x: read DPLL types and pin properties from system firmware Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 06/14] dpll: sit9531x: implement input pin state on a DPLL Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-30 23:33     ` Ali Rouhi
2026-09-21 20:11 ` [PATCH v10 05/14] dpll: sit9531x: register DPLL devices and pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 07/14] dpll: sit9531x: add support to get and set priority on input pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 08/14] dpll: sit9531x: add support to get and set frequency on pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 09/14] dpll: sit9531x: implement output pin state on a DPLL Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 11/14] dpll: sit9531x: add support to get phase offset on the connected input pin Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 10/14] dpll: sit9531x: add support to adjust output phase Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 12/14] dpll: sit9531x: add support to get fractional frequency offset Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` [PATCH v10 13/14] dpll: sit9531x: model the inter-PLL sync net as a pair of pins Ali Rouhi
2026-09-26  2:34   ` Jakub Kicinski
2026-09-21 20:11 ` Ali Rouhi [this message]
2026-09-26  2:34   ` [PATCH v10 14/14] dpll: sit9531x: allow the device tree to override two board facts Jakub Kicinski
2026-09-28 23:29 ` [PATCH v10 00/14] dpll: add SiTime SiT9531x DPLL clock driver Jakub Kicinski
2026-09-29  0:38   ` Ali Rouhi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260921201108.42676-15-arouhi@sitime.com \
    --to=arouhi@sitime.com \
    --cc=Oleg.Zadorozhnyi@devoxsoftware.com \
    --cc=arkadiusz.kubalewski@intel.com \
    --cc=cjubran@nvidia.com \
    --cc=conor+dt@kernel.org \
    --cc=dev@kael-k.io \
    --cc=devicetree@vger.kernel.org \
    --cc=ivecera@redhat.com \
    --cc=jiri@resnulli.us \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=prabhakar.mahadev-lad.rj@bp.renesas.com \
    --cc=robh@kernel.org \
    --cc=vadim.fedorenko@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox