kernel-fxtec-pro1x/soc/snd_event.c
Banajit Goswami 06183689db 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>
2018-08-22 10:34:32 -07:00

490 lines
11 KiB
C

// 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");