// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include #include /* This structure represents a reference to DPLL, one is created * per mdev instance. */ struct mlx5_dpll { struct dpll_device *dpll; struct dpll_pin *dpll_pin; struct mlx5_core_dev *mdev; struct workqueue_struct *wq; struct delayed_work work; struct { bool valid; enum dpll_lock_status lock_status; enum dpll_pin_state pin_state; } last; struct notifier_block mdev_nb; struct net_device *tracking_netdev; }; static int mlx5_dpll_clock_id_get(struct mlx5_core_dev *mdev, u64 *clock_id) { u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {}; u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {}; int err; err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), MLX5_REG_MSECQ, 0, 0); if (err) return err; *clock_id = MLX5_GET64(msecq_reg, out, local_clock_identity); return 0; } struct mlx5_dpll_synce_status { enum mlx5_msees_admin_status admin_status; enum mlx5_msees_oper_status oper_status; bool ho_acq; bool oper_freq_measure; enum mlx5_msees_failure_reason failure_reason; s32 frequency_diff; }; static int mlx5_dpll_synce_status_get(struct mlx5_core_dev *mdev, struct mlx5_dpll_synce_status *synce_status) { u32 out[MLX5_ST_SZ_DW(msees_reg)] = {}; u32 in[MLX5_ST_SZ_DW(msees_reg)] = {}; int err; err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), MLX5_REG_MSEES, 0, 0); if (err) return err; synce_status->admin_status = MLX5_GET(msees_reg, out, admin_status); synce_status->oper_status = MLX5_GET(msees_reg, out, oper_status); synce_status->ho_acq = MLX5_GET(msees_reg, out, ho_acq); synce_status->oper_freq_measure = MLX5_GET(msees_reg, out, oper_freq_measure); synce_status->failure_reason = MLX5_GET(msees_reg, out, failure_reason); synce_status->frequency_diff = MLX5_GET(msees_reg, out, frequency_diff); return 0; } static int mlx5_dpll_synce_status_set(struct mlx5_core_dev *mdev, enum mlx5_msees_admin_status admin_status) { u32 out[MLX5_ST_SZ_DW(msees_reg)] = {}; u32 in[MLX5_ST_SZ_DW(msees_reg)] = {}; MLX5_SET(msees_reg, in, field_select, MLX5_MSEES_FIELD_SELECT_ENABLE | MLX5_MSEES_FIELD_SELECT_ADMIN_FREQ_MEASURE | MLX5_MSEES_FIELD_SELECT_ADMIN_STATUS); MLX5_SET(msees_reg, in, admin_status, admin_status); MLX5_SET(msees_reg, in, admin_freq_measure, true); return mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out), MLX5_REG_MSEES, 0, 1); } static enum dpll_lock_status mlx5_dpll_lock_status_get(struct mlx5_dpll_synce_status *synce_status) { switch (synce_status->oper_status) { case MLX5_MSEES_OPER_STATUS_SELF_TRACK: fallthrough; case MLX5_MSEES_OPER_STATUS_OTHER_TRACK: return synce_status->ho_acq ? DPLL_LOCK_STATUS_LOCKED_HO_ACQ : DPLL_LOCK_STATUS_LOCKED; case MLX5_MSEES_OPER_STATUS_HOLDOVER: fallthrough; case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER: return DPLL_LOCK_STATUS_HOLDOVER; default: return DPLL_LOCK_STATUS_UNLOCKED; } } static enum dpll_lock_status_error mlx5_dpll_lock_status_error_get(struct mlx5_dpll_synce_status *synce_status) { switch (synce_status->oper_status) { case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER: fallthrough; case MLX5_MSEES_OPER_STATUS_FAIL_FREE_RUNNING: switch (synce_status->failure_reason) { case MLX5_MSEES_FAILURE_REASON_PORT_DOWN: return DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN; case MLX5_MSEES_FAILURE_REASON_TOO_HIGH_FREQUENCY_DIFF: return DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH; default: return DPLL_LOCK_STATUS_ERROR_UNDEFINED; } default: return DPLL_LOCK_STATUS_ERROR_NONE; } } static enum dpll_pin_state mlx5_dpll_pin_state_get(struct mlx5_dpll_synce_status *synce_status) { return (synce_status->admin_status == MLX5_MSEES_ADMIN_STATUS_TRACK && (synce_status->oper_status == MLX5_MSEES_OPER_STATUS_SELF_TRACK || synce_status->oper_status == MLX5_MSEES_OPER_STATUS_OTHER_TRACK)) ? DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED; } static int mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status *synce_status, s64 *ffo) { if (!synce_status->oper_freq_measure) return -ENODATA; *ffo = synce_status->frequency_diff; return 0; } 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) { struct mlx5_dpll_synce_status synce_status; struct mlx5_dpll *mdpll = priv; int err; err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); if (err) return err; *status = mlx5_dpll_lock_status_get(&synce_status); *status_error = mlx5_dpll_lock_status_error_get(&synce_status); return 0; } static int mlx5_dpll_device_mode_get(const struct dpll_device *dpll, void *priv, enum dpll_mode *mode, struct netlink_ext_ack *extack) { *mode = DPLL_MODE_MANUAL; return 0; } static const struct dpll_device_ops mlx5_dpll_device_ops = { .lock_status_get = mlx5_dpll_device_lock_status_get, .mode_get = mlx5_dpll_device_mode_get, }; static int 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) { *direction = DPLL_PIN_DIRECTION_INPUT; return 0; } static int 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) { struct mlx5_dpll_synce_status synce_status; struct mlx5_dpll *mdpll = pin_priv; int err; err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); if (err) return err; *state = mlx5_dpll_pin_state_get(&synce_status); return 0; } static int 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) { struct mlx5_dpll *mdpll = pin_priv; return mlx5_dpll_synce_status_set(mdpll->mdev, state == DPLL_PIN_STATE_CONNECTED ? MLX5_MSEES_ADMIN_STATUS_TRACK : MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); } static int 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) { struct mlx5_dpll_synce_status synce_status; struct mlx5_dpll *mdpll = pin_priv; int err; err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); if (err) return err; return mlx5_dpll_pin_ffo_get(&synce_status, ffo); } static const struct dpll_pin_ops mlx5_dpll_pins_ops = { .direction_get = mlx5_dpll_pin_direction_get, .state_on_dpll_get = mlx5_dpll_state_on_dpll_get, .state_on_dpll_set = mlx5_dpll_state_on_dpll_set, .ffo_get = mlx5_dpll_ffo_get, }; static const struct dpll_pin_properties mlx5_dpll_pin_properties = { .type = DPLL_PIN_TYPE_SYNCE_ETH_PORT, .capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE, }; #define MLX5_DPLL_PERIODIC_WORK_INTERVAL 500 /* ms */ static void mlx5_dpll_periodic_work_queue(struct mlx5_dpll *mdpll) { queue_delayed_work(mdpll->wq, &mdpll->work, msecs_to_jiffies(MLX5_DPLL_PERIODIC_WORK_INTERVAL)); } static void mlx5_dpll_periodic_work(struct work_struct *work) { struct mlx5_dpll *mdpll = container_of(work, struct mlx5_dpll, work.work); struct mlx5_dpll_synce_status synce_status; enum dpll_lock_status lock_status; enum dpll_pin_state pin_state; int err; err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); if (err) goto err_out; lock_status = mlx5_dpll_lock_status_get(&synce_status); pin_state = mlx5_dpll_pin_state_get(&synce_status); if (!mdpll->last.valid) goto invalid_out; if (mdpll->last.lock_status != lock_status) dpll_device_change_ntf(mdpll->dpll); if (mdpll->last.pin_state != pin_state) dpll_pin_change_ntf(mdpll->dpll_pin); invalid_out: mdpll->last.lock_status = lock_status; mdpll->last.pin_state = pin_state; mdpll->last.valid = true; err_out: mlx5_dpll_periodic_work_queue(mdpll); } static void mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll *mdpll, struct net_device *netdev) { if (mdpll->tracking_netdev) return; dpll_netdev_pin_set(netdev, mdpll->dpll_pin); mdpll->tracking_netdev = netdev; } static void mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll *mdpll) { if (!mdpll->tracking_netdev) return; dpll_netdev_pin_clear(mdpll->tracking_netdev); mdpll->tracking_netdev = NULL; } static int mlx5_dpll_mdev_notifier_event(struct notifier_block *nb, unsigned long event, void *data) { struct mlx5_dpll *mdpll = container_of(nb, struct mlx5_dpll, mdev_nb); struct net_device *netdev = data; switch (event) { case MLX5_DRIVER_EVENT_UPLINK_NETDEV: if (netdev) mlx5_dpll_netdev_dpll_pin_set(mdpll, netdev); else mlx5_dpll_netdev_dpll_pin_clear(mdpll); break; default: return NOTIFY_DONE; } return NOTIFY_OK; } static void mlx5_dpll_mdev_netdev_track(struct mlx5_dpll *mdpll, struct mlx5_core_dev *mdev) { mdpll->mdev_nb.notifier_call = mlx5_dpll_mdev_notifier_event; mlx5_blocking_notifier_register(mdev, &mdpll->mdev_nb); mlx5_core_uplink_netdev_event_replay(mdev); } static void mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll *mdpll, struct mlx5_core_dev *mdev) { mlx5_blocking_notifier_unregister(mdev, &mdpll->mdev_nb); mlx5_dpll_netdev_dpll_pin_clear(mdpll); } static int mlx5_dpll_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev); struct mlx5_core_dev *mdev = edev->mdev; struct mlx5_dpll *mdpll; u64 clock_id; int err; err = mlx5_dpll_synce_status_set(mdev, MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); if (err) return err; err = mlx5_dpll_clock_id_get(mdev, &clock_id); if (err) return err; mdpll = kzalloc(sizeof(*mdpll), GFP_KERNEL); if (!mdpll) return -ENOMEM; mdpll->mdev = mdev; auxiliary_set_drvdata(adev, mdpll); /* Multiple mdev instances might share one DPLL device. */ mdpll->dpll = dpll_device_get(clock_id, 0, THIS_MODULE); if (IS_ERR(mdpll->dpll)) { err = PTR_ERR(mdpll->dpll); goto err_free_mdpll; } err = dpll_device_register(mdpll->dpll, DPLL_TYPE_EEC, &mlx5_dpll_device_ops, mdpll); if (err) goto err_put_dpll_device; /* Multiple mdev instances might share one DPLL pin. */ mdpll->dpll_pin = dpll_pin_get(clock_id, mlx5_get_dev_index(mdev), THIS_MODULE, &mlx5_dpll_pin_properties); if (IS_ERR(mdpll->dpll_pin)) { err = PTR_ERR(mdpll->dpll_pin); goto err_unregister_dpll_device; } err = dpll_pin_register(mdpll->dpll, mdpll->dpll_pin, &mlx5_dpll_pins_ops, mdpll); if (err) goto err_put_dpll_pin; mdpll->wq = create_singlethread_workqueue("mlx5_dpll"); if (!mdpll->wq) { err = -ENOMEM; goto err_unregister_dpll_pin; } mlx5_dpll_mdev_netdev_track(mdpll, mdev); INIT_DELAYED_WORK(&mdpll->work, &mlx5_dpll_periodic_work); mlx5_dpll_periodic_work_queue(mdpll); return 0; err_unregister_dpll_pin: dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin, &mlx5_dpll_pins_ops, mdpll); err_put_dpll_pin: dpll_pin_put(mdpll->dpll_pin); err_unregister_dpll_device: dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll); err_put_dpll_device: dpll_device_put(mdpll->dpll); err_free_mdpll: kfree(mdpll); return err; } static void mlx5_dpll_remove(struct auxiliary_device *adev) { struct mlx5_dpll *mdpll = auxiliary_get_drvdata(adev); struct mlx5_core_dev *mdev = mdpll->mdev; cancel_delayed_work_sync(&mdpll->work); mlx5_dpll_mdev_netdev_untrack(mdpll, mdev); destroy_workqueue(mdpll->wq); dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin, &mlx5_dpll_pins_ops, mdpll); dpll_pin_put(mdpll->dpll_pin); dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll); dpll_device_put(mdpll->dpll); kfree(mdpll); mlx5_dpll_synce_status_set(mdev, MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING); } static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state) { return 0; } static int mlx5_dpll_resume(struct auxiliary_device *adev) { return 0; } static const struct auxiliary_device_id mlx5_dpll_id_table[] = { { .name = MLX5_ADEV_NAME ".dpll", }, {}, }; MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table); static struct auxiliary_driver mlx5_dpll_driver = { .name = "dpll", .probe = mlx5_dpll_probe, .remove = mlx5_dpll_remove, .suspend = mlx5_dpll_suspend, .resume = mlx5_dpll_resume, .id_table = mlx5_dpll_id_table, }; static int __init mlx5_dpll_init(void) { return auxiliary_driver_register(&mlx5_dpll_driver); } static void __exit mlx5_dpll_exit(void) { auxiliary_driver_unregister(&mlx5_dpll_driver); } module_init(mlx5_dpll_init); module_exit(mlx5_dpll_exit); MODULE_AUTHOR("Jiri Pirko "); MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver"); MODULE_LICENSE("Dual BSD/GPL");