xref: /linux/drivers/dpll/zl3073x/prop.c (revision 8be4d31cb8aaeea27bde4b7ddb26e28a89062ebf)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/array_size.h>
4 #include <linux/dev_printk.h>
5 #include <linux/err.h>
6 #include <linux/errno.h>
7 #include <linux/fwnode.h>
8 #include <linux/property.h>
9 #include <linux/slab.h>
10 #include <linux/string.h>
11 
12 #include "core.h"
13 #include "prop.h"
14 
15 /**
16  * zl3073x_pin_check_freq - verify frequency for given pin
17  * @zldev: pointer to zl3073x device
18  * @dir: pin direction
19  * @id: pin index
20  * @freq: frequency to check
21  *
22  * The function checks the given frequency is valid for the device. For input
23  * pins it checks that the frequency can be factorized using supported base
24  * frequencies. For output pins it checks that the frequency divides connected
25  * synth frequency without remainder.
26  *
27  * Return: true if the frequency is valid, false if not.
28  */
29 static bool
zl3073x_pin_check_freq(struct zl3073x_dev * zldev,enum dpll_pin_direction dir,u8 id,u64 freq)30 zl3073x_pin_check_freq(struct zl3073x_dev *zldev, enum dpll_pin_direction dir,
31 		       u8 id, u64 freq)
32 {
33 	if (freq > U32_MAX)
34 		goto err_inv_freq;
35 
36 	if (dir == DPLL_PIN_DIRECTION_INPUT) {
37 		int rc;
38 
39 		/* Check if the frequency can be factorized */
40 		rc = zl3073x_ref_freq_factorize(freq, NULL, NULL);
41 		if (rc)
42 			goto err_inv_freq;
43 	} else {
44 		u32 synth_freq;
45 		u8 out, synth;
46 
47 		/* Get output pin synthesizer */
48 		out = zl3073x_output_pin_out_get(id);
49 		synth = zl3073x_out_synth_get(zldev, out);
50 
51 		/* Get synth frequency */
52 		synth_freq = zl3073x_synth_freq_get(zldev, synth);
53 
54 		/* Check the frequency divides synth frequency */
55 		if (synth_freq % (u32)freq)
56 			goto err_inv_freq;
57 	}
58 
59 	return true;
60 
61 err_inv_freq:
62 	dev_warn(zldev->dev,
63 		 "Unsupported frequency %llu Hz in firmware node\n", freq);
64 
65 	return false;
66 }
67 
68 /**
69  * zl3073x_prop_pin_package_label_set - get package label for the pin
70  * @zldev: pointer to zl3073x device
71  * @props: pointer to pin properties
72  * @dir: pin direction
73  * @id: pin index
74  *
75  * Generates package label string and stores it into pin properties structure.
76  *
77  * Possible formats:
78  * REF<n> - differential input reference
79  * REF<n>P & REF<n>N - single-ended input reference (P or N pin)
80  * OUT<n> - differential output
81  * OUT<n>P & OUT<n>N - single-ended output (P or N pin)
82  */
83 static void
zl3073x_prop_pin_package_label_set(struct zl3073x_dev * zldev,struct zl3073x_pin_props * props,enum dpll_pin_direction dir,u8 id)84 zl3073x_prop_pin_package_label_set(struct zl3073x_dev *zldev,
85 				   struct zl3073x_pin_props *props,
86 				   enum dpll_pin_direction dir, u8 id)
87 {
88 	const char *prefix, *suffix;
89 	bool is_diff;
90 
91 	if (dir == DPLL_PIN_DIRECTION_INPUT) {
92 		u8 ref;
93 
94 		prefix = "REF";
95 		ref = zl3073x_input_pin_ref_get(id);
96 		is_diff = zl3073x_ref_is_diff(zldev, ref);
97 	} else {
98 		u8 out;
99 
100 		prefix = "OUT";
101 		out = zl3073x_output_pin_out_get(id);
102 		is_diff = zl3073x_out_is_diff(zldev, out);
103 	}
104 
105 	if (!is_diff)
106 		suffix = zl3073x_is_p_pin(id) ? "P" : "N";
107 	else
108 		suffix = ""; /* No suffix for differential one */
109 
110 	snprintf(props->package_label, sizeof(props->package_label), "%s%u%s",
111 		 prefix, id / 2, suffix);
112 
113 	/* Set package_label pointer in DPLL core properties to generated
114 	 * string.
115 	 */
116 	props->dpll_props.package_label = props->package_label;
117 }
118 
119 /**
120  * zl3073x_prop_pin_fwnode_get - get fwnode for given pin
121  * @zldev: pointer to zl3073x device
122  * @props: pointer to pin properties
123  * @dir: pin direction
124  * @id: pin index
125  *
126  * Return: 0 on success, -ENOENT if the firmware node does not exist
127  */
128 static int
zl3073x_prop_pin_fwnode_get(struct zl3073x_dev * zldev,struct zl3073x_pin_props * props,enum dpll_pin_direction dir,u8 id)129 zl3073x_prop_pin_fwnode_get(struct zl3073x_dev *zldev,
130 			    struct zl3073x_pin_props *props,
131 			    enum dpll_pin_direction dir, u8 id)
132 {
133 	struct fwnode_handle *pins_node, *pin_node;
134 	const char *node_name;
135 
136 	if (dir == DPLL_PIN_DIRECTION_INPUT)
137 		node_name = "input-pins";
138 	else
139 		node_name = "output-pins";
140 
141 	/* Get node containing input or output pins */
142 	pins_node = device_get_named_child_node(zldev->dev, node_name);
143 	if (!pins_node) {
144 		dev_dbg(zldev->dev, "'%s' sub-node is missing\n", node_name);
145 		return -ENOENT;
146 	}
147 
148 	/* Enumerate child pin nodes and find the requested one */
149 	fwnode_for_each_child_node(pins_node, pin_node) {
150 		u32 reg;
151 
152 		if (fwnode_property_read_u32(pin_node, "reg", &reg))
153 			continue;
154 
155 		if (id == reg)
156 			break;
157 	}
158 
159 	/* Release pin parent node */
160 	fwnode_handle_put(pins_node);
161 
162 	/* Save found node */
163 	props->fwnode = pin_node;
164 
165 	dev_dbg(zldev->dev, "Firmware node for %s %sfound\n",
166 		props->package_label, pin_node ? "" : "NOT ");
167 
168 	return pin_node ? 0 : -ENOENT;
169 }
170 
171 /**
172  * zl3073x_pin_props_get - get pin properties
173  * @zldev: pointer to zl3073x device
174  * @dir: pin direction
175  * @index: pin index
176  *
177  * The function looks for firmware node for the given pin if it is provided
178  * by the system firmware (DT or ACPI), allocates pin properties structure,
179  * generates package label string according pin type and optionally fetches
180  * board label, connection type, supported frequencies and esync capability
181  * from the firmware node if it does exist.
182  *
183  * Pointer that is returned by this function should be freed using
184  * @zl3073x_pin_props_put().
185  *
186  * Return:
187  * * pointer to allocated pin properties structure on success
188  * * error pointer in case of error
189  */
zl3073x_pin_props_get(struct zl3073x_dev * zldev,enum dpll_pin_direction dir,u8 index)190 struct zl3073x_pin_props *zl3073x_pin_props_get(struct zl3073x_dev *zldev,
191 						enum dpll_pin_direction dir,
192 						u8 index)
193 {
194 	struct dpll_pin_frequency *ranges;
195 	struct zl3073x_pin_props *props;
196 	int i, j, num_freqs, rc;
197 	const char *type;
198 	u64 *freqs;
199 
200 	props = kzalloc(sizeof(*props), GFP_KERNEL);
201 	if (!props)
202 		return ERR_PTR(-ENOMEM);
203 
204 	/* Set default pin type and capabilities */
205 	if (dir == DPLL_PIN_DIRECTION_INPUT) {
206 		props->dpll_props.type = DPLL_PIN_TYPE_EXT;
207 		props->dpll_props.capabilities =
208 			DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
209 			DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE;
210 	} else {
211 		props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
212 	}
213 
214 	props->dpll_props.phase_range.min = S32_MIN;
215 	props->dpll_props.phase_range.max = S32_MAX;
216 
217 	zl3073x_prop_pin_package_label_set(zldev, props, dir, index);
218 
219 	/* Get firmware node for the given pin */
220 	rc = zl3073x_prop_pin_fwnode_get(zldev, props, dir, index);
221 	if (rc)
222 		return props; /* Return if it does not exist */
223 
224 	/* Look for label property and store the value as board label */
225 	fwnode_property_read_string(props->fwnode, "label",
226 				    &props->dpll_props.board_label);
227 
228 	/* Look for pin type property and translate its value to DPLL
229 	 * pin type enum if it is present.
230 	 */
231 	if (!fwnode_property_read_string(props->fwnode, "connection-type",
232 					 &type)) {
233 		if (!strcmp(type, "ext"))
234 			props->dpll_props.type = DPLL_PIN_TYPE_EXT;
235 		else if (!strcmp(type, "gnss"))
236 			props->dpll_props.type = DPLL_PIN_TYPE_GNSS;
237 		else if (!strcmp(type, "int"))
238 			props->dpll_props.type = DPLL_PIN_TYPE_INT_OSCILLATOR;
239 		else if (!strcmp(type, "synce"))
240 			props->dpll_props.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT;
241 		else
242 			dev_warn(zldev->dev,
243 				 "Unknown or unsupported pin type '%s'\n",
244 				 type);
245 	}
246 
247 	/* Check if the pin supports embedded sync control */
248 	props->esync_control = fwnode_property_read_bool(props->fwnode,
249 							 "esync-control");
250 
251 	/* Read supported frequencies property if it is specified */
252 	num_freqs = fwnode_property_count_u64(props->fwnode,
253 					      "supported-frequencies-hz");
254 	if (num_freqs <= 0)
255 		/* Return if the property does not exist or number is 0 */
256 		return props;
257 
258 	/* The firmware node specifies list of supported frequencies while
259 	 * DPLL core pin properties requires list of frequency ranges.
260 	 * So read the frequency list into temporary array.
261 	 */
262 	freqs = kcalloc(num_freqs, sizeof(*freqs), GFP_KERNEL);
263 	if (!freqs) {
264 		rc = -ENOMEM;
265 		goto err_alloc_freqs;
266 	}
267 
268 	/* Read frequencies list from firmware node */
269 	fwnode_property_read_u64_array(props->fwnode,
270 				       "supported-frequencies-hz", freqs,
271 				       num_freqs);
272 
273 	/* Allocate frequency ranges list and fill it */
274 	ranges = kcalloc(num_freqs, sizeof(*ranges), GFP_KERNEL);
275 	if (!ranges) {
276 		rc = -ENOMEM;
277 		goto err_alloc_ranges;
278 	}
279 
280 	/* Convert list of frequencies to list of frequency ranges but
281 	 * filter-out frequencies that are not representable by device
282 	 */
283 	for (i = 0, j = 0; i < num_freqs; i++) {
284 		struct dpll_pin_frequency freq = DPLL_PIN_FREQUENCY(freqs[i]);
285 
286 		if (zl3073x_pin_check_freq(zldev, dir, index, freqs[i])) {
287 			ranges[j] = freq;
288 			j++;
289 		}
290 	}
291 
292 	/* Save number of freq ranges and pointer to them into pin properties */
293 	props->dpll_props.freq_supported = ranges;
294 	props->dpll_props.freq_supported_num = j;
295 
296 	/* Free temporary array */
297 	kfree(freqs);
298 
299 	return props;
300 
301 err_alloc_ranges:
302 	kfree(freqs);
303 err_alloc_freqs:
304 	fwnode_handle_put(props->fwnode);
305 	kfree(props);
306 
307 	return ERR_PTR(rc);
308 }
309 
310 /**
311  * zl3073x_pin_props_put - release pin properties
312  * @props: pin properties to free
313  *
314  * The function deallocates given pin properties structure.
315  */
zl3073x_pin_props_put(struct zl3073x_pin_props * props)316 void zl3073x_pin_props_put(struct zl3073x_pin_props *props)
317 {
318 	/* Free supported frequency ranges list if it is present */
319 	kfree(props->dpll_props.freq_supported);
320 
321 	/* Put firmware handle if it is present */
322 	if (props->fwnode)
323 		fwnode_handle_put(props->fwnode);
324 
325 	kfree(props);
326 }
327 
328 /**
329  * zl3073x_prop_dpll_type_get - get DPLL channel type
330  * @zldev: pointer to zl3073x device
331  * @index: DPLL channel index
332  *
333  * Return: DPLL type for given DPLL channel
334  */
335 enum dpll_type
zl3073x_prop_dpll_type_get(struct zl3073x_dev * zldev,u8 index)336 zl3073x_prop_dpll_type_get(struct zl3073x_dev *zldev, u8 index)
337 {
338 	const char *types[ZL3073X_MAX_CHANNELS];
339 	int count;
340 
341 	/* Read dpll types property from firmware */
342 	count = device_property_read_string_array(zldev->dev, "dpll-types",
343 						  types, ARRAY_SIZE(types));
344 
345 	/* Return default if property or entry for given channel is missing */
346 	if (index >= count)
347 		return DPLL_TYPE_PPS;
348 
349 	if (!strcmp(types[index], "pps"))
350 		return DPLL_TYPE_PPS;
351 	else if (!strcmp(types[index], "eec"))
352 		return DPLL_TYPE_EEC;
353 
354 	dev_info(zldev->dev, "Unknown DPLL type '%s', using default\n",
355 		 types[index]);
356 
357 	return DPLL_TYPE_PPS; /* Default */
358 }
359