| // SPDX-License-Identifier: GPL-2.0-only | |
| // | |
| // Copyright(c) 2022 Intel Corporation | |
| // | |
| // 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 <sound/sof/ipc4/header.h> | |
| #include "ops.h" | |
| #include "sof-client.h" | |
| #include "sof-priv.h" | |
| #include "ipc3-priv.h" | |
| #include "ipc4-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; | |
| }; | |
| /** | |
| * struct sof_client_dev_entry - client device entry for internal management use | |
| * @sdev: pointer to SOF core device struct | |
| * @list: item in SOF core client dev list | |
| * @client_dev: SOF client device | |
| */ | |
| struct sof_client_dev_entry { | |
| struct snd_sof_dev *sdev; | |
| struct list_head list; | |
| struct sof_client_dev client_dev; | |
| }; | |
| #define cdev_to_centry(cdev) \ | |
| container_of(cdev, struct sof_client_dev_entry, client_dev) | |
| 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); | |
| struct sof_client_dev_entry *centry = cdev_to_centry(cdev); | |
| kfree(cdev->auxdev.dev.platform_data); | |
| kfree(centry); | |
| } | |
| 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; | |
| } | |
| #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) | |
| static int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) | |
| { | |
| int ret = 0; | |
| int i; | |
| if (sdev->pdata->ipc_type != SOF_IPC_TYPE_3) | |
| return 0; | |
| for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) { | |
| ret = sof_client_dev_register(sdev, "ipc_flood", i, NULL, 0); | |
| if (ret < 0) | |
| break; | |
| } | |
| if (ret) { | |
| for (; i >= 0; --i) | |
| sof_client_dev_unregister(sdev, "ipc_flood", i); | |
| } | |
| return ret; | |
| } | |
| static void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) | |
| { | |
| int i; | |
| for (i = 0; i < CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST_NUM; i++) | |
| sof_client_dev_unregister(sdev, "ipc_flood", i); | |
| } | |
| #else | |
| static inline int sof_register_ipc_flood_test(struct snd_sof_dev *sdev) | |
| { | |
| return 0; | |
| } | |
| static inline void sof_unregister_ipc_flood_test(struct snd_sof_dev *sdev) {} | |
| #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST */ | |
| #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) | |
| static int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) | |
| { | |
| return sof_client_dev_register(sdev, "msg_injector", 0, NULL, 0); | |
| } | |
| static void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) | |
| { | |
| sof_client_dev_unregister(sdev, "msg_injector", 0); | |
| } | |
| #else | |
| static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) | |
| { | |
| return 0; | |
| } | |
| static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {} | |
| #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */ | |
| #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) | |
| static int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev) | |
| { | |
| /* Only IPC3 supported right now */ | |
| if (sdev->pdata->ipc_type != SOF_IPC_TYPE_3) | |
| return 0; | |
| return sof_client_dev_register(sdev, "kernel_injector", 0, NULL, 0); | |
| } | |
| static void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) | |
| { | |
| sof_client_dev_unregister(sdev, "kernel_injector", 0); | |
| } | |
| #else | |
| static inline int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev) | |
| { | |
| return 0; | |
| } | |
| static inline void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) {} | |
| #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR */ | |
| int sof_register_clients(struct snd_sof_dev *sdev) | |
| { | |
| int ret; | |
| if (sdev->dspless_mode_selected) | |
| return 0; | |
| /* Register platform independent client devices */ | |
| ret = sof_register_ipc_flood_test(sdev); | |
| if (ret) { | |
| dev_err(sdev->dev, "IPC flood test client registration failed\n"); | |
| return ret; | |
| } | |
| ret = sof_register_ipc_msg_injector(sdev); | |
| if (ret) { | |
| dev_err(sdev->dev, "IPC message injector client registration failed\n"); | |
| goto err_msg_injector; | |
| } | |
| ret = sof_register_ipc_kernel_injector(sdev); | |
| if (ret) { | |
| dev_err(sdev->dev, "IPC kernel injector client registration failed\n"); | |
| goto err_kernel_injector; | |
| } | |
| /* Platform dependent client device registration */ | |
| if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) | |
| ret = sof_ops(sdev)->register_ipc_clients(sdev); | |
| if (!ret) | |
| return 0; | |
| sof_unregister_ipc_kernel_injector(sdev); | |
| err_kernel_injector: | |
| sof_unregister_ipc_msg_injector(sdev); | |
| err_msg_injector: | |
| sof_unregister_ipc_flood_test(sdev); | |
| return ret; | |
| } | |
| 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); | |
| sof_unregister_ipc_kernel_injector(sdev); | |
| sof_unregister_ipc_msg_injector(sdev); | |
| sof_unregister_ipc_flood_test(sdev); | |
| } | |
| int sof_client_dev_register(struct snd_sof_dev *sdev, const char *name, u32 id, | |
| const void *data, size_t size) | |
| { | |
| struct sof_client_dev_entry *centry; | |
| struct auxiliary_device *auxdev; | |
| struct sof_client_dev *cdev; | |
| int ret; | |
| centry = kzalloc_obj(*centry); | |
| if (!centry) | |
| return -ENOMEM; | |
| cdev = ¢ry->client_dev; | |
| centry->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 */ | |
| scoped_guard(mutex, &sdev->ipc_client_mutex) | |
| list_add(¢ry->list, &sdev->ipc_client_list); | |
| return 0; | |
| err_dev_init: | |
| kfree(cdev->auxdev.dev.platform_data); | |
| err_dev_add_data: | |
| kfree(centry); | |
| 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_entry *centry; | |
| guard(mutex)(&sdev->ipc_client_mutex); | |
| /* | |
| * sof_client_auxdev_release() will be invoked to free up memory | |
| * allocations through put_device() | |
| */ | |
| list_for_each_entry(centry, &sdev->ipc_client_list, list) { | |
| struct sof_client_dev *cdev = ¢ry->client_dev; | |
| if (!strcmp(cdev->auxdev.name, name) && cdev->auxdev.id == id) { | |
| list_del(¢ry->list); | |
| auxiliary_device_delete(&cdev->auxdev); | |
| auxiliary_device_uninit(&cdev->auxdev); | |
| break; | |
| } | |
| } | |
| } | |
| 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 snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) { | |
| struct sof_ipc_cmd_hdr *hdr = ipc_msg; | |
| return sof_ipc_tx_message(sdev->ipc, ipc_msg, hdr->size, | |
| reply_data, reply_bytes); | |
| } else if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { | |
| struct sof_ipc4_msg *msg = ipc_msg; | |
| return sof_ipc_tx_message(sdev->ipc, ipc_msg, msg->data_size, | |
| reply_data, reply_bytes); | |
| } | |
| return -EINVAL; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, "SND_SOC_SOF_CLIENT"); | |
| int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| if (IS_ENABLED(CONFIG_SND_SOC_SOF_IPC3) && | |
| sdev->pdata->ipc_type == SOF_IPC_TYPE_3) { | |
| struct sof_ipc_cmd_hdr *hdr = ipc_msg; | |
| if (hdr->size < sizeof(hdr)) { | |
| dev_err(sdev->dev, "The received message size is invalid\n"); | |
| return -EINVAL; | |
| } | |
| sof_ipc3_do_rx_work(sdev, ipc_msg, msg_buf); | |
| return 0; | |
| } | |
| return -EOPNOTSUPP; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_ipc_rx_message, "SND_SOC_SOF_CLIENT"); | |
| int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg, | |
| bool set) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) { | |
| struct sof_ipc_cmd_hdr *hdr = ipc_msg; | |
| return sof_ipc_set_get_data(sdev->ipc, ipc_msg, hdr->size, set); | |
| } else if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { | |
| struct sof_ipc4_msg *msg = ipc_msg; | |
| return sof_ipc_set_get_data(sdev->ipc, ipc_msg, msg->data_size, | |
| set); | |
| } | |
| return -EINVAL; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_ipc_set_get_data, "SND_SOC_SOF_CLIENT"); | |
| #ifdef CONFIG_SND_SOC_SOF_IPC4 | |
| struct sof_ipc4_fw_module *sof_client_ipc4_find_module(struct sof_client_dev *c, const guid_t *uuid) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(c); | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) | |
| return sof_ipc4_find_module_by_uuid(sdev, uuid); | |
| dev_err(sdev->dev, "Only supported with IPC4\n"); | |
| return NULL; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_ipc4_find_module, "SND_SOC_SOF_CLIENT"); | |
| struct snd_sof_widget *sof_client_ipc4_find_swidget_by_id(struct sof_client_dev *cdev, | |
| u32 module_id, int instance_id) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) | |
| return sof_ipc4_find_swidget_by_ids(sdev, module_id, instance_id); | |
| dev_err(sdev->dev, "Only supported with IPC4\n"); | |
| return NULL; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_ipc4_find_swidget_by_id, "SND_SOC_SOF_CLIENT"); | |
| #endif | |
| int sof_suspend_clients(struct snd_sof_dev *sdev, pm_message_t state) | |
| { | |
| const struct auxiliary_driver *adrv; | |
| struct sof_client_dev_entry *centry; | |
| guard(mutex)(&sdev->ipc_client_mutex); | |
| list_for_each_entry(centry, &sdev->ipc_client_list, list) { | |
| struct sof_client_dev *cdev = ¢ry->client_dev; | |
| /* Skip devices without loaded driver */ | |
| if (!cdev->auxdev.dev.driver) | |
| continue; | |
| adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); | |
| if (adrv->suspend) | |
| adrv->suspend(&cdev->auxdev, state); | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_suspend_clients, "SND_SOC_SOF_CLIENT"); | |
| int sof_resume_clients(struct snd_sof_dev *sdev) | |
| { | |
| const struct auxiliary_driver *adrv; | |
| struct sof_client_dev_entry *centry; | |
| guard(mutex)(&sdev->ipc_client_mutex); | |
| list_for_each_entry(centry, &sdev->ipc_client_list, list) { | |
| struct sof_client_dev *cdev = ¢ry->client_dev; | |
| /* Skip devices without loaded driver */ | |
| if (!cdev->auxdev.dev.driver) | |
| continue; | |
| adrv = to_auxiliary_drv(cdev->auxdev.dev.driver); | |
| if (adrv->resume) | |
| adrv->resume(&cdev->auxdev); | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_resume_clients, "SND_SOC_SOF_CLIENT"); | |
| struct dentry *sof_client_get_debugfs_root(struct sof_client_dev *cdev) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| return 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) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| return 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"); | |
| size_t sof_client_get_ipc_max_payload_size(struct sof_client_dev *cdev) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| return sdev->ipc->max_payload_size; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_max_payload_size, "SND_SOC_SOF_CLIENT"); | |
| enum sof_ipc_type sof_client_get_ipc_type(struct sof_client_dev *cdev) | |
| { | |
| struct snd_sof_dev *sdev = sof_client_dev_to_sof_dev(cdev); | |
| return sdev->pdata->ipc_type; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_get_ipc_type, "SND_SOC_SOF_CLIENT"); | |
| int sof_client_boot_dsp(struct sof_client_dev *cdev) | |
| { | |
| return snd_sof_boot_dsp_firmware(sof_client_dev_to_sof_dev(cdev)); | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_boot_dsp, "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_event_entry *event; | |
| u32 msg_type; | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) { | |
| struct sof_ipc_cmd_hdr *hdr = msg_buf; | |
| msg_type = hdr->cmd & SOF_GLB_TYPE_MASK; | |
| } else if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { | |
| struct sof_ipc4_msg *msg = msg_buf; | |
| msg_type = SOF_IPC4_NOTIFICATION_TYPE_GET(msg->primary); | |
| } else { | |
| dev_dbg_once(sdev->dev, "Not supported IPC version: %d\n", | |
| sdev->pdata->ipc_type); | |
| return; | |
| } | |
| guard(mutex)(&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); | |
| } | |
| } | |
| 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) | |
| return -EINVAL; | |
| if (sdev->pdata->ipc_type == SOF_IPC_TYPE_3) { | |
| if (!(ipc_msg_type & SOF_GLB_TYPE_MASK)) | |
| return -EINVAL; | |
| } else if (sdev->pdata->ipc_type == SOF_IPC_TYPE_4) { | |
| if (!(ipc_msg_type & SOF_IPC4_NOTIFICATION_TYPE_MASK)) | |
| return -EINVAL; | |
| } else { | |
| dev_warn(sdev->dev, "%s: Not supported IPC version: %d\n", | |
| __func__, sdev->pdata->ipc_type); | |
| return -EINVAL; | |
| } | |
| event = kmalloc_obj(*event); | |
| if (!event) | |
| return -ENOMEM; | |
| event->ipc_msg_type = ipc_msg_type; | |
| event->cdev = cdev; | |
| event->callback = callback; | |
| /* add to list of SOF client devices */ | |
| guard(mutex)(&sdev->client_event_handler_mutex); | |
| list_add(&event->list, &sdev->ipc_rx_handler_list); | |
| 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; | |
| guard(mutex)(&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; | |
| } | |
| } | |
| } | |
| 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; | |
| guard(mutex)(&sdev->client_event_handler_mutex); | |
| list_for_each_entry(event, &sdev->fw_state_handler_list, list) | |
| event->callback(event->cdev, sdev->fw_state); | |
| } | |
| 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_obj(*event); | |
| if (!event) | |
| return -ENOMEM; | |
| event->cdev = cdev; | |
| event->callback = callback; | |
| /* add to list of SOF client devices */ | |
| guard(mutex)(&sdev->client_event_handler_mutex); | |
| list_add(&event->list, &sdev->fw_state_handler_list); | |
| 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; | |
| guard(mutex)(&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; | |
| } | |
| } | |
| } | |
| 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"); | |
| struct snd_sof_dev *sof_client_dev_to_sof_dev(struct sof_client_dev *cdev) | |
| { | |
| struct sof_client_dev_entry *centry = cdev_to_centry(cdev); | |
| return centry->sdev; | |
| } | |
| EXPORT_SYMBOL_NS_GPL(sof_client_dev_to_sof_dev, "SND_SOC_SOF_CLIENT"); |