
Change APR driver to platform driver and add any child device only after ADSP up notification is received. The idea is to have machine driver as a child device under APR, and add the platform device for machine driver only after ADSP is up. This will help invoke audio drivers waiting with deferred probe and eventually should help sound card registers successfully. Change-Id: Ib0c0f7ec1d7dd93a1b54a9a66260861223d55c67 Signed-off-by: Laxminath Kasam <lkasam@codeaurora.org> Signed-off-by: Meng Wang <mwang@codeaurora.org> Signed-off-by: Banajit Goswami <bgoswami@codeaurora.org>
1271 lines
29 KiB
C
1271 lines
29 KiB
C
/* Copyright (c) 2010-2014, 2016-2018 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/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/list.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ipc_logging.h>
|
|
#include <soc/qcom/subsystem_restart.h>
|
|
#include <soc/qcom/scm.h>
|
|
#include <dsp/apr_audio-v2.h>
|
|
#include <dsp/audio_notifier.h>
|
|
#include <ipc/apr.h>
|
|
#include <ipc/apr_tal.h>
|
|
|
|
#define APR_PKT_IPC_LOG_PAGE_CNT 2
|
|
|
|
static struct apr_q6 q6;
|
|
static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX];
|
|
static void *apr_pkt_ctx;
|
|
static wait_queue_head_t dsp_wait;
|
|
static wait_queue_head_t modem_wait;
|
|
static bool is_modem_up;
|
|
/* Subsystem restart: QDSP6 data, functions */
|
|
static struct workqueue_struct *apr_reset_workqueue;
|
|
static void apr_reset_deregister(struct work_struct *work);
|
|
static void dispatch_event(unsigned long code, uint16_t proc);
|
|
struct apr_reset_work {
|
|
void *handle;
|
|
struct work_struct work;
|
|
};
|
|
|
|
struct apr_chld_device {
|
|
struct platform_device *pdev;
|
|
struct list_head node;
|
|
};
|
|
|
|
struct apr_private {
|
|
struct device *dev;
|
|
spinlock_t apr_lock;
|
|
bool is_initial_boot;
|
|
struct work_struct add_chld_dev_work;
|
|
spinlock_t apr_chld_lock;
|
|
struct list_head apr_chlds;
|
|
};
|
|
|
|
static struct apr_private *apr_priv;
|
|
static bool apr_cf_debug;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static struct dentry *debugfs_apr_debug;
|
|
static ssize_t apr_debug_write(struct file *filp, const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
char cmd;
|
|
|
|
if (copy_from_user(&cmd, ubuf, 1))
|
|
return -EFAULT;
|
|
|
|
apr_cf_debug = (cmd == '1') ? true : false;
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static const struct file_operations apr_debug_ops = {
|
|
.write = apr_debug_write,
|
|
};
|
|
#endif
|
|
|
|
#define APR_PKT_INFO(x...) \
|
|
do { \
|
|
if (apr_pkt_ctx) \
|
|
ipc_log_string(apr_pkt_ctx, "<APR>: "x); \
|
|
} while (0)
|
|
|
|
|
|
struct apr_svc_table {
|
|
char name[64];
|
|
int idx;
|
|
int id;
|
|
int client_id;
|
|
};
|
|
|
|
static const struct apr_svc_table svc_tbl_qdsp6[] = {
|
|
{
|
|
.name = "AFE",
|
|
.idx = 0,
|
|
.id = APR_SVC_AFE,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "ASM",
|
|
.idx = 1,
|
|
.id = APR_SVC_ASM,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "ADM",
|
|
.idx = 2,
|
|
.id = APR_SVC_ADM,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "CORE",
|
|
.idx = 3,
|
|
.id = APR_SVC_ADSP_CORE,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "TEST",
|
|
.idx = 4,
|
|
.id = APR_SVC_TEST_CLIENT,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "MVM",
|
|
.idx = 5,
|
|
.id = APR_SVC_ADSP_MVM,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "CVS",
|
|
.idx = 6,
|
|
.id = APR_SVC_ADSP_CVS,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "CVP",
|
|
.idx = 7,
|
|
.id = APR_SVC_ADSP_CVP,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "USM",
|
|
.idx = 8,
|
|
.id = APR_SVC_USM,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
{
|
|
.name = "VIDC",
|
|
.idx = 9,
|
|
.id = APR_SVC_VIDC,
|
|
},
|
|
{
|
|
.name = "LSM",
|
|
.idx = 10,
|
|
.id = APR_SVC_LSM,
|
|
.client_id = APR_CLIENT_AUDIO,
|
|
},
|
|
};
|
|
|
|
static struct apr_svc_table svc_tbl_voice[] = {
|
|
{
|
|
.name = "VSM",
|
|
.idx = 0,
|
|
.id = APR_SVC_VSM,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "VPM",
|
|
.idx = 1,
|
|
.id = APR_SVC_VPM,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "MVS",
|
|
.idx = 2,
|
|
.id = APR_SVC_MVS,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "MVM",
|
|
.idx = 3,
|
|
.id = APR_SVC_MVM,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "CVS",
|
|
.idx = 4,
|
|
.id = APR_SVC_CVS,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "CVP",
|
|
.idx = 5,
|
|
.id = APR_SVC_CVP,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "SRD",
|
|
.idx = 6,
|
|
.id = APR_SVC_SRD,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
{
|
|
.name = "TEST",
|
|
.idx = 7,
|
|
.id = APR_SVC_TEST_CLIENT,
|
|
.client_id = APR_CLIENT_VOICE,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* apr_get_modem_state:
|
|
*
|
|
* Returns current modem load status
|
|
*
|
|
*/
|
|
enum apr_subsys_state apr_get_modem_state(void)
|
|
{
|
|
return atomic_read(&q6.modem_state);
|
|
}
|
|
EXPORT_SYMBOL(apr_get_modem_state);
|
|
|
|
/**
|
|
* apr_set_modem_state - Update modem load status.
|
|
*
|
|
* @state: State to update modem load status
|
|
*
|
|
*/
|
|
void apr_set_modem_state(enum apr_subsys_state state)
|
|
{
|
|
atomic_set(&q6.modem_state, state);
|
|
}
|
|
EXPORT_SYMBOL(apr_set_modem_state);
|
|
|
|
enum apr_subsys_state apr_cmpxchg_modem_state(enum apr_subsys_state prev,
|
|
enum apr_subsys_state new)
|
|
{
|
|
return atomic_cmpxchg(&q6.modem_state, prev, new);
|
|
}
|
|
|
|
static void apr_modem_down(unsigned long opcode)
|
|
{
|
|
apr_set_modem_state(APR_SUBSYS_DOWN);
|
|
dispatch_event(opcode, APR_DEST_MODEM);
|
|
}
|
|
|
|
static void apr_modem_up(void)
|
|
{
|
|
if (apr_cmpxchg_modem_state(APR_SUBSYS_DOWN, APR_SUBSYS_UP) ==
|
|
APR_SUBSYS_DOWN)
|
|
wake_up(&modem_wait);
|
|
is_modem_up = 1;
|
|
}
|
|
|
|
enum apr_subsys_state apr_get_q6_state(void)
|
|
{
|
|
return atomic_read(&q6.q6_state);
|
|
}
|
|
EXPORT_SYMBOL(apr_get_q6_state);
|
|
|
|
int apr_set_q6_state(enum apr_subsys_state state)
|
|
{
|
|
pr_debug("%s: setting adsp state %d\n", __func__, state);
|
|
if (state < APR_SUBSYS_DOWN || state > APR_SUBSYS_LOADED)
|
|
return -EINVAL;
|
|
atomic_set(&q6.q6_state, state);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(apr_set_q6_state);
|
|
|
|
enum apr_subsys_state apr_cmpxchg_q6_state(enum apr_subsys_state prev,
|
|
enum apr_subsys_state new)
|
|
{
|
|
return atomic_cmpxchg(&q6.q6_state, prev, new);
|
|
}
|
|
|
|
static void apr_adsp_down(unsigned long opcode)
|
|
{
|
|
pr_debug("%s: Q6 is Down\n", __func__);
|
|
apr_set_q6_state(APR_SUBSYS_DOWN);
|
|
dispatch_event(opcode, APR_DEST_QDSP6);
|
|
}
|
|
|
|
static void apr_add_child_devices(struct work_struct *work)
|
|
{
|
|
int ret;
|
|
struct device_node *node;
|
|
struct platform_device *pdev;
|
|
struct apr_chld_device *apr_chld_dev;
|
|
|
|
for_each_child_of_node(apr_priv->dev->of_node, node) {
|
|
apr_chld_dev = kzalloc(sizeof(*apr_chld_dev), GFP_KERNEL);
|
|
if (!apr_chld_dev)
|
|
continue;
|
|
pdev = platform_device_alloc(node->name, -1);
|
|
if (!pdev) {
|
|
dev_err(apr_priv->dev,
|
|
"%s: pdev memory alloc failed for %s\n",
|
|
__func__, node->name);
|
|
kfree(apr_chld_dev);
|
|
continue;
|
|
}
|
|
pdev->dev.parent = apr_priv->dev;
|
|
pdev->dev.of_node = node;
|
|
|
|
ret = platform_device_add(pdev);
|
|
if (ret) {
|
|
dev_err(apr_priv->dev,
|
|
"%s: Cannot add platform device %s\n",
|
|
__func__, node->name);
|
|
platform_device_put(pdev);
|
|
kfree(apr_chld_dev);
|
|
continue;
|
|
}
|
|
|
|
apr_chld_dev->pdev = pdev;
|
|
|
|
spin_lock(&apr_priv->apr_chld_lock);
|
|
list_add_tail(&apr_chld_dev->node, &apr_priv->apr_chlds);
|
|
spin_unlock(&apr_priv->apr_chld_lock);
|
|
|
|
dev_dbg(apr_priv->dev, "%s: Added APR child dev: %s\n",
|
|
__func__, dev_name(&pdev->dev));
|
|
}
|
|
}
|
|
|
|
static void apr_adsp_up(void)
|
|
{
|
|
pr_debug("%s: Q6 is Up\n", __func__);
|
|
if (apr_cmpxchg_q6_state(APR_SUBSYS_DOWN, APR_SUBSYS_LOADED) ==
|
|
APR_SUBSYS_DOWN)
|
|
wake_up(&dsp_wait);
|
|
|
|
spin_lock(&apr_priv->apr_lock);
|
|
if (apr_priv->is_initial_boot)
|
|
schedule_work(&apr_priv->add_chld_dev_work);
|
|
spin_unlock(&apr_priv->apr_lock);
|
|
}
|
|
|
|
int apr_wait_for_device_up(int dest_id)
|
|
{
|
|
int rc = -1;
|
|
|
|
if (dest_id == APR_DEST_MODEM)
|
|
rc = wait_event_interruptible_timeout(modem_wait,
|
|
(apr_get_modem_state() == APR_SUBSYS_UP),
|
|
(1 * HZ));
|
|
else if (dest_id == APR_DEST_QDSP6)
|
|
rc = wait_event_interruptible_timeout(dsp_wait,
|
|
(apr_get_q6_state() == APR_SUBSYS_UP),
|
|
(1 * HZ));
|
|
else
|
|
pr_err("%s: unknown dest_id %d\n", __func__, dest_id);
|
|
/* returns left time */
|
|
return rc;
|
|
}
|
|
|
|
int apr_load_adsp_image(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
mutex_lock(&q6.lock);
|
|
if (apr_get_q6_state() == APR_SUBSYS_UP) {
|
|
q6.pil = subsystem_get("adsp");
|
|
if (IS_ERR(q6.pil)) {
|
|
rc = PTR_ERR(q6.pil);
|
|
pr_err("APR: Unable to load q6 image, error:%d\n", rc);
|
|
} else {
|
|
apr_set_q6_state(APR_SUBSYS_LOADED);
|
|
pr_debug("APR: Image is loaded, stated\n");
|
|
}
|
|
} else if (apr_get_q6_state() == APR_SUBSYS_LOADED) {
|
|
pr_debug("APR: q6 image already loaded\n");
|
|
} else {
|
|
pr_debug("APR: cannot load state %d\n", apr_get_q6_state());
|
|
}
|
|
mutex_unlock(&q6.lock);
|
|
return rc;
|
|
}
|
|
|
|
struct apr_client *apr_get_client(int dest_id, int client_id)
|
|
{
|
|
return &client[dest_id][client_id];
|
|
}
|
|
|
|
/**
|
|
* apr_send_pkt - Clients call to send packet
|
|
* to destination processor.
|
|
*
|
|
* @handle: APR service handle
|
|
* @buf: payload to send to destination processor.
|
|
*
|
|
* Returns Bytes(>0)pkt_size on success or error on failure.
|
|
*/
|
|
int apr_send_pkt(void *handle, uint32_t *buf)
|
|
{
|
|
struct apr_svc *svc = handle;
|
|
struct apr_client *clnt;
|
|
struct apr_hdr *hdr;
|
|
uint16_t dest_id;
|
|
uint16_t client_id;
|
|
uint16_t w_len;
|
|
int rc;
|
|
unsigned long flags;
|
|
|
|
if (!handle || !buf) {
|
|
pr_err("APR: Wrong parameters\n");
|
|
return -EINVAL;
|
|
}
|
|
if (svc->need_reset) {
|
|
pr_err("apr: send_pkt service need reset\n");
|
|
return -ENETRESET;
|
|
}
|
|
|
|
if ((svc->dest_id == APR_DEST_QDSP6) &&
|
|
(apr_get_q6_state() != APR_SUBSYS_LOADED)) {
|
|
pr_err("%s: Still dsp is not Up\n", __func__);
|
|
return -ENETRESET;
|
|
} else if ((svc->dest_id == APR_DEST_MODEM) &&
|
|
(apr_get_modem_state() == APR_SUBSYS_DOWN)) {
|
|
pr_err("apr: Still Modem is not Up\n");
|
|
return -ENETRESET;
|
|
}
|
|
|
|
spin_lock_irqsave(&svc->w_lock, flags);
|
|
dest_id = svc->dest_id;
|
|
client_id = svc->client_id;
|
|
clnt = &client[dest_id][client_id];
|
|
|
|
if (!client[dest_id][client_id].handle) {
|
|
pr_err("APR: Still service is not yet opened\n");
|
|
spin_unlock_irqrestore(&svc->w_lock, flags);
|
|
return -EINVAL;
|
|
}
|
|
hdr = (struct apr_hdr *)buf;
|
|
|
|
hdr->src_domain = APR_DOMAIN_APPS;
|
|
hdr->src_svc = svc->id;
|
|
hdr->dest_domain = svc->dest_domain;
|
|
hdr->dest_svc = svc->id;
|
|
|
|
if (unlikely(apr_cf_debug)) {
|
|
APR_PKT_INFO(
|
|
"Tx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]",
|
|
(hdr->src_domain << 8) | hdr->src_svc,
|
|
(hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode,
|
|
hdr->token);
|
|
}
|
|
|
|
rc = apr_tal_write(clnt->handle, buf,
|
|
(struct apr_pkt_priv *)&svc->pkt_owner,
|
|
hdr->pkt_size);
|
|
if (rc >= 0) {
|
|
w_len = rc;
|
|
if (w_len != hdr->pkt_size) {
|
|
pr_err("%s: Unable to write whole APR pkt successfully: %d\n",
|
|
__func__, rc);
|
|
rc = -EINVAL;
|
|
}
|
|
} else {
|
|
pr_err("%s: Write APR pkt failed with error %d\n",
|
|
__func__, rc);
|
|
}
|
|
spin_unlock_irqrestore(&svc->w_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(apr_send_pkt);
|
|
|
|
int apr_pkt_config(void *handle, struct apr_pkt_cfg *cfg)
|
|
{
|
|
struct apr_svc *svc = (struct apr_svc *)handle;
|
|
uint16_t dest_id;
|
|
uint16_t client_id;
|
|
struct apr_client *clnt;
|
|
|
|
if (!handle) {
|
|
pr_err("%s: Invalid handle\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (svc->need_reset) {
|
|
pr_err("%s: service need reset\n", __func__);
|
|
return -ENETRESET;
|
|
}
|
|
|
|
svc->pkt_owner = cfg->pkt_owner;
|
|
dest_id = svc->dest_id;
|
|
client_id = svc->client_id;
|
|
clnt = &client[dest_id][client_id];
|
|
|
|
return apr_tal_rx_intents_config(clnt->handle,
|
|
cfg->intents.num_of_intents, cfg->intents.size);
|
|
}
|
|
|
|
/**
|
|
* apr_register - Clients call to register
|
|
* to APR.
|
|
*
|
|
* @dest: destination processor
|
|
* @svc_name: name of service to register as
|
|
* @svc_fn: callback function to trigger when response
|
|
* ack or packets received from destination processor.
|
|
* @src_port: Port number within a service
|
|
* @priv: private data of client, passed back in cb fn.
|
|
*
|
|
* Returns apr_svc handle on success or NULL on failure.
|
|
*/
|
|
struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn,
|
|
uint32_t src_port, void *priv)
|
|
{
|
|
struct apr_client *clnt;
|
|
int client_id = 0;
|
|
int svc_idx = 0;
|
|
int svc_id = 0;
|
|
int dest_id = 0;
|
|
int domain_id = 0;
|
|
int temp_port = 0;
|
|
struct apr_svc *svc = NULL;
|
|
int rc = 0;
|
|
bool can_open_channel = true;
|
|
|
|
if (!dest || !svc_name || !svc_fn)
|
|
return NULL;
|
|
|
|
if (!strcmp(dest, "ADSP"))
|
|
domain_id = APR_DOMAIN_ADSP;
|
|
else if (!strcmp(dest, "MODEM")) {
|
|
/* Don't request for SMD channels if destination is MODEM,
|
|
* as these channels are no longer used and these clients
|
|
* are to listen only for MODEM SSR events
|
|
*/
|
|
can_open_channel = false;
|
|
domain_id = APR_DOMAIN_MODEM;
|
|
} else {
|
|
pr_err("APR: wrong destination\n");
|
|
goto done;
|
|
}
|
|
|
|
dest_id = apr_get_dest_id(dest);
|
|
|
|
if (dest_id == APR_DEST_QDSP6) {
|
|
if (apr_get_q6_state() != APR_SUBSYS_LOADED) {
|
|
pr_err("%s: adsp not up\n", __func__);
|
|
return NULL;
|
|
}
|
|
pr_debug("%s: adsp Up\n", __func__);
|
|
} else if (dest_id == APR_DEST_MODEM) {
|
|
if (apr_get_modem_state() == APR_SUBSYS_DOWN) {
|
|
if (is_modem_up) {
|
|
pr_err("%s: modem shutdown due to SSR, ret",
|
|
__func__);
|
|
return NULL;
|
|
}
|
|
pr_debug("%s: Wait for modem to bootup\n", __func__);
|
|
rc = apr_wait_for_device_up(APR_DEST_MODEM);
|
|
if (rc == 0) {
|
|
pr_err("%s: Modem is not Up\n", __func__);
|
|
return NULL;
|
|
}
|
|
}
|
|
pr_debug("%s: modem Up\n", __func__);
|
|
}
|
|
|
|
if (apr_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id)) {
|
|
pr_err("%s: apr_get_svc failed\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
clnt = &client[dest_id][client_id];
|
|
mutex_lock(&clnt->m_lock);
|
|
if (!clnt->handle && can_open_channel) {
|
|
clnt->handle = apr_tal_open(client_id, dest_id,
|
|
APR_DL_SMD, apr_cb_func, NULL);
|
|
if (!clnt->handle) {
|
|
svc = NULL;
|
|
pr_err("APR: Unable to open handle\n");
|
|
mutex_unlock(&clnt->m_lock);
|
|
goto done;
|
|
}
|
|
}
|
|
mutex_unlock(&clnt->m_lock);
|
|
svc = &clnt->svc[svc_idx];
|
|
mutex_lock(&svc->m_lock);
|
|
clnt->id = client_id;
|
|
if (svc->need_reset) {
|
|
mutex_unlock(&svc->m_lock);
|
|
pr_err("APR: Service needs reset\n");
|
|
goto done;
|
|
}
|
|
svc->id = svc_id;
|
|
svc->dest_id = dest_id;
|
|
svc->client_id = client_id;
|
|
svc->dest_domain = domain_id;
|
|
svc->pkt_owner = APR_PKT_OWNER_DRIVER;
|
|
|
|
if (src_port != 0xFFFFFFFF) {
|
|
temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF);
|
|
pr_debug("port = %d t_port = %d\n", src_port, temp_port);
|
|
if (temp_port >= APR_MAX_PORTS || temp_port < 0) {
|
|
pr_err("APR: temp_port out of bounds\n");
|
|
mutex_unlock(&svc->m_lock);
|
|
return NULL;
|
|
}
|
|
if (!svc->svc_cnt)
|
|
clnt->svc_cnt++;
|
|
svc->port_cnt++;
|
|
svc->port_fn[temp_port] = svc_fn;
|
|
svc->port_priv[temp_port] = priv;
|
|
svc->svc_cnt++;
|
|
} else {
|
|
if (!svc->fn) {
|
|
if (!svc->svc_cnt)
|
|
clnt->svc_cnt++;
|
|
svc->fn = svc_fn;
|
|
svc->priv = priv;
|
|
svc->svc_cnt++;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&svc->m_lock);
|
|
done:
|
|
return svc;
|
|
}
|
|
EXPORT_SYMBOL(apr_register);
|
|
|
|
|
|
void apr_cb_func(void *buf, int len, void *priv)
|
|
{
|
|
struct apr_client_data data;
|
|
struct apr_client *apr_client;
|
|
struct apr_svc *c_svc;
|
|
struct apr_hdr *hdr;
|
|
uint16_t hdr_size;
|
|
uint16_t msg_type;
|
|
uint16_t ver;
|
|
uint16_t src;
|
|
uint16_t svc;
|
|
uint16_t clnt;
|
|
int i;
|
|
int temp_port = 0;
|
|
uint32_t *ptr;
|
|
|
|
pr_debug("APR2: len = %d\n", len);
|
|
ptr = buf;
|
|
pr_debug("\n*****************\n");
|
|
for (i = 0; i < len/4; i++)
|
|
pr_debug("%x ", ptr[i]);
|
|
pr_debug("\n");
|
|
pr_debug("\n*****************\n");
|
|
|
|
if (!buf || len <= APR_HDR_SIZE) {
|
|
pr_err("APR: Improper apr pkt received:%pK %d\n", buf, len);
|
|
return;
|
|
}
|
|
hdr = buf;
|
|
|
|
ver = hdr->hdr_field;
|
|
ver = (ver & 0x000F);
|
|
if (ver > APR_PKT_VER + 1) {
|
|
pr_err("APR: Wrong version: %d\n", ver);
|
|
return;
|
|
}
|
|
|
|
hdr_size = hdr->hdr_field;
|
|
hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4;
|
|
if (hdr_size < APR_HDR_SIZE) {
|
|
pr_err("APR: Wrong hdr size:%d\n", hdr_size);
|
|
return;
|
|
}
|
|
|
|
if (hdr->pkt_size < APR_HDR_SIZE) {
|
|
pr_err("APR: Wrong paket size\n");
|
|
return;
|
|
}
|
|
msg_type = hdr->hdr_field;
|
|
msg_type = (msg_type >> 0x08) & 0x0003;
|
|
if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) {
|
|
pr_err("APR: Wrong message type: %d\n", msg_type);
|
|
return;
|
|
}
|
|
|
|
if (hdr->src_domain >= APR_DOMAIN_MAX ||
|
|
hdr->dest_domain >= APR_DOMAIN_MAX ||
|
|
hdr->src_svc >= APR_SVC_MAX ||
|
|
hdr->dest_svc >= APR_SVC_MAX) {
|
|
pr_err("APR: Wrong APR header\n");
|
|
return;
|
|
}
|
|
|
|
svc = hdr->dest_svc;
|
|
if (hdr->src_domain == APR_DOMAIN_MODEM) {
|
|
if (svc == APR_SVC_MVS || svc == APR_SVC_MVM ||
|
|
svc == APR_SVC_CVS || svc == APR_SVC_CVP ||
|
|
svc == APR_SVC_TEST_CLIENT)
|
|
clnt = APR_CLIENT_VOICE;
|
|
else {
|
|
pr_err("APR: Wrong svc :%d\n", svc);
|
|
return;
|
|
}
|
|
} else if (hdr->src_domain == APR_DOMAIN_ADSP) {
|
|
if (svc == APR_SVC_AFE || svc == APR_SVC_ASM ||
|
|
svc == APR_SVC_VSM || svc == APR_SVC_VPM ||
|
|
svc == APR_SVC_ADM || svc == APR_SVC_ADSP_CORE ||
|
|
svc == APR_SVC_USM ||
|
|
svc == APR_SVC_TEST_CLIENT || svc == APR_SVC_ADSP_MVM ||
|
|
svc == APR_SVC_ADSP_CVS || svc == APR_SVC_ADSP_CVP ||
|
|
svc == APR_SVC_LSM)
|
|
clnt = APR_CLIENT_AUDIO;
|
|
else if (svc == APR_SVC_VIDC)
|
|
clnt = APR_CLIENT_AUDIO;
|
|
else {
|
|
pr_err("APR: Wrong svc :%d\n", svc);
|
|
return;
|
|
}
|
|
} else {
|
|
pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain);
|
|
return;
|
|
}
|
|
|
|
src = apr_get_data_src(hdr);
|
|
if (src == APR_DEST_MAX)
|
|
return;
|
|
|
|
pr_debug("src =%d clnt = %d\n", src, clnt);
|
|
apr_client = &client[src][clnt];
|
|
for (i = 0; i < APR_SVC_MAX; i++)
|
|
if (apr_client->svc[i].id == svc) {
|
|
pr_debug("%d\n", apr_client->svc[i].id);
|
|
c_svc = &apr_client->svc[i];
|
|
break;
|
|
}
|
|
|
|
if (i == APR_SVC_MAX) {
|
|
pr_err("APR: service is not registered\n");
|
|
return;
|
|
}
|
|
pr_debug("svc_idx = %d\n", i);
|
|
pr_debug("%x %x %x %pK %pK\n", c_svc->id, c_svc->dest_id,
|
|
c_svc->client_id, c_svc->fn, c_svc->priv);
|
|
data.payload_size = hdr->pkt_size - hdr_size;
|
|
data.opcode = hdr->opcode;
|
|
data.src = src;
|
|
data.src_port = hdr->src_port;
|
|
data.dest_port = hdr->dest_port;
|
|
data.token = hdr->token;
|
|
data.msg_type = msg_type;
|
|
data.payload = NULL;
|
|
if (data.payload_size > 0)
|
|
data.payload = (char *)hdr + hdr_size;
|
|
|
|
if (unlikely(apr_cf_debug)) {
|
|
if (hdr->opcode == APR_BASIC_RSP_RESULT && data.payload) {
|
|
uint32_t *ptr = data.payload;
|
|
|
|
APR_PKT_INFO(
|
|
"Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X] rc[0x%X]",
|
|
(hdr->src_domain << 8) | hdr->src_svc,
|
|
(hdr->dest_domain << 8) | hdr->dest_svc,
|
|
hdr->opcode, hdr->token, ptr[1]);
|
|
} else {
|
|
APR_PKT_INFO(
|
|
"Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]",
|
|
(hdr->src_domain << 8) | hdr->src_svc,
|
|
(hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode,
|
|
hdr->token);
|
|
}
|
|
}
|
|
|
|
temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF);
|
|
if (((temp_port >= 0) && (temp_port < APR_MAX_PORTS))
|
|
&& (c_svc->port_cnt && c_svc->port_fn[temp_port]))
|
|
c_svc->port_fn[temp_port](&data,
|
|
c_svc->port_priv[temp_port]);
|
|
else if (c_svc->fn)
|
|
c_svc->fn(&data, c_svc->priv);
|
|
else
|
|
pr_err("APR: Rxed a packet for NULL callback\n");
|
|
}
|
|
|
|
int apr_get_svc(const char *svc_name, int domain_id, int *client_id,
|
|
int *svc_idx, int *svc_id)
|
|
{
|
|
int i;
|
|
int size;
|
|
struct apr_svc_table *tbl;
|
|
int ret = 0;
|
|
|
|
if (domain_id == APR_DOMAIN_ADSP) {
|
|
tbl = (struct apr_svc_table *)&svc_tbl_qdsp6;
|
|
size = ARRAY_SIZE(svc_tbl_qdsp6);
|
|
} else {
|
|
tbl = (struct apr_svc_table *)&svc_tbl_voice;
|
|
size = ARRAY_SIZE(svc_tbl_voice);
|
|
}
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (!strcmp(svc_name, tbl[i].name)) {
|
|
*client_id = tbl[i].client_id;
|
|
*svc_idx = tbl[i].idx;
|
|
*svc_id = tbl[i].id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pr_debug("%s: svc_name = %s c_id = %d domain_id = %d\n",
|
|
__func__, svc_name, *client_id, domain_id);
|
|
if (i == size) {
|
|
pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void apr_reset_deregister(struct work_struct *work)
|
|
{
|
|
struct apr_svc *handle = NULL;
|
|
struct apr_reset_work *apr_reset =
|
|
container_of(work, struct apr_reset_work, work);
|
|
|
|
handle = apr_reset->handle;
|
|
pr_debug("%s:handle[%pK]\n", __func__, handle);
|
|
apr_deregister(handle);
|
|
kfree(apr_reset);
|
|
}
|
|
|
|
/**
|
|
* apr_start_rx_rt - Clients call to vote for thread
|
|
* priority upgrade whenever needed.
|
|
*
|
|
* @handle: APR service handle
|
|
*
|
|
* Returns 0 on success or error otherwise.
|
|
*/
|
|
int apr_start_rx_rt(void *handle)
|
|
{
|
|
int rc = 0;
|
|
struct apr_svc *svc = handle;
|
|
uint16_t dest_id = 0;
|
|
uint16_t client_id = 0;
|
|
|
|
if (!svc) {
|
|
pr_err("%s: Invalid APR handle\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&svc->m_lock);
|
|
dest_id = svc->dest_id;
|
|
client_id = svc->client_id;
|
|
|
|
if ((client_id >= APR_CLIENT_MAX) || (dest_id >= APR_DEST_MAX)) {
|
|
pr_err("%s: %s invalid. client_id = %u, dest_id = %u\n",
|
|
__func__,
|
|
client_id >= APR_CLIENT_MAX ? "Client ID" : "Dest ID",
|
|
client_id, dest_id);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (!client[dest_id][client_id].handle) {
|
|
pr_err("%s: Client handle is NULL\n", __func__);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = apr_tal_start_rx_rt(client[dest_id][client_id].handle);
|
|
if (rc)
|
|
pr_err("%s: failed to set RT thread priority for APR RX. rc = %d\n",
|
|
__func__, rc);
|
|
|
|
exit:
|
|
mutex_unlock(&svc->m_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(apr_start_rx_rt);
|
|
|
|
/**
|
|
* apr_end_rx_rt - Clients call to unvote for thread
|
|
* priority upgrade (perviously voted with
|
|
* apr_start_rx_rt()).
|
|
*
|
|
* @handle: APR service handle
|
|
*
|
|
* Returns 0 on success or error otherwise.
|
|
*/
|
|
int apr_end_rx_rt(void *handle)
|
|
{
|
|
int rc = 0;
|
|
struct apr_svc *svc = handle;
|
|
uint16_t dest_id = 0;
|
|
uint16_t client_id = 0;
|
|
|
|
if (!svc) {
|
|
pr_err("%s: Invalid APR handle\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&svc->m_lock);
|
|
dest_id = svc->dest_id;
|
|
client_id = svc->client_id;
|
|
|
|
if ((client_id >= APR_CLIENT_MAX) || (dest_id >= APR_DEST_MAX)) {
|
|
pr_err("%s: %s invalid. client_id = %u, dest_id = %u\n",
|
|
__func__,
|
|
client_id >= APR_CLIENT_MAX ? "Client ID" : "Dest ID",
|
|
client_id, dest_id);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (!client[dest_id][client_id].handle) {
|
|
pr_err("%s: Client handle is NULL\n", __func__);
|
|
rc = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
rc = apr_tal_end_rx_rt(client[dest_id][client_id].handle);
|
|
if (rc)
|
|
pr_err("%s: failed to reset RT thread priority for APR RX. rc = %d\n",
|
|
__func__, rc);
|
|
|
|
exit:
|
|
mutex_unlock(&svc->m_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(apr_end_rx_rt);
|
|
|
|
/**
|
|
* apr_deregister - Clients call to de-register
|
|
* from APR.
|
|
*
|
|
* @handle: APR service handle to de-register
|
|
*
|
|
* Returns 0 on success or -EINVAL on error.
|
|
*/
|
|
int apr_deregister(void *handle)
|
|
{
|
|
struct apr_svc *svc = handle;
|
|
struct apr_client *clnt;
|
|
uint16_t dest_id;
|
|
uint16_t client_id;
|
|
|
|
if (!handle)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&svc->m_lock);
|
|
if (!svc->svc_cnt) {
|
|
pr_err("%s: svc already deregistered. svc = %pK\n",
|
|
__func__, svc);
|
|
mutex_unlock(&svc->m_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dest_id = svc->dest_id;
|
|
client_id = svc->client_id;
|
|
clnt = &client[dest_id][client_id];
|
|
|
|
if (svc->svc_cnt > 0) {
|
|
if (svc->port_cnt)
|
|
svc->port_cnt--;
|
|
svc->svc_cnt--;
|
|
if (!svc->svc_cnt) {
|
|
client[dest_id][client_id].svc_cnt--;
|
|
pr_debug("%s: service is reset %pK\n", __func__, svc);
|
|
}
|
|
}
|
|
|
|
if (!svc->svc_cnt) {
|
|
svc->priv = NULL;
|
|
svc->id = 0;
|
|
svc->fn = NULL;
|
|
svc->dest_id = 0;
|
|
svc->client_id = 0;
|
|
svc->need_reset = 0x0;
|
|
}
|
|
if (client[dest_id][client_id].handle &&
|
|
!client[dest_id][client_id].svc_cnt) {
|
|
apr_tal_close(client[dest_id][client_id].handle);
|
|
client[dest_id][client_id].handle = NULL;
|
|
}
|
|
mutex_unlock(&svc->m_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(apr_deregister);
|
|
|
|
/**
|
|
* apr_reset - sets up workqueue to de-register
|
|
* the given APR service handle.
|
|
*
|
|
* @handle: APR service handle
|
|
*
|
|
*/
|
|
void apr_reset(void *handle)
|
|
{
|
|
struct apr_reset_work *apr_reset_worker = NULL;
|
|
|
|
if (!handle)
|
|
return;
|
|
pr_debug("%s: handle[%pK]\n", __func__, handle);
|
|
|
|
if (apr_reset_workqueue == NULL) {
|
|
pr_err("%s: apr_reset_workqueue is NULL\n", __func__);
|
|
return;
|
|
}
|
|
|
|
apr_reset_worker = kzalloc(sizeof(struct apr_reset_work),
|
|
GFP_ATOMIC);
|
|
|
|
if (apr_reset_worker == NULL) {
|
|
pr_err("%s: mem failure\n", __func__);
|
|
return;
|
|
}
|
|
|
|
apr_reset_worker->handle = handle;
|
|
INIT_WORK(&apr_reset_worker->work, apr_reset_deregister);
|
|
queue_work(apr_reset_workqueue, &apr_reset_worker->work);
|
|
}
|
|
EXPORT_SYMBOL(apr_reset);
|
|
|
|
/* Dispatch the Reset events to Modem and audio clients */
|
|
static void dispatch_event(unsigned long code, uint16_t proc)
|
|
{
|
|
struct apr_client *apr_client;
|
|
struct apr_client_data data;
|
|
struct apr_svc *svc;
|
|
uint16_t clnt;
|
|
int i, j;
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
data.opcode = RESET_EVENTS;
|
|
data.reset_event = code;
|
|
|
|
/* Service domain can be different from the processor */
|
|
data.reset_proc = apr_get_reset_domain(proc);
|
|
|
|
clnt = APR_CLIENT_AUDIO;
|
|
apr_client = &client[proc][clnt];
|
|
for (i = 0; i < APR_SVC_MAX; i++) {
|
|
mutex_lock(&apr_client->svc[i].m_lock);
|
|
if (apr_client->svc[i].fn) {
|
|
apr_client->svc[i].need_reset = 0x1;
|
|
apr_client->svc[i].fn(&data, apr_client->svc[i].priv);
|
|
}
|
|
if (apr_client->svc[i].port_cnt) {
|
|
svc = &(apr_client->svc[i]);
|
|
svc->need_reset = 0x1;
|
|
for (j = 0; j < APR_MAX_PORTS; j++)
|
|
if (svc->port_fn[j])
|
|
svc->port_fn[j](&data,
|
|
svc->port_priv[j]);
|
|
}
|
|
mutex_unlock(&apr_client->svc[i].m_lock);
|
|
}
|
|
|
|
clnt = APR_CLIENT_VOICE;
|
|
apr_client = &client[proc][clnt];
|
|
for (i = 0; i < APR_SVC_MAX; i++) {
|
|
mutex_lock(&apr_client->svc[i].m_lock);
|
|
if (apr_client->svc[i].fn) {
|
|
apr_client->svc[i].need_reset = 0x1;
|
|
apr_client->svc[i].fn(&data, apr_client->svc[i].priv);
|
|
}
|
|
if (apr_client->svc[i].port_cnt) {
|
|
svc = &(apr_client->svc[i]);
|
|
svc->need_reset = 0x1;
|
|
for (j = 0; j < APR_MAX_PORTS; j++)
|
|
if (svc->port_fn[j])
|
|
svc->port_fn[j](&data,
|
|
svc->port_priv[j]);
|
|
}
|
|
mutex_unlock(&apr_client->svc[i].m_lock);
|
|
}
|
|
}
|
|
|
|
static int apr_notifier_service_cb(struct notifier_block *this,
|
|
unsigned long opcode, void *data)
|
|
{
|
|
struct audio_notifier_cb_data *cb_data = data;
|
|
|
|
if (cb_data == NULL) {
|
|
pr_err("%s: Callback data is NULL!\n", __func__);
|
|
goto done;
|
|
}
|
|
|
|
pr_debug("%s: Service opcode 0x%lx, domain %d\n",
|
|
__func__, opcode, cb_data->domain);
|
|
|
|
switch (opcode) {
|
|
case AUDIO_NOTIFIER_SERVICE_DOWN:
|
|
/*
|
|
* Use flag to ignore down notifications during
|
|
* initial boot. There is no benefit from error
|
|
* recovery notifications during initial boot
|
|
* up since everything is expected to be down.
|
|
*/
|
|
spin_lock(&apr_priv->apr_lock);
|
|
if (apr_priv->is_initial_boot) {
|
|
spin_unlock(&apr_priv->apr_lock);
|
|
break;
|
|
}
|
|
spin_unlock(&apr_priv->apr_lock);
|
|
if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN)
|
|
apr_modem_down(opcode);
|
|
else
|
|
apr_adsp_down(opcode);
|
|
break;
|
|
case AUDIO_NOTIFIER_SERVICE_UP:
|
|
if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN)
|
|
apr_modem_up();
|
|
else
|
|
apr_adsp_up();
|
|
spin_lock(&apr_priv->apr_lock);
|
|
apr_priv->is_initial_boot = false;
|
|
spin_unlock(&apr_priv->apr_lock);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
done:
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block adsp_service_nb = {
|
|
.notifier_call = apr_notifier_service_cb,
|
|
.priority = 0,
|
|
};
|
|
|
|
static struct notifier_block modem_service_nb = {
|
|
.notifier_call = apr_notifier_service_cb,
|
|
.priority = 0,
|
|
};
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static int __init apr_debug_init(void)
|
|
{
|
|
debugfs_apr_debug = debugfs_create_file("msm_apr_debug",
|
|
S_IFREG | 0444, NULL, NULL,
|
|
&apr_debug_ops);
|
|
return 0;
|
|
}
|
|
#else
|
|
static int __init apr_debug_init(void)
|
|
(
|
|
return 0;
|
|
)
|
|
#endif
|
|
|
|
static void apr_cleanup(void)
|
|
{
|
|
int i, j, k;
|
|
|
|
subsys_notif_deregister("apr_modem");
|
|
subsys_notif_deregister("apr_adsp");
|
|
if (apr_reset_workqueue) {
|
|
flush_workqueue(apr_reset_workqueue);
|
|
destroy_workqueue(apr_reset_workqueue);
|
|
}
|
|
mutex_destroy(&q6.lock);
|
|
for (i = 0; i < APR_DEST_MAX; i++) {
|
|
for (j = 0; j < APR_CLIENT_MAX; j++) {
|
|
mutex_destroy(&client[i][j].m_lock);
|
|
for (k = 0; k < APR_SVC_MAX; k++)
|
|
mutex_destroy(&client[i][j].svc[k].m_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int apr_probe(struct platform_device *pdev)
|
|
{
|
|
int i, j, k;
|
|
|
|
init_waitqueue_head(&dsp_wait);
|
|
init_waitqueue_head(&modem_wait);
|
|
|
|
apr_priv = devm_kzalloc(&pdev->dev, sizeof(*apr_priv), GFP_KERNEL);
|
|
if (!apr_priv)
|
|
return -ENOMEM;
|
|
|
|
apr_priv->dev = &pdev->dev;
|
|
spin_lock_init(&apr_priv->apr_lock);
|
|
spin_lock_init(&apr_priv->apr_chld_lock);
|
|
INIT_LIST_HEAD(&apr_priv->apr_chlds);
|
|
INIT_WORK(&apr_priv->add_chld_dev_work, apr_add_child_devices);
|
|
|
|
for (i = 0; i < APR_DEST_MAX; i++)
|
|
for (j = 0; j < APR_CLIENT_MAX; j++) {
|
|
mutex_init(&client[i][j].m_lock);
|
|
for (k = 0; k < APR_SVC_MAX; k++) {
|
|
mutex_init(&client[i][j].svc[k].m_lock);
|
|
spin_lock_init(&client[i][j].svc[k].w_lock);
|
|
}
|
|
}
|
|
apr_set_subsys_state();
|
|
mutex_init(&q6.lock);
|
|
apr_reset_workqueue = create_singlethread_workqueue("apr_driver");
|
|
if (!apr_reset_workqueue) {
|
|
apr_priv = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
apr_pkt_ctx = ipc_log_context_create(APR_PKT_IPC_LOG_PAGE_CNT,
|
|
"apr", 0);
|
|
if (!apr_pkt_ctx)
|
|
pr_err("%s: Unable to create ipc log context\n", __func__);
|
|
|
|
spin_lock(&apr_priv->apr_lock);
|
|
apr_priv->is_initial_boot = true;
|
|
spin_unlock(&apr_priv->apr_lock);
|
|
subsys_notif_register("apr_adsp", AUDIO_NOTIFIER_ADSP_DOMAIN,
|
|
&adsp_service_nb);
|
|
subsys_notif_register("apr_modem", AUDIO_NOTIFIER_MODEM_DOMAIN,
|
|
&modem_service_nb);
|
|
|
|
apr_tal_init();
|
|
return apr_debug_init();
|
|
}
|
|
|
|
static int apr_remove(struct platform_device *pdev)
|
|
{
|
|
struct apr_chld_device *chld, *tmp;
|
|
|
|
apr_cleanup();
|
|
apr_tal_exit();
|
|
spin_lock(&apr_priv->apr_chld_lock);
|
|
list_for_each_entry_safe(chld, tmp, &apr_priv->apr_chlds, node) {
|
|
platform_device_unregister(chld->pdev);
|
|
list_del(&chld->node);
|
|
kfree(chld);
|
|
}
|
|
spin_unlock(&apr_priv->apr_chld_lock);
|
|
apr_priv = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id apr_machine_of_match[] = {
|
|
{ .compatible = "qcom,msm-audio-apr", },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver apr_driver = {
|
|
.probe = apr_probe,
|
|
.remove = apr_remove,
|
|
.driver = {
|
|
.name = "audio_apr",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = apr_machine_of_match,
|
|
}
|
|
};
|
|
|
|
module_platform_driver(apr_driver);
|
|
|
|
MODULE_DESCRIPTION("APR DRIVER");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DEVICE_TABLE(of, apr_machine_of_match);
|