blob: 4cf7e8aefcb373b9f4c20684c0d5974be5b1f5d0 [file] [log] [blame]
// 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 */
}