1 // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2 /* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
3
4 #include <linux/dpll.h>
5 #include <linux/mlx5/driver.h>
6
7 /* This structure represents a reference to DPLL, one is created
8 * per mdev instance.
9 */
10 struct mlx5_dpll {
11 struct dpll_device *dpll;
12 struct dpll_pin *dpll_pin;
13 struct mlx5_core_dev *mdev;
14 struct workqueue_struct *wq;
15 struct delayed_work work;
16 struct {
17 bool valid;
18 enum dpll_lock_status lock_status;
19 enum dpll_pin_state pin_state;
20 } last;
21 struct notifier_block mdev_nb;
22 struct net_device *tracking_netdev;
23 };
24
mlx5_dpll_clock_id_get(struct mlx5_core_dev * mdev,u64 * clock_id)25 static int mlx5_dpll_clock_id_get(struct mlx5_core_dev *mdev, u64 *clock_id)
26 {
27 u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {};
28 u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {};
29 int err;
30
31 err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
32 MLX5_REG_MSECQ, 0, 0);
33 if (err)
34 return err;
35 *clock_id = MLX5_GET64(msecq_reg, out, local_clock_identity);
36 return 0;
37 }
38
39 struct mlx5_dpll_synce_status {
40 enum mlx5_msees_admin_status admin_status;
41 enum mlx5_msees_oper_status oper_status;
42 bool ho_acq;
43 bool oper_freq_measure;
44 enum mlx5_msees_failure_reason failure_reason;
45 s32 frequency_diff;
46 };
47
48 static int
mlx5_dpll_synce_status_get(struct mlx5_core_dev * mdev,struct mlx5_dpll_synce_status * synce_status)49 mlx5_dpll_synce_status_get(struct mlx5_core_dev *mdev,
50 struct mlx5_dpll_synce_status *synce_status)
51 {
52 u32 out[MLX5_ST_SZ_DW(msees_reg)] = {};
53 u32 in[MLX5_ST_SZ_DW(msees_reg)] = {};
54 int err;
55
56 err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
57 MLX5_REG_MSEES, 0, 0);
58 if (err)
59 return err;
60 synce_status->admin_status = MLX5_GET(msees_reg, out, admin_status);
61 synce_status->oper_status = MLX5_GET(msees_reg, out, oper_status);
62 synce_status->ho_acq = MLX5_GET(msees_reg, out, ho_acq);
63 synce_status->oper_freq_measure = MLX5_GET(msees_reg, out, oper_freq_measure);
64 synce_status->failure_reason = MLX5_GET(msees_reg, out, failure_reason);
65 synce_status->frequency_diff = MLX5_GET(msees_reg, out, frequency_diff);
66 return 0;
67 }
68
69 static int
mlx5_dpll_synce_status_set(struct mlx5_core_dev * mdev,enum mlx5_msees_admin_status admin_status)70 mlx5_dpll_synce_status_set(struct mlx5_core_dev *mdev,
71 enum mlx5_msees_admin_status admin_status)
72 {
73 u32 out[MLX5_ST_SZ_DW(msees_reg)] = {};
74 u32 in[MLX5_ST_SZ_DW(msees_reg)] = {};
75
76 MLX5_SET(msees_reg, in, field_select,
77 MLX5_MSEES_FIELD_SELECT_ENABLE |
78 MLX5_MSEES_FIELD_SELECT_ADMIN_FREQ_MEASURE |
79 MLX5_MSEES_FIELD_SELECT_ADMIN_STATUS);
80 MLX5_SET(msees_reg, in, admin_status, admin_status);
81 MLX5_SET(msees_reg, in, admin_freq_measure, true);
82 return mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
83 MLX5_REG_MSEES, 0, 1);
84 }
85
86 static enum dpll_lock_status
mlx5_dpll_lock_status_get(struct mlx5_dpll_synce_status * synce_status)87 mlx5_dpll_lock_status_get(struct mlx5_dpll_synce_status *synce_status)
88 {
89 switch (synce_status->oper_status) {
90 case MLX5_MSEES_OPER_STATUS_SELF_TRACK:
91 fallthrough;
92 case MLX5_MSEES_OPER_STATUS_OTHER_TRACK:
93 return synce_status->ho_acq ? DPLL_LOCK_STATUS_LOCKED_HO_ACQ :
94 DPLL_LOCK_STATUS_LOCKED;
95 case MLX5_MSEES_OPER_STATUS_HOLDOVER:
96 fallthrough;
97 case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER:
98 return DPLL_LOCK_STATUS_HOLDOVER;
99 default:
100 return DPLL_LOCK_STATUS_UNLOCKED;
101 }
102 }
103
104 static enum dpll_lock_status_error
mlx5_dpll_lock_status_error_get(struct mlx5_dpll_synce_status * synce_status)105 mlx5_dpll_lock_status_error_get(struct mlx5_dpll_synce_status *synce_status)
106 {
107 switch (synce_status->oper_status) {
108 case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER:
109 fallthrough;
110 case MLX5_MSEES_OPER_STATUS_FAIL_FREE_RUNNING:
111 switch (synce_status->failure_reason) {
112 case MLX5_MSEES_FAILURE_REASON_PORT_DOWN:
113 return DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN;
114 case MLX5_MSEES_FAILURE_REASON_TOO_HIGH_FREQUENCY_DIFF:
115 return DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH;
116 default:
117 return DPLL_LOCK_STATUS_ERROR_UNDEFINED;
118 }
119 default:
120 return DPLL_LOCK_STATUS_ERROR_NONE;
121 }
122 }
123
124 static enum dpll_pin_state
mlx5_dpll_pin_state_get(struct mlx5_dpll_synce_status * synce_status)125 mlx5_dpll_pin_state_get(struct mlx5_dpll_synce_status *synce_status)
126 {
127 return (synce_status->admin_status == MLX5_MSEES_ADMIN_STATUS_TRACK &&
128 (synce_status->oper_status == MLX5_MSEES_OPER_STATUS_SELF_TRACK ||
129 synce_status->oper_status == MLX5_MSEES_OPER_STATUS_OTHER_TRACK)) ?
130 DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED;
131 }
132
133 static int
mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status * synce_status,s64 * ffo)134 mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status *synce_status,
135 s64 *ffo)
136 {
137 if (!synce_status->oper_freq_measure)
138 return -ENODATA;
139 *ffo = synce_status->frequency_diff;
140 return 0;
141 }
142
143 static int
mlx5_dpll_device_lock_status_get(const struct dpll_device * dpll,void * priv,enum dpll_lock_status * status,enum dpll_lock_status_error * status_error,struct netlink_ext_ack * extack)144 mlx5_dpll_device_lock_status_get(const struct dpll_device *dpll, void *priv,
145 enum dpll_lock_status *status,
146 enum dpll_lock_status_error *status_error,
147 struct netlink_ext_ack *extack)
148 {
149 struct mlx5_dpll_synce_status synce_status;
150 struct mlx5_dpll *mdpll = priv;
151 int err;
152
153 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
154 if (err)
155 return err;
156 *status = mlx5_dpll_lock_status_get(&synce_status);
157 *status_error = mlx5_dpll_lock_status_error_get(&synce_status);
158 return 0;
159 }
160
mlx5_dpll_device_mode_get(const struct dpll_device * dpll,void * priv,enum dpll_mode * mode,struct netlink_ext_ack * extack)161 static int mlx5_dpll_device_mode_get(const struct dpll_device *dpll,
162 void *priv, enum dpll_mode *mode,
163 struct netlink_ext_ack *extack)
164 {
165 *mode = DPLL_MODE_MANUAL;
166 return 0;
167 }
168
169 static const struct dpll_device_ops mlx5_dpll_device_ops = {
170 .lock_status_get = mlx5_dpll_device_lock_status_get,
171 .mode_get = mlx5_dpll_device_mode_get,
172 };
173
mlx5_dpll_pin_direction_get(const struct dpll_pin * pin,void * pin_priv,const struct dpll_device * dpll,void * dpll_priv,enum dpll_pin_direction * direction,struct netlink_ext_ack * extack)174 static int mlx5_dpll_pin_direction_get(const struct dpll_pin *pin,
175 void *pin_priv,
176 const struct dpll_device *dpll,
177 void *dpll_priv,
178 enum dpll_pin_direction *direction,
179 struct netlink_ext_ack *extack)
180 {
181 *direction = DPLL_PIN_DIRECTION_INPUT;
182 return 0;
183 }
184
mlx5_dpll_state_on_dpll_get(const struct dpll_pin * pin,void * pin_priv,const struct dpll_device * dpll,void * dpll_priv,enum dpll_pin_state * state,struct netlink_ext_ack * extack)185 static int mlx5_dpll_state_on_dpll_get(const struct dpll_pin *pin,
186 void *pin_priv,
187 const struct dpll_device *dpll,
188 void *dpll_priv,
189 enum dpll_pin_state *state,
190 struct netlink_ext_ack *extack)
191 {
192 struct mlx5_dpll_synce_status synce_status;
193 struct mlx5_dpll *mdpll = pin_priv;
194 int err;
195
196 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
197 if (err)
198 return err;
199 *state = mlx5_dpll_pin_state_get(&synce_status);
200 return 0;
201 }
202
mlx5_dpll_state_on_dpll_set(const struct dpll_pin * pin,void * pin_priv,const struct dpll_device * dpll,void * dpll_priv,enum dpll_pin_state state,struct netlink_ext_ack * extack)203 static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin,
204 void *pin_priv,
205 const struct dpll_device *dpll,
206 void *dpll_priv,
207 enum dpll_pin_state state,
208 struct netlink_ext_ack *extack)
209 {
210 struct mlx5_dpll *mdpll = pin_priv;
211
212 return mlx5_dpll_synce_status_set(mdpll->mdev,
213 state == DPLL_PIN_STATE_CONNECTED ?
214 MLX5_MSEES_ADMIN_STATUS_TRACK :
215 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
216 }
217
mlx5_dpll_ffo_get(const struct dpll_pin * pin,void * pin_priv,const struct dpll_device * dpll,void * dpll_priv,s64 * ffo,struct netlink_ext_ack * extack)218 static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
219 const struct dpll_device *dpll, void *dpll_priv,
220 s64 *ffo, struct netlink_ext_ack *extack)
221 {
222 struct mlx5_dpll_synce_status synce_status;
223 struct mlx5_dpll *mdpll = pin_priv;
224 int err;
225
226 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
227 if (err)
228 return err;
229 return mlx5_dpll_pin_ffo_get(&synce_status, ffo);
230 }
231
232 static const struct dpll_pin_ops mlx5_dpll_pins_ops = {
233 .direction_get = mlx5_dpll_pin_direction_get,
234 .state_on_dpll_get = mlx5_dpll_state_on_dpll_get,
235 .state_on_dpll_set = mlx5_dpll_state_on_dpll_set,
236 .ffo_get = mlx5_dpll_ffo_get,
237 };
238
239 static const struct dpll_pin_properties mlx5_dpll_pin_properties = {
240 .type = DPLL_PIN_TYPE_SYNCE_ETH_PORT,
241 .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
242 };
243
244 #define MLX5_DPLL_PERIODIC_WORK_INTERVAL 500 /* ms */
245
mlx5_dpll_periodic_work_queue(struct mlx5_dpll * mdpll)246 static void mlx5_dpll_periodic_work_queue(struct mlx5_dpll *mdpll)
247 {
248 queue_delayed_work(mdpll->wq, &mdpll->work,
249 msecs_to_jiffies(MLX5_DPLL_PERIODIC_WORK_INTERVAL));
250 }
251
mlx5_dpll_periodic_work(struct work_struct * work)252 static void mlx5_dpll_periodic_work(struct work_struct *work)
253 {
254 struct mlx5_dpll *mdpll = container_of(work, struct mlx5_dpll,
255 work.work);
256 struct mlx5_dpll_synce_status synce_status;
257 enum dpll_lock_status lock_status;
258 enum dpll_pin_state pin_state;
259 int err;
260
261 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
262 if (err)
263 goto err_out;
264 lock_status = mlx5_dpll_lock_status_get(&synce_status);
265 pin_state = mlx5_dpll_pin_state_get(&synce_status);
266
267 if (!mdpll->last.valid)
268 goto invalid_out;
269
270 if (mdpll->last.lock_status != lock_status)
271 dpll_device_change_ntf(mdpll->dpll);
272 if (mdpll->last.pin_state != pin_state)
273 dpll_pin_change_ntf(mdpll->dpll_pin);
274
275 invalid_out:
276 mdpll->last.lock_status = lock_status;
277 mdpll->last.pin_state = pin_state;
278 mdpll->last.valid = true;
279 err_out:
280 mlx5_dpll_periodic_work_queue(mdpll);
281 }
282
mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll * mdpll,struct net_device * netdev)283 static void mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll *mdpll,
284 struct net_device *netdev)
285 {
286 if (mdpll->tracking_netdev)
287 return;
288 dpll_netdev_pin_set(netdev, mdpll->dpll_pin);
289 mdpll->tracking_netdev = netdev;
290 }
291
mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll * mdpll)292 static void mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll *mdpll)
293 {
294 if (!mdpll->tracking_netdev)
295 return;
296 dpll_netdev_pin_clear(mdpll->tracking_netdev);
297 mdpll->tracking_netdev = NULL;
298 }
299
mlx5_dpll_mdev_notifier_event(struct notifier_block * nb,unsigned long event,void * data)300 static int mlx5_dpll_mdev_notifier_event(struct notifier_block *nb,
301 unsigned long event, void *data)
302 {
303 struct mlx5_dpll *mdpll = container_of(nb, struct mlx5_dpll, mdev_nb);
304 struct net_device *netdev = data;
305
306 switch (event) {
307 case MLX5_DRIVER_EVENT_UPLINK_NETDEV:
308 if (netdev)
309 mlx5_dpll_netdev_dpll_pin_set(mdpll, netdev);
310 else
311 mlx5_dpll_netdev_dpll_pin_clear(mdpll);
312 break;
313 default:
314 return NOTIFY_DONE;
315 }
316
317 return NOTIFY_OK;
318 }
319
mlx5_dpll_mdev_netdev_track(struct mlx5_dpll * mdpll,struct mlx5_core_dev * mdev)320 static void mlx5_dpll_mdev_netdev_track(struct mlx5_dpll *mdpll,
321 struct mlx5_core_dev *mdev)
322 {
323 mdpll->mdev_nb.notifier_call = mlx5_dpll_mdev_notifier_event;
324 mlx5_blocking_notifier_register(mdev, &mdpll->mdev_nb);
325 mlx5_core_uplink_netdev_event_replay(mdev);
326 }
327
mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll * mdpll,struct mlx5_core_dev * mdev)328 static void mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll *mdpll,
329 struct mlx5_core_dev *mdev)
330 {
331 mlx5_blocking_notifier_unregister(mdev, &mdpll->mdev_nb);
332 mlx5_dpll_netdev_dpll_pin_clear(mdpll);
333 }
334
mlx5_dpll_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)335 static int mlx5_dpll_probe(struct auxiliary_device *adev,
336 const struct auxiliary_device_id *id)
337 {
338 struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev);
339 struct mlx5_core_dev *mdev = edev->mdev;
340 struct mlx5_dpll *mdpll;
341 u64 clock_id;
342 int err;
343
344 err = mlx5_dpll_synce_status_set(mdev,
345 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
346 if (err)
347 return err;
348
349 err = mlx5_dpll_clock_id_get(mdev, &clock_id);
350 if (err)
351 return err;
352
353 mdpll = kzalloc(sizeof(*mdpll), GFP_KERNEL);
354 if (!mdpll)
355 return -ENOMEM;
356 mdpll->mdev = mdev;
357 auxiliary_set_drvdata(adev, mdpll);
358
359 /* Multiple mdev instances might share one DPLL device. */
360 mdpll->dpll = dpll_device_get(clock_id, 0, THIS_MODULE);
361 if (IS_ERR(mdpll->dpll)) {
362 err = PTR_ERR(mdpll->dpll);
363 goto err_free_mdpll;
364 }
365
366 err = dpll_device_register(mdpll->dpll, DPLL_TYPE_EEC,
367 &mlx5_dpll_device_ops, mdpll);
368 if (err)
369 goto err_put_dpll_device;
370
371 /* Multiple mdev instances might share one DPLL pin. */
372 mdpll->dpll_pin = dpll_pin_get(clock_id, mlx5_get_dev_index(mdev),
373 THIS_MODULE, &mlx5_dpll_pin_properties);
374 if (IS_ERR(mdpll->dpll_pin)) {
375 err = PTR_ERR(mdpll->dpll_pin);
376 goto err_unregister_dpll_device;
377 }
378
379 err = dpll_pin_register(mdpll->dpll, mdpll->dpll_pin,
380 &mlx5_dpll_pins_ops, mdpll);
381 if (err)
382 goto err_put_dpll_pin;
383
384 mdpll->wq = create_singlethread_workqueue("mlx5_dpll");
385 if (!mdpll->wq) {
386 err = -ENOMEM;
387 goto err_unregister_dpll_pin;
388 }
389
390 mlx5_dpll_mdev_netdev_track(mdpll, mdev);
391
392 INIT_DELAYED_WORK(&mdpll->work, &mlx5_dpll_periodic_work);
393 mlx5_dpll_periodic_work_queue(mdpll);
394
395 return 0;
396
397 err_unregister_dpll_pin:
398 dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
399 &mlx5_dpll_pins_ops, mdpll);
400 err_put_dpll_pin:
401 dpll_pin_put(mdpll->dpll_pin);
402 err_unregister_dpll_device:
403 dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
404 err_put_dpll_device:
405 dpll_device_put(mdpll->dpll);
406 err_free_mdpll:
407 kfree(mdpll);
408 return err;
409 }
410
mlx5_dpll_remove(struct auxiliary_device * adev)411 static void mlx5_dpll_remove(struct auxiliary_device *adev)
412 {
413 struct mlx5_dpll *mdpll = auxiliary_get_drvdata(adev);
414 struct mlx5_core_dev *mdev = mdpll->mdev;
415
416 cancel_delayed_work_sync(&mdpll->work);
417 mlx5_dpll_mdev_netdev_untrack(mdpll, mdev);
418 destroy_workqueue(mdpll->wq);
419 dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
420 &mlx5_dpll_pins_ops, mdpll);
421 dpll_pin_put(mdpll->dpll_pin);
422 dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
423 dpll_device_put(mdpll->dpll);
424 kfree(mdpll);
425
426 mlx5_dpll_synce_status_set(mdev,
427 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
428 }
429
mlx5_dpll_suspend(struct auxiliary_device * adev,pm_message_t state)430 static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state)
431 {
432 return 0;
433 }
434
mlx5_dpll_resume(struct auxiliary_device * adev)435 static int mlx5_dpll_resume(struct auxiliary_device *adev)
436 {
437 return 0;
438 }
439
440 static const struct auxiliary_device_id mlx5_dpll_id_table[] = {
441 { .name = MLX5_ADEV_NAME ".dpll", },
442 {},
443 };
444
445 MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table);
446
447 static struct auxiliary_driver mlx5_dpll_driver = {
448 .name = "dpll",
449 .probe = mlx5_dpll_probe,
450 .remove = mlx5_dpll_remove,
451 .suspend = mlx5_dpll_suspend,
452 .resume = mlx5_dpll_resume,
453 .id_table = mlx5_dpll_id_table,
454 };
455
mlx5_dpll_init(void)456 static int __init mlx5_dpll_init(void)
457 {
458 return auxiliary_driver_register(&mlx5_dpll_driver);
459 }
460
mlx5_dpll_exit(void)461 static void __exit mlx5_dpll_exit(void)
462 {
463 auxiliary_driver_unregister(&mlx5_dpll_driver);
464 }
465
466 module_init(mlx5_dpll_init);
467 module_exit(mlx5_dpll_exit);
468
469 MODULE_AUTHOR("Jiri Pirko <jiri@nvidia.com>");
470 MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver");
471 MODULE_LICENSE("Dual BSD/GPL");
472