blob: 585fcfbada6a8f9449c856a245b88ecc6e5ff774 [file] [log] [blame]
Srinivas Kandagatlaa13e8722018-05-18 13:56:01 +01001// SPDX-License-Identifier: GPL-2.0
2// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
3// Copyright (c) 2018, Linaro Limited
4
5#include <linux/mutex.h>
6#include <linux/wait.h>
7#include <linux/module.h>
8#include <linux/soc/qcom/apr.h>
9#include <linux/device.h>
10#include <linux/of_platform.h>
11#include <linux/spinlock.h>
12#include <linux/kref.h>
13#include <linux/of.h>
14#include <linux/delay.h>
15#include <linux/slab.h>
16#include <linux/mm.h>
17#include "q6asm.h"
18#include "q6core.h"
19#include "q6dsp-errno.h"
20#include "q6dsp-common.h"
21
22#define ASM_SYNC_IO_MODE 0x0001
23#define ASM_ASYNC_IO_MODE 0x0002
24#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */
25#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */
26
27struct q6asm {
28 struct apr_device *adev;
29 struct device *dev;
30 struct q6core_svc_api_info ainfo;
31 wait_queue_head_t mem_wait;
32 struct platform_device *pcmdev;
33 spinlock_t slock;
34 struct audio_client *session[MAX_SESSIONS + 1];
35 struct platform_device *pdev_dais;
36};
37
38struct audio_client {
39 int session;
40 q6asm_cb cb;
41 void *priv;
42 uint32_t io_mode;
43 struct apr_device *adev;
44 struct mutex cmd_lock;
45 spinlock_t lock;
46 struct kref refcount;
47 wait_queue_head_t cmd_wait;
48 struct aprv2_ibasic_rsp_result_t result;
49 int perf_mode;
50 int stream_id;
51 struct q6asm *q6asm;
52 struct device *dev;
53};
54
55static void q6asm_audio_client_release(struct kref *ref)
56{
57 struct audio_client *ac;
58 struct q6asm *a;
59 unsigned long flags;
60
61 ac = container_of(ref, struct audio_client, refcount);
62 a = ac->q6asm;
63
64 spin_lock_irqsave(&a->slock, flags);
65 a->session[ac->session] = NULL;
66 spin_unlock_irqrestore(&a->slock, flags);
67
68 kfree(ac);
69}
70
71/**
72 * q6asm_audio_client_free() - Freee allocated audio client
73 *
74 * @ac: audio client to free
75 */
76void q6asm_audio_client_free(struct audio_client *ac)
77{
78 kref_put(&ac->refcount, q6asm_audio_client_release);
79}
80EXPORT_SYMBOL_GPL(q6asm_audio_client_free);
81
82static struct audio_client *q6asm_get_audio_client(struct q6asm *a,
83 int session_id)
84{
85 struct audio_client *ac = NULL;
86 unsigned long flags;
87
88 spin_lock_irqsave(&a->slock, flags);
89 if ((session_id <= 0) || (session_id > MAX_SESSIONS)) {
90 dev_err(a->dev, "invalid session: %d\n", session_id);
91 goto err;
92 }
93
94 /* check for valid session */
95 if (!a->session[session_id])
96 goto err;
97 else if (a->session[session_id]->session != session_id)
98 goto err;
99
100 ac = a->session[session_id];
101 kref_get(&ac->refcount);
102err:
103 spin_unlock_irqrestore(&a->slock, flags);
104 return ac;
105}
106
107static int q6asm_srvc_callback(struct apr_device *adev,
108 struct apr_resp_pkt *data)
109{
110 struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
111 struct audio_client *ac = NULL;
112 struct apr_hdr *hdr = &data->hdr;
113 uint32_t sid = 0;
114
115 sid = (hdr->token >> 8) & 0x0F;
116 ac = q6asm_get_audio_client(q6asm, sid);
117 if (!ac) {
118 dev_err(&adev->dev, "Audio Client not active\n");
119 return 0;
120 }
121
122 if (ac->cb)
123 ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv);
124
125 kref_put(&ac->refcount, q6asm_audio_client_release);
126
127 return 0;
128}
129
130/**
131 * q6asm_get_session_id() - get session id for audio client
132 *
133 * @c: audio client pointer
134 *
135 * Return: Will be an session id of the audio client.
136 */
137int q6asm_get_session_id(struct audio_client *c)
138{
139 return c->session;
140}
141EXPORT_SYMBOL_GPL(q6asm_get_session_id);
142
143/**
144 * q6asm_audio_client_alloc() - Allocate a new audio client
145 *
146 * @dev: Pointer to asm child device.
147 * @cb: event callback.
148 * @priv: private data associated with this client.
149 * @stream_id: stream id
150 * @perf_mode: performace mode for this client
151 *
152 * Return: Will be an error pointer on error or a valid audio client
153 * on success.
154 */
155struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb,
156 void *priv, int stream_id,
157 int perf_mode)
158{
159 struct q6asm *a = dev_get_drvdata(dev->parent);
160 struct audio_client *ac;
161 unsigned long flags;
162
163 ac = q6asm_get_audio_client(a, stream_id + 1);
164 if (ac) {
165 dev_err(dev, "Audio Client already active\n");
166 return ac;
167 }
168
169 ac = kzalloc(sizeof(*ac), GFP_KERNEL);
170 if (!ac)
171 return ERR_PTR(-ENOMEM);
172
173 spin_lock_irqsave(&a->slock, flags);
174 a->session[stream_id + 1] = ac;
175 spin_unlock_irqrestore(&a->slock, flags);
176 ac->session = stream_id + 1;
177 ac->cb = cb;
178 ac->dev = dev;
179 ac->q6asm = a;
180 ac->priv = priv;
181 ac->io_mode = ASM_SYNC_IO_MODE;
182 ac->perf_mode = perf_mode;
183 /* DSP expects stream id from 1 */
184 ac->stream_id = 1;
185 ac->adev = a->adev;
186 kref_init(&ac->refcount);
187
188 init_waitqueue_head(&ac->cmd_wait);
189 mutex_init(&ac->cmd_lock);
190 spin_lock_init(&ac->lock);
191
192 return ac;
193}
194EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc);
195
196
197static int q6asm_probe(struct apr_device *adev)
198{
199 struct device *dev = &adev->dev;
200 struct device_node *dais_np;
201 struct q6asm *q6asm;
202
203 q6asm = devm_kzalloc(dev, sizeof(*q6asm), GFP_KERNEL);
204 if (!q6asm)
205 return -ENOMEM;
206
207 q6core_get_svc_api_info(adev->svc_id, &q6asm->ainfo);
208
209 q6asm->dev = dev;
210 q6asm->adev = adev;
211 init_waitqueue_head(&q6asm->mem_wait);
212 spin_lock_init(&q6asm->slock);
213 dev_set_drvdata(dev, q6asm);
214
215 dais_np = of_get_child_by_name(dev->of_node, "dais");
216 if (dais_np) {
217 q6asm->pdev_dais = of_platform_device_create(dais_np,
218 "q6asm-dai", dev);
219 of_node_put(dais_np);
220 }
221
222 return 0;
223}
224
225static int q6asm_remove(struct apr_device *adev)
226{
227 struct q6asm *q6asm = dev_get_drvdata(&adev->dev);
228
229 if (q6asm->pdev_dais)
230 of_platform_device_destroy(&q6asm->pdev_dais->dev, NULL);
231
232 return 0;
233}
234static const struct of_device_id q6asm_device_id[] = {
235 { .compatible = "qcom,q6asm" },
236 {},
237};
238MODULE_DEVICE_TABLE(of, q6asm_device_id);
239
240static struct apr_driver qcom_q6asm_driver = {
241 .probe = q6asm_probe,
242 .remove = q6asm_remove,
243 .callback = q6asm_srvc_callback,
244 .driver = {
245 .name = "qcom-q6asm",
246 .of_match_table = of_match_ptr(q6asm_device_id),
247 },
248};
249
250module_apr_driver(qcom_q6asm_driver);
251MODULE_DESCRIPTION("Q6 Audio Stream Manager driver");
252MODULE_LICENSE("GPL v2");