diff options
Diffstat (limited to 'drivers/dpll/zl3073x/devlink.c')
-rw-r--r-- | drivers/dpll/zl3073x/devlink.c | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/drivers/dpll/zl3073x/devlink.c b/drivers/dpll/zl3073x/devlink.c new file mode 100644 index 000000000000..7e7fe726ee37 --- /dev/null +++ b/drivers/dpll/zl3073x/devlink.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/device/devres.h> +#include <linux/netlink.h> +#include <linux/sprintf.h> +#include <linux/types.h> +#include <net/devlink.h> + +#include "core.h" +#include "devlink.h" +#include "dpll.h" +#include "regs.h" + +/** + * zl3073x_devlink_info_get - Devlink device info callback + * @devlink: devlink structure pointer + * @req: devlink request pointer to store information + * @extack: netlink extack pointer to report errors + * + * Return: 0 on success, <0 on error + */ +static int +zl3073x_devlink_info_get(struct devlink *devlink, struct devlink_info_req *req, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + u16 id, revision, fw_ver; + char buf[16]; + u32 cfg_ver; + int rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_ID, &id); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", id); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_ID, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%X", revision); + rc = devlink_info_version_fixed_put(req, + DEVLINK_INFO_VERSION_GENERIC_ASIC_REV, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u16(zldev, ZL_REG_FW_VER, &fw_ver); + if (rc) + return rc; + + snprintf(buf, sizeof(buf), "%u", fw_ver); + rc = devlink_info_version_running_put(req, + DEVLINK_INFO_VERSION_GENERIC_FW, + buf); + if (rc) + return rc; + + rc = zl3073x_read_u32(zldev, ZL_REG_CUSTOM_CONFIG_VER, &cfg_ver); + if (rc) + return rc; + + /* No custom config version */ + if (cfg_ver == U32_MAX) + return 0; + + snprintf(buf, sizeof(buf), "%lu.%lu.%lu.%lu", + FIELD_GET(GENMASK(31, 24), cfg_ver), + FIELD_GET(GENMASK(23, 16), cfg_ver), + FIELD_GET(GENMASK(15, 8), cfg_ver), + FIELD_GET(GENMASK(7, 0), cfg_ver)); + + return devlink_info_version_running_put(req, "custom_cfg", buf); +} + +static int +zl3073x_devlink_reload_down(struct devlink *devlink, bool netns_change, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + struct zl3073x_dpll *zldpll; + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + /* Unregister all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) + zl3073x_dpll_unregister(zldpll); + + return 0; +} + +static int +zl3073x_devlink_reload_up(struct devlink *devlink, + enum devlink_reload_action action, + enum devlink_reload_limit limit, + u32 *actions_performed, + struct netlink_ext_ack *extack) +{ + struct zl3073x_dev *zldev = devlink_priv(devlink); + union devlink_param_value val; + struct zl3073x_dpll *zldpll; + int rc; + + if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) + return -EOPNOTSUPP; + + rc = devl_param_driverinit_value_get(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + &val); + if (rc) + return rc; + + if (zldev->clock_id != val.vu64) { + dev_dbg(zldev->dev, + "'clock_id' changed to %016llx\n", val.vu64); + zldev->clock_id = val.vu64; + } + + /* Re-register all DPLLs */ + list_for_each_entry(zldpll, &zldev->dplls, list) { + rc = zl3073x_dpll_register(zldpll); + if (rc) + dev_warn(zldev->dev, + "Failed to re-register DPLL%u\n", zldpll->id); + } + + *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT); + + return 0; +} + +static const struct devlink_ops zl3073x_devlink_ops = { + .info_get = zl3073x_devlink_info_get, + .reload_actions = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT), + .reload_down = zl3073x_devlink_reload_down, + .reload_up = zl3073x_devlink_reload_up, +}; + +static void +zl3073x_devlink_free(void *ptr) +{ + devlink_free(ptr); +} + +/** + * zl3073x_devm_alloc - allocates zl3073x device structure + * @dev: pointer to device structure + * + * Allocates zl3073x device structure as device resource. + * + * Return: pointer to zl3073x device on success, error pointer on error + */ +struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev) +{ + struct zl3073x_dev *zldev; + struct devlink *devlink; + int rc; + + devlink = devlink_alloc(&zl3073x_devlink_ops, sizeof(*zldev), dev); + if (!devlink) + return ERR_PTR(-ENOMEM); + + /* Add devres action to free devlink device */ + rc = devm_add_action_or_reset(dev, zl3073x_devlink_free, devlink); + if (rc) + return ERR_PTR(rc); + + zldev = devlink_priv(devlink); + zldev->dev = dev; + dev_set_drvdata(zldev->dev, zldev); + + return zldev; +} +EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, "ZL3073X"); + +static int +zl3073x_devlink_param_clock_id_validate(struct devlink *devlink, u32 id, + union devlink_param_value val, + struct netlink_ext_ack *extack) +{ + if (!val.vu64) { + NL_SET_ERR_MSG_MOD(extack, "'clock_id' must be non-zero"); + return -EINVAL; + } + + return 0; +} + +static const struct devlink_param zl3073x_devlink_params[] = { + DEVLINK_PARAM_GENERIC(CLOCK_ID, BIT(DEVLINK_PARAM_CMODE_DRIVERINIT), + NULL, NULL, + zl3073x_devlink_param_clock_id_validate), +}; + +static void +zl3073x_devlink_unregister(void *ptr) +{ + struct devlink *devlink = priv_to_devlink(ptr); + + devl_lock(devlink); + + /* Unregister devlink params */ + devl_params_unregister(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + + /* Unregister devlink instance */ + devl_unregister(devlink); + + devl_unlock(devlink); +} + +/** + * zl3073x_devlink_register - register devlink instance and params + * @zldev: zl3073x device to register the devlink for + * + * Register the devlink instance and parameters associated with the device. + * + * Return: 0 on success, <0 on error + */ +int zl3073x_devlink_register(struct zl3073x_dev *zldev) +{ + struct devlink *devlink = priv_to_devlink(zldev); + union devlink_param_value value; + int rc; + + devl_lock(devlink); + + /* Register devlink params */ + rc = devl_params_register(devlink, zl3073x_devlink_params, + ARRAY_SIZE(zl3073x_devlink_params)); + if (rc) { + devl_unlock(devlink); + + return rc; + } + + value.vu64 = zldev->clock_id; + devl_param_driverinit_value_set(devlink, + DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, + value); + + /* Register devlink instance */ + devl_register(devlink); + + devl_unlock(devlink); + + /* Add devres action to unregister devlink device */ + return devm_add_action_or_reset(zldev->dev, zl3073x_devlink_unregister, + zldev); +} |