472 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB2/* 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 created8 * 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 int49mlx5_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 int70mlx5_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_status87mlx5_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_error105mlx5_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_state125mlx5_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 int134mlx5_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 int144mlx5_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 169static 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 174static 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 185static 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 203static 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 218static 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 232static 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 239static 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 246static 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 252static 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 275invalid_out:276 mdpll->last.lock_status = lock_status;277 mdpll->last.pin_state = pin_state;278 mdpll->last.valid = true;279err_out:280 mlx5_dpll_periodic_work_queue(mdpll);281}282 283static 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 292static 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 300static 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 else311 mlx5_dpll_netdev_dpll_pin_clear(mdpll);312 break;313 default:314 return NOTIFY_DONE;315 }316 317 return NOTIFY_OK;318}319 320static 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 328static 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 335static 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 397err_unregister_dpll_pin:398 dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,399 &mlx5_dpll_pins_ops, mdpll);400err_put_dpll_pin:401 dpll_pin_put(mdpll->dpll_pin);402err_unregister_dpll_device:403 dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);404err_put_dpll_device:405 dpll_device_put(mdpll->dpll);406err_free_mdpll:407 kfree(mdpll);408 return err;409}410 411static 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 430static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state)431{432 return 0;433}434 435static int mlx5_dpll_resume(struct auxiliary_device *adev)436{437 return 0;438}439 440static const struct auxiliary_device_id mlx5_dpll_id_table[] = {441 { .name = MLX5_ADEV_NAME ".dpll", },442 {},443};444 445MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table);446 447static 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 456static int __init mlx5_dpll_init(void)457{458 return auxiliary_driver_register(&mlx5_dpll_driver);459}460 461static void __exit mlx5_dpll_exit(void)462{463 auxiliary_driver_unregister(&mlx5_dpll_driver);464}465 466module_init(mlx5_dpll_init);467module_exit(mlx5_dpll_exit);468 469MODULE_AUTHOR("Jiri Pirko <jiri@nvidia.com>");470MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver");471MODULE_LICENSE("Dual BSD/GPL");472