xref: /linux/drivers/dpll/zl3073x/chan.c (revision 91ec2035134982b98fab0609a9fd8480e8217dc1)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/cleanup.h>
4 #include <linux/delay.h>
5 #include <linux/dev_printk.h>
6 #include <linux/ptp_clock_kernel.h>
7 #include <linux/string.h>
8 #include <linux/types.h>
9 
10 #include "chan.h"
11 #include "core.h"
12 
13 /**
14  * zl3073x_chan_state_update - update DPLL channel status from HW
15  * @zldev: pointer to zl3073x_dev structure
16  * @index: DPLL channel index
17  *
18  * Return: 0 on success, <0 on error
19  */
zl3073x_chan_state_update(struct zl3073x_dev * zldev,u8 index)20 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
21 {
22 	struct zl3073x_chan *chan = &zldev->chan[index];
23 	u64 val;
24 	int rc;
25 
26 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
27 			     &chan->mon_status);
28 	if (rc)
29 		return rc;
30 
31 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
32 			     &chan->refsel_status);
33 	if (rc)
34 		return rc;
35 
36 	/* Read df_offset only when locked to a reference. In NCO mode
37 	 * df_offset was captured at entry by nco_mode_set() - preserve it.
38 	 */
39 	if (!zl3073x_chan_is_locked(chan)) {
40 		if (!zl3073x_chan_mode_is_nco(chan))
41 			chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
42 		return 0;
43 	}
44 
45 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
46 				  ZL_DPLL_DF_READ_SEM,
47 				  ZL_POLL_DF_READ_TIMEOUT_US);
48 	if (rc)
49 		return rc;
50 
51 	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
52 			      ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
53 	if (rc)
54 		return rc;
55 
56 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
57 				  ZL_DPLL_DF_READ_SEM,
58 				  ZL_POLL_DF_READ_TIMEOUT_US);
59 	if (rc)
60 		return rc;
61 
62 	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
63 	if (rc)
64 		return rc;
65 
66 	chan->df_offset = sign_extend64(val, 47);
67 
68 	return 0;
69 }
70 
71 /**
72  * zl3073x_chan_nco_mode_set - switch DPLL channel to NCO mode
73  * @zldev: pointer to zl3073x_dev structure
74  * @index: DPLL channel index
75  *
76  * Switches the channel to NCO mode and reads the df_offset
77  * auto-captured by nco_auto_read directly from the register.
78  * No DF_READ handshake is needed as nco_auto_read populates
79  * the register before the mode switch completes.
80  *
81  * Return: 0 on success, <0 on error
82  */
zl3073x_chan_nco_mode_set(struct zl3073x_dev * zldev,u8 index)83 int zl3073x_chan_nco_mode_set(struct zl3073x_dev *zldev, u8 index)
84 {
85 	struct zl3073x_chan *chan = &zldev->chan[index];
86 	u8 prev_mode, df_read;
87 	u64 val;
88 	int rc;
89 
90 	prev_mode = zl3073x_chan_mode_get(chan);
91 
92 	/* nco_auto_read captures the tracking offset at NCO entry only
93 	 * from reflock, auto or holdover mode. From freerun the captured
94 	 * value is not meaningful.
95 	 */
96 	if (prev_mode == ZL_DPLL_MODE_REFSEL_MODE_FREERUN) {
97 		zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
98 
99 		rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
100 				      chan->mode_refsel);
101 		if (rc) {
102 			zl3073x_chan_mode_set(chan, prev_mode);
103 			return rc;
104 		}
105 
106 		chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
107 		return 0;
108 	}
109 
110 	/* Configure df_read for nco_auto_read:
111 	 * ref_ofst=0 - reads offset relative to master clock (not input ref)
112 	 * cmd=CMD_ACC_I - accumulated I-part covering both locked and
113 	 *                 holdover entry.
114 	 *
115 	 * No semaphore is set - this only configures what the df_offset
116 	 * value represents after the mode switch; nco_auto_read performs
117 	 * the actual read automatically.
118 	 */
119 	df_read = FIELD_PREP(ZL_DPLL_DF_READ_REF_OFST, 0) |
120 		  FIELD_PREP(ZL_DPLL_DF_READ_CMD, ZL_DPLL_DF_READ_CMD_ACC_I);
121 	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index), df_read);
122 	if (rc)
123 		return rc;
124 
125 	/* Wait for df_read configuration to take effect before
126 	 * triggering nco_auto_read via mode switch. The worst-case
127 	 * internal register update time is 25 ms.
128 	 */
129 	fsleep(25000);
130 
131 	zl3073x_chan_mode_set(chan, ZL_DPLL_MODE_REFSEL_MODE_NCO);
132 	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
133 			      chan->mode_refsel);
134 	if (rc) {
135 		zl3073x_chan_mode_set(chan, prev_mode);
136 		return rc;
137 	}
138 
139 	/* Wait for nco_auto_read to populate df_offset. The worst-case
140 	 * internal register update time is 25 ms.
141 	 */
142 	fsleep(25000);
143 
144 	/* Read df_offset captured by nco_auto_read during mode switch.
145 	 * No DF_READ semaphore handshake needed. Mode switch already
146 	 * succeeded, so don't propagate a read failure back to userspace.
147 	 */
148 	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
149 	if (rc) {
150 		dev_warn(zldev->dev,
151 			 "Failed to read DPLL%u df_offset: %pe\n",
152 			 index, ERR_PTR(rc));
153 		chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
154 	} else {
155 		chan->df_offset = sign_extend64(val, 47);
156 	}
157 
158 	return 0;
159 }
160 
161 /**
162  * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
163  * @zldev: pointer to zl3073x_dev structure
164  * @index: DPLL channel index to fetch state for
165  *
166  * Reads the mode_refsel, status and reference priority registers for
167  * the given DPLL channel and stores the values for later use.
168  *
169  * Return: 0 on success, <0 on error
170  */
zl3073x_chan_state_fetch(struct zl3073x_dev * zldev,u8 index)171 int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
172 {
173 	struct zl3073x_chan *chan = &zldev->chan[index];
174 	int rc, i;
175 
176 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_CTRL(index), &chan->ctrl);
177 	if (rc)
178 		return rc;
179 
180 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
181 			     &chan->mode_refsel);
182 	if (rc)
183 		return rc;
184 
185 	dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
186 		zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
187 
188 	rc = zl3073x_chan_state_update(zldev, index);
189 	if (rc)
190 		return rc;
191 
192 	/* If firmware left the channel in NCO mode, mark df_offset as
193 	 * unknown - we cannot know whether the preconditions for a valid
194 	 * nco_auto_read capture were met.
195 	 */
196 	if (zl3073x_chan_mode_is_nco(chan))
197 		chan->df_offset = ZL_DPLL_DF_OFFSET_UNKNOWN;
198 
199 	dev_dbg(zldev->dev,
200 		"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
201 		index, zl3073x_chan_lock_state_get(chan),
202 		zl3073x_chan_is_ho_ready(chan) ? 1 : 0,
203 		zl3073x_chan_refsel_state_get(chan),
204 		zl3073x_chan_refsel_ref_get(chan));
205 
206 	guard(mutex)(&zldev->multiop_lock);
207 
208 	/* Read DPLL configuration from mailbox */
209 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
210 			   ZL_REG_DPLL_MB_MASK, BIT(index));
211 	if (rc)
212 		return rc;
213 
214 	/* Read reference priority registers */
215 	for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
216 		rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),
217 				     &chan->ref_prio[i]);
218 		if (rc)
219 			return rc;
220 	}
221 
222 	return 0;
223 }
224 
225 /**
226  * zl3073x_chan_state_get - get current DPLL channel state
227  * @zldev: pointer to zl3073x_dev structure
228  * @index: DPLL channel index to get state for
229  *
230  * Return: pointer to given DPLL channel state
231  */
zl3073x_chan_state_get(struct zl3073x_dev * zldev,u8 index)232 const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
233 						  u8 index)
234 {
235 	return &zldev->chan[index];
236 }
237 
238 /**
239  * zl3073x_chan_tod_ready_wait - wait for ToD semaphore to clear
240  * @zldev: pointer to zl3073x device
241  * @ch: DPLL channel index
242  *
243  * Checks the ToD control register semaphore bit. If clear, returns
244  * immediately. Otherwise polls until the bit is cleared by the device.
245  *
246  * Return:
247  * * 0			- success
248  * * %-EBUSY		- timeout
249  * * %-EOPNOTSUPP	- unknown command detected
250  * * negative		- other error
251  */
zl3073x_chan_tod_ready_wait(struct zl3073x_dev * zldev,u8 ch)252 int zl3073x_chan_tod_ready_wait(struct zl3073x_dev *zldev, u8 ch)
253 {
254 	unsigned int timeout;
255 	u8 tod_ctrl;
256 	int rc;
257 
258 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch), &tod_ctrl);
259 	if (rc)
260 		return rc;
261 
262 	if (!(tod_ctrl & ZL_DPLL_TOD_CTRL_SEM))
263 		return 0;
264 
265 	switch (FIELD_GET(ZL_DPLL_TOD_CTRL_CMD, tod_ctrl)) {
266 	case ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ:
267 		timeout = ZL_POLL_TOD_WR_TIMEOUT_US;
268 		break;
269 	case ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT:
270 	case ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ:
271 		timeout = ZL_POLL_TOD_RD_TIMEOUT_US;
272 		break;
273 	default:
274 		return -EOPNOTSUPP;
275 	}
276 
277 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
278 				  ZL_DPLL_TOD_CTRL_SEM, timeout);
279 
280 	return rc == -ETIMEDOUT ? -EBUSY : rc;
281 }
282 
283 /**
284  * zl3073x_chan_tod_ctrl - issue ToD command
285  * @zldev: pointer to zl3073x device
286  * @ch: DPLL channel index
287  * @cmd: ToD command to execute
288  *
289  * Writes the semaphore and command to dpll_tod_ctrl. The caller must
290  * ensure the device is ready (semaphore clear) before calling and
291  * must wait for completion if needed.
292  *
293  * Return: 0 on success, <0 on error
294  */
zl3073x_chan_tod_ctrl(struct zl3073x_dev * zldev,u8 ch,u8 cmd)295 static int zl3073x_chan_tod_ctrl(struct zl3073x_dev *zldev, u8 ch, u8 cmd)
296 {
297 	return zl3073x_write_u8(zldev, ZL_REG_DPLL_TOD_CTRL(ch),
298 				ZL_DPLL_TOD_CTRL_SEM | cmd);
299 }
300 
301 /**
302  * zl3073x_chan_tod_read - read ToD registers after issuing a command
303  * @zldev: pointer to zl3073x device
304  * @ch: DPLL channel index
305  * @next_hz: if true, read predicted ToD at next 1 Hz; otherwise read current
306  * @ts: timespec to store the result
307  * @sts: optional system timestamp pair for cross-timestamping
308  *
309  * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
310  * Return: 0 on success, <0 on error
311  */
zl3073x_chan_tod_read(struct zl3073x_dev * zldev,u8 ch,bool next_hz,struct timespec64 * ts,struct ptp_system_timestamp * sts)312 int zl3073x_chan_tod_read(struct zl3073x_dev *zldev, u8 ch,
313 			  bool next_hz, struct timespec64 *ts,
314 			  struct ptp_system_timestamp *sts)
315 {
316 	u32 nsec;
317 	u64 sec;
318 	u8 cmd;
319 	int rc;
320 
321 	if (next_hz)
322 		cmd = ZL_DPLL_TOD_CTRL_CMD_RD_NEXT_1HZ;
323 	else
324 		cmd = ZL_DPLL_TOD_CTRL_CMD_RD_CURRENT;
325 
326 	/* Wait for any previous ToD operation to complete */
327 	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
328 	if (rc)
329 		return rc;
330 
331 	ptp_read_system_prets(sts);
332 	rc = zl3073x_chan_tod_ctrl(zldev, ch, cmd);
333 	if (rc)
334 		return rc;
335 
336 	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
337 	if (rc)
338 		return rc;
339 	ptp_read_system_postts(sts);
340 
341 	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), &sec);
342 	if (rc)
343 		return rc;
344 
345 	/* HW nanoseconds are always in [0, NSEC_PER_SEC) range */
346 	rc = zl3073x_read_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), &nsec);
347 	if (rc)
348 		return rc;
349 
350 	ts->tv_sec = sec;
351 	ts->tv_nsec = nsec;
352 
353 	return 0;
354 }
355 
356 /**
357  * zl3073x_chan_tod_write - write ToD registers and trigger 1 Hz update
358  * @zldev: pointer to zl3073x device
359  * @ch: DPLL channel index
360  * @ts: time to set
361  *
362  * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
363  * Return: 0 on success, <0 on error
364  */
zl3073x_chan_tod_write(struct zl3073x_dev * zldev,u8 ch,struct timespec64 ts)365 int zl3073x_chan_tod_write(struct zl3073x_dev *zldev, u8 ch,
366 			   struct timespec64 ts)
367 {
368 	int rc;
369 
370 	/* Wait for any previous ToD operation to complete */
371 	rc = zl3073x_chan_tod_ready_wait(zldev, ch);
372 	if (rc)
373 		return rc;
374 
375 	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TOD_SEC(ch), ts.tv_sec);
376 	if (rc)
377 		return rc;
378 
379 	rc = zl3073x_write_u32(zldev, ZL_REG_DPLL_TOD_NS(ch), ts.tv_nsec);
380 	if (rc)
381 		return rc;
382 
383 	return zl3073x_chan_tod_ctrl(zldev, ch,
384 				     ZL_DPLL_TOD_CTRL_CMD_WR_NEXT_1HZ);
385 }
386 
387 /**
388  * zl3073x_chan_tod_adjust - atomic ToD read-modify-write with rollover guard
389  * @zldev: pointer to zl3073x device
390  * @ch: DPLL channel index
391  * @delta: time adjustment to apply
392  *
393  * Reads the next-Hz ToD and current ToD, then checks whether enough time
394  * remains before the next 1 Hz rollover to safely complete the write.
395  * Re-reads if the 1 Hz tick crossed between the two reads or if less
396  * than 20 ms remains before the next rollover. Applies @delta and writes
397  * the result back.
398  *
399  * Context: Caller must serialize all zl3073x_chan_tod_* calls externally.
400  * Return: 0 on success, <0 on error
401  */
zl3073x_chan_tod_adjust(struct zl3073x_dev * zldev,u8 ch,struct timespec64 delta)402 int zl3073x_chan_tod_adjust(struct zl3073x_dev *zldev, u8 ch,
403 			    struct timespec64 delta)
404 {
405 #define ZL_TOD_MAX_RETRIES	20
406 	static const long threshold_ns = 20 * NSEC_PER_MSEC;
407 	struct timespec64 ts_next, ts_cur, diff;
408 	int rc, i;
409 
410 	for (i = 0; i < ZL_TOD_MAX_RETRIES; i++) {
411 		rc = zl3073x_chan_tod_read(zldev, ch, true, &ts_next, NULL);
412 		if (rc)
413 			return rc;
414 
415 		rc = zl3073x_chan_tod_read(zldev, ch, false, &ts_cur, NULL);
416 		if (rc)
417 			return rc;
418 
419 		/* Ensure the 1 Hz tick did not cross between the two reads
420 		 * and that enough margin remains to complete the write.
421 		 */
422 		diff = timespec64_sub(ts_next, ts_cur);
423 		if (diff.tv_sec > 0 ||
424 		    (!diff.tv_sec && diff.tv_nsec >= threshold_ns))
425 			break;
426 	}
427 	if (i == ZL_TOD_MAX_RETRIES) {
428 		dev_warn(zldev->dev,
429 			 "DPLL%u ToD adjust failed to get stable margin\n",
430 			 ch);
431 		return -EBUSY;
432 	}
433 
434 	/* Apply delta to the next-Hz ToD */
435 	ts_next = timespec64_add(ts_next, delta);
436 	if (!timespec64_valid_settod(&ts_next))
437 		return -EINVAL;
438 
439 	return zl3073x_chan_tod_write(zldev, ch, ts_next);
440 #undef ZL_TOD_MAX_RETRIES
441 }
442 
443 /**
444  * zl3073x_chan_df_offset_set - write delta frequency offset to hardware
445  * @zldev: pointer to zl3073x device
446  * @ch: DPLL channel index
447  * @offset: frequency offset in 2^-48 steps
448  *
449  * Context: Caller must hold the per-DPLL lock.
450  * Return: 0 on success, <0 on error
451  */
zl3073x_chan_df_offset_set(struct zl3073x_dev * zldev,u8 ch,s64 offset)452 int zl3073x_chan_df_offset_set(struct zl3073x_dev *zldev, u8 ch, s64 offset)
453 {
454 	int rc;
455 
456 	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_DF_OFFSET(ch), offset);
457 	if (!rc)
458 		zldev->chan[ch].df_offset = offset;
459 
460 	return rc;
461 }
462 
463 /**
464  * zl3073x_chan_tie_write - adjust DPLL phase using TIE write
465  * @zldev: pointer to zl3073x device
466  * @ch: DPLL channel index
467  * @delta_ns: phase adjustment in nanoseconds (must be in (-1s, 1s))
468  *
469  * Converts nanoseconds to TIE units (0.01 ps) and writes TIE data
470  * to the specified channel.
471  *
472  * Return: 0 on success, <0 on error
473  */
zl3073x_chan_tie_write(struct zl3073x_dev * zldev,u8 ch,s64 delta_ns)474 int zl3073x_chan_tie_write(struct zl3073x_dev *zldev, u8 ch, s64 delta_ns)
475 {
476 	s64 tie_data;
477 	int rc;
478 
479 	guard(mutex)(&zldev->tie_lock);
480 
481 	/* Wait for any previous TIE operation to complete */
482 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
483 				  ZL_DPLL_TIE_CTRL_OP,
484 				  ZL_POLL_TIE_WR_TIMEOUT_US);
485 	if (rc)
486 		return rc;
487 
488 	/* Convert ns to TIE units (0.01 ps = 10^-14 s) */
489 	tie_data = delta_ns * 100000LL;
490 
491 	rc = zl3073x_write_u48(zldev, ZL_REG_DPLL_TIE_DATA(ch), tie_data);
492 	if (rc)
493 		return rc;
494 
495 	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL_MASK, BIT(ch));
496 	if (rc)
497 		return rc;
498 
499 	return zl3073x_write_u8(zldev, ZL_REG_DPLL_TIE_CTRL,
500 				ZL_DPLL_TIE_CTRL_OP_WR);
501 }
502 
503 /**
504  * zl3073x_chan_phase_step - execute one output phase step operation
505  * @zldev: pointer to zl3073x device
506  * @ch: DPLL channel index
507  * @out_mask: bitmask of outputs to step
508  * @step_cycles: phase step in synthesizer clock cycles
509  * @tod_step: also step the ToD counter
510  *
511  * All masked outputs must use synthesizers of the same frequency since
512  * the step value is in synthesizer clock cycles.
513  *
514  * Return: 0 on success, <0 on error
515  */
zl3073x_chan_phase_step(struct zl3073x_dev * zldev,u8 ch,u16 out_mask,s32 step_cycles,bool tod_step)516 int zl3073x_chan_phase_step(struct zl3073x_dev *zldev, u8 ch,
517 			    u16 out_mask, s32 step_cycles,
518 			    bool tod_step)
519 {
520 	u8 ctrl;
521 	int rc;
522 
523 	guard(mutex)(&zldev->phase_step_lock);
524 
525 	/* Wait for any previous phase step operation to complete */
526 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL,
527 				  ZL_OUTPUT_PHASE_STEP_CTRL_OP,
528 				  ZL_POLL_PHASE_STEP_TIMEOUT_US);
529 	if (rc)
530 		return rc;
531 
532 	rc = zl3073x_write_u32(zldev, ZL_REG_OUTPUT_PHASE_STEP_DATA,
533 			       step_cycles);
534 	if (rc)
535 		return rc;
536 
537 	rc = zl3073x_write_u16(zldev, ZL_REG_OUTPUT_PHASE_STEP_MASK, out_mask);
538 	if (rc)
539 		return rc;
540 
541 	rc = zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_NUMBER, 1);
542 	if (rc)
543 		return rc;
544 
545 	ctrl = FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_DPLL, ch) |
546 	       FIELD_PREP(ZL_OUTPUT_PHASE_STEP_CTRL_OP,
547 			  ZL_OUTPUT_PHASE_STEP_CTRL_OP_WRITE);
548 	if (tod_step)
549 		ctrl |= ZL_OUTPUT_PHASE_STEP_CTRL_TOD_STEP;
550 
551 	return zl3073x_write_u8(zldev, ZL_REG_OUTPUT_PHASE_STEP_CTRL, ctrl);
552 }
553 
554 /**
555  * zl3073x_chan_state_set - commit DPLL channel state changes to hardware
556  * @zldev: pointer to zl3073x_dev structure
557  * @index: DPLL channel index to set state for
558  * @chan: desired channel state
559  *
560  * Skips the HW write if the configuration is unchanged, and otherwise
561  * writes only the changed registers to hardware. The mode_refsel register
562  * is written directly, while the reference priority registers are written
563  * via the DPLL mailbox interface.
564  *
565  * Return: 0 on success, <0 on HW error
566  */
zl3073x_chan_state_set(struct zl3073x_dev * zldev,u8 index,const struct zl3073x_chan * chan)567 int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
568 			   const struct zl3073x_chan *chan)
569 {
570 	struct zl3073x_chan *dchan = &zldev->chan[index];
571 	int rc, i;
572 
573 	/* Skip HW write if configuration hasn't changed */
574 	if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
575 		return 0;
576 
577 	/* Direct register writes for ctrl and mode_refsel */
578 	if (dchan->ctrl != chan->ctrl) {
579 		rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_CTRL(index),
580 				      chan->ctrl);
581 		if (rc)
582 			return rc;
583 		dchan->ctrl = chan->ctrl;
584 	}
585 
586 	if (dchan->mode_refsel != chan->mode_refsel) {
587 		rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
588 				      chan->mode_refsel);
589 		if (rc)
590 			return rc;
591 		dchan->mode_refsel = chan->mode_refsel;
592 	}
593 
594 	/* Mailbox write for ref_prio if changed */
595 	if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) {
596 		dchan->cfg = chan->cfg;
597 		return 0;
598 	}
599 
600 	guard(mutex)(&zldev->multiop_lock);
601 
602 	/* Read DPLL configuration into mailbox */
603 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
604 			   ZL_REG_DPLL_MB_MASK, BIT(index));
605 	if (rc)
606 		return rc;
607 
608 	/* Update changed ref_prio registers */
609 	for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
610 		if (dchan->ref_prio[i] != chan->ref_prio[i]) {
611 			rc = zl3073x_write_u8(zldev,
612 					      ZL_REG_DPLL_REF_PRIO(i),
613 					      chan->ref_prio[i]);
614 			if (rc)
615 				return rc;
616 		}
617 	}
618 
619 	/* Commit DPLL configuration */
620 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
621 			   ZL_REG_DPLL_MB_MASK, BIT(index));
622 	if (rc)
623 		return rc;
624 
625 	/* After successful write store new state */
626 	dchan->cfg = chan->cfg;
627 
628 	return 0;
629 }
630