File Index
Files and directories
Sign in
FILE

dpll.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/ethernet/mellanox/mlx5/core/dpll.c C
  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 */
 10struct 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
 25static 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
 39struct 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
 48static int
 49mlx5_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
 69static int
 70mlx5_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
 86static enum dpll_lock_status
 87mlx5_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
104static enum dpll_lock_status_error
105mlx5_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
124static enum dpll_pin_state
125mlx5_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
133static int
134mlx5_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
143static int
144mlx5_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
161static 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
169enum {
170	MLX5_DPLL_SSM_CODE_PRC = 0b0010,
171	MLX5_DPLL_SSM_CODE_SSU_A = 0b0100,
172	MLX5_DPLL_SSM_CODE_SSU_B = 0b1000,
173	MLX5_DPLL_SSM_CODE_EEC1 = 0b1011,
174	MLX5_DPLL_SSM_CODE_PRTC = 0b0010,
175	MLX5_DPLL_SSM_CODE_EPRTC = 0b0010,
176	MLX5_DPLL_SSM_CODE_EEEC = 0b1011,
177	MLX5_DPLL_SSM_CODE_EPRC = 0b0010,
178};
179
180enum {
181	MLX5_DPLL_ENHANCED_SSM_CODE_PRC = 0xff,
182	MLX5_DPLL_ENHANCED_SSM_CODE_SSU_A = 0xff,
183	MLX5_DPLL_ENHANCED_SSM_CODE_SSU_B = 0xff,
184	MLX5_DPLL_ENHANCED_SSM_CODE_EEC1 = 0xff,
185	MLX5_DPLL_ENHANCED_SSM_CODE_PRTC = 0x20,
186	MLX5_DPLL_ENHANCED_SSM_CODE_EPRTC = 0x21,
187	MLX5_DPLL_ENHANCED_SSM_CODE_EEEC = 0x22,
188	MLX5_DPLL_ENHANCED_SSM_CODE_EPRC = 0x23,
189};
190
191#define __MLX5_DPLL_SSM_COMBINED_CODE(ssm_code, enhanced_ssm_code)		\
192	((ssm_code) | ((enhanced_ssm_code) << 8))
193
194#define MLX5_DPLL_SSM_COMBINED_CODE(type)					\
195	__MLX5_DPLL_SSM_COMBINED_CODE(MLX5_DPLL_SSM_CODE_##type,		\
196				      MLX5_DPLL_ENHANCED_SSM_CODE_##type)
197
198static int mlx5_dpll_clock_quality_level_get(const struct dpll_device *dpll,
199					     void *priv, unsigned long *qls,
200					     struct netlink_ext_ack *extack)
201{
202	u8 network_option, ssm_code, enhanced_ssm_code;
203	u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {};
204	u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {};
205	struct mlx5_dpll *mdpll = priv;
206	int err;
207
208	err = mlx5_core_access_reg(mdpll->mdev, in, sizeof(in),
209				   out, sizeof(out), MLX5_REG_MSECQ, 0, 0);
210	if (err)
211		return err;
212	network_option = MLX5_GET(msecq_reg, out, network_option);
213	if (network_option != 1)
214		goto errout;
215	ssm_code = MLX5_GET(msecq_reg, out, local_ssm_code);
216	enhanced_ssm_code = MLX5_GET(msecq_reg, out, local_enhanced_ssm_code);
217
218	switch (__MLX5_DPLL_SSM_COMBINED_CODE(ssm_code, enhanced_ssm_code)) {
219	case MLX5_DPLL_SSM_COMBINED_CODE(PRC):
220		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRC, qls);
221		return 0;
222	case MLX5_DPLL_SSM_COMBINED_CODE(SSU_A):
223		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_A, qls);
224		return 0;
225	case MLX5_DPLL_SSM_COMBINED_CODE(SSU_B):
226		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_B, qls);
227		return 0;
228	case MLX5_DPLL_SSM_COMBINED_CODE(EEC1):
229		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEC1, qls);
230		return 0;
231	case MLX5_DPLL_SSM_COMBINED_CODE(PRTC):
232		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRTC, qls);
233		return 0;
234	case MLX5_DPLL_SSM_COMBINED_CODE(EPRTC):
235		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRTC, qls);
236		return 0;
237	case MLX5_DPLL_SSM_COMBINED_CODE(EEEC):
238		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEEC, qls);
239		return 0;
240	case MLX5_DPLL_SSM_COMBINED_CODE(EPRC):
241		__set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRC, qls);
242		return 0;
243	}
244errout:
245	NL_SET_ERR_MSG_MOD(extack, "Invalid clock quality level obtained from firmware");
246	return -EINVAL;
247}
248
249static const struct dpll_device_ops mlx5_dpll_device_ops = {
250	.lock_status_get = mlx5_dpll_device_lock_status_get,
251	.mode_get = mlx5_dpll_device_mode_get,
252	.clock_quality_level_get = mlx5_dpll_clock_quality_level_get,
253};
254
255static int mlx5_dpll_pin_direction_get(const struct dpll_pin *pin,
256				       void *pin_priv,
257				       const struct dpll_device *dpll,
258				       void *dpll_priv,
259				       enum dpll_pin_direction *direction,
260				       struct netlink_ext_ack *extack)
261{
262	*direction = DPLL_PIN_DIRECTION_INPUT;
263	return 0;
264}
265
266static int mlx5_dpll_state_on_dpll_get(const struct dpll_pin *pin,
267				       void *pin_priv,
268				       const struct dpll_device *dpll,
269				       void *dpll_priv,
270				       enum dpll_pin_state *state,
271				       struct netlink_ext_ack *extack)
272{
273	struct mlx5_dpll_synce_status synce_status;
274	struct mlx5_dpll *mdpll = pin_priv;
275	int err;
276
277	err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
278	if (err)
279		return err;
280	*state = mlx5_dpll_pin_state_get(&synce_status);
281	return 0;
282}
283
284static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin,
285				       void *pin_priv,
286				       const struct dpll_device *dpll,
287				       void *dpll_priv,
288				       enum dpll_pin_state state,
289				       struct netlink_ext_ack *extack)
290{
291	struct mlx5_dpll *mdpll = pin_priv;
292
293	return mlx5_dpll_synce_status_set(mdpll->mdev,
294					  state == DPLL_PIN_STATE_CONNECTED ?
295					  MLX5_MSEES_ADMIN_STATUS_TRACK :
296					  MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
297}
298
299static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
300			     const struct dpll_device *dpll, void *dpll_priv,
301			     s64 *ffo, struct netlink_ext_ack *extack)
302{
303	struct mlx5_dpll_synce_status synce_status;
304	struct mlx5_dpll *mdpll = pin_priv;
305	int err;
306
307	err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
308	if (err)
309		return err;
310	return mlx5_dpll_pin_ffo_get(&synce_status, ffo);
311}
312
313static const struct dpll_pin_ops mlx5_dpll_pins_ops = {
314	.direction_get = mlx5_dpll_pin_direction_get,
315	.state_on_dpll_get = mlx5_dpll_state_on_dpll_get,
316	.state_on_dpll_set = mlx5_dpll_state_on_dpll_set,
317	.ffo_get = mlx5_dpll_ffo_get,
318};
319
320static const struct dpll_pin_properties mlx5_dpll_pin_properties = {
321	.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT,
322	.capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
323};
324
325#define MLX5_DPLL_PERIODIC_WORK_INTERVAL 500 /* ms */
326
327static void mlx5_dpll_periodic_work_queue(struct mlx5_dpll *mdpll)
328{
329	queue_delayed_work(mdpll->wq, &mdpll->work,
330			   msecs_to_jiffies(MLX5_DPLL_PERIODIC_WORK_INTERVAL));
331}
332
333static void mlx5_dpll_periodic_work(struct work_struct *work)
334{
335	struct mlx5_dpll *mdpll = container_of(work, struct mlx5_dpll,
336					       work.work);
337	struct mlx5_dpll_synce_status synce_status;
338	enum dpll_lock_status lock_status;
339	enum dpll_pin_state pin_state;
340	int err;
341
342	err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
343	if (err)
344		goto err_out;
345	lock_status = mlx5_dpll_lock_status_get(&synce_status);
346	pin_state = mlx5_dpll_pin_state_get(&synce_status);
347
348	if (!mdpll->last.valid)
349		goto invalid_out;
350
351	if (mdpll->last.lock_status != lock_status)
352		dpll_device_change_ntf(mdpll->dpll);
353	if (mdpll->last.pin_state != pin_state)
354		dpll_pin_change_ntf(mdpll->dpll_pin);
355
356invalid_out:
357	mdpll->last.lock_status = lock_status;
358	mdpll->last.pin_state = pin_state;
359	mdpll->last.valid = true;
360err_out:
361	mlx5_dpll_periodic_work_queue(mdpll);
362}
363
364static void mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll *mdpll,
365					  struct net_device *netdev)
366{
367	if (mdpll->tracking_netdev)
368		return;
369	dpll_netdev_pin_set(netdev, mdpll->dpll_pin);
370	mdpll->tracking_netdev = netdev;
371}
372
373static void mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll *mdpll)
374{
375	if (!mdpll->tracking_netdev)
376		return;
377	dpll_netdev_pin_clear(mdpll->tracking_netdev);
378	mdpll->tracking_netdev = NULL;
379}
380
381static int mlx5_dpll_mdev_notifier_event(struct notifier_block *nb,
382					 unsigned long event, void *data)
383{
384	struct mlx5_dpll *mdpll = container_of(nb, struct mlx5_dpll, mdev_nb);
385	struct net_device *netdev = data;
386
387	switch (event) {
388	case MLX5_DRIVER_EVENT_UPLINK_NETDEV:
389		if (netdev)
390			mlx5_dpll_netdev_dpll_pin_set(mdpll, netdev);
391		else
392			mlx5_dpll_netdev_dpll_pin_clear(mdpll);
393		break;
394	default:
395		return NOTIFY_DONE;
396	}
397
398	return NOTIFY_OK;
399}
400
401static void mlx5_dpll_mdev_netdev_track(struct mlx5_dpll *mdpll,
402					struct mlx5_core_dev *mdev)
403{
404	mdpll->mdev_nb.notifier_call = mlx5_dpll_mdev_notifier_event;
405	mlx5_blocking_notifier_register(mdev, &mdpll->mdev_nb);
406	mlx5_core_uplink_netdev_event_replay(mdev);
407}
408
409static void mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll *mdpll,
410					  struct mlx5_core_dev *mdev)
411{
412	mlx5_blocking_notifier_unregister(mdev, &mdpll->mdev_nb);
413	mlx5_dpll_netdev_dpll_pin_clear(mdpll);
414}
415
416static int mlx5_dpll_probe(struct auxiliary_device *adev,
417			   const struct auxiliary_device_id *id)
418{
419	struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev);
420	struct mlx5_core_dev *mdev = edev->mdev;
421	struct mlx5_dpll *mdpll;
422	u64 clock_id;
423	int err;
424
425	err = mlx5_dpll_synce_status_set(mdev,
426					 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
427	if (err)
428		return err;
429
430	err = mlx5_dpll_clock_id_get(mdev, &clock_id);
431	if (err)
432		return err;
433
434	mdpll = kzalloc(sizeof(*mdpll), GFP_KERNEL);
435	if (!mdpll)
436		return -ENOMEM;
437	mdpll->mdev = mdev;
438	auxiliary_set_drvdata(adev, mdpll);
439
440	/* Multiple mdev instances might share one DPLL device. */
441	mdpll->dpll = dpll_device_get(clock_id, 0, THIS_MODULE);
442	if (IS_ERR(mdpll->dpll)) {
443		err = PTR_ERR(mdpll->dpll);
444		goto err_free_mdpll;
445	}
446
447	err = dpll_device_register(mdpll->dpll, DPLL_TYPE_EEC,
448				   &mlx5_dpll_device_ops, mdpll);
449	if (err)
450		goto err_put_dpll_device;
451
452	/* Multiple mdev instances might share one DPLL pin. */
453	mdpll->dpll_pin = dpll_pin_get(clock_id, mlx5_get_dev_index(mdev),
454				       THIS_MODULE, &mlx5_dpll_pin_properties);
455	if (IS_ERR(mdpll->dpll_pin)) {
456		err = PTR_ERR(mdpll->dpll_pin);
457		goto err_unregister_dpll_device;
458	}
459
460	err = dpll_pin_register(mdpll->dpll, mdpll->dpll_pin,
461				&mlx5_dpll_pins_ops, mdpll);
462	if (err)
463		goto err_put_dpll_pin;
464
465	mdpll->wq = create_singlethread_workqueue("mlx5_dpll");
466	if (!mdpll->wq) {
467		err = -ENOMEM;
468		goto err_unregister_dpll_pin;
469	}
470
471	mlx5_dpll_mdev_netdev_track(mdpll, mdev);
472
473	INIT_DELAYED_WORK(&mdpll->work, &mlx5_dpll_periodic_work);
474	mlx5_dpll_periodic_work_queue(mdpll);
475
476	return 0;
477
478err_unregister_dpll_pin:
479	dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
480			    &mlx5_dpll_pins_ops, mdpll);
481err_put_dpll_pin:
482	dpll_pin_put(mdpll->dpll_pin);
483err_unregister_dpll_device:
484	dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
485err_put_dpll_device:
486	dpll_device_put(mdpll->dpll);
487err_free_mdpll:
488	kfree(mdpll);
489	return err;
490}
491
492static void mlx5_dpll_remove(struct auxiliary_device *adev)
493{
494	struct mlx5_dpll *mdpll = auxiliary_get_drvdata(adev);
495	struct mlx5_core_dev *mdev = mdpll->mdev;
496
497	cancel_delayed_work_sync(&mdpll->work);
498	mlx5_dpll_mdev_netdev_untrack(mdpll, mdev);
499	destroy_workqueue(mdpll->wq);
500	dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
501			    &mlx5_dpll_pins_ops, mdpll);
502	dpll_pin_put(mdpll->dpll_pin);
503	dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
504	dpll_device_put(mdpll->dpll);
505	kfree(mdpll);
506
507	mlx5_dpll_synce_status_set(mdev,
508				   MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
509}
510
511static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state)
512{
513	return 0;
514}
515
516static int mlx5_dpll_resume(struct auxiliary_device *adev)
517{
518	return 0;
519}
520
521static const struct auxiliary_device_id mlx5_dpll_id_table[] = {
522	{ .name = MLX5_ADEV_NAME ".dpll", },
523	{},
524};
525
526MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table);
527
528static struct auxiliary_driver mlx5_dpll_driver = {
529	.name = "dpll",
530	.probe = mlx5_dpll_probe,
531	.remove = mlx5_dpll_remove,
532	.suspend = mlx5_dpll_suspend,
533	.resume = mlx5_dpll_resume,
534	.id_table = mlx5_dpll_id_table,
535};
536
537static int __init mlx5_dpll_init(void)
538{
539	return auxiliary_driver_register(&mlx5_dpll_driver);
540}
541
542static void __exit mlx5_dpll_exit(void)
543{
544	auxiliary_driver_unregister(&mlx5_dpll_driver);
545}
546
547module_init(mlx5_dpll_init);
548module_exit(mlx5_dpll_exit);
549
550MODULE_AUTHOR("Jiri Pirko <jiri@nvidia.com>");
551MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver");
552MODULE_LICENSE("Dual BSD/GPL");
~/ Read-only file preview.