287 lines
7.1 KiB
C
287 lines
7.1 KiB
C
/* Copyright (c) 2010-2011, 2013-2014 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/mutex.h>
|
|
#include <linux/list.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/clk.h>
|
|
#include <soc/qcom/smd.h>
|
|
#include <linux/qdsp6v2/apr_tal.h>
|
|
|
|
static char *svc_names[APR_DEST_MAX][APR_CLIENT_MAX] = {
|
|
{
|
|
"apr_audio_svc",
|
|
"apr_voice_svc",
|
|
},
|
|
{
|
|
"apr_audio_svc",
|
|
"apr_voice_svc",
|
|
},
|
|
};
|
|
|
|
struct apr_svc_ch_dev apr_svc_ch[APR_DL_MAX][APR_DEST_MAX][APR_CLIENT_MAX];
|
|
|
|
int __apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len)
|
|
{
|
|
int w_len;
|
|
unsigned long flags;
|
|
|
|
|
|
spin_lock_irqsave(&apr_ch->w_lock, flags);
|
|
if (smd_write_avail(apr_ch->ch) < len) {
|
|
spin_unlock_irqrestore(&apr_ch->w_lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
w_len = smd_write(apr_ch->ch, data, len);
|
|
spin_unlock_irqrestore(&apr_ch->w_lock, flags);
|
|
pr_debug("apr_tal:w_len = %d\n", w_len);
|
|
|
|
if (w_len != len) {
|
|
pr_err("apr_tal: Error in write\n");
|
|
return -ENETRESET;
|
|
}
|
|
return w_len;
|
|
}
|
|
|
|
int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len)
|
|
{
|
|
int rc = 0, retries = 0;
|
|
|
|
if (!apr_ch->ch)
|
|
return -EINVAL;
|
|
|
|
do {
|
|
if (rc == -EAGAIN)
|
|
udelay(50);
|
|
|
|
rc = __apr_tal_write(apr_ch, data, len);
|
|
} while (rc == -EAGAIN && retries++ < 300);
|
|
|
|
if (rc == -EAGAIN)
|
|
pr_err("apr_tal: TIMEOUT for write\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void apr_tal_notify(void *priv, unsigned event)
|
|
{
|
|
struct apr_svc_ch_dev *apr_ch = priv;
|
|
int len, r_len, sz;
|
|
int pkt_cnt = 0;
|
|
unsigned long flags;
|
|
|
|
pr_debug("event = %d\n", event);
|
|
switch (event) {
|
|
case SMD_EVENT_DATA:
|
|
pkt_cnt = 0;
|
|
spin_lock_irqsave(&apr_ch->lock, flags);
|
|
check_pending:
|
|
len = smd_read_avail(apr_ch->ch);
|
|
if (len < 0) {
|
|
pr_err("apr_tal: Invalid Read Event :%d\n", len);
|
|
spin_unlock_irqrestore(&apr_ch->lock, flags);
|
|
return;
|
|
}
|
|
sz = smd_cur_packet_size(apr_ch->ch);
|
|
if (sz < 0) {
|
|
pr_debug("pkt size is zero\n");
|
|
spin_unlock_irqrestore(&apr_ch->lock, flags);
|
|
return;
|
|
}
|
|
if (!len && !sz && !pkt_cnt)
|
|
goto check_write_avail;
|
|
if (!len) {
|
|
pr_debug("len = %d pkt_cnt = %d\n", len, pkt_cnt);
|
|
spin_unlock_irqrestore(&apr_ch->lock, flags);
|
|
return;
|
|
}
|
|
r_len = smd_read_from_cb(apr_ch->ch, apr_ch->data, len);
|
|
if (len != r_len) {
|
|
pr_err("apr_tal: Invalid Read\n");
|
|
spin_unlock_irqrestore(&apr_ch->lock, flags);
|
|
return;
|
|
}
|
|
pkt_cnt++;
|
|
pr_debug("%d %d %d\n", len, sz, pkt_cnt);
|
|
if (apr_ch->func)
|
|
apr_ch->func(apr_ch->data, r_len, apr_ch->priv);
|
|
goto check_pending;
|
|
check_write_avail:
|
|
if (smd_write_avail(apr_ch->ch))
|
|
wake_up(&apr_ch->wait);
|
|
spin_unlock_irqrestore(&apr_ch->lock, flags);
|
|
break;
|
|
case SMD_EVENT_OPEN:
|
|
pr_debug("apr_tal: SMD_EVENT_OPEN\n");
|
|
apr_ch->smd_state = 1;
|
|
wake_up(&apr_ch->wait);
|
|
break;
|
|
case SMD_EVENT_CLOSE:
|
|
pr_debug("apr_tal: SMD_EVENT_CLOSE\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct apr_svc_ch_dev *apr_tal_open(uint32_t svc, uint32_t dest,
|
|
uint32_t dl, apr_svc_cb_fn func, void *priv)
|
|
{
|
|
int rc;
|
|
|
|
if ((svc >= APR_CLIENT_MAX) || (dest >= APR_DEST_MAX) ||
|
|
(dl >= APR_DL_MAX)) {
|
|
pr_err("apr_tal: Invalid params\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (apr_svc_ch[dl][dest][svc].ch) {
|
|
pr_err("apr_tal: This channel alreday openend\n");
|
|
return NULL;
|
|
}
|
|
|
|
mutex_lock(&apr_svc_ch[dl][dest][svc].m_lock);
|
|
if (!apr_svc_ch[dl][dest][svc].dest_state) {
|
|
rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].dest,
|
|
apr_svc_ch[dl][dest][svc].dest_state,
|
|
msecs_to_jiffies(APR_OPEN_TIMEOUT_MS));
|
|
if (rc == 0) {
|
|
pr_err("apr_tal:open timeout\n");
|
|
mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock);
|
|
return NULL;
|
|
}
|
|
pr_debug("apr_tal:Wakeup done\n");
|
|
apr_svc_ch[dl][dest][svc].dest_state = 0;
|
|
}
|
|
rc = smd_named_open_on_edge(svc_names[dest][svc], dest,
|
|
&apr_svc_ch[dl][dest][svc].ch,
|
|
&apr_svc_ch[dl][dest][svc],
|
|
apr_tal_notify);
|
|
if (rc < 0) {
|
|
pr_err("apr_tal: smd_open failed %s\n",
|
|
svc_names[dest][svc]);
|
|
mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock);
|
|
return NULL;
|
|
}
|
|
rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].wait,
|
|
(apr_svc_ch[dl][dest][svc].smd_state == 1), 5 * HZ);
|
|
if (rc == 0) {
|
|
pr_err("apr_tal:TIMEOUT for OPEN event\n");
|
|
mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock);
|
|
apr_tal_close(&apr_svc_ch[dl][dest][svc]);
|
|
return NULL;
|
|
}
|
|
|
|
smd_disable_read_intr(apr_svc_ch[dl][dest][svc].ch);
|
|
|
|
if (!apr_svc_ch[dl][dest][svc].dest_state) {
|
|
apr_svc_ch[dl][dest][svc].dest_state = 1;
|
|
pr_debug("apr_tal:Waiting for apr svc init\n");
|
|
msleep(200);
|
|
pr_debug("apr_tal:apr svc init done\n");
|
|
}
|
|
apr_svc_ch[dl][dest][svc].smd_state = 0;
|
|
|
|
apr_svc_ch[dl][dest][svc].func = func;
|
|
apr_svc_ch[dl][dest][svc].priv = priv;
|
|
mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock);
|
|
|
|
return &apr_svc_ch[dl][dest][svc];
|
|
}
|
|
|
|
int apr_tal_close(struct apr_svc_ch_dev *apr_ch)
|
|
{
|
|
int r;
|
|
|
|
if (!apr_ch->ch)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&apr_ch->m_lock);
|
|
r = smd_close(apr_ch->ch);
|
|
apr_ch->ch = NULL;
|
|
apr_ch->func = NULL;
|
|
apr_ch->priv = NULL;
|
|
mutex_unlock(&apr_ch->m_lock);
|
|
return r;
|
|
}
|
|
|
|
static int apr_smd_probe(struct platform_device *pdev)
|
|
{
|
|
int dest;
|
|
int clnt;
|
|
|
|
if (pdev->id == APR_DEST_MODEM) {
|
|
pr_info("apr_tal:Modem Is Up\n");
|
|
dest = APR_DEST_MODEM;
|
|
if (!strcmp(pdev->name, "apr_audio_svc"))
|
|
clnt = APR_CLIENT_AUDIO;
|
|
else
|
|
clnt = APR_CLIENT_VOICE;
|
|
apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1;
|
|
wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest);
|
|
} else if (pdev->id == APR_DEST_QDSP6) {
|
|
pr_info("apr_tal:Q6 Is Up\n");
|
|
dest = APR_DEST_QDSP6;
|
|
clnt = APR_CLIENT_AUDIO;
|
|
apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1;
|
|
wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest);
|
|
} else
|
|
pr_err("apr_tal:Invalid Dest Id: %d\n", pdev->id);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver apr_q6_driver = {
|
|
.probe = apr_smd_probe,
|
|
.driver = {
|
|
.name = "apr_audio_svc",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static struct platform_driver apr_modem_driver = {
|
|
.probe = apr_smd_probe,
|
|
.driver = {
|
|
.name = "apr_voice_svc",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static int __init apr_tal_init(void)
|
|
{
|
|
int i, j, k;
|
|
|
|
for (i = 0; i < APR_DL_MAX; i++)
|
|
for (j = 0; j < APR_DEST_MAX; j++)
|
|
for (k = 0; k < APR_CLIENT_MAX; k++) {
|
|
init_waitqueue_head(&apr_svc_ch[i][j][k].wait);
|
|
init_waitqueue_head(&apr_svc_ch[i][j][k].dest);
|
|
spin_lock_init(&apr_svc_ch[i][j][k].lock);
|
|
spin_lock_init(&apr_svc_ch[i][j][k].w_lock);
|
|
mutex_init(&apr_svc_ch[i][j][k].m_lock);
|
|
}
|
|
platform_driver_register(&apr_q6_driver);
|
|
platform_driver_register(&apr_modem_driver);
|
|
return 0;
|
|
}
|
|
device_initcall(apr_tal_init);
|