diff --git a/include/soc/snd_event.h b/include/soc/snd_event.h new file mode 100644 index 000000000000..af566735343e --- /dev/null +++ b/include/soc/snd_event.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _SND_EVENT_H_ +#define _SND_EVENT_H_ + +enum { + SND_EVENT_DOWN = 0, + SND_EVENT_UP, +}; + +struct snd_event_clients; + +struct snd_event_ops { + int (*enable)(struct device *dev, void *data); + void (*disable)(struct device *dev, void *data); +}; + +#ifdef CONFIG_SND_EVENT +int snd_event_client_register(struct device *dev, + const struct snd_event_ops *snd_ev_ops, + void *data); +int snd_event_client_deregister(struct device *dev); +int snd_event_master_register(struct device *dev, + const struct snd_event_ops *ops, + struct snd_event_clients *clients, + void *data); +int snd_event_master_deregister(struct device *dev); +int snd_event_notify(struct device *dev, unsigned int state); + +void snd_event_mstr_add_client(struct snd_event_clients **snd_clients, + int (*compare)(struct device *, void *), + void *data); +inline bool is_snd_event_fwk_enabled(void) +{ + return 1; +} +#else +int snd_event_client_register(struct device *dev, + const struct snd_event_ops *snd_ev_ops, + void *data) +{ + return 0; +} +int snd_event_client_deregister(struct device *dev) +{ + return 0; +} +int snd_event_master_register(struct device *dev, + const struct snd_event_ops *ops, + struct snd_event_clients *clients, + void *data) +{ + return 0; +} +int snd_event_master_deregister(struct device *dev) +{ + return 0; +} +int snd_event_notify(struct device *dev, unsigned int state) +{ + return 0; +} + +void snd_event_mstr_add_client(struct snd_event_clients **snd_clients, + int (*compare)(struct device *, void *), + void *data) +{ + return; +} +inline bool is_snd_event_fwk_enabled(void) +{ + return 0; +} + +#endif /* CONFIG_SND_EVENT */ +#endif /* _SND_EVENT_H_ */ diff --git a/soc/Android.mk b/soc/Android.mk index dc6a5b32f822..24016adacb63 100644 --- a/soc/Android.mk +++ b/soc/Android.mk @@ -82,6 +82,16 @@ LOCAL_MODULE_DEBUG_ENABLE := true LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) include $(DLKM_DIR)/AndroidKernelModule.mk ########################################################### +ifeq ($(call is-board-platform-in-list, ),true) +include $(CLEAR_VARS) +LOCAL_MODULE := $(AUDIO_CHIPSET)_snd_event.ko +LOCAL_MODULE_KBUILD_NAME := snd_event_dlkm.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_DEBUG_ENABLE := true +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk +endif +########################################################### endif # DLKM check endif # supported target check diff --git a/soc/Kbuild b/soc/Kbuild index 54ca077e4936..cb067bbb54a1 100644 --- a/soc/Kbuild +++ b/soc/Kbuild @@ -102,6 +102,10 @@ ifdef CONFIG_SOUNDWIRE SWR_OBJS += soundwire.o endif +ifdef CONFIG_SND_EVENT + SND_EVENT_OBJS += snd_event.o +endif + LINUX_INC += -Iinclude/linux INCS += $(COMMON_INC) \ @@ -149,6 +153,9 @@ pinctrl_lpi_dlkm-y := $(PINCTRL_LPI_OBJS) obj-$(CONFIG_SOUNDWIRE) += swr_dlkm.o swr_dlkm-y := $(SWR_OBJS) +obj-$(CONFIG_SND_EVENT) += snd_event_dlkm.o +snd_event_dlkm-y := $(SND_EVENT_OBJS) + obj-$(CONFIG_SOUNDWIRE_WCD_CTRL) += swr_ctrl_dlkm.o obj-$(CONFIG_SOUNDWIRE_MSTR_CTRL) += swr_ctrl_dlkm.o swr_ctrl_dlkm-y := $(SWR_CTRL_OBJS) diff --git a/soc/snd_event.c b/soc/snd_event.c new file mode 100644 index 000000000000..32407742b2a2 --- /dev/null +++ b/soc/snd_event.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include + +struct snd_event_client { + struct list_head node; + + struct device *dev; + const struct snd_event_ops *ops; + void *data; + + bool attached; + bool state; +}; + +struct snd_event_client_array { + struct device *dev; + struct snd_event_client *clnt; + void *data; + int (*compare)(struct device *, void *); +}; + +struct snd_event_clients { + size_t num_clients; + struct snd_event_client_array *cl_arr; +}; + +struct snd_master { + struct device *dev; + const struct snd_event_ops *ops; + void *data; + + bool state; + bool fwk_state; + bool clients_found; + struct snd_event_clients *clients; +}; + +static DEFINE_MUTEX(snd_event_mutex); +static LIST_HEAD(snd_event_client_list); +static struct snd_master *master; + +static struct snd_event_client *find_snd_event_client(struct device *dev) +{ + struct snd_event_client *c; + + list_for_each_entry(c, &snd_event_client_list, node) + if ((c->dev == dev) && c->ops) + return c; + + return NULL; +} + +static int check_and_update_fwk_state(void) +{ + bool new_fwk_state = true; + struct snd_event_client *c; + int ret = 0; + int i = 0; + + for (i = 0; i < master->clients->num_clients; i++) { + c = master->clients->cl_arr[i].clnt; + new_fwk_state &= c->state; + } + new_fwk_state &= master->state; + + if (master->fwk_state ^ new_fwk_state) { + if (new_fwk_state) { + for (i = 0; i < master->clients->num_clients; i++) { + c = master->clients->cl_arr[i].clnt; + if (c->ops->enable) { + ret = c->ops->enable(c->dev, c->data); + if (ret) { + dev_err(c->dev, + "%s: enable failed\n", + __func__); + goto dev_en_failed; + } + } + } + if (master->ops->enable) { + ret = master->ops->enable(master->dev, + master->data); + if (ret) { + dev_err(master->dev, + "%s: enable failed\n", + __func__); + goto mstr_en_failed; + } + } + } else { + if (master->ops->disable) + master->ops->disable(master->dev, + master->data); + for (i = 0; i < master->clients->num_clients; i++) { + c = master->clients->cl_arr[i].clnt; + if (c->ops->disable) + c->ops->disable(c->dev, c->data); + } + } + master->fwk_state = new_fwk_state; + } + goto exit; + +mstr_en_failed: + i = master->clients->num_clients; +dev_en_failed: + for (; i > 0; i--) { + c = master->clients->cl_arr[i - 1].clnt; + if (c->ops->disable) + c->ops->disable(c->dev, c->data); + } +exit: + return ret; +} + +static int snd_event_find_clients(struct snd_master *master) +{ + struct snd_event_clients *clients = master->clients; + int i = 0; + int ret = 0; + + for (i = 0; i < clients->num_clients; i++) { + struct snd_event_client_array *c_arr = &clients->cl_arr[i]; + struct snd_event_client *c; + + if (c_arr->dev) { + pr_err("%s: client already present dev=%pK\n", + __func__, c_arr->dev); + continue; + } + + list_for_each_entry(c, &snd_event_client_list, node) { + if (c->attached) + continue; + + if (c_arr->compare(c->dev, c_arr->data)) { + dev_dbg(master->dev, + "%s: found client, dev=%pK\n", + __func__, c->dev); + c_arr->dev = c->dev; + c_arr->clnt = c; + c->attached = true; + break; + } + } + if (!c_arr->dev) { + dev_dbg(master->dev, + "%s: failed to find some client\n", + __func__); + ret = -ENXIO; + break; + } + } + + return ret; +} + +/* + * snd_event_client_register - Register a client with the SND event FW + * + * @dev: Pointer to the "struct device" associated with the client + * @snd_ev_ops: Pointer to the snd_event_ops struct for the client containing + * callback functions + * @data: Pointer to any additional data that the caller wants to get back + * with callback functions + * + * Returns 0 on success or error on failure. + */ +int snd_event_client_register(struct device *dev, + const struct snd_event_ops *snd_ev_ops, + void *data) +{ + struct snd_event_client *c; + + if (!dev) { + pr_err("%s: dev is NULL\n", __func__); + return -EINVAL; + } + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + c->dev = dev; + c->ops = snd_ev_ops; + c->data = data; + + dev_dbg(dev, "%s: adding client to SND event FW (ops %pK)\n", + __func__, snd_ev_ops); + + mutex_lock(&snd_event_mutex); + list_add_tail(&c->node, &snd_event_client_list); + + if (master && !master->clients_found) { + if (snd_event_find_clients(master)) { + dev_dbg(dev, "%s: Failed to find all clients\n", + __func__); + goto exit; + } + master->clients_found = true; + } + +exit: + mutex_unlock(&snd_event_mutex); + return 0; +} +EXPORT_SYMBOL(snd_event_client_register); + +/* + * snd_event_client_deregister - Remove a client from the SND event FW + * + * @dev: Pointer to the "struct device" associated with the client + * + * Returns 0 on success or error on failure. + */ +int snd_event_client_deregister(struct device *dev) +{ + struct snd_event_client *c; + int ret = 0; + int i = 0; + + if (!dev) { + pr_err("%s: dev is NULL\n", __func__); + return -EINVAL; + } + + mutex_lock(&snd_event_mutex); + if (list_empty(&snd_event_client_list)) { + dev_dbg(dev, "%s: No SND client registered\n", __func__); + ret = -ENODEV; + goto exit; + } + + c = find_snd_event_client(dev); + if (!c || (c->dev != dev)) { + dev_dbg(dev, "%s: No matching snd dev found\n", __func__); + ret = -ENODEV; + goto exit; + } + + c->state = false; + + if (master && master->clients_found) { + struct snd_event_client *d; + bool dev_found = false; + + for (i = 0; i < master->clients->num_clients; i++) { + d = master->clients->cl_arr[i].clnt; + if (c->dev == d->dev) { + dev_found = true; + break; + } + } + if (dev_found) { + ret = check_and_update_fwk_state(); + master->clients_found = false; + } + } + + list_del(&c->node); + kfree(c); +exit: + mutex_unlock(&snd_event_mutex); + return ret; +} +EXPORT_SYMBOL(snd_event_client_deregister); + +/* + * snd_event_mstr_add_client - Add a client to the master's list of clients + * + * @snd_clients: list of clients associated with this master + * @compare: Pointer to the compare callback function that master will use to + * confirm the clients + * @data: Address to any additional data that the master wants to get back with + * compare callback functions + */ +void snd_event_mstr_add_client(struct snd_event_clients **snd_clients, + int (*compare)(struct device *, void *), + void *data) +{ + struct snd_event_clients *client = *snd_clients; + + if (IS_ERR(client)) { + pr_err("%s: snd_clients is invalid\n", __func__); + return; + } + + if (!client) { + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) { + *snd_clients = ERR_PTR(-ENOMEM); + return; + } + client->cl_arr = kzalloc(sizeof(struct snd_event_client_array), + GFP_KERNEL); + *snd_clients = client; + } else { + struct snd_event_client_array *new; + + new = krealloc(client->cl_arr, + (client->num_clients + 1) * sizeof(*new), + GFP_KERNEL | __GFP_ZERO); + if (!new) { + *snd_clients = ERR_PTR(-ENOMEM); + return; + } + client->cl_arr = new; + } + + client->cl_arr[client->num_clients].dev = NULL; + client->cl_arr[client->num_clients].data = data; + client->cl_arr[client->num_clients].compare = compare; + client->num_clients++; +} +EXPORT_SYMBOL(snd_event_mstr_add_client); + +/* + * snd_event_master_register - Register a master with the SND event FW + * + * @dev: Pointer to the "struct device" associated with the master + * @ops: Pointer to the snd_event_ops struct for the master containing + * callback functions + * @clients: List of clients for the master + * @data: Pointer to any additional data that the caller wants to get back + * with callback functions + * + * Returns 0 on success or error on failure. + * + * Prerequisite: + * clients list must not be empty. + * All clients for the master must have to be registered by calling + * snd_event_mstr_add_client() before calling this API to register a + * master with SND event fwk. + */ +int snd_event_master_register(struct device *dev, + const struct snd_event_ops *ops, + struct snd_event_clients *clients, + void *data) +{ + struct snd_master *new_master; + int ret = 0; + + if (!dev) { + pr_err("%s: dev is NULL\n", __func__); + return -EINVAL; + } + + mutex_lock(&snd_event_mutex); + if (master) { + dev_err(dev, "%s: master already allocated with %pK\n", + __func__, master->dev); + ret = -EALREADY; + goto exit; + } + mutex_unlock(&snd_event_mutex); + + if (!clients || IS_ERR(clients)) { + dev_err(dev, "%s: Invalid clients ptr\n", __func__); + return -EINVAL; + } + + new_master = kzalloc(sizeof(*new_master), GFP_KERNEL); + if (!new_master) + return -ENOMEM; + + new_master->dev = dev; + new_master->ops = ops; + new_master->data = data; + new_master->clients = clients; + + dev_dbg(dev, "adding master to SND event FW (ops %pK)\n", ops); + + mutex_lock(&snd_event_mutex); + + master = new_master; + + ret = snd_event_find_clients(master); + if (ret) { + dev_dbg(dev, "%s: Failed to find all clients\n", __func__); + ret = 0; + goto exit; + } + master->clients_found = true; + +exit: + mutex_unlock(&snd_event_mutex); + return ret; +} +EXPORT_SYMBOL(snd_event_master_register); + +/* + * snd_event_master_deregister - Remove a master from the SND event FW + * + * @dev: Pointer to the "struct device" associated with the master + * + * Returns 0 on success or error on failure. + */ +int snd_event_master_deregister(struct device *dev) +{ + int ret = 0; + + if (!dev) { + pr_err("%s: dev is NULL\n", __func__); + return -EINVAL; + } + + mutex_lock(&snd_event_mutex); + if (!master) { + dev_dbg(dev, "%s: No master found\n", __func__); + ret = -ENODEV; + goto exit; + } + + if (master->dev != dev) { + dev_dbg(dev, "%s: device is not a Master\n", __func__); + ret = -ENXIO; + goto exit; + } + + master->state = false; + + if (master && master->clients_found) + ret = check_and_update_fwk_state(); + + kfree(master->clients->cl_arr); + kfree(master->clients); + kfree(master); + master = NULL; +exit: + mutex_unlock(&snd_event_mutex); + return ret; +} +EXPORT_SYMBOL(snd_event_master_deregister); + +/* + * snd_event_notify - Update the state of the Master/client in the SND event FW + * + * @dev: Pointer to the "struct device" associated with the master/client + * @state: UP/DOWN state of the caller (master/client) + * + * Returns 0 on success or error on failure. + */ +int snd_event_notify(struct device *dev, unsigned int state) +{ + struct snd_event_client *c; + int ret = 0; + + if (!dev) { + pr_err("%s: dev is NULL\n", __func__); + return -EINVAL; + } + + mutex_lock(&snd_event_mutex); + if (list_empty(&snd_event_client_list) && !master) { + dev_err(dev, "%s: No device registered\n", __func__); + ret = -ENODEV; + goto exit; + } + + c = find_snd_event_client(dev); + if (!c && (!master || (master->dev != dev))) { + dev_err(dev, "%s: No snd dev entry found\n", __func__); + ret = -ENXIO; + goto exit; + } + + if (c) + c->state = !!state; + else + master->state = !!state; + + if (master && master->clients_found) + ret = check_and_update_fwk_state(); + +exit: + mutex_unlock(&snd_event_mutex); + return ret; +} +EXPORT_SYMBOL(snd_event_notify); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("SND event module");