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 dpll_tracker dpll_tracker; 13 struct dpll_pin *dpll_pin; 14 dpll_tracker pin_tracker; 15 struct mlx5_core_dev *mdev; 16 struct workqueue_struct *wq; 17 struct delayed_work work; 18 struct { 19 bool valid; 20 enum dpll_lock_status lock_status; 21 enum dpll_pin_state pin_state; 22 } last; 23 struct notifier_block mdev_nb; 24 struct net_device *tracking_netdev; 25 }; 26 27 static int mlx5_dpll_clock_id_get(struct mlx5_core_dev *mdev, u64 *clock_id) 28 { 29 u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {}; 30 u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {}; 31 int err; 32 33 err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), 34 MLX5_REG_MSECQ, 0, 0); 35 if (err) 36 return err; 37 *clock_id = MLX5_GET64(msecq_reg, out, local_clock_identity); 38 return 0; 39 } 40 41 struct mlx5_dpll_synce_status { 42 enum mlx5_msees_admin_status admin_status; 43 enum mlx5_msees_oper_status oper_status; 44 bool ho_acq; 45 bool oper_freq_measure; 46 enum mlx5_msees_failure_reason failure_reason; 47 s32 frequency_diff; 48 }; 49 50 static int 51 mlx5_dpll_synce_status_get(struct mlx5_core_dev *mdev, 52 struct mlx5_dpll_synce_status *synce_status) 53 { 54 u32 out[MLX5_ST_SZ_DW(msees_reg)] = {}; 55 u32 in[MLX5_ST_SZ_DW(msees_reg)] = {}; 56 int err; 57 58 err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), 59 MLX5_REG_MSEES, 0, 0); 60 if (err) 61 return err; 62 synce_status->admin_status = MLX5_GET(msees_reg, out, admin_status); 63 synce_status->oper_status = MLX5_GET(msees_reg, out, oper_status); 64 synce_status->ho_acq = MLX5_GET(msees_reg, out, ho_acq); 65 synce_status->oper_freq_measure = MLX5_GET(msees_reg, out, oper_freq_measure); 66 synce_status->failure_reason = MLX5_GET(msees_reg, out, failure_reason); 67 synce_status->frequency_diff = MLX5_GET(msees_reg, out, frequency_diff); 68 return 0; 69 } 70 71 static int 72 mlx5_dpll_synce_status_set(struct mlx5_core_dev *mdev, 73 enum mlx5_msees_admin_status admin_status) 74 { 75 u32 out[MLX5_ST_SZ_DW(msees_reg)] = {}; 76 u32 in[MLX5_ST_SZ_DW(msees_reg)] = {}; 77 78 MLX5_SET(msees_reg, in, field_select, 79 MLX5_MSEES_FIELD_SELECT_ENABLE | 80 MLX5_MSEES_FIELD_SELECT_ADMIN_FREQ_MEASURE | 81 MLX5_MSEES_FIELD_SELECT_ADMIN_STATUS); 82 MLX5_SET(msees_reg, in, admin_status, admin_status); 83 MLX5_SET(msees_reg, in, admin_freq_measure, true); 84 return mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), 85 MLX5_REG_MSEES, 0, 1); 86 } 87 88 static enum dpll_lock_status 89 mlx5_dpll_lock_status_get(struct mlx5_dpll_synce_status *synce_status) 90 { 91 switch (synce_status->oper_status) { 92 case MLX5_MSEES_OPER_STATUS_SELF_TRACK: 93 fallthrough; 94 case MLX5_MSEES_OPER_STATUS_OTHER_TRACK: 95 return synce_status->ho_acq ? DPLL_LOCK_STATUS_LOCKED_HO_ACQ : 96 DPLL_LOCK_STATUS_LOCKED; 97 case MLX5_MSEES_OPER_STATUS_HOLDOVER: 98 fallthrough; 99 case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER: 100 return DPLL_LOCK_STATUS_HOLDOVER; 101 default: 102 return DPLL_LOCK_STATUS_UNLOCKED; 103 } 104 } 105 106 static enum dpll_lock_status_error 107 mlx5_dpll_lock_status_error_get(struct mlx5_dpll_synce_status *synce_status) 108 { 109 switch (synce_status->oper_status) { 110 case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER: 111 fallthrough; 112 case MLX5_MSEES_OPER_STATUS_FAIL_FREE_RUNNING: 113 switch (synce_status->failure_reason) { 114 case MLX5_MSEES_FAILURE_REASON_PORT_DOWN: 115 return DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN; 116 case MLX5_MSEES_FAILURE_REASON_TOO_HIGH_FREQUENCY_DIFF: 117 return DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH; 118 default: 119 return DPLL_LOCK_STATUS_ERROR_UNDEFINED; 120 } 121 default: 122 return DPLL_LOCK_STATUS_ERROR_NONE; 123 } 124 } 125 126 static enum dpll_pin_state 127 mlx5_dpll_pin_state_get(struct mlx5_dpll_synce_status *synce_status) 128 { 129 return (synce_status->admin_status == MLX5_MSEES_ADMIN_STATUS_TRACK && 130 (synce_status->oper_status == MLX5_MSEES_OPER_STATUS_SELF_TRACK || 131 synce_status->oper_status == MLX5_MSEES_OPER_STATUS_OTHER_TRACK)) ? 132 DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED; 133 } 134 135 static int 136 mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status *synce_status, 137 s64 *ffo) 138 { 139 if (!synce_status->oper_freq_measure) 140 return -ENODATA; 141 *ffo = 1000000LL * synce_status->frequency_diff; 142 return 0; 143 } 144 145 static int 146 mlx5_dpll_device_lock_status_get(const struct dpll_device *dpll, void *priv, 147 enum dpll_lock_status *status, 148 enum dpll_lock_status_error *status_error, 149 struct netlink_ext_ack *extack) 150 { 151 struct mlx5_dpll_synce_status synce_status; 152 struct mlx5_dpll *mdpll = priv; 153 int err; 154 155 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); 156 if (err) 157 return err; 158 *status = mlx5_dpll_lock_status_get(&synce_status); 159 *status_error = mlx5_dpll_lock_status_error_get(&synce_status); 160 return 0; 161 } 162 163 static int mlx5_dpll_device_mode_get(const struct dpll_device *dpll, 164 void *priv, enum dpll_mode *mode, 165 struct netlink_ext_ack *extack) 166 { 167 *mode = DPLL_MODE_MANUAL; 168 return 0; 169 } 170 171 enum { 172 MLX5_DPLL_SSM_CODE_PRC = 0b0010, 173 MLX5_DPLL_SSM_CODE_SSU_A = 0b0100, 174 MLX5_DPLL_SSM_CODE_SSU_B = 0b1000, 175 MLX5_DPLL_SSM_CODE_EEC1 = 0b1011, 176 MLX5_DPLL_SSM_CODE_PRTC = 0b0010, 177 MLX5_DPLL_SSM_CODE_EPRTC = 0b0010, 178 MLX5_DPLL_SSM_CODE_EEEC = 0b1011, 179 MLX5_DPLL_SSM_CODE_EPRC = 0b0010, 180 }; 181 182 enum { 183 MLX5_DPLL_ENHANCED_SSM_CODE_PRC = 0xff, 184 MLX5_DPLL_ENHANCED_SSM_CODE_SSU_A = 0xff, 185 MLX5_DPLL_ENHANCED_SSM_CODE_SSU_B = 0xff, 186 MLX5_DPLL_ENHANCED_SSM_CODE_EEC1 = 0xff, 187 MLX5_DPLL_ENHANCED_SSM_CODE_PRTC = 0x20, 188 MLX5_DPLL_ENHANCED_SSM_CODE_EPRTC = 0x21, 189 MLX5_DPLL_ENHANCED_SSM_CODE_EEEC = 0x22, 190 MLX5_DPLL_ENHANCED_SSM_CODE_EPRC = 0x23, 191 }; 192 193 #define __MLX5_DPLL_SSM_COMBINED_CODE(ssm_code, enhanced_ssm_code) \ 194 ((ssm_code) | ((enhanced_ssm_code) << 8)) 195 196 #define MLX5_DPLL_SSM_COMBINED_CODE(type) \ 197 __MLX5_DPLL_SSM_COMBINED_CODE(MLX5_DPLL_SSM_CODE_##type, \ 198 MLX5_DPLL_ENHANCED_SSM_CODE_##type) 199 200 static int mlx5_dpll_clock_quality_level_get(const struct dpll_device *dpll, 201 void *priv, unsigned long *qls, 202 struct netlink_ext_ack *extack) 203 { 204 u8 network_option, ssm_code, enhanced_ssm_code; 205 u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {}; 206 u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {}; 207 struct mlx5_dpll *mdpll = priv; 208 int err; 209 210 err = mlx5_core_access_reg(mdpll->mdev, in, sizeof(in), 211 out, sizeof(out), MLX5_REG_MSECQ, 0, 0); 212 if (err) 213 return err; 214 network_option = MLX5_GET(msecq_reg, out, network_option); 215 if (network_option != 1) 216 goto errout; 217 ssm_code = MLX5_GET(msecq_reg, out, local_ssm_code); 218 enhanced_ssm_code = MLX5_GET(msecq_reg, out, local_enhanced_ssm_code); 219 220 switch (__MLX5_DPLL_SSM_COMBINED_CODE(ssm_code, enhanced_ssm_code)) { 221 case MLX5_DPLL_SSM_COMBINED_CODE(PRC): 222 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRC, qls); 223 return 0; 224 case MLX5_DPLL_SSM_COMBINED_CODE(SSU_A): 225 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_A, qls); 226 return 0; 227 case MLX5_DPLL_SSM_COMBINED_CODE(SSU_B): 228 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_SSU_B, qls); 229 return 0; 230 case MLX5_DPLL_SSM_COMBINED_CODE(EEC1): 231 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEC1, qls); 232 return 0; 233 case MLX5_DPLL_SSM_COMBINED_CODE(PRTC): 234 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_PRTC, qls); 235 return 0; 236 case MLX5_DPLL_SSM_COMBINED_CODE(EPRTC): 237 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRTC, qls); 238 return 0; 239 case MLX5_DPLL_SSM_COMBINED_CODE(EEEC): 240 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EEEC, qls); 241 return 0; 242 case MLX5_DPLL_SSM_COMBINED_CODE(EPRC): 243 __set_bit(DPLL_CLOCK_QUALITY_LEVEL_ITU_OPT1_EPRC, qls); 244 return 0; 245 } 246 errout: 247 NL_SET_ERR_MSG_MOD(extack, "Invalid clock quality level obtained from firmware"); 248 return -EINVAL; 249 } 250 251 static const struct dpll_device_ops mlx5_dpll_device_ops = { 252 .lock_status_get = mlx5_dpll_device_lock_status_get, 253 .mode_get = mlx5_dpll_device_mode_get, 254 .clock_quality_level_get = mlx5_dpll_clock_quality_level_get, 255 }; 256 257 static int mlx5_dpll_pin_direction_get(const struct dpll_pin *pin, 258 void *pin_priv, 259 const struct dpll_device *dpll, 260 void *dpll_priv, 261 enum dpll_pin_direction *direction, 262 struct netlink_ext_ack *extack) 263 { 264 *direction = DPLL_PIN_DIRECTION_INPUT; 265 return 0; 266 } 267 268 static int mlx5_dpll_state_on_dpll_get(const struct dpll_pin *pin, 269 void *pin_priv, 270 const struct dpll_device *dpll, 271 void *dpll_priv, 272 enum dpll_pin_state *state, 273 struct netlink_ext_ack *extack) 274 { 275 struct mlx5_dpll_synce_status synce_status; 276 struct mlx5_dpll *mdpll = pin_priv; 277 int err; 278 279 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); 280 if (err) 281 return err; 282 *state = mlx5_dpll_pin_state_get(&synce_status); 283 return 0; 284 } 285 286 static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin, 287 void *pin_priv, 288 const struct dpll_device *dpll, 289 void *dpll_priv, 290 enum dpll_pin_state state, 291 struct netlink_ext_ack *extack) 292 { 293 struct mlx5_dpll *mdpll = pin_priv; 294 295 return mlx5_dpll_synce_status_set(mdpll->mdev, 296 state == DPLL_PIN_STATE_CONNECTED ? 297 MLX5_MSEES_ADMIN_STATUS_TRACK : 298 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); 299 } 300 301 static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv, 302 const struct dpll_device *dpll, void *dpll_priv, 303 struct dpll_ffo_param *ffo, 304 struct netlink_ext_ack *extack) 305 { 306 struct mlx5_dpll_synce_status synce_status; 307 struct mlx5_dpll *mdpll = pin_priv; 308 int err; 309 310 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); 311 if (err) 312 return err; 313 return mlx5_dpll_pin_ffo_get(&synce_status, &ffo->ffo); 314 } 315 316 static const struct dpll_pin_ops mlx5_dpll_pins_ops = { 317 .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE), 318 .direction_get = mlx5_dpll_pin_direction_get, 319 .state_on_dpll_get = mlx5_dpll_state_on_dpll_get, 320 .state_on_dpll_set = mlx5_dpll_state_on_dpll_set, 321 .ffo_get = mlx5_dpll_ffo_get, 322 }; 323 324 static const struct dpll_pin_properties mlx5_dpll_pin_properties = { 325 .type = DPLL_PIN_TYPE_SYNCE_ETH_PORT, 326 .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, 327 }; 328 329 #define MLX5_DPLL_PERIODIC_WORK_INTERVAL 500 /* ms */ 330 331 static void mlx5_dpll_periodic_work_queue(struct mlx5_dpll *mdpll) 332 { 333 queue_delayed_work(mdpll->wq, &mdpll->work, 334 msecs_to_jiffies(MLX5_DPLL_PERIODIC_WORK_INTERVAL)); 335 } 336 337 static void mlx5_dpll_periodic_work(struct work_struct *work) 338 { 339 struct mlx5_dpll *mdpll = container_of(work, struct mlx5_dpll, 340 work.work); 341 struct mlx5_dpll_synce_status synce_status; 342 enum dpll_lock_status lock_status; 343 enum dpll_pin_state pin_state; 344 int err; 345 346 err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); 347 if (err) 348 goto err_out; 349 lock_status = mlx5_dpll_lock_status_get(&synce_status); 350 pin_state = mlx5_dpll_pin_state_get(&synce_status); 351 352 if (!mdpll->last.valid) 353 goto invalid_out; 354 355 if (mdpll->last.lock_status != lock_status) 356 dpll_device_change_ntf(mdpll->dpll); 357 if (mdpll->last.pin_state != pin_state) 358 dpll_pin_change_ntf(mdpll->dpll_pin); 359 360 invalid_out: 361 mdpll->last.lock_status = lock_status; 362 mdpll->last.pin_state = pin_state; 363 mdpll->last.valid = true; 364 err_out: 365 mlx5_dpll_periodic_work_queue(mdpll); 366 } 367 368 static void mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll *mdpll, 369 struct net_device *netdev) 370 { 371 if (mdpll->tracking_netdev) 372 return; 373 dpll_netdev_pin_set(netdev, mdpll->dpll_pin); 374 mdpll->tracking_netdev = netdev; 375 } 376 377 static void mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll *mdpll) 378 { 379 if (!mdpll->tracking_netdev) 380 return; 381 dpll_netdev_pin_clear(mdpll->tracking_netdev); 382 mdpll->tracking_netdev = NULL; 383 } 384 385 static int mlx5_dpll_mdev_notifier_event(struct notifier_block *nb, 386 unsigned long event, void *data) 387 { 388 struct mlx5_dpll *mdpll = container_of(nb, struct mlx5_dpll, mdev_nb); 389 struct net_device *netdev = data; 390 391 switch (event) { 392 case MLX5_DRIVER_EVENT_UPLINK_NETDEV: 393 if (netdev) 394 mlx5_dpll_netdev_dpll_pin_set(mdpll, netdev); 395 else 396 mlx5_dpll_netdev_dpll_pin_clear(mdpll); 397 break; 398 default: 399 return NOTIFY_DONE; 400 } 401 402 return NOTIFY_OK; 403 } 404 405 static void mlx5_dpll_mdev_netdev_track(struct mlx5_dpll *mdpll, 406 struct mlx5_core_dev *mdev) 407 { 408 mdpll->mdev_nb.notifier_call = mlx5_dpll_mdev_notifier_event; 409 mlx5_blocking_notifier_register(mdev, &mdpll->mdev_nb); 410 mlx5_core_uplink_netdev_event_replay(mdev); 411 } 412 413 static void mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll *mdpll, 414 struct mlx5_core_dev *mdev) 415 { 416 mlx5_blocking_notifier_unregister(mdev, &mdpll->mdev_nb); 417 mlx5_dpll_netdev_dpll_pin_clear(mdpll); 418 } 419 420 static int mlx5_dpll_probe(struct auxiliary_device *adev, 421 const struct auxiliary_device_id *id) 422 { 423 struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev); 424 struct mlx5_core_dev *mdev = edev->mdev; 425 struct mlx5_dpll *mdpll; 426 u64 clock_id; 427 int err; 428 429 err = mlx5_dpll_synce_status_set(mdev, 430 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); 431 if (err) 432 return err; 433 434 err = mlx5_dpll_clock_id_get(mdev, &clock_id); 435 if (err) 436 return err; 437 438 mdpll = kzalloc_obj(*mdpll); 439 if (!mdpll) 440 return -ENOMEM; 441 mdpll->mdev = mdev; 442 auxiliary_set_drvdata(adev, mdpll); 443 444 /* Multiple mdev instances might share one DPLL device. */ 445 mdpll->dpll = dpll_device_get(clock_id, 0, THIS_MODULE, 446 &mdpll->dpll_tracker); 447 if (IS_ERR(mdpll->dpll)) { 448 err = PTR_ERR(mdpll->dpll); 449 goto err_free_mdpll; 450 } 451 452 err = dpll_device_register(mdpll->dpll, DPLL_TYPE_EEC, 453 &mlx5_dpll_device_ops, mdpll); 454 if (err) 455 goto err_put_dpll_device; 456 457 /* Multiple mdev instances might share one DPLL pin. */ 458 mdpll->dpll_pin = dpll_pin_get(clock_id, mlx5_get_dev_index(mdev), 459 THIS_MODULE, &mlx5_dpll_pin_properties, 460 &mdpll->pin_tracker); 461 if (IS_ERR(mdpll->dpll_pin)) { 462 err = PTR_ERR(mdpll->dpll_pin); 463 goto err_unregister_dpll_device; 464 } 465 466 err = dpll_pin_register(mdpll->dpll, mdpll->dpll_pin, 467 &mlx5_dpll_pins_ops, mdpll); 468 if (err) 469 goto err_put_dpll_pin; 470 471 mdpll->wq = create_singlethread_workqueue("mlx5_dpll"); 472 if (!mdpll->wq) { 473 err = -ENOMEM; 474 goto err_unregister_dpll_pin; 475 } 476 477 mlx5_dpll_mdev_netdev_track(mdpll, mdev); 478 479 INIT_DELAYED_WORK(&mdpll->work, &mlx5_dpll_periodic_work); 480 mlx5_dpll_periodic_work_queue(mdpll); 481 482 return 0; 483 484 err_unregister_dpll_pin: 485 dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin, 486 &mlx5_dpll_pins_ops, mdpll); 487 err_put_dpll_pin: 488 dpll_pin_put(mdpll->dpll_pin, &mdpll->pin_tracker); 489 err_unregister_dpll_device: 490 dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll); 491 err_put_dpll_device: 492 dpll_device_put(mdpll->dpll, &mdpll->dpll_tracker); 493 err_free_mdpll: 494 kfree(mdpll); 495 return err; 496 } 497 498 static void mlx5_dpll_remove(struct auxiliary_device *adev) 499 { 500 struct mlx5_dpll *mdpll = auxiliary_get_drvdata(adev); 501 struct mlx5_core_dev *mdev = mdpll->mdev; 502 503 cancel_delayed_work_sync(&mdpll->work); 504 mlx5_dpll_mdev_netdev_untrack(mdpll, mdev); 505 destroy_workqueue(mdpll->wq); 506 dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin, 507 &mlx5_dpll_pins_ops, mdpll); 508 dpll_pin_put(mdpll->dpll_pin, &mdpll->pin_tracker); 509 dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll); 510 dpll_device_put(mdpll->dpll, &mdpll->dpll_tracker); 511 kfree(mdpll); 512 513 mlx5_dpll_synce_status_set(mdev, 514 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); 515 } 516 517 static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state) 518 { 519 return 0; 520 } 521 522 static int mlx5_dpll_resume(struct auxiliary_device *adev) 523 { 524 return 0; 525 } 526 527 static const struct auxiliary_device_id mlx5_dpll_id_table[] = { 528 { .name = MLX5_ADEV_NAME ".dpll", }, 529 {}, 530 }; 531 532 MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table); 533 534 static struct auxiliary_driver mlx5_dpll_driver = { 535 .name = "dpll", 536 .probe = mlx5_dpll_probe, 537 .remove = mlx5_dpll_remove, 538 .suspend = mlx5_dpll_suspend, 539 .resume = mlx5_dpll_resume, 540 .id_table = mlx5_dpll_id_table, 541 }; 542 543 static int __init mlx5_dpll_init(void) 544 { 545 return auxiliary_driver_register(&mlx5_dpll_driver); 546 } 547 548 static void __exit mlx5_dpll_exit(void) 549 { 550 auxiliary_driver_unregister(&mlx5_dpll_driver); 551 } 552 553 module_init(mlx5_dpll_init); 554 module_exit(mlx5_dpll_exit); 555 556 MODULE_AUTHOR("Jiri Pirko <jiri@nvidia.com>"); 557 MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver"); 558 MODULE_LICENSE("Dual BSD/GPL"); 559