| // 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 "flash.h" | |
| #include "fw.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); | |
| if (action != DEVLINK_RELOAD_ACTION_DRIVER_REINIT) | |
| return -EOPNOTSUPP; | |
| /* Stop normal operation */ | |
| zl3073x_dev_stop(zldev); | |
| 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; | |
| 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; | |
| } | |
| /* Restart normal operation */ | |
| rc = zl3073x_dev_start(zldev, false); | |
| if (rc) | |
| dev_warn(zldev->dev, "Failed to re-start normal operation\n"); | |
| *actions_performed = BIT(DEVLINK_RELOAD_ACTION_DRIVER_REINIT); | |
| return 0; | |
| } | |
| void zl3073x_devlink_flash_notify(struct zl3073x_dev *zldev, const char *msg, | |
| const char *component, u32 done, u32 total) | |
| { | |
| struct devlink *devlink = priv_to_devlink(zldev); | |
| devlink_flash_update_status_notify(devlink, msg, component, done, | |
| total); | |
| } | |
| /** | |
| * zl3073x_devlink_flash_prepare - Prepare and enter flash mode | |
| * @zldev: zl3073x device pointer | |
| * @zlfw: pointer to loaded firmware | |
| * @extack: netlink extack pointer to report errors | |
| * | |
| * The function stops normal operation and switches the device to flash mode. | |
| * If an error occurs the normal operation is resumed. | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| static int | |
| zl3073x_devlink_flash_prepare(struct zl3073x_dev *zldev, | |
| struct zl3073x_fw *zlfw, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct zl3073x_fw_component *util; | |
| int rc; | |
| util = zlfw->component[ZL_FW_COMPONENT_UTIL]; | |
| if (!util) { | |
| zl3073x_devlink_flash_notify(zldev, | |
| "Utility is missing in firmware", | |
| NULL, 0, 0); | |
| return -ENOEXEC; | |
| } | |
| /* Stop normal operation prior entering flash mode */ | |
| zl3073x_dev_stop(zldev); | |
| rc = zl3073x_flash_mode_enter(zldev, util->data, util->size, extack); | |
| if (rc) { | |
| zl3073x_devlink_flash_notify(zldev, | |
| "Failed to enter flash mode", | |
| NULL, 0, 0); | |
| /* Resume normal operation */ | |
| zl3073x_dev_start(zldev, true); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * zl3073x_devlink_flash_finish - Leave flash mode and resume normal operation | |
| * @zldev: zl3073x device pointer | |
| * @extack: netlink extack pointer to report errors | |
| * | |
| * The function switches the device back to standard mode and resumes normal | |
| * operation. | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| static int | |
| zl3073x_devlink_flash_finish(struct zl3073x_dev *zldev, | |
| struct netlink_ext_ack *extack) | |
| { | |
| int rc; | |
| /* Reset device CPU to normal mode */ | |
| zl3073x_flash_mode_leave(zldev, extack); | |
| /* Resume normal operation */ | |
| rc = zl3073x_dev_start(zldev, true); | |
| if (rc) | |
| zl3073x_devlink_flash_notify(zldev, | |
| "Failed to start normal operation", | |
| NULL, 0, 0); | |
| return rc; | |
| } | |
| /** | |
| * zl3073x_devlink_flash_update - Devlink flash update callback | |
| * @devlink: devlink structure pointer | |
| * @params: flashing parameters pointer | |
| * @extack: netlink extack pointer to report errors | |
| * | |
| * Return: 0 on success, <0 on error | |
| */ | |
| static int | |
| zl3073x_devlink_flash_update(struct devlink *devlink, | |
| struct devlink_flash_update_params *params, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct zl3073x_dev *zldev = devlink_priv(devlink); | |
| struct zl3073x_fw *zlfw; | |
| int rc = 0; | |
| zlfw = zl3073x_fw_load(zldev, params->fw->data, params->fw->size, | |
| extack); | |
| if (IS_ERR(zlfw)) { | |
| zl3073x_devlink_flash_notify(zldev, "Failed to load firmware", | |
| NULL, 0, 0); | |
| rc = PTR_ERR(zlfw); | |
| goto finish; | |
| } | |
| /* Stop normal operation and enter flash mode */ | |
| rc = zl3073x_devlink_flash_prepare(zldev, zlfw, extack); | |
| if (rc) | |
| goto finish; | |
| rc = zl3073x_fw_flash(zldev, zlfw, extack); | |
| if (rc) { | |
| zl3073x_devlink_flash_finish(zldev, extack); | |
| goto finish; | |
| } | |
| /* Resume normal mode */ | |
| rc = zl3073x_devlink_flash_finish(zldev, extack); | |
| finish: | |
| if (!IS_ERR(zlfw)) | |
| zl3073x_fw_free(zlfw); | |
| zl3073x_devlink_flash_notify(zldev, | |
| rc ? "Flashing failed" : "Flashing done", | |
| NULL, 0, 0); | |
| return rc; | |
| } | |
| 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, | |
| .flash_update = zl3073x_devlink_flash_update, | |
| }; | |
| 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); | |
| } |