root/drivers/dpll/zl3073x/prop.c
// 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_dev_out_synth_get(zldev, out);

                /* Get synth frequency */
                synth_freq = zl3073x_dev_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_dev_ref_is_diff(zldev, ref);
        } else {
                u8 out;

                prefix = "OUT";
                out = zl3073x_output_pin_out_get(id);
                is_diff = zl3073x_dev_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", &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 = 0, rc;
        u64 *freqs = NULL;
        const char *type;
        u32 curr_freq;

        props = kzalloc_obj(*props);
        if (!props)
                return ERR_PTR(-ENOMEM);

        /* Set default pin type and capabilities */
        if (dir == DPLL_PIN_DIRECTION_INPUT) {
                props->dpll_props.type = DPLL_PIN_TYPE_EXT;
                props->dpll_props.capabilities =
                        DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
                        DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
                curr_freq = zl3073x_dev_ref_freq_get(zldev, index);
        } else {
                u8 out, synth;
                u32 f;

                props->dpll_props.type = DPLL_PIN_TYPE_GNSS;

                /* The output pin phase adjustment granularity equals half of
                 * the synth frequency count.
                 */
                out = zl3073x_output_pin_out_get(index);
                synth = zl3073x_dev_out_synth_get(zldev, out);
                f = 2 * zl3073x_dev_synth_freq_get(zldev, synth);
                props->dpll_props.phase_gran = f ? div_u64(PSEC_PER_SEC, f) : 1;
                curr_freq = zl3073x_dev_output_pin_freq_get(zldev, index);
        }

        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)
                goto skip_fwnode_props;

        /* 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 if (!strcmp(type, "mux"))
                        props->dpll_props.type = DPLL_PIN_TYPE_MUX;
                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) {
                num_freqs = 0;
                goto skip_fwnode_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);

skip_fwnode_props:
        /* Allocate frequency ranges list - extra slot for current frequency */
        ranges = kzalloc_objs(*ranges, num_freqs + 1);
        if (!ranges) {
                rc = -ENOMEM;
                goto err_alloc_ranges;
        }

        /* Start with current frequency at index 0 */
        ranges[0] = (struct dpll_pin_frequency)DPLL_PIN_FREQUENCY(curr_freq);

        /* Add frequencies from firmware node, skipping current frequency
         * and filtering out frequencies not representable by device
         */
        for (i = 0, j = 1; i < num_freqs; i++) {
                struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);

                if (freqs[i] == curr_freq)
                        continue;
                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 */
}