--- /dev/null
+What:          /sys/class/firmware/.../data
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   The data sysfs file is used for firmware-fallback and for
+               firmware uploads. Cat a firmware image to this sysfs file
+               after you echo 1 to the loading sysfs file. When the firmware
+               image write is complete, echo 0 to the loading sysfs file. This
+               sequence will signal the completion of the firmware write and
+               signal the lower-level driver that the firmware data is
+               available.
+
+What:          /sys/class/firmware/.../loading
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   The loading sysfs file is used for both firmware-fallback and
+               for firmware uploads. Echo 1 onto the loading file to indicate
+               you are writing a firmware file to the data sysfs node. Echo
+               -1 onto this file to abort the data write or echo 0 onto this
+               file to indicate that the write is complete. For firmware
+               uploads, the zero value also triggers the transfer of the
+               firmware data to the lower-level device driver.
+
+What:          /sys/class/firmware/.../timeout
+Date:          July 2022
+KernelVersion: 5.19
+Contact:       Russ Weight <russell.h.weight@intel.com>
+Description:   This file supports the timeout mechanism for firmware
+               fallback.  This file has no affect on firmware uploads. For
+               more information on timeouts please see the documentation
+               for firmware fallback.
 
--- /dev/null
+.. SPDX-License-Identifier: GPL-2.0
+
+===================
+Firmware Upload API
+===================
+
+A device driver that registers with the firmware loader will expose
+persistent sysfs nodes to enable users to initiate firmware updates for
+that device.  It is the responsibility of the device driver and/or the
+device itself to perform any validation on the data received. Firmware
+upload uses the same *loading* and *data* sysfs files described in the
+documentation for firmware fallback.
+
+Register for firmware upload
+============================
+
+A device driver registers for firmware upload by calling
+firmware_upload_register(). Among the parameter list is a name to
+identify the device under /sys/class/firmware. A user may initiate a
+firmware upload by echoing a 1 to the *loading* sysfs file for the target
+device. Next, the user writes the firmware image to the *data* sysfs
+file. After writing the firmware data, the user echos 0 to the *loading*
+sysfs file to signal completion. Echoing 0 to *loading* also triggers the
+transfer of the firmware to the lower-lever device driver in the context
+of a kernel worker thread.
+
+To use the firmware upload API, write a driver that implements a set of
+ops.  The probe function calls firmware_upload_register() and the remove
+function calls firmware_upload_unregister() such as::
+
+       static const struct fw_upload_ops m10bmc_ops = {
+               .prepare = m10bmc_sec_prepare,
+               .write = m10bmc_sec_write,
+               .poll_complete = m10bmc_sec_poll_complete,
+               .cancel = m10bmc_sec_cancel,
+               .cleanup = m10bmc_sec_cleanup,
+       };
+
+       static int m10bmc_sec_probe(struct platform_device *pdev)
+       {
+               const char *fw_name, *truncate;
+               struct m10bmc_sec *sec;
+               struct fw_upload *fwl;
+               unsigned int len;
+
+               sec = devm_kzalloc(&pdev->dev, sizeof(*sec), GFP_KERNEL);
+               if (!sec)
+                       return -ENOMEM;
+
+               sec->dev = &pdev->dev;
+               sec->m10bmc = dev_get_drvdata(pdev->dev.parent);
+               dev_set_drvdata(&pdev->dev, sec);
+
+               fw_name = dev_name(sec->dev);
+               truncate = strstr(fw_name, ".auto");
+               len = (truncate) ? truncate - fw_name : strlen(fw_name);
+               sec->fw_name = kmemdup_nul(fw_name, len, GFP_KERNEL);
+
+               fwl = firmware_upload_register(sec->dev, sec->fw_name, &m10bmc_ops, sec);
+               if (IS_ERR(fwl)) {
+                       dev_err(sec->dev, "Firmware Upload driver failed to start\n");
+                       kfree(sec->fw_name);
+                       return PTR_ERR(fwl);
+               }
+
+               sec->fwl = fwl;
+               return 0;
+       }
+
+       static int m10bmc_sec_remove(struct platform_device *pdev)
+       {
+               struct m10bmc_sec *sec = dev_get_drvdata(&pdev->dev);
+
+               firmware_upload_unregister(sec->fwl);
+               kfree(sec->fw_name);
+               return 0;
+       }
+
+firmware_upload_register
+------------------------
+.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.c
+   :identifiers: firmware_upload_register
+
+firmware_upload_unregister
+--------------------------
+.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.c
+   :identifiers: firmware_upload_unregister
+
+Firmware Upload Ops
+-------------------
+.. kernel-doc:: include/linux/firmware.h
+   :identifiers: fw_upload_ops
+
+Firmware Upload Progress Codes
+------------------------------
+The following progress codes are used internally by the firmware loader:
+
+.. kernel-doc:: drivers/base/firmware_loader/sysfs_upload.h
+   :identifiers: fw_upload_prog
+
+Firmware Upload Error Codes
+---------------------------
+The following error codes may be returned by the driver ops in case of
+failure:
+
+.. kernel-doc:: include/linux/firmware.h
+   :identifiers: fw_upload_err
 
    core
    efi/index
    request_firmware
