Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Ivan Vecera 1122 100.00% 5 100.00%
Total 1122 5


// 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", &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 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;
	} 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 */
}