soc: add support for SND event framework
Add support for SND event framework for helping with notifications among different audio modules/drivers. The SND event framework functions with a master/client mechanism, where each client and the master register with the framework, and then notifies its own status (UP/DOWN). Each master will share a list of clients it is interested in, and once all the clients are registered and notified UP, the framework's state will be UP. On the other hand, as and when any one of the client, or the master reports its state as DOWN while the framework is UP, the framework state would be changed to DOWN, and all clients and the master would be let know about the change. Change-Id: Ief6f26c5d5626c29246472ad71c247d71ee9e92f Signed-off-by: Banajit Goswami <bgoswami@codeaurora.org>
This commit is contained in:
parent
711dbb3dca
commit
06183689db
4 changed files with 586 additions and 0 deletions
79
include/soc/snd_event.h
Normal file
79
include/soc/snd_event.h
Normal file
|
@ -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_ */
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
490
soc/snd_event.c
Normal file
490
soc/snd_event.c
Normal file
|
@ -0,0 +1,490 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <soc/snd_event.h>
|
||||
|
||||
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");
|
Loading…
Reference in a new issue