};
 EXPORT_SYMBOL_NS_GPL(zl3073x_regmap_config, "ZL3073X");
 
+/**
+ * zl3073x_ref_freq_factorize - factorize given frequency
+ * @freq: input frequency
+ * @base: base frequency
+ * @mult: multiplier
+ *
+ * Checks if the given frequency can be factorized using one of the
+ * supported base frequencies. If so the base frequency and multiplier
+ * are stored into appropriate parameters if they are not NULL.
+ *
+ * Return: 0 on success, -EINVAL if the frequency cannot be factorized
+ */
+int
+zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult)
+{
+       static const u16 base_freqs[] = {
+               1, 2, 4, 5, 8, 10, 16, 20, 25, 32, 40, 50, 64, 80, 100, 125,
+               128, 160, 200, 250, 256, 320, 400, 500, 625, 640, 800, 1000,
+               1250, 1280, 1600, 2000, 2500, 3125, 3200, 4000, 5000, 6250,
+               6400, 8000, 10000, 12500, 15625, 16000, 20000, 25000, 31250,
+               32000, 40000, 50000, 62500,
+       };
+       u32 div;
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(base_freqs); i++) {
+               div = freq / base_freqs[i];
+
+               if (div <= U16_MAX && (freq % base_freqs[i]) == 0) {
+                       if (base)
+                               *base = base_freqs[i];
+                       if (mult)
+                               *mult = div;
+
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
+}
+
 static bool
 zl3073x_check_reg(struct zl3073x_dev *zldev, unsigned int reg, size_t size)
 {
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/array_size.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/fwnode.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "core.h"
+#include "prop.h"
+
+/**
+ * zl3073x_pin_check_freq - verify frequency for given pin
+ * @zldev: pointer to zl3073x device
+ * @dir: pin direction
+ * @id: pin index
+ * @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.
+ *
+ * Return: true if the frequency is valid, false if not.
+ */
+static bool
+zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir,
+                      u8 id, u64 freq)
+{
+       if (freq > U32_MAX)
+               goto err_inv_freq;
+
+       if (dir == DPLL_PIN_DIRECTION_INPUT) {
+               int rc;
+
+               /* Check if the frequency can be factorized */
+               rc = zl3073x_ref_freq_factorize(freq, NULL, NULL);
+               if (rc)
+                       goto err_inv_freq;
+       } else {
+               u32 synth_freq;
+               u8 out, synth;
+
+               /* Get output pin synthesizer */
+               out = zl3073x_output_pin_out_get(id);
+               synth = zl3073x_out_synth_get(zldev, out);
+
+               /* Get synth frequency */
+               synth_freq = zl3073x_synth_freq_get(zldev, synth);
+
+               /* Check the frequency divides synth frequency */
+               if (synth_freq % (u32)freq)
+                       goto err_inv_freq;
+       }
+
+       return true;
+
+err_inv_freq:
+       dev_warn(zldev->dev,
+                "Unsupported frequency %llu Hz in firmware node\n", freq);
+
+       return false;
+}
+
+/**
+ * zl3073x_prop_pin_package_label_set - get package label for the pin
+ * @zldev: pointer to zl3073x device
+ * @props: pointer to pin properties
+ * @dir: pin direction
+ * @id: pin index
+ *
+ * Generates package label string and stores it into pin properties structure.
+ *
+ * Possible formats:
+ * REF<n> - differential input reference
+ * REF<n>P & REF<n>N - single-ended input reference (P or N pin)
+ * OUT<n> - differential output
+ * OUT<n>P & OUT<n>N - single-ended output (P or N pin)
+ */
+static void
+zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev,
+                                  struct zl3073x_pin_props *props,
+                                  enum dpll_pin_direction dir, u8 id)
+{
+       const char *prefix, *suffix;
+       bool is_diff;
+
+       if (dir == DPLL_PIN_DIRECTION_INPUT) {
+               u8 ref;
+
+               prefix = "REF";
+               ref = zl3073x_input_pin_ref_get(id);
+               is_diff = zl3073x_ref_is_diff(zldev, ref);
+       } else {
+               u8 out;
+
+               prefix = "OUT";
+               out = zl3073x_output_pin_out_get(id);
+               is_diff = zl3073x_out_is_diff(zldev, out);
+       }
+
+       if (!is_diff)
+               suffix = zl3073x_is_p_pin(id) ? "P" : "N";
+       else
+               suffix = ""; /* No suffix for differential one */
+
+       snprintf(props->package_label, sizeof(props->package_label), "%s%u%s",
+                prefix, id / 2, suffix);
+
+       /* Set package_label pointer in DPLL core properties to generated
+        * string.
+        */
+       props->dpll_props.package_label = props->package_label;
+}
+
+/**
+ * zl3073x_prop_pin_fwnode_get - get fwnode for given pin
+ * @zldev: pointer to zl3073x device
+ * @props: pointer to pin properties
+ * @dir: pin direction
+ * @id: pin index
+ *
+ * Return: 0 on success, -ENOENT if the firmware node does not exist
+ */
+static int
+zl3073x_prop_pin_fwnode_get(struct zl3073x_dev *zldev,
+                           struct zl3073x_pin_props *props,
+                           enum dpll_pin_direction dir, u8 id)
+{
+       struct fwnode_handle *pins_node, *pin_node;
+       const char *node_name;
+
+       if (dir == DPLL_PIN_DIRECTION_INPUT)
+               node_name = "input-pins";
+       else
+               node_name = "output-pins";
+
+       /* Get node containing input or output pins */
+       pins_node = device_get_named_child_node(zldev->dev, node_name);
+       if (!pins_node) {
+               dev_dbg(zldev->dev, "'%s' sub-node is missing\n", node_name);
+               return -ENOENT;
+       }
+
+       /* Enumerate child pin nodes and find the requested one */
+       fwnode_for_each_child_node(pins_node, pin_node) {
+               u32 reg;
+
+               if (fwnode_property_read_u32(pin_node, "reg", ®))
+                       continue;
+
+               if (id == reg)
+                       break;
+       }
+
+       /* Release pin parent node */
+       fwnode_handle_put(pins_node);
+
+       /* Save found node */
+       props->fwnode = pin_node;
+
+       dev_dbg(zldev->dev, "Firmware node for %s %sfound\n",
+               props->package_label, pin_node ? "" : "NOT ");
+
+       return pin_node ? 0 : -ENOENT;
+}
+
+/**
+ * zl3073x_pin_props_get - get pin properties
+ * @zldev: pointer to zl3073x device
+ * @dir: pin direction
+ * @index: pin index
+ *
+ * The function looks for firmware node for the given pin if it is provided
+ * by the system firmware (DT or ACPI), allocates pin properties structure,
+ * generates package label string according pin type and optionally fetches
+ * board label, connection type, supported frequencies and esync capability
+ * from the firmware node if it does exist.
+ *
+ * Pointer that is returned by this function should be freed using
+ * @zl3073x_pin_props_put().
+ *
+ * Return:
+ * * pointer to allocated pin properties structure on success
+ * * error pointer in case of error
+ */
+struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
+                                               enum dpll_pin_direction dir,
+                                               u8 index)
+{
+       struct dpll_pin_frequency *ranges;
+       struct zl3073x_pin_props *props;
+       int i, j, num_freqs, rc;
+       const char *type;
+       u64 *freqs;
+
+       props = kzalloc(sizeof(*props), GFP_KERNEL);
+       if (!props)
+               return ERR_PTR(-ENOMEM);
+
+       /* Set default pin type */
+       if (dir == DPLL_PIN_DIRECTION_INPUT)
+               props->dpll_props.type = DPLL_PIN_TYPE_EXT;
+       else
+               props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
+
+       props->dpll_props.phase_range.min = S32_MIN;
+       props->dpll_props.phase_range.max = S32_MAX;
+
+       zl3073x_prop_pin_package_label_set(zldev, props, dir, index);
+
+       /* Get firmware node for the given pin */
+       rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index);
+       if (rc)
+               return props; /* Return if it does not exist */
+
+       /* Look for label property and store the value as board label */
+       fwnode_property_read_string(props->fwnode, "label",
+                                   &props->dpll_props.board_label);
+
+       /* Look for pin type property and translate its value to DPLL
+        * pin type enum if it is present.
+        */
+       if (!fwnode_property_read_string(props->fwnode, "connection-type",
+                                        &type)) {
+               if (!strcmp(type, "ext"))
+                       props->dpll_props.type = DPLL_PIN_TYPE_EXT;
+               else if (!strcmp(type, "gnss"))
+                       props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
+               else if (!strcmp(type, "int"))
+                       props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
+               else if (!strcmp(type, "synce"))
+                       props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
+               else
+                       dev_warn(zldev->dev,
+                                "Unknown or unsupported pin type '%s'\n",
+                                type);
+       }
+
+       /* Check if the pin supports embedded sync control */
+       props->esync_control = fwnode_property_read_bool(props->fwnode,
+                                                        "esync-control");
+
+       /* Read supported frequencies property if it is specified */
+       num_freqs = fwnode_property_count_u64(props->fwnode,
+                                             "supported-frequencies-hz");
+       if (num_freqs <= 0)
+               /* Return if the property does not exist or number is 0 */
+               return props;
+
+       /* 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(props->fwnode,
+                                      "supported-frequencies-hz", 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_pin_check_freq(zldev, dir, index, freqs[i])) {
+                       ranges[j] = freq;
+                       j++;
+               }
+       }
+
+       /* Save number of freq ranges and pointer to them into pin properties */
+       props->dpll_props.freq_supported = ranges;
+       props->dpll_props.freq_supported_num = j;
+
+       /* Free temporary array */
+       kfree(freqs);
+
+       return props;
+
+err_alloc_ranges:
+       kfree(freqs);
+err_alloc_freqs:
+       fwnode_handle_put(props->fwnode);
+       kfree(props);
+
+       return ERR_PTR(rc);
+}
+
+/**
+ * zl3073x_pin_props_put - release pin properties
+ * @props: pin properties to free
+ *
+ * The function deallocates given pin properties structure.
+ */
+void zl3073x_pin_props_put(struct zl3073x_pin_props *props)
+{
+       /* Free supported frequency ranges list if it is present */
+       kfree(props->dpll_props.freq_supported);
+
+       /* Put firmware handle if it is present */
+       if (props->fwnode)
+               fwnode_handle_put(props->fwnode);
+
+       kfree(props);
+}
+
+/**
+ * zl3073x_prop_dpll_type_get - get DPLL channel type
+ * @zldev: pointer to zl3073x device
+ * @index: DPLL channel index
+ *
+ * Return: DPLL type for given DPLL channel
+ */
+enum dpll_type
+zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index)
+{
+       const char *types[ZL3073X_MAX_CHANNELS];
+       int count;
+
+       /* Read dpll types property from firmware */
+       count = device_property_read_string_array(zldev->dev, "dpll-types",
+                                                 types, ARRAY_SIZE(types));
+
+       /* Return default if property or entry for given channel is missing */
+       if (index >= count)
+               return DPLL_TYPE_PPS;
+
+       if (!strcmp(types[index], "pps"))
+               return DPLL_TYPE_PPS;
+       else if (!strcmp(types[index], "eec"))
+               return DPLL_TYPE_EEC;
+
+       dev_info(zldev->dev, "Unknown DPLL type '%s', using default\n",
+                types[index]);
+
+       return DPLL_TYPE_PPS; /* Default */
+}