}
 EXPORT_SYMBOL(sof_print_oops_and_stack);
 
+/* Helper to manage DSP state */
+void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state)
+{
+       if (sdev->fw_state == new_state)
+               return;
+
+       dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state);
+       sdev->fw_state = new_state;
+
+       switch (new_state) {
+       case SOF_FW_BOOT_NOT_STARTED:
+       case SOF_FW_BOOT_COMPLETE:
+       case SOF_FW_CRASHED:
+               sof_client_fw_state_dispatcher(sdev);
+               fallthrough;
+       default:
+               break;
+       }
+}
+EXPORT_SYMBOL(sof_set_fw_state);
+
 /*
  *                     FW Boot State Transition Diagram
  *
                goto fw_trace_err;
        }
 
+       ret = sof_register_clients(sdev);
+       if (ret < 0) {
+               dev_err(sdev->dev, "failed to register clients %d\n", ret);
+               goto sof_machine_err;
+       }
+
        /*
         * Some platforms in SOF, ex: BYT, may not have their platform PM
         * callbacks set. Increment the usage count so as to
 
        return 0;
 
+sof_machine_err:
+       snd_sof_machine_unregister(sdev, plat_data);
 fw_trace_err:
        snd_sof_free_trace(sdev);
 fw_run_err:
 
        sdev->pdata = plat_data;
        sdev->first_boot = true;
-       sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
 #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_PROBES)
        sdev->extractor_stream_tag = SOF_PROBE_INVALID_NODE_ID;
 #endif
        INIT_LIST_HEAD(&sdev->widget_list);
        INIT_LIST_HEAD(&sdev->dai_list);
        INIT_LIST_HEAD(&sdev->route_list);
+       INIT_LIST_HEAD(&sdev->ipc_client_list);
+       INIT_LIST_HEAD(&sdev->ipc_rx_handler_list);
+       INIT_LIST_HEAD(&sdev->fw_state_handler_list);
        spin_lock_init(&sdev->ipc_lock);
        spin_lock_init(&sdev->hw_lock);
        mutex_init(&sdev->power_state_access);
+       mutex_init(&sdev->ipc_client_mutex);
+       mutex_init(&sdev->client_event_handler_mutex);
 
        /* set default timeouts if none provided */
        if (plat_data->desc->ipc_timeout == 0)
        else
                sdev->boot_timeout = plat_data->desc->boot_timeout;
 
+       sof_set_fw_state(sdev, SOF_FW_BOOT_NOT_STARTED);
+
        if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) {
                INIT_WORK(&sdev->probe_work, sof_probe_work);
                schedule_work(&sdev->probe_work);
        if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE))
                cancel_work_sync(&sdev->probe_work);
 
