root/drivers/dpll/zl3073x/devlink.c
// 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);
}