| /* 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 = ¬ifier_ssr_adsp_nb |
| }, |
| { |
| .name = "SSR_MODEM", |
| .domain_id = AUDIO_SSR_DOMAIN_MODEM, |
| .state = AUDIO_NOTIFIER_SERVICE_DOWN, |
| .nb = ¬ifier_ssr_modem_nb |
| } }, |
| |
| {{ |
| .name = "PDR_ADSP", |
| .domain_id = AUDIO_PDR_DOMAIN_ADSP, |
| .state = UNINIT_SERVICE, |
| .nb = ¬ifier_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(¬ifier_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(¬ifier_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, ¬ifier_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(¬ifier_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(¬ifier_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(¬ifier_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(¬ifier_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(¬ifier_mutex); |
| ret = audio_notifer_reg_client(client_data); |
| if (ret < 0) { |
| mutex_unlock(¬ifier_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(¬ifier_mutex); |
| done: |
| return ret; |
| } |
| EXPORT_SYMBOL(audio_notifier_register); |
| |
| static int __init audio_notifier_subsys_init(void) |
| { |
| int i, j; |
| |
| mutex_init(¬ifier_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(¬ifier_mutex); |
| if (!audio_notifer_is_service_enabled(AUDIO_NOTIFIER_PDR_SERVICE)) |
| audio_notifer_reg_all_clients(); |
| |
| mutex_unlock(¬ifier_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"); |