blob: 904e08de852eac9dbe5fa56974c685cf1401c657 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */
#include <linux/dpll.h>
#include <linux/mlx5/driver.h>
/* 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 <jiri@nvidia.com>");
MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver");
MODULE_LICENSE("Dual BSD/GPL");