+   fw_upload
    other_interfaces
 
 .. only::  subproject and html
 
 
          If unsure, say Y.
 
+config FW_UPLOAD
+       bool "Enable users to initiate firmware updates using sysfs"
+       select FW_LOADER_SYSFS
+       select FW_LOADER_PAGED_BUF
+       help
+         Enabling this option will allow device drivers to expose a persistent
+         sysfs interface that allows firmware updates to be initiated from
+         userspace. For example, FPGA based PCIe cards load firmware and FPGA
+         images from local FLASH when the card boots. The images in FLASH may
+         be updated with new images provided by the user. Enable this device
+         to support cards that rely on user-initiated updates for firmware files.
+
+         If unsure, say N.
+
 endif # FW_LOADER
 endmenu
 
 firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o
 firmware_class-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += fallback_platform.o
 firmware_class-$(CONFIG_FW_LOADER_SYSFS) += sysfs.o
+firmware_class-$(CONFIG_FW_UPLOAD) += sysfs_upload.o
 
 obj-y += builtin/
 
 };
 
 extern struct mutex fw_lock;
+extern struct firmware_cache fw_cache;
 
 static inline bool __fw_state_check(struct fw_priv *fw_priv,
                                    enum fw_status status)
        return __fw_state_check(fw_priv, FW_STATUS_LOADING);
 }
 
+int alloc_lookup_fw_priv(const char *fw_name, struct firmware_cache *fwc,
+                        struct fw_priv **fw_priv, void *dbuf, size_t size,
+                        size_t offset, u32 opt_flags);
 int assign_fw(struct firmware *fw, struct device *device);
+void free_fw_priv(struct fw_priv *fw_priv);
+void fw_state_init(struct fw_priv *fw_priv);
 
 #ifdef CONFIG_FW_LOADER
 bool firmware_is_builtin(const struct firmware *fw);
 
  * guarding for corner cases a global lock should be OK */
 DEFINE_MUTEX(fw_lock);
 
-static struct firmware_cache fw_cache;
+struct firmware_cache fw_cache;
 
-static void fw_state_init(struct fw_priv *fw_priv)
+void fw_state_init(struct fw_priv *fw_priv)
 {
        struct fw_state *fw_st = &fw_priv->fw_st;
 
 }
 
 /* Returns 1 for batching firmware requests with the same name */
-static int alloc_lookup_fw_priv(const char *fw_name,
-                               struct firmware_cache *fwc,
-                               struct fw_priv **fw_priv,
-                               void *dbuf,
-                               size_t size,
-                               size_t offset,
-                               u32 opt_flags)
+int alloc_lookup_fw_priv(const char *fw_name, struct firmware_cache *fwc,
+                        struct fw_priv **fw_priv, void *dbuf, size_t size,
+                        size_t offset, u32 opt_flags)
 {
        struct fw_priv *tmp;
 
        kfree(fw_priv);
 }
 
-static void free_fw_priv(struct fw_priv *fw_priv)
+void free_fw_priv(struct fw_priv *fw_priv)
 {
        struct firmware_cache *fwc = fw_priv->fwc;
        spin_lock(&fwc->lock);
 
 #include <linux/slab.h>
 #include <linux/types.h>
 
-#include "firmware.h"
 #include "sysfs.h"
+#include "sysfs_upload.h"
 
 /*
  * sysfs support for firmware loader
 {
        struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
 
+       if (fw_sysfs->fw_upload_priv) {
+               free_fw_priv(fw_sysfs->fw_priv);
+               kfree(fw_sysfs->fw_upload_priv);
+       }
        kfree(fw_sysfs);
 }
 
                                written = rc;
                        } else {
                                fw_state_done(fw_priv);
+
+                               /*
+                                * If this is a user-initiated firmware upload
+                                * then start the upload in a worker thread now.
+                                */
+                               rc = fw_upload_start(fw_sysfs);
+                               if (rc)
+                                       written = rc;
                        }
                        break;
                }
                fallthrough;
        case -1:
                fw_load_abort(fw_sysfs);
