[PATCH 24/28] dpll: zl3073x: Read pin supported frequencies from firmware

Ivan Vecera
2025-04-07T17:33:40Z

The firmware (DT, ACPI...) can specify what frequencies are supported
for particular pins. Load the frequencies from the appropriate property
and use them during pin registation. The unsupported frequencies that
cannot be represented in device are filtered out.

Reviewed-by: Michal Schmidt <mschmidt@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
drivers/dpll/dpll_zl3073x.c | 113 +++++++++++++++++++++++++++++++++++-
1 file changed, 112 insertions(+), 1 deletion(-)

diff --git a/drivers/dpll/dpll_zl3073x.c b/drivers/dpll/dpll_zl3073x.c
index 07a547aaee0f1..c920904008e22 100644
--- a/drivers/dpll/dpll_zl3073x.c
+++ b/drivers/dpll/dpll_zl3073x.c
@@ -1246,6 +1246,50 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
pin_info->props.package_label = pin_info->package_label;
}

+/**
+ * zl3073x_dpll_check_frequency - verify frequency for given pin
+ * @pin: pointer to pin
+ * @freq: frequency to check
+ *
+ * The function checks the given frequency is valid for the device. For input
+ * pins it checks that the frequency can be factorized using supported base
+ * frequencies. For output pins it checks that the frequency divides connected
+ * synth frequency without remainder.
+ *
+ * Returns true if the frequency is valid or false if not.
+ */
+static bool
+zl3073x_dpll_check_frequency(struct zl3073x_dpll_pin *pin, u64 freq)
+{
+ if (zl3073x_dpll_is_input_pin(pin)) {
+ u16 base, mult;
+ int rc;
+
+ /* Check if the frequency can be factorized */
+ rc = zl3073x_dpll_input_ref_frequency_factorize(freq, &base,
+ &mult);
+ if (!rc)
+ return true;
+ } else {
+ struct zl3073x_dev *zldev = pin_to_dev(pin);
+ u64 synth_freq, rem;
+ u8 synth;
+
+ /* Get output pin synthesizer */
+ synth = zl3073x_dpll_pin_synth_get(pin);
+
+ /* Get synth frequency */
+ synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+ /* Check the frequency divides synth frequency */
+ div64_u64_rem(synth_freq, freq, &rem);
+ if (!rem)
+ return true;
+ }
+
+ return false;
+}
+
/**
* zl3073x_dpll_pin_info_get - get pin info
* @pin: pin whose info is returned
@@ -1253,7 +1297,8 @@ zl3073x_dpll_pin_info_package_label_set(struct zl3073x_dpll_pin *pin,
* The function looks for firmware node for the given pin if it is provided
* by the system firmware (DT or ACPI), allocates pin info structure,
* generates package label string according pin type and its order number
- * and optionally fetches board label from the firmware node if it exists.
+ * and optionally fetches board label and supported frequencies from
+ * the firmware node if they exist.
*
* Returns pointer to allocated pin info structure that has to be freed
* by @zl3073x_dpll_pin_info_put by the caller and in case of error
@@ -1264,7 +1309,10 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
{
struct zl3073x_dev *zldev = pin_to_dev(pin);
struct zl3073x_dpll_pin_info *pin_info;
+ struct dpll_pin_frequency *ranges;
+ int i, j, num_freqs, rc;
const char *pin_type;
+ u64 *freqs;

/* Allocate pin info structure */
pin_info = kzalloc(sizeof(*pin_info), GFP_KERNEL);
@@ -1315,7 +1363,67 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
pin_type);
}

+ /* Read supported frequencies property if it is specified */
+ num_freqs = fwnode_property_count_u64(pin_info->fwnode,
+ "supported-frequencies");
+ if (num_freqs <= 0)
+ /* Return if the property does not exist or number is 0 */
+ return pin_info;
+
+ /* The firmware node specifies list of supported frequencies while
+ * DPLL core pin properties requires list of frequency ranges.
+ * So read the frequency list into temporary array.
+ */
+ freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL);
+ if (!freqs) {
+ rc = -ENOMEM;
+ goto err_alloc_freqs;
+ }
+
+ /* Read frequencies list from firmware node */
+ fwnode_property_read_u64_array(pin_info->fwnode,
+ "supported-frequencies", freqs,
+ num_freqs);
+
+ /* Allocate frequency ranges list and fill it */
+ ranges = kcalloc(num_freqs, sizeof(*ranges), GFP_KERNEL);
+ if (!ranges) {
+ rc = -ENOMEM;
+ goto err_alloc_ranges;
+ }
+
+ /* Convert list of frequencies to list of frequency ranges but
+ * filter-out frequencies that are not representable by device
+ */
+ for (i = 0, j = 0; i < num_freqs; i++) {
+ struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
+
+ if (zl3073x_dpll_check_frequency(pin, freqs[i])) {
+ ranges[j] = freq;
+ j++;
+ } else {
+ dev_warn(zldev->dev,
+ "Unsupported frequency %llu Hz in firmware node\n",
+ freqs[i]);
+ }
+ }
+
+ /* Save number of freq ranges and pointer to them into pin properties */
+ pin_info->props.freq_supported = ranges;
+ pin_info->props.freq_supported_num = j;
+
+ /* Free temporary array */
+ kfree(freqs);
+
return pin_info;
+
+err_alloc_ranges:
+ kfree(freqs);
+err_alloc_freqs:
+ fwnode_handle_put(pin_info->fwnode);
+ kfree(pin_info);
+
+ return ERR_PTR(rc);
}

/**
@@ -1327,6 +1435,9 @@ zl3073x_dpll_pin_info_get(struct zl3073x_dpll_pin *pin)
static void
zl3073x_dpll_pin_info_put(struct zl3073x_dpll_pin_info *pin_info)
{
+ /* Free supported frequency ranges list if it is present */
+ kfree(pin_info->props.freq_supported);
+
/* Put firmware handle if it is present */
if (pin_info->fwnode)
fwnode_handle_put(pin_info->fwnode);
--
2.48.1