// SPDX-License-Identifier: GPL-2.0-only #include #include #include #include #include #include #include #include #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 - differential input reference * REFP & REFN - single-ended input reference (P or N pin) * OUT - differential output * OUTP & OUTN - 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 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 */ }