| // 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", ®)) | |
| 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 */ | |
| } |