blob: 485c703d25b1884ed1a64c12dd88883d8fb8d622 [file] [log] [blame]
/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/service-notifier.h>
#include <dsp/audio_notifier.h>
#include "audio_ssr.h"
#include "audio_pdr.h"
/* Audio states internal to notifier. Client */
/* used states defined in audio_notifier.h */
/* for AUDIO_NOTIFIER_SERVICE_DOWN & UP */
#define NO_SERVICE -2
#define UNINIT_SERVICE -1
/*
* Used for each client registered with audio notifier
*/
struct client_data {
struct list_head list;
/* Notifier block given by client */
struct notifier_block *nb;
char client_name[20];
int service;
int domain;
};
/*
* Used for each service and domain combination
* Tracks information specific to the underlying
* service.
*/
struct service_info {
const char name[20];
int domain_id;
int state;
void *handle;
/* Notifier block registered to service */
struct notifier_block *nb;
/* Used to determine when to register and deregister service */
int num_of_clients;
/* List of all clients registered to the service and domain */
struct srcu_notifier_head client_nb_list;
};
static int audio_notifer_ssr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data);
static int audio_notifer_ssr_modem_cb(struct notifier_block *this,
unsigned long opcode, void *data);
static int audio_notifer_pdr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data);
static struct notifier_block notifier_ssr_adsp_nb = {
.notifier_call = audio_notifer_ssr_adsp_cb,
.priority = 0,
};
static struct notifier_block notifier_ssr_modem_nb = {
.notifier_call = audio_notifer_ssr_modem_cb,
.priority = 0,
};
static struct notifier_block notifier_pdr_adsp_nb = {
.notifier_call = audio_notifer_pdr_adsp_cb,
.priority = 0,
};
static struct service_info service_data[AUDIO_NOTIFIER_MAX_SERVICES]
[AUDIO_NOTIFIER_MAX_DOMAINS] = {
{{
.name = "SSR_ADSP",
.domain_id = AUDIO_SSR_DOMAIN_ADSP,
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
.nb = &notifier_ssr_adsp_nb
},
{
.name = "SSR_MODEM",
.domain_id = AUDIO_SSR_DOMAIN_MODEM,
.state = AUDIO_NOTIFIER_SERVICE_DOWN,
.nb = &notifier_ssr_modem_nb
} },
{{
.name = "PDR_ADSP",
.domain_id = AUDIO_PDR_DOMAIN_ADSP,
.state = UNINIT_SERVICE,
.nb = &notifier_pdr_adsp_nb
},
{ /* PDR MODEM service not enabled */
.name = "INVALID",
.state = NO_SERVICE,
.nb = NULL
} }
};
/* Master list of all audio notifier clients */
struct list_head client_list;
struct mutex notifier_mutex;
static int audio_notifer_get_default_service(int domain)
{
int service = NO_SERVICE;
/* initial service to connect per domain */
switch (domain) {
case AUDIO_NOTIFIER_ADSP_DOMAIN:
service = AUDIO_NOTIFIER_PDR_SERVICE;
break;
case AUDIO_NOTIFIER_MODEM_DOMAIN:
service = AUDIO_NOTIFIER_SSR_SERVICE;
break;
}
return service;
}
static void audio_notifer_disable_service(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
service_data[service][i].state = NO_SERVICE;
}
static bool audio_notifer_is_service_enabled(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++)
if (service_data[service][i].state != NO_SERVICE)
return true;
return false;
}
static void audio_notifer_init_service(int service)
{
int i;
for (i = 0; i < AUDIO_NOTIFIER_MAX_DOMAINS; i++) {
if (service_data[service][i].state == UNINIT_SERVICE)
service_data[service][i].state =
AUDIO_NOTIFIER_SERVICE_DOWN;
}
}
static int audio_notifer_reg_service(int service, int domain)
{
void *handle;
int ret = 0;
int curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
handle = audio_ssr_register(
service_data[service][domain].domain_id,
service_data[service][domain].nb);
break;
case AUDIO_NOTIFIER_PDR_SERVICE:
handle = audio_pdr_service_register(
service_data[service][domain].domain_id,
service_data[service][domain].nb, &curr_state);
if (curr_state == SERVREG_NOTIF_SERVICE_STATE_UP_V01)
curr_state = AUDIO_NOTIFIER_SERVICE_UP;
else
curr_state = AUDIO_NOTIFIER_SERVICE_DOWN;
break;
default:
pr_err("%s: Invalid service %d\n",
__func__, service);
ret = -EINVAL;
goto done;
}
if (IS_ERR_OR_NULL(handle)) {
pr_err("%s: handle is incorrect for service %s\n",
__func__, service_data[service][domain].name);
ret = -EINVAL;
goto done;
}
service_data[service][domain].state = curr_state;
service_data[service][domain].handle = handle;
pr_info("%s: service %s is in use\n",
__func__, service_data[service][domain].name);
pr_debug("%s: service %s has current state %d, handle 0x%pK\n",
__func__, service_data[service][domain].name,
service_data[service][domain].state,
service_data[service][domain].handle);
done:
return ret;
}
static int audio_notifer_dereg_service(int service, int domain)
{
int ret;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
ret = audio_ssr_deregister(
service_data[service][domain].handle,
service_data[service][domain].nb);
break;
case AUDIO_NOTIFIER_PDR_SERVICE:
ret = audio_pdr_service_deregister(
service_data[service][domain].handle,
service_data[service][domain].nb);
break;
default:
pr_err("%s: Invalid service %d\n",
__func__, service);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err("%s: deregister failed for service %s, ret %d\n",
__func__, service_data[service][domain].name, ret);
goto done;
}
pr_debug("%s: service %s with handle 0x%pK deregistered\n",
__func__, service_data[service][domain].name,
service_data[service][domain].handle);
service_data[service][domain].state = AUDIO_NOTIFIER_SERVICE_DOWN;
service_data[service][domain].handle = NULL;
done:
return ret;
}
static int audio_notifer_reg_client_service(struct client_data *client_data,
int service)
{
int ret = 0;
int domain = client_data->domain;
struct audio_notifier_cb_data data;
switch (service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
case AUDIO_NOTIFIER_PDR_SERVICE:
if (service_data[service][domain].num_of_clients == 0)
ret = audio_notifer_reg_service(service, domain);
break;
default:
pr_err("%s: Invalid service for client %s, service %d, domain %d\n",
__func__, client_data->client_name, service, domain);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err("%s: service registration failed on service %s for client %s\n",
__func__, service_data[service][domain].name,
client_data->client_name);
goto done;
}
client_data->service = service;
srcu_notifier_chain_register(
&service_data[service][domain].client_nb_list,
client_data->nb);
service_data[service][domain].num_of_clients++;
pr_debug("%s: registered client %s on service %s, current state 0x%x\n",
__func__, client_data->client_name,
service_data[service][domain].name,
service_data[service][domain].state);
/*
* PDR registration returns current state
* Force callback of client with current state for PDR
*/
if (client_data->service == AUDIO_NOTIFIER_PDR_SERVICE) {
data.service = service;
data.domain = domain;
(void)client_data->nb->notifier_call(client_data->nb,
service_data[service][domain].state, &data);
}
done:
return ret;
}
static int audio_notifer_reg_client(struct client_data *client_data)
{
int ret = 0;
int service;
int domain = client_data->domain;
service = audio_notifer_get_default_service(domain);
if (service < 0) {
pr_err("%s: service %d is incorrect\n", __func__, service);
ret = -EINVAL;
goto done;
}
/* Search through services to find a valid one to register client on. */
for (; service >= 0; service--) {
/* If a service is not initialized, wait for it to come up. */
if (service_data[service][domain].state == UNINIT_SERVICE)
goto done;
/* Skip unsupported service and domain combinations. */
if (service_data[service][domain].state < 0)
continue;
/* Only register clients who have not acquired a service. */
if (client_data->service != NO_SERVICE)
continue;
/*
* Only register clients, who have not acquired a service, on
* the best available service for their domain. Uninitialized
* services will try to register all of their clients after
* they initialize correctly or will disable their service and
* register clients on the next best avaialable service.
*/
pr_debug("%s: register client %s on service %s",
__func__, client_data->client_name,
service_data[service][domain].name);
ret = audio_notifer_reg_client_service(client_data, service);
if (ret < 0)
pr_err("%s: client %s failed to register on service %s",
__func__, client_data->client_name,
service_data[service][domain].name);
}
done:
return ret;
}
static int audio_notifer_dereg_client(struct client_data *client_data)
{
int ret = 0;
int service = client_data->service;
int domain = client_data->domain;
switch (client_data->service) {
case AUDIO_NOTIFIER_SSR_SERVICE:
case AUDIO_NOTIFIER_PDR_SERVICE:
if (service_data[service][domain].num_of_clients == 1)
ret = audio_notifer_dereg_service(service, domain);
break;
case NO_SERVICE:
goto done;
default:
pr_err("%s: Invalid service for client %s, service %d\n",
__func__, client_data->client_name,
client_data->service);
ret = -EINVAL;
goto done;
}
if (ret < 0) {
pr_err("%s: deregister failed for client %s on service %s, ret %d\n",
__func__, client_data->client_name,
service_data[service][domain].name, ret);
goto done;
}
ret = srcu_notifier_chain_unregister(&service_data[service][domain].
client_nb_list, client_data->nb);
if (ret < 0) {
pr_err("%s: srcu_notifier_chain_unregister failed, ret %d\n",
__func__, ret);
goto done;
}
pr_debug("%s: deregistered client %s on service %s\n",
__func__, client_data->client_name,
service_data[service][domain].name);
client_data->service = NO_SERVICE;
if (service_data[service][domain].num_of_clients > 0)
service_data[service][domain].num_of_clients--;
done:
return ret;
}
static void audio_notifer_reg_all_clients(void)
{
struct list_head *ptr, *next;
struct client_data *client_data;
int ret;
list_for_each_safe(ptr, next, &client_list) {
client_data = list_entry(ptr, struct client_data, list);
ret = audio_notifer_reg_client(client_data);
if (ret < 0)
pr_err("%s: audio_notifer_reg_client failed for client %s, ret %d\n",
__func__, client_data->client_name,
ret);
}
}
static int audio_notifer_pdr_callback(struct notifier_block *this,
unsigned long opcode, void *data)
{
pr_debug("%s: Audio PDR framework state 0x%lx\n",
__func__, opcode);
mutex_lock(&notifier_mutex);
if (opcode == AUDIO_PDR_FRAMEWORK_DOWN)
audio_notifer_disable_service(AUDIO_NOTIFIER_PDR_SERVICE);
else
audio_notifer_init_service(AUDIO_NOTIFIER_PDR_SERVICE);
audio_notifer_reg_all_clients();
mutex_unlock(&notifier_mutex);
return 0;
}
static struct notifier_block pdr_nb = {
.notifier_call = audio_notifer_pdr_callback,
.priority = 0,
};
static int audio_notifer_convert_opcode(unsigned long opcode,
unsigned long *notifier_opcode)
{
int ret = 0;
switch (opcode) {
case SUBSYS_BEFORE_SHUTDOWN:
case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01:
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_DOWN;
break;
case SUBSYS_AFTER_POWERUP:
case SERVREG_NOTIF_SERVICE_STATE_UP_V01:
*notifier_opcode = AUDIO_NOTIFIER_SERVICE_UP;
break;
default:
pr_debug("%s: Unused opcode 0x%lx\n", __func__, opcode);
ret = -EINVAL;
}
return ret;
}
static int audio_notifer_service_cb(unsigned long opcode,
int service, int domain)
{
int ret = 0;
unsigned long notifier_opcode;
struct audio_notifier_cb_data data;
if (audio_notifer_convert_opcode(opcode, &notifier_opcode) < 0)
goto done;
data.service = service;
data.domain = domain;
pr_debug("%s: service %s, opcode 0x%lx\n",
__func__, service_data[service][domain].name, notifier_opcode);
mutex_lock(&notifier_mutex);
service_data[service][domain].state = notifier_opcode;
ret = srcu_notifier_call_chain(&service_data[service][domain].
client_nb_list, notifier_opcode, &data);
if (ret < 0)
pr_err("%s: srcu_notifier_call_chain returned %d, service %s, opcode 0x%lx\n",
__func__, ret, service_data[service][domain].name,
notifier_opcode);
mutex_unlock(&notifier_mutex);
done:
return NOTIFY_OK;
}
static int audio_notifer_pdr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
return audio_notifer_service_cb(opcode,
AUDIO_NOTIFIER_PDR_SERVICE,
AUDIO_NOTIFIER_ADSP_DOMAIN);
}
static int audio_notifer_ssr_adsp_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
return audio_notifer_service_cb(opcode,
AUDIO_NOTIFIER_SSR_SERVICE,
AUDIO_NOTIFIER_ADSP_DOMAIN);
}
static int audio_notifer_ssr_modem_cb(struct notifier_block *this,
unsigned long opcode, void *data)
{
return audio_notifer_service_cb(opcode,
AUDIO_NOTIFIER_SSR_SERVICE,
AUDIO_NOTIFIER_MODEM_DOMAIN);
}
int audio_notifier_deregister(char *client_name)
{
int ret = 0;
int ret2;
struct list_head *ptr, *next;
struct client_data *client_data = NULL;
if (client_name == NULL) {
pr_err("%s: client_name is NULL\n", __func__);
ret = -EINVAL;
goto done;
}
mutex_lock(&notifier_mutex);
list_for_each_safe(ptr, next, &client_list) {
client_data = list_entry(ptr, struct client_data, list);
if (!strcmp(client_name, client_data->client_name)) {
ret2 = audio_notifer_dereg_client(client_data);
if (ret2 < 0) {
pr_err("%s: audio_notifer_dereg_client failed, ret %d\n, service %d, domain %d",
__func__, ret2, client_data->service,
client_data->domain);
ret = ret2;
continue;
}
list_del(&client_data->list);
kfree(client_data);
}
}
mutex_unlock(&notifier_mutex);
done:
return ret;
}
EXPORT_SYMBOL(audio_notifier_deregister);
int audio_notifier_register(char *client_name, int domain,
struct notifier_block *nb)
{
int ret;
struct client_data *client_data;
if (client_name == NULL) {
pr_err("%s: client_name is NULL\n", __func__);
ret = -EINVAL;
goto done;
} else if (nb == NULL) {
pr_err("%s: Notifier block is NULL\n", __func__);
ret = -EINVAL;
goto done;
}
client_data = kmalloc(sizeof(*client_data), GFP_KERNEL);
if (client_data == NULL) {
ret = -ENOMEM;
goto done;
}
INIT_LIST_HEAD(&client_data->list);
client_data->nb = nb;
strlcpy(client_data->client_name, client_name,
sizeof(client_data->client_name));
client_data->service = NO_SERVICE;
client_data->domain = domain;
mutex_lock(&notifier_mutex);
ret = audio_notifer_reg_client(client_data);
if (ret < 0) {
mutex_unlock(&notifier_mutex);
pr_err("%s: audio_notifer_reg_client for client %s failed ret = %d\n",
__func__, client_data->client_name,
ret);
kfree(client_data);
goto done;
}
list_add_tail(&client_data->list, &client_list);
mutex_unlock(&notifier_mutex);
done:
return ret;
}
EXPORT_SYMBOL(audio_notifier_register);
static int __init audio_notifier_subsys_init(void)
{
int i, j;
mutex_init(&notifier_mutex);
INIT_LIST_HEAD(&client_list);
for (i = 0; i < AUDIO_NOTIFIER_MAX_SERVICES; i++) {
for (j = 0; j < AUDIO_NOTIFIER_MAX_DOMAINS; j++) {
if (service_data[i][j].state <= NO_SERVICE)
continue;
srcu_init_notifier_head(
&service_data[i][j].client_nb_list);
}
}
return 0;
}
static int __init audio_notifier_late_init(void)
{
/*
* If pdr registration failed, register clients on next service
* Do in late init to ensure that SSR subsystem is initialized
*/
mutex_lock(&notifier_mutex);
if (!audio_notifer_is_service_enabled(AUDIO_NOTIFIER_PDR_SERVICE))
audio_notifer_reg_all_clients();
mutex_unlock(&notifier_mutex);
return 0;
}
static int __init audio_notifier_init(void)
{
int ret;
audio_notifier_subsys_init();
ret = audio_pdr_register(&pdr_nb);
if (ret < 0) {
pr_err("%s: PDR register failed, ret = %d, disable service\n",
__func__, ret);
audio_notifer_disable_service(AUDIO_NOTIFIER_PDR_SERVICE);
}
/* Do not return error since PDR enablement is not critical */
audio_notifier_late_init();
return 0;
}
module_init(audio_notifier_init);
static void __exit audio_notifier_exit(void)
{
audio_pdr_deregister(&pdr_nb);
}
module_exit(audio_notifier_exit);
MODULE_DESCRIPTION("Audio notifier driver");
MODULE_LICENSE("GPL v2");