+               if (fw_sysfs->fw_upload_priv)
+                       fw_state_init(fw_sysfs->fw_priv);
+
                break;
        }
 out:
        return written;
 }
 
-static DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
+DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
 
 static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer,
                             loff_t offset, size_t count, bool read)
 
 
 #include <linux/device.h>
 
+#include "firmware.h"
+
 MODULE_IMPORT_NS(FIRMWARE_LOADER_PRIVATE);
 
 extern struct firmware_fallback_config fw_fallback_config;
+extern struct device_attribute dev_attr_loading;
 
 #ifdef CONFIG_FW_LOADER_USER_HELPER
 /**
        struct device dev;
        struct fw_priv *fw_priv;
        struct firmware *fw;
+       void *fw_upload_priv;
 };
 
 static inline struct fw_sysfs *to_fw_sysfs(struct device *dev)
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "sysfs.h"
+#include "sysfs_upload.h"
+
+/*
+ * Support for user-space to initiate a firmware upload to a device.
+ */
+
+static void fw_upload_update_progress(struct fw_upload_priv *fwlp,
+                                     enum fw_upload_prog new_progress)
+{
+       mutex_lock(&fwlp->lock);
+       fwlp->progress = new_progress;
+       mutex_unlock(&fwlp->lock);
+}
+
+static void fw_upload_set_error(struct fw_upload_priv *fwlp,
+                               enum fw_upload_err err_code)
+{
+       mutex_lock(&fwlp->lock);
+       fwlp->err_progress = fwlp->progress;
+       fwlp->err_code = err_code;
+       mutex_unlock(&fwlp->lock);
+}
+
+static void fw_upload_prog_complete(struct fw_upload_priv *fwlp)
+{
+       mutex_lock(&fwlp->lock);
+       fwlp->progress = FW_UPLOAD_PROG_IDLE;
+       mutex_unlock(&fwlp->lock);
+}
+
+static void fw_upload_main(struct work_struct *work)
+{
+       struct fw_upload_priv *fwlp;
+       struct fw_sysfs *fw_sysfs;
+       u32 written = 0, offset = 0;
+       enum fw_upload_err ret;
+       struct device *fw_dev;
+       struct fw_upload *fwl;
+
+       fwlp = container_of(work, struct fw_upload_priv, work);
+       fwl = fwlp->fw_upload;
+       fw_sysfs = (struct fw_sysfs *)fwl->priv;
+       fw_dev = &fw_sysfs->dev;
+
+       fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_PREPARING);
+       ret = fwlp->ops->prepare(fwl, fwlp->data, fwlp->remaining_size);
+       if (ret != FW_UPLOAD_ERR_NONE) {
+               fw_upload_set_error(fwlp, ret);
+               goto putdev_exit;
+       }
+
+       fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_TRANSFERRING);
+       while (fwlp->remaining_size) {
+               ret = fwlp->ops->write(fwl, fwlp->data, offset,
+                                       fwlp->remaining_size, &written);
+               if (ret != FW_UPLOAD_ERR_NONE || !written) {
+                       if (ret == FW_UPLOAD_ERR_NONE) {
+                               dev_warn(fw_dev, "write-op wrote zero data\n");
+                               ret = FW_UPLOAD_ERR_RW_ERROR;
+                       }
+                       fw_upload_set_error(fwlp, ret);
+                       goto done;
+               }
+
+               fwlp->remaining_size -= written;
+               offset += written;
+       }
+
+       fw_upload_update_progress(fwlp, FW_UPLOAD_PROG_PROGRAMMING);
+       ret = fwlp->ops->poll_complete(fwl);
+       if (ret != FW_UPLOAD_ERR_NONE)
+               fw_upload_set_error(fwlp, ret);
+
+done:
+       if (fwlp->ops->cleanup)
+               fwlp->ops->cleanup(fwl);
+
+putdev_exit:
+       put_device(fw_dev->parent);
+
+       /*
+        * Note: fwlp->remaining_size is left unmodified here to provide
+        * additional information on errors. It will be reinitialized when
+        * the next firmeware upload begins.
+        */
+       mutex_lock(&fw_lock);
+       fw_free_paged_buf(fw_sysfs->fw_priv);
+       fw_state_init(fw_sysfs->fw_priv);
+       mutex_unlock(&fw_lock);
+       fwlp->data = NULL;
+       fw_upload_prog_complete(fwlp);
+}
+
+/*
+ * Start a worker thread to upload data to the parent driver.
+ * Must be called with fw_lock held.
+ */
+int fw_upload_start(struct fw_sysfs *fw_sysfs)
+{
+       struct fw_priv *fw_priv = fw_sysfs->fw_priv;
+       struct device *fw_dev = &fw_sysfs->dev;
+       struct fw_upload_priv *fwlp;
+
+       if (!fw_sysfs->fw_upload_priv)
+               return 0;
+
+       if (!fw_priv->size) {
+               fw_free_paged_buf(fw_priv);
+               fw_state_init(fw_sysfs->fw_priv);
+               return 0;
+       }
+
+       fwlp = fw_sysfs->fw_upload_priv;
+       mutex_lock(&fwlp->lock);
+
+       /* Do not interfere with an on-going fw_upload */
+       if (fwlp->progress != FW_UPLOAD_PROG_IDLE) {
+               mutex_unlock(&fwlp->lock);
+               return -EBUSY;
+       }
+
+       get_device(fw_dev->parent); /* released in fw_upload_main */
+
+       fwlp->progress = FW_UPLOAD_PROG_RECEIVING;
+       fwlp->err_code = 0;
+       fwlp->remaining_size = fw_priv->size;
+       fwlp->data = fw_priv->data;
+
+       pr_debug("%s: fw-%s fw_priv=%p data=%p size=%u\n",
+                __func__, fw_priv->fw_name,
+                fw_priv, fw_priv->data,
+                (unsigned int)fw_priv->size);
+
+       queue_work(system_long_wq, &fwlp->work);
+       mutex_unlock(&fwlp->lock);
+
+       return 0;
+}
+
+/**
+ * firmware_upload_register() - register for the firmware upload sysfs API
+ * @parent: parent device instantiating firmware upload
+ * @name: firmware name to be associated with this device
+ * @ops: pointer to structure of firmware upload ops
+ * @dd_handle: pointer to parent driver private data
+ *
+ *     @name must be unique among all users of firmware upload. The firmware
+ *     sysfs files for this device will be found at /sys/class/firmware/@name.
+ *
+ *     Return: struct fw_upload pointer or ERR_PTR()
+ *
+ **/
+struct fw_upload *
+firmware_upload_register(struct module *module, struct device *parent,
+                        const char *name, const struct fw_upload_ops *ops,
+                        void *dd_handle)
+{
+       u32 opt_flags = FW_OPT_NOCACHE;
+       struct fw_upload *fw_upload;
+       struct fw_upload_priv *fw_upload_priv;
+       struct fw_sysfs *fw_sysfs;
+       struct fw_priv *fw_priv;
+       struct device *fw_dev;
+       int ret;
+
+       if (!name || name[0] == '\0')
+               return ERR_PTR(-EINVAL);
+
+       if (!ops || !ops->cancel || !ops->prepare ||
+           !ops->write || !ops->poll_complete) {
+               dev_err(parent, "Attempt to register without all required ops\n");
+               return ERR_PTR(-EINVAL);
+       }
+
+       if (!try_module_get(module))
+               return ERR_PTR(-EFAULT);
+
+       fw_upload = kzalloc(sizeof(*fw_upload), GFP_KERNEL);
+       if (!fw_upload) {
+               ret = -ENOMEM;
+               goto exit_module_put;
+       }
+
+       fw_upload_priv = kzalloc(sizeof(*fw_upload_priv), GFP_KERNEL);
+       if (!fw_upload_priv) {
+               ret = -ENOMEM;
+               goto free_fw_upload;
+       }
+
+       fw_upload_priv->fw_upload = fw_upload;
+       fw_upload_priv->ops = ops;
+       mutex_init(&fw_upload_priv->lock);
+       fw_upload_priv->module = module;
+       fw_upload_priv->name = name;
+       fw_upload_priv->err_code = 0;
+       fw_upload_priv->progress = FW_UPLOAD_PROG_IDLE;
+       INIT_WORK(&fw_upload_priv->work, fw_upload_main);
+       fw_upload->dd_handle = dd_handle;
+
+       fw_sysfs = fw_create_instance(NULL, name, parent, opt_flags);
+       if (IS_ERR(fw_sysfs)) {
+               ret = PTR_ERR(fw_sysfs);
+               goto free_fw_upload_priv;
+       }
+       fw_upload->priv = fw_sysfs;
+       fw_sysfs->fw_upload_priv = fw_upload_priv;
+       fw_dev = &fw_sysfs->dev;
+
+       ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv,  NULL, 0, 0,
+                                  FW_OPT_NOCACHE);
+       if (ret != 0) {
+               if (ret > 0)
+                       ret = -EINVAL;
+               goto free_fw_sysfs;
+       }
+       fw_priv->is_paged_buf = true;
+       fw_sysfs->fw_priv = fw_priv;
+
+       ret = device_add(fw_dev);
+       if (ret) {
+               dev_err(fw_dev, "%s: device_register failed\n", __func__);
+               put_device(fw_dev);
+               goto exit_module_put;
+       }
+
+       return fw_upload;
+
+free_fw_sysfs:
+       kfree(fw_sysfs);
+
+free_fw_upload_priv:
+       kfree(fw_upload_priv);
+
+free_fw_upload:
+       kfree(fw_upload);
+
+exit_module_put:
+       module_put(module);
+
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(firmware_upload_register);
+
+/**
+ * firmware_upload_unregister() - Unregister firmware upload interface
+ * @fw_upload: pointer to struct fw_upload
+ **/
+void firmware_upload_unregister(struct fw_upload *fw_upload)
+{
+       struct fw_sysfs *fw_sysfs = fw_upload->priv;
+       struct fw_upload_priv *fw_upload_priv = fw_sysfs->fw_upload_priv;
+
+       mutex_lock(&fw_upload_priv->lock);
+       if (fw_upload_priv->progress == FW_UPLOAD_PROG_IDLE) {
+               mutex_unlock(&fw_upload_priv->lock);
+               goto unregister;
+       }
+
+       fw_upload_priv->ops->cancel(fw_upload);
+       mutex_unlock(&fw_upload_priv->lock);
+
+       /* Ensure lower-level device-driver is finished */
+       flush_work(&fw_upload_priv->work);
+
+unregister:
+       device_unregister(&fw_sysfs->dev);
+       module_put(fw_upload_priv->module);
+}
+EXPORT_SYMBOL_GPL(firmware_upload_unregister);
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __FIRMWARE_UPLOAD_H
+#define __FIRMWARE_UPLOAD_H
+
+#include <linux/device.h>
+
+/**
+ * enum fw_upload_prog - firmware upload progress codes
+ * @FW_UPLOAD_PROG_IDLE: there is no firmware upload in progress
+ * @FW_UPLOAD_PROG_RECEIVING: worker thread is receiving firmware data
+ * @FW_UPLOAD_PROG_PREPARING: target device is preparing for firmware upload
+ * @FW_UPLOAD_PROG_TRANSFERRING: data is being copied to the device
+ * @FW_UPLOAD_PROG_PROGRAMMING: device is performing the firmware update
+ * @FW_UPLOAD_PROG_MAX: Maximum progress code marker
+ */
+enum fw_upload_prog {
+       FW_UPLOAD_PROG_IDLE,
+       FW_UPLOAD_PROG_RECEIVING,
+       FW_UPLOAD_PROG_PREPARING,
+       FW_UPLOAD_PROG_TRANSFERRING,
+       FW_UPLOAD_PROG_PROGRAMMING,
+       FW_UPLOAD_PROG_MAX
+};
+
+struct fw_upload_priv {
+       struct fw_upload *fw_upload;
+       struct module *module;
+       const char *name;
+       const struct fw_upload_ops *ops;
+       struct mutex lock;                /* protect data structure contents */
+       struct work_struct work;
+       const u8 *data;                   /* pointer to update data */
+       u32 remaining_size;               /* size remaining to transfer */
+       enum fw_upload_prog progress;
+       enum fw_upload_prog err_progress; /* progress at time of failure */
+       enum fw_upload_err err_code;      /* security manager error code */
+};
+
+#ifdef CONFIG_FW_UPLOAD
+int fw_upload_start(struct fw_sysfs *fw_sysfs);
+umode_t fw_upload_is_visible(struct kobject *kobj, struct attribute *attr, int n);
+#else
+static inline int fw_upload_start(struct fw_sysfs *fw_sysfs)
+{
+       return 0;
+}
+#endif
+
+#endif /* __FIRMWARE_UPLOAD_H */
 
        void *priv;
 };
 
+/**
+ * enum fw_upload_err - firmware upload error codes
+ * @FW_UPLOAD_ERR_NONE: returned to indicate success
+ * @FW_UPLOAD_ERR_HW_ERROR: error signalled by hardware, see kernel log
+ * @FW_UPLOAD_ERR_TIMEOUT: SW timed out on handshake with HW/firmware
+ * @FW_UPLOAD_ERR_CANCELED: upload was cancelled by the user
+ * @FW_UPLOAD_ERR_BUSY: there is an upload operation already in progress
+ * @FW_UPLOAD_ERR_INVALID_SIZE: invalid firmware image size
+ * @FW_UPLOAD_ERR_RW_ERROR: read or write to HW failed, see kernel log
+ * @FW_UPLOAD_ERR_WEAROUT: FLASH device is approaching wear-out, wait & retry
+ * @FW_UPLOAD_ERR_MAX: Maximum error code marker
+ */
+enum fw_upload_err {
+       FW_UPLOAD_ERR_NONE,
+       FW_UPLOAD_ERR_HW_ERROR,
+       FW_UPLOAD_ERR_TIMEOUT,
+       FW_UPLOAD_ERR_CANCELED,
+       FW_UPLOAD_ERR_BUSY,
+       FW_UPLOAD_ERR_INVALID_SIZE,
+       FW_UPLOAD_ERR_RW_ERROR,
+       FW_UPLOAD_ERR_WEAROUT,
+       FW_UPLOAD_ERR_MAX
+};
+
+struct fw_upload {
+       void *dd_handle; /* reference to parent driver */
+       void *priv;      /* firmware loader private fields */
+};
+
+/**
+ * struct fw_upload_ops - device specific operations to support firmware upload
+ * @prepare:             Required: Prepare secure update
+ * @write:               Required: The write() op receives the remaining
+ *                       size to be written and must return the actual
+ *                       size written or a negative error code. The write()
+ *                       op will be called repeatedly until all data is
+ *                       written.
+ * @poll_complete:       Required: Check for the completion of the
+ *                       HW authentication/programming process.
+ * @cancel:              Required: Request cancellation of update. This op
+ *                       is called from the context of a different kernel
+ *                       thread, so race conditions need to be considered.
+ * @cleanup:             Optional: Complements the prepare()
+ *                       function and is called at the completion
+ *                       of the update, on success or failure, if the
+ *                       prepare function succeeded.
+ */
+struct fw_upload_ops {
+       enum fw_upload_err (*prepare)(struct fw_upload *fw_upload,
+                                     const u8 *data, u32 size);
+       enum fw_upload_err (*write)(struct fw_upload *fw_upload,
+                                   const u8 *data, u32 offset,
+                                   u32 size, u32 *written);
+       enum fw_upload_err (*poll_complete)(struct fw_upload *fw_upload);
+       void (*cancel)(struct fw_upload *fw_upload);
+       void (*cleanup)(struct fw_upload *fw_upload);
+};
+
 struct module;
 struct device;
 
 
 #endif
 
+#ifdef CONFIG_FW_UPLOAD
+
+struct fw_upload *
+firmware_upload_register(struct module *module, struct device *parent,
+                        const char *name, const struct fw_upload_ops *ops,
+                        void *dd_handle);
+void firmware_upload_unregister(struct fw_upload *fw_upload);
+
+#else
+
+static inline struct fw_upload *
+firmware_upload_register(struct module *module, struct device *parent,
+                        const char *name, const struct fw_upload_ops *ops,
+                        void *dd_handle)
+{
+               return ERR_PTR(-EINVAL);
+}
+
+static inline void firmware_upload_unregister(struct fw_upload *fw_upload)
+{
+}
+
+#endif
+
 int firmware_request_cache(struct device *device, const char *name);
 
 #endif