xref: /linux/drivers/dpll/zl3073x/chan.c (revision 2b109eccd80b660e58d4523e8b80ce11bc3e532e)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <linux/cleanup.h>
4 #include <linux/dev_printk.h>
5 #include <linux/string.h>
6 #include <linux/types.h>
7 
8 #include "chan.h"
9 #include "core.h"
10 
11 /**
12  * zl3073x_chan_state_update - update DPLL channel status from HW
13  * @zldev: pointer to zl3073x_dev structure
14  * @index: DPLL channel index
15  *
16  * Return: 0 on success, <0 on error
17  */
18 int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
19 {
20 	struct zl3073x_chan *chan = &zldev->chan[index];
21 	u64 val;
22 	int rc;
23 
24 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
25 			     &chan->mon_status);
26 	if (rc)
27 		return rc;
28 
29 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
30 			     &chan->refsel_status);
31 	if (rc)
32 		return rc;
33 
34 	/* Read df_offset vs tracked reference */
35 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
36 				  ZL_DPLL_DF_READ_SEM);
37 	if (rc)
38 		return rc;
39 
40 	rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
41 			      ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
42 	if (rc)
43 		return rc;
44 
45 	rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
46 				  ZL_DPLL_DF_READ_SEM);
47 	if (rc)
48 		return rc;
49 
50 	rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
51 	if (rc)
52 		return rc;
53 
54 	chan->df_offset = sign_extend64(val, 47);
55 
56 	return 0;
57 }
58 
59 /**
60  * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
61  * @zldev: pointer to zl3073x_dev structure
62  * @index: DPLL channel index to fetch state for
63  *
64  * Reads the mode_refsel register and reference priority registers for
65  * the given DPLL channel and stores the raw values for later use.
66  *
67  * Return: 0 on success, <0 on error
68  */
69 int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
70 {
71 	struct zl3073x_chan *chan = &zldev->chan[index];
72 	int rc, i;
73 
74 	rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
75 			     &chan->mode_refsel);
76 	if (rc)
77 		return rc;
78 
79 	dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
80 		zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
81 
82 	rc = zl3073x_chan_state_update(zldev, index);
83 	if (rc)
84 		return rc;
85 
86 	dev_dbg(zldev->dev,
87 		"DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
88 		index, zl3073x_chan_lock_state_get(chan),
89 		zl3073x_chan_is_ho_ready(chan) ? 1 : 0,
90 		zl3073x_chan_refsel_state_get(chan),
91 		zl3073x_chan_refsel_ref_get(chan));
92 
93 	guard(mutex)(&zldev->multiop_lock);
94 
95 	/* Read DPLL configuration from mailbox */
96 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
97 			   ZL_REG_DPLL_MB_MASK, BIT(index));
98 	if (rc)
99 		return rc;
100 
101 	/* Read reference priority registers */
102 	for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
103 		rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),
104 				     &chan->ref_prio[i]);
105 		if (rc)
106 			return rc;
107 	}
108 
109 	return 0;
110 }
111 
112 /**
113  * zl3073x_chan_state_get - get current DPLL channel state
114  * @zldev: pointer to zl3073x_dev structure
115  * @index: DPLL channel index to get state for
116  *
117  * Return: pointer to given DPLL channel state
118  */
119 const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
120 						  u8 index)
121 {
122 	return &zldev->chan[index];
123 }
124 
125 /**
126  * zl3073x_chan_state_set - commit DPLL channel state changes to hardware
127  * @zldev: pointer to zl3073x_dev structure
128  * @index: DPLL channel index to set state for
129  * @chan: desired channel state
130  *
131  * Skips the HW write if the configuration is unchanged, and otherwise
132  * writes only the changed registers to hardware. The mode_refsel register
133  * is written directly, while the reference priority registers are written
134  * via the DPLL mailbox interface.
135  *
136  * Return: 0 on success, <0 on HW error
137  */
138 int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
139 			   const struct zl3073x_chan *chan)
140 {
141 	struct zl3073x_chan *dchan = &zldev->chan[index];
142 	int rc, i;
143 
144 	/* Skip HW write if configuration hasn't changed */
145 	if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
146 		return 0;
147 
148 	/* Direct register write for mode_refsel */
149 	if (dchan->mode_refsel != chan->mode_refsel) {
150 		rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
151 				      chan->mode_refsel);
152 		if (rc)
153 			return rc;
154 		dchan->mode_refsel = chan->mode_refsel;
155 	}
156 
157 	/* Mailbox write for ref_prio if changed */
158 	if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) {
159 		dchan->cfg = chan->cfg;
160 		return 0;
161 	}
162 
163 	guard(mutex)(&zldev->multiop_lock);
164 
165 	/* Read DPLL configuration into mailbox */
166 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
167 			   ZL_REG_DPLL_MB_MASK, BIT(index));
168 	if (rc)
169 		return rc;
170 
171 	/* Update changed ref_prio registers */
172 	for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
173 		if (dchan->ref_prio[i] != chan->ref_prio[i]) {
174 			rc = zl3073x_write_u8(zldev,
175 					      ZL_REG_DPLL_REF_PRIO(i),
176 					      chan->ref_prio[i]);
177 			if (rc)
178 				return rc;
179 		}
180 	}
181 
182 	/* Commit DPLL configuration */
183 	rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
184 			   ZL_REG_DPLL_MB_MASK, BIT(index));
185 	if (rc)
186 		return rc;
187 
188 	/* After successful write store new state */
189 	dchan->cfg = chan->cfg;
190 
191 	return 0;
192 }
193