+       /*
+        * Unregister any registered client device first before IPC and debugfs
+        * to allow client drivers to be removed cleanly
+        */
+       sof_unregister_clients(sdev);
+
        /*
         * Unregister machine driver. This will unbind the snd_card which
         * will remove the component driver and unload the topology
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright(c) 2022 Intel Corporation. All rights reserved.
+//
+// Authors: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
+//         Peter Ujfalusi <peter.ujfalusi@linux.intel.com>
+//
+
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include "ops.h"
+#include "sof-client.h"
+#include "sof-priv.h"
+
+/**
+ * struct sof_ipc_event_entry - IPC client event description
+ * @ipc_msg_type:      IPC msg type of the event the client is interested
+ * @cdev:              sof_client_dev of the requesting client
+ * @callback:          Callback function of the client
+ * @list:              item in SOF core client event list
+ */
+struct sof_ipc_event_entry {
+       u32 ipc_msg_type;
+       struct sof_client_dev *cdev;
+       sof_client_event_callback callback;
+       struct list_head list;
+};
+
+/**
+ * struct sof_state_event_entry - DSP panic event subscription entry
+ * @cdev:              sof_client_dev of the requesting client
+ * @callback:          Callback function of the client
+ * @list:              item in SOF core client event list
+ */
+struct sof_state_event_entry {
+       struct sof_client_dev *cdev;
+       sof_client_fw_state_callback callback;
+       struct list_head list;
+};
+
+static void sof_client_auxdev_release(struct device *dev)
+{
+       struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+       struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev);
+
+       kfree(cdev->auxdev.dev.platform_data);
+       kfree(cdev);
+}
+
+static int sof_client_dev_add_data(struct sof_client_dev *cdev, const void *data,
+                                  size_t size)
+{
+       void *d = NULL;
+
+       if (data) {
+               d = kmemdup(data, size, GFP_KERNEL);
+               if (!d)
+                       return -ENOMEM;
+       }
+
+       cdev->auxdev.dev.platform_data = d;
+       return 0;
+}
+
+int sof_register_clients(struct snd_sof_dev *sdev)
+{
+       if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients)
+               return sof_ops(sdev)->register_ipc_clients(sdev);
+
+       return 0;
+}
+
+void sof_unregister_clients(struct snd_sof_dev *sdev)
+{
+       if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients)
+               sof_ops(sdev)->unregister_ipc_clients(sdev);
+}
+
+int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id,
+                           const void *data, size_t size)
+{
+       struct auxiliary_device *auxdev;
+       struct sof_client_dev *cdev;
+       int ret;
+
+       cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);
+       if (!cdev)
+               return -ENOMEM;
+
+       cdev->sdev = sdev;
+       auxdev = &cdev->auxdev;
+       auxdev->name = name;
+       auxdev->dev.parent = sdev->dev;
+       auxdev->dev.release = sof_client_auxdev_release;
+       auxdev->id = id;
+
+       ret = sof_client_dev_add_data(cdev, data, size);
+       if (ret < 0)
+               goto err_dev_add_data;
+
+       ret = auxiliary_device_init(auxdev);
+       if (ret < 0) {
+               dev_err(sdev->dev, "failed to initialize client dev %s.%d\n", name, id);
+               goto err_dev_init;
+       }
+
+       ret = auxiliary_device_add(&cdev->auxdev);
+       if (ret < 0) {
+               dev_err(sdev->dev, "failed to add client dev %s.%d\n", name, id);
+               /*
+                * sof_client_auxdev_release() will be invoked to free up memory
+                * allocations through put_device()
+                */
+               auxiliary_device_uninit(&cdev->auxdev);
+               return ret;
+       }
+
+       /* add to list of SOF client devices */
+       mutex_lock(&sdev->ipc_client_mutex);
+       list_add(&cdev->list, &sdev->ipc_client_list);
+       mutex_unlock(&sdev->ipc_client_mutex);
+
+       return 0;
+
+err_dev_init:
+       kfree(cdev->auxdev.dev.platform_data);
+
+err_dev_add_data:
+       kfree(cdev);
+
+       return ret;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_dev_register, SND_SOC_SOF_CLIENT);
+
+void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id)
+{
+       struct sof_client_dev *cdev;
+
+       mutex_lock(&sdev->ipc_client_mutex);
+
+       /*
+        * sof_client_auxdev_release() will be invoked to free up memory
+        * allocations through put_device()
+        */
+       list_for_each_entry(cdev, &sdev->ipc_client_list, list) {
+               if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) {
+                       list_del(&cdev->list);
+                       auxiliary_device_delete(&cdev->auxdev);
+                       auxiliary_device_uninit(&cdev->auxdev);
+                       break;
+               }
+       }
+
+       mutex_unlock(&sdev->ipc_client_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_dev_unregister, SND_SOC_SOF_CLIENT);
+
+int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg,
+                             void *reply_data, size_t reply_bytes)
+{
+       struct sof_ipc_cmd_hdr *hdr = ipc_msg;
+
+       return sof_ipc_tx_message(cdev->sdev->ipc, hdr->cmd, ipc_msg, hdr->size,
+                                 reply_data, reply_bytes);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT);
+
+struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev)
+{
+       return cdev->sdev->debugfs_root;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_debugfs_root, SND_SOC_SOF_CLIENT);
+
+/* DMA buffer allocation in client drivers must use the core SOF device */
+struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev)
+{
+       return cdev->sdev->dev;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_dma_dev, SND_SOC_SOF_CLIENT);
+
+const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+       return &sdev->fw_ready.version;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_version, SND_SOC_SOF_CLIENT);
+
+/* module refcount management of SOF core */
+int sof_client_core_module_get(struct sof_client_dev *cdev)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+       if (!try_module_get(sdev->dev->driver->owner))
+               return -ENODEV;
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_core_module_get, SND_SOC_SOF_CLIENT);
+
+void sof_client_core_module_put(struct sof_client_dev *cdev)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+       module_put(sdev->dev->driver->owner);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_core_module_put, SND_SOC_SOF_CLIENT);
+
+/* IPC event handling */
+void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf)
+{
+       struct sof_ipc_cmd_hdr *hdr = msg_buf;
+       u32 msg_type = hdr->cmd & SOF_GLB_TYPE_MASK;
+       struct sof_ipc_event_entry *event;
+
+       mutex_lock(&sdev->client_event_handler_mutex);
+
+       list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) {
+               if (event->ipc_msg_type == msg_type)
+                       event->callback(event->cdev, msg_buf);
+       }
+
+       mutex_unlock(&sdev->client_event_handler_mutex);
+}
+
+int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev,
+                                      u32 ipc_msg_type,
+                                      sof_client_event_callback callback)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+       struct sof_ipc_event_entry *event;
+
+       if (!callback || !(ipc_msg_type & SOF_GLB_TYPE_MASK))
+               return -EINVAL;
+
+       event = kmalloc(sizeof(*event), GFP_KERNEL);
+       if (!event)
+               return -ENOMEM;
+
+       event->ipc_msg_type = ipc_msg_type;
+       event->cdev = cdev;
+       event->callback = callback;
+
+       /* add to list of SOF client devices */
+       mutex_lock(&sdev->client_event_handler_mutex);
+       list_add(&event->list, &sdev->ipc_rx_handler_list);
+       mutex_unlock(&sdev->client_event_handler_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_register_ipc_rx_handler, SND_SOC_SOF_CLIENT);
+
+void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev,
+                                         u32 ipc_msg_type)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+       struct sof_ipc_event_entry *event;
+
+       mutex_lock(&sdev->client_event_handler_mutex);
+
+       list_for_each_entry(event, &sdev->ipc_rx_handler_list, list) {
+               if (event->cdev == cdev && event->ipc_msg_type == ipc_msg_type) {
+                       list_del(&event->list);
+                       kfree(event);
+                       break;
+               }
+       }
+
+       mutex_unlock(&sdev->client_event_handler_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_unregister_ipc_rx_handler, SND_SOC_SOF_CLIENT);
+
+/*DSP state notification and query */
+void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev)
+{
+       struct sof_state_event_entry *event;
+
+       mutex_lock(&sdev->client_event_handler_mutex);
+
+       list_for_each_entry(event, &sdev->fw_state_handler_list, list)
+               event->callback(event->cdev, sdev->fw_state);
+
+       mutex_unlock(&sdev->client_event_handler_mutex);
+}
+
+int sof_client_register_fw_state_handler(struct sof_client_dev *cdev,
+                                        sof_client_fw_state_callback callback)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+       struct sof_state_event_entry *event;
+
+       if (!callback)
+               return -EINVAL;
+
+       event = kmalloc(sizeof(*event), GFP_KERNEL);
+       if (!event)
+               return -ENOMEM;
+
+       event->cdev = cdev;
+       event->callback = callback;
+
+       /* add to list of SOF client devices */
+       mutex_lock(&sdev->client_event_handler_mutex);
+       list_add(&event->list, &sdev->fw_state_handler_list);
+       mutex_unlock(&sdev->client_event_handler_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_register_fw_state_handler, SND_SOC_SOF_CLIENT);
+
+void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+       struct sof_state_event_entry *event;
+
+       mutex_lock(&sdev->client_event_handler_mutex);
+
+       list_for_each_entry(event, &sdev->fw_state_handler_list, list) {
+               if (event->cdev == cdev) {
+                       list_del(&event->list);
+                       kfree(event);
+                       break;
+               }
+       }
+
+       mutex_unlock(&sdev->client_event_handler_mutex);
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_unregister_fw_state_handler, SND_SOC_SOF_CLIENT);
+
+enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev)
+{
+       struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev);
+
+       return sdev->fw_state;
+}
+EXPORT_SYMBOL_NS_GPL(sof_client_get_fw_state, SND_SOC_SOF_CLIENT);
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __SOC_SOF_CLIENT_H
+#define __SOC_SOF_CLIENT_H
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <sound/sof.h>
+
+struct sof_ipc_fw_version;
+struct sof_ipc_cmd_hdr;
+struct snd_sof_dev;
+struct dentry;
+
+/**
+ * struct sof_client_dev - SOF client device
+ * @auxdev:    auxiliary device
+ * @sdev:      pointer to SOF core device struct
+ * @list:      item in SOF core client dev list
+ * @data:      device specific data
+ */
+struct sof_client_dev {
+       struct auxiliary_device auxdev;
+       struct snd_sof_dev *sdev;
+       struct list_head list;
+       void *data;
+};
+
+#define sof_client_dev_to_sof_dev(cdev)                ((cdev)->sdev)
+
+#define auxiliary_dev_to_sof_client_dev(auxiliary_dev) \
+       container_of(auxiliary_dev, struct sof_client_dev, auxdev)
+
+#define dev_to_sof_client_dev(dev) \
+       container_of(to_auxiliary_dev(dev), struct sof_client_dev, auxdev)
+
+int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg,
+                             void *reply_data, size_t reply_bytes);
+
+struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev);
+struct device *sof_client_get_dma_dev(struct sof_client_dev *cdev);
+const struct sof_ipc_fw_version *sof_client_get_fw_version(struct sof_client_dev *cdev);
+
+/* module refcount management of SOF core */
+int sof_client_core_module_get(struct sof_client_dev *cdev);
+void sof_client_core_module_put(struct sof_client_dev *cdev);
+
+/* IPC notification */
+typedef void (*sof_client_event_callback)(struct sof_client_dev *cdev, void *msg_buf);
+
+int sof_client_register_ipc_rx_handler(struct sof_client_dev *cdev,
+                                      u32 ipc_msg_type,
+                                      sof_client_event_callback callback);
+void sof_client_unregister_ipc_rx_handler(struct sof_client_dev *cdev,
+                                         u32 ipc_msg_type);
+
+/* DSP state notification and query */
+typedef void (*sof_client_fw_state_callback)(struct sof_client_dev *cdev,
+                                            enum sof_fw_state state);
+
+int sof_client_register_fw_state_handler(struct sof_client_dev *cdev,
+                                        sof_client_fw_state_callback callback);
+void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev);
+enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev);
+
+#endif /* __SOC_SOF_CLIENT_H */
 
        void (*set_mach_params)(struct snd_soc_acpi_mach *mach,
                                struct snd_sof_dev *sdev); /* optional */
 
+       /* IPC client ops */
+       int (*register_ipc_clients)(struct snd_sof_dev *sdev); /* optional */
+       void (*unregister_ipc_clients)(struct snd_sof_dev *sdev); /* optional */
+
        /* DAI ops */
        struct snd_soc_dai_driver *drv;
        int num_drv;
         */
        int dsp_core_ref_count[SOF_MAX_DSP_NUM_CORES];
 
+       /*
+        * Used to keep track of registered IPC client devices so that they can
+        * be removed when the parent SOF module is removed.
+        */
+       struct list_head ipc_client_list;
+
+       /* mutex to protect client list */
+       struct mutex ipc_client_mutex;
+
+       /*
+        * Used for tracking the IPC client's RX registration for DSP initiated
+        * message handling.
+        */
+       struct list_head ipc_rx_handler_list;
+
+       /*
+        * Used for tracking the IPC client's registration for DSP state change
+        * notification
+        */
+       struct list_head fw_state_handler_list;
+
+       /* to protect the ipc_rx_handler_list  and  dsp_state_handler_list list */
+       struct mutex client_event_handler_mutex;
+
        void *private;                  /* core does not touch this */
 };
 
 /*
  * Firmware state tracking
  */
-static inline void sof_set_fw_state(struct snd_sof_dev *sdev,
-                                   enum sof_fw_state new_state)
-{
-       if (sdev->fw_state == new_state)
-               return;
-
-       dev_dbg(sdev->dev, "fw_state change: %d -> %d\n", sdev->fw_state, new_state);
-       sdev->fw_state = new_state;
-}
+void sof_set_fw_state(struct snd_sof_dev *sdev, enum sof_fw_state new_state);
 
 /*
  * Utilities
                         struct snd_pcm_substream *substream);
 
 int sof_machine_check(struct snd_sof_dev *sdev);
+
+/* SOF client support */
+#if IS_ENABLED(CONFIG_SND_SOC_SOF_CLIENT)
+int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id,
+                           const void *data, size_t size);
+void sof_client_dev_unregister(struct snd_sof_dev *sdev, const char *name, u32 id);
+int sof_register_clients(struct snd_sof_dev *sdev);
+void sof_unregister_clients(struct snd_sof_dev *sdev);
+void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf);
+void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev);
+#else /* CONFIG_SND_SOC_SOF_CLIENT */
+static inline int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name,
+                                         u32 id, const void *data, size_t size)
+{
+       return 0;
+}
+
+static inline void sof_client_dev_unregister(struct snd_sof_dev *sdev,
+                                            const char *name, u32 id)
+{
+}
+
+static inline int sof_register_clients(struct snd_sof_dev *sdev)
+{
+       return 0;
+}
+
+static inline  void sof_unregister_clients(struct snd_sof_dev *sdev)
+{
+}
+
+static inline void sof_client_ipc_rx_dispatcher(struct snd_sof_dev *sdev, void *msg_buf)
+{
+}
+
+static inline void sof_client_fw_state_dispatcher(struct snd_sof_dev *sdev)
+{
+}
+#endif /* CONFIG_SND_SOC_SOF_CLIENT */
+
 #endif