]>
Commit | Line | Data |
---|---|---|
caff4cae IM |
1 | |
2 | /* arch/arm/mach-msm/qdsp5/audpp.c | |
3 | * | |
4 | * common code to deal with the AUDPP dsp task (audio postproc) | |
5 | * | |
6 | * Copyright (C) 2008 Google, Inc. | |
7 | * | |
8 | * This software is licensed under the terms of the GNU General Public | |
9 | * License version 2, as published by the Free Software Foundation, and | |
10 | * may be copied, distributed, and modified under those terms. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | */ | |
18 | ||
19 | #include <linux/kernel.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/wait.h> | |
22 | #include <linux/delay.h> | |
23 | ||
24 | #include <asm/atomic.h> | |
25 | #include <asm/ioctls.h> | |
26 | #include <mach/msm_adsp.h> | |
27 | ||
28 | #include "audmgr.h" | |
29 | ||
30 | #include <mach/qdsp5/qdsp5audppcmdi.h> | |
31 | #include <mach/qdsp5/qdsp5audppmsg.h> | |
32 | ||
33 | /* for queue ids - should be relative to module number*/ | |
34 | #include "adsp.h" | |
35 | ||
36 | #include "evlog.h" | |
37 | ||
38 | ||
39 | enum { | |
40 | EV_NULL, | |
41 | EV_ENABLE, | |
42 | EV_DISABLE, | |
43 | EV_EVENT, | |
44 | EV_DATA, | |
45 | }; | |
46 | ||
47 | static const char *dsp_log_strings[] = { | |
48 | "NULL", | |
49 | "ENABLE", | |
50 | "DISABLE", | |
51 | "EVENT", | |
52 | "DATA", | |
53 | }; | |
54 | ||
55 | DECLARE_LOG(dsp_log, 64, dsp_log_strings); | |
56 | ||
57 | static int __init _dsp_log_init(void) | |
58 | { | |
59 | return ev_log_init(&dsp_log); | |
60 | } | |
61 | module_init(_dsp_log_init); | |
62 | #define LOG(id,arg) ev_log_write(&dsp_log, id, arg) | |
63 | ||
64 | static DEFINE_MUTEX(audpp_lock); | |
65 | ||
66 | #define CH_COUNT 5 | |
67 | #define AUDPP_CLNT_MAX_COUNT 6 | |
68 | #define AUDPP_AVSYNC_INFO_SIZE 7 | |
69 | ||
70 | struct audpp_state { | |
71 | struct msm_adsp_module *mod; | |
72 | audpp_event_func func[AUDPP_CLNT_MAX_COUNT]; | |
73 | void *private[AUDPP_CLNT_MAX_COUNT]; | |
74 | struct mutex *lock; | |
75 | unsigned open_count; | |
76 | unsigned enabled; | |
77 | ||
78 | /* which channels are actually enabled */ | |
79 | unsigned avsync_mask; | |
80 | ||
81 | /* flags, 48 bits sample/bytes counter per channel */ | |
82 | uint16_t avsync[CH_COUNT * AUDPP_CLNT_MAX_COUNT + 1]; | |
83 | }; | |
84 | ||
85 | struct audpp_state the_audpp_state = { | |
86 | .lock = &audpp_lock, | |
87 | }; | |
88 | ||
89 | int audpp_send_queue1(void *cmd, unsigned len) | |
90 | { | |
91 | return msm_adsp_write(the_audpp_state.mod, | |
92 | QDSP_uPAudPPCmd1Queue, cmd, len); | |
93 | } | |
94 | EXPORT_SYMBOL(audpp_send_queue1); | |
95 | ||
96 | int audpp_send_queue2(void *cmd, unsigned len) | |
97 | { | |
98 | return msm_adsp_write(the_audpp_state.mod, | |
99 | QDSP_uPAudPPCmd2Queue, cmd, len); | |
100 | } | |
101 | EXPORT_SYMBOL(audpp_send_queue2); | |
102 | ||
103 | int audpp_send_queue3(void *cmd, unsigned len) | |
104 | { | |
105 | return msm_adsp_write(the_audpp_state.mod, | |
106 | QDSP_uPAudPPCmd3Queue, cmd, len); | |
107 | } | |
108 | EXPORT_SYMBOL(audpp_send_queue3); | |
109 | ||
110 | static int audpp_dsp_config(int enable) | |
111 | { | |
112 | audpp_cmd_cfg cmd; | |
113 | ||
114 | cmd.cmd_id = AUDPP_CMD_CFG; | |
115 | cmd.cfg = enable ? AUDPP_CMD_CFG_ENABLE : AUDPP_CMD_CFG_SLEEP; | |
116 | ||
117 | return audpp_send_queue1(&cmd, sizeof(cmd)); | |
118 | } | |
119 | ||
120 | static void audpp_broadcast(struct audpp_state *audpp, unsigned id, | |
121 | uint16_t *msg) | |
122 | { | |
123 | unsigned n; | |
124 | for (n = 0; n < AUDPP_CLNT_MAX_COUNT; n++) { | |
125 | if (audpp->func[n]) | |
126 | audpp->func[n] (audpp->private[n], id, msg); | |
127 | } | |
128 | } | |
129 | ||
130 | static void audpp_notify_clnt(struct audpp_state *audpp, unsigned clnt_id, | |
131 | unsigned id, uint16_t *msg) | |
132 | { | |
133 | if (clnt_id < AUDPP_CLNT_MAX_COUNT && audpp->func[clnt_id]) | |
134 | audpp->func[clnt_id] (audpp->private[clnt_id], id, msg); | |
135 | } | |
136 | ||
137 | static void audpp_dsp_event(void *data, unsigned id, size_t len, | |
138 | void (*getevent)(void *ptr, size_t len)) | |
139 | { | |
140 | struct audpp_state *audpp = data; | |
141 | uint16_t msg[8]; | |
142 | ||
143 | if (id == AUDPP_MSG_AVSYNC_MSG) { | |
144 | getevent(audpp->avsync, sizeof(audpp->avsync)); | |
145 | ||
146 | /* mask off any channels we're not watching to avoid | |
147 | * cases where we might get one last update after | |
148 | * disabling avsync and end up in an odd state when | |
149 | * we next read... | |
150 | */ | |
151 | audpp->avsync[0] &= audpp->avsync_mask; | |
152 | return; | |
153 | } | |
154 | ||
155 | getevent(msg, sizeof(msg)); | |
156 | ||
157 | LOG(EV_EVENT, (id << 16) | msg[0]); | |
158 | LOG(EV_DATA, (msg[1] << 16) | msg[2]); | |
159 | ||
160 | switch (id) { | |
161 | case AUDPP_MSG_STATUS_MSG:{ | |
162 | unsigned cid = msg[0]; | |
163 | pr_info("audpp: status %d %d %d\n", cid, msg[1], | |
164 | msg[2]); | |
165 | if ((cid < 5) && audpp->func[cid]) | |
166 | audpp->func[cid] (audpp->private[cid], id, msg); | |
167 | break; | |
168 | } | |
169 | case AUDPP_MSG_HOST_PCM_INTF_MSG: | |
170 | if (audpp->func[5]) | |
171 | audpp->func[5] (audpp->private[5], id, msg); | |
172 | break; | |
173 | case AUDPP_MSG_PCMDMAMISSED: | |
174 | pr_err("audpp: DMA missed obj=%x\n", msg[0]); | |
175 | break; | |
176 | case AUDPP_MSG_CFG_MSG: | |
177 | if (msg[0] == AUDPP_MSG_ENA_ENA) { | |
178 | pr_info("audpp: ENABLE\n"); | |
179 | audpp->enabled = 1; | |
180 | audpp_broadcast(audpp, id, msg); | |
181 | } else if (msg[0] == AUDPP_MSG_ENA_DIS) { | |
182 | pr_info("audpp: DISABLE\n"); | |
183 | audpp->enabled = 0; | |
184 | audpp_broadcast(audpp, id, msg); | |
185 | } else { | |
186 | pr_err("audpp: invalid config msg %d\n", msg[0]); | |
187 | } | |
188 | break; | |
189 | case AUDPP_MSG_ROUTING_ACK: | |
190 | audpp_broadcast(audpp, id, msg); | |
191 | break; | |
192 | case AUDPP_MSG_FLUSH_ACK: | |
193 | audpp_notify_clnt(audpp, msg[0], id, msg); | |
194 | break; | |
195 | default: | |
196 | pr_info("audpp: unhandled msg id %x\n", id); | |
197 | } | |
198 | } | |
199 | ||
200 | static struct msm_adsp_ops adsp_ops = { | |
201 | .event = audpp_dsp_event, | |
202 | }; | |
203 | ||
204 | static void audpp_fake_event(struct audpp_state *audpp, int id, | |
205 | unsigned event, unsigned arg) | |
206 | { | |
207 | uint16_t msg[1]; | |
208 | msg[0] = arg; | |
209 | audpp->func[id] (audpp->private[id], event, msg); | |
210 | } | |
211 | ||
212 | int audpp_enable(int id, audpp_event_func func, void *private) | |
213 | { | |
214 | struct audpp_state *audpp = &the_audpp_state; | |
215 | int res = 0; | |
216 | ||
217 | if (id < -1 || id > 4) | |
218 | return -EINVAL; | |
219 | ||
220 | if (id == -1) | |
221 | id = 5; | |
222 | ||
223 | mutex_lock(audpp->lock); | |
224 | if (audpp->func[id]) { | |
225 | res = -EBUSY; | |
226 | goto out; | |
227 | } | |
228 | ||
229 | audpp->func[id] = func; | |
230 | audpp->private[id] = private; | |
231 | ||
232 | LOG(EV_ENABLE, 1); | |
233 | if (audpp->open_count++ == 0) { | |
234 | pr_info("audpp: enable\n"); | |
235 | res = msm_adsp_get("AUDPPTASK", &audpp->mod, &adsp_ops, audpp); | |
236 | if (res < 0) { | |
237 | pr_err("audpp: cannot open AUDPPTASK\n"); | |
238 | audpp->open_count = 0; | |
239 | audpp->func[id] = NULL; | |
240 | audpp->private[id] = NULL; | |
241 | goto out; | |
242 | } | |
243 | LOG(EV_ENABLE, 2); | |
244 | msm_adsp_enable(audpp->mod); | |
245 | audpp_dsp_config(1); | |
246 | } else { | |
247 | unsigned long flags; | |
248 | local_irq_save(flags); | |
249 | if (audpp->enabled) | |
250 | audpp_fake_event(audpp, id, | |
251 | AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_ENA); | |
252 | local_irq_restore(flags); | |
253 | } | |
254 | ||
255 | res = 0; | |
256 | out: | |
257 | mutex_unlock(audpp->lock); | |
258 | return res; | |
259 | } | |
260 | EXPORT_SYMBOL(audpp_enable); | |
261 | ||
262 | void audpp_disable(int id, void *private) | |
263 | { | |
264 | struct audpp_state *audpp = &the_audpp_state; | |
265 | unsigned long flags; | |
266 | ||
267 | if (id < -1 || id > 4) | |
268 | return; | |
269 | ||
270 | if (id == -1) | |
271 | id = 5; | |
272 | ||
273 | mutex_lock(audpp->lock); | |
274 | LOG(EV_DISABLE, 1); | |
275 | if (!audpp->func[id]) | |
276 | goto out; | |
277 | if (audpp->private[id] != private) | |
278 | goto out; | |
279 | ||
280 | local_irq_save(flags); | |
281 | audpp_fake_event(audpp, id, AUDPP_MSG_CFG_MSG, AUDPP_MSG_ENA_DIS); | |
282 | audpp->func[id] = NULL; | |
283 | audpp->private[id] = NULL; | |
284 | local_irq_restore(flags); | |
285 | ||
286 | if (--audpp->open_count == 0) { | |
287 | pr_info("audpp: disable\n"); | |
288 | LOG(EV_DISABLE, 2); | |
289 | audpp_dsp_config(0); | |
290 | msm_adsp_disable(audpp->mod); | |
291 | msm_adsp_put(audpp->mod); | |
292 | audpp->mod = NULL; | |
293 | } | |
294 | out: | |
295 | mutex_unlock(audpp->lock); | |
296 | } | |
297 | EXPORT_SYMBOL(audpp_disable); | |
298 | ||
299 | #define BAD_ID(id) ((id < 0) || (id >= CH_COUNT)) | |
300 | ||
301 | void audpp_avsync(int id, unsigned rate) | |
302 | { | |
303 | unsigned long flags; | |
304 | audpp_cmd_avsync cmd; | |
305 | ||
306 | if (BAD_ID(id)) | |
307 | return; | |
308 | ||
309 | local_irq_save(flags); | |
310 | if (rate) | |
311 | the_audpp_state.avsync_mask |= (1 << id); | |
312 | else | |
313 | the_audpp_state.avsync_mask &= (~(1 << id)); | |
314 | the_audpp_state.avsync[0] &= the_audpp_state.avsync_mask; | |
315 | local_irq_restore(flags); | |
316 | ||
317 | cmd.cmd_id = AUDPP_CMD_AVSYNC; | |
318 | cmd.object_number = id; | |
319 | cmd.interrupt_interval_lsw = rate; | |
320 | cmd.interrupt_interval_msw = rate >> 16; | |
321 | audpp_send_queue1(&cmd, sizeof(cmd)); | |
322 | } | |
323 | EXPORT_SYMBOL(audpp_avsync); | |
324 | ||
325 | unsigned audpp_avsync_sample_count(int id) | |
326 | { | |
327 | uint16_t *avsync = the_audpp_state.avsync; | |
328 | unsigned val; | |
329 | unsigned long flags; | |
330 | unsigned mask; | |
331 | ||
332 | if (BAD_ID(id)) | |
333 | return 0; | |
334 | ||
335 | mask = 1 << id; | |
336 | id = id * AUDPP_AVSYNC_INFO_SIZE + 2; | |
337 | local_irq_save(flags); | |
338 | if (avsync[0] & mask) | |
339 | val = (avsync[id] << 16) | avsync[id + 1]; | |
340 | else | |
341 | val = 0; | |
342 | local_irq_restore(flags); | |
343 | ||
344 | return val; | |
345 | } | |
346 | EXPORT_SYMBOL(audpp_avsync_sample_count); | |
347 | ||
348 | unsigned audpp_avsync_byte_count(int id) | |
349 | { | |
350 | uint16_t *avsync = the_audpp_state.avsync; | |
351 | unsigned val; | |
352 | unsigned long flags; | |
353 | unsigned mask; | |
354 | ||
355 | if (BAD_ID(id)) | |
356 | return 0; | |
357 | ||
358 | mask = 1 << id; | |
359 | id = id * AUDPP_AVSYNC_INFO_SIZE + 5; | |
360 | local_irq_save(flags); | |
361 | if (avsync[0] & mask) | |
362 | val = (avsync[id] << 16) | avsync[id + 1]; | |
363 | else | |
364 | val = 0; | |
365 | local_irq_restore(flags); | |
366 | ||
367 | return val; | |
368 | } | |
369 | EXPORT_SYMBOL(audpp_avsync_byte_count); | |
370 | ||
371 | #define AUDPP_CMD_CFG_OBJ_UPDATE 0x8000 | |
372 | #define AUDPP_CMD_VOLUME_PAN 0 | |
373 | ||
374 | int audpp_set_volume_and_pan(unsigned id, unsigned volume, int pan) | |
375 | { | |
376 | /* cmd, obj_cfg[7], cmd_type, volume, pan */ | |
377 | uint16_t cmd[11]; | |
378 | ||
379 | if (id > 6) | |
380 | return -EINVAL; | |
381 | ||
382 | memset(cmd, 0, sizeof(cmd)); | |
383 | cmd[0] = AUDPP_CMD_CFG_OBJECT_PARAMS; | |
384 | cmd[1 + id] = AUDPP_CMD_CFG_OBJ_UPDATE; | |
385 | cmd[8] = AUDPP_CMD_VOLUME_PAN; | |
386 | cmd[9] = volume; | |
387 | cmd[10] = pan; | |
388 | ||
389 | return audpp_send_queue3(cmd, sizeof(cmd)); | |
390 | } | |
391 | EXPORT_SYMBOL(audpp_set_volume_and_pan); | |
392 | ||
393 | int audpp_pause(unsigned id, int pause) | |
394 | { | |
395 | /* pause 1 = pause 0 = resume */ | |
396 | u16 pause_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; | |
397 | ||
398 | if (id >= CH_COUNT) | |
399 | return -EINVAL; | |
400 | ||
401 | memset(pause_cmd, 0, sizeof(pause_cmd)); | |
402 | ||
403 | pause_cmd[0] = AUDPP_CMD_DEC_CTRL; | |
404 | if (pause == 1) | |
405 | pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_PAUSE_V; | |
406 | else if (pause == 0) | |
407 | pause_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_RESUME_V; | |
408 | else | |
409 | return -EINVAL; | |
410 | ||
411 | return audpp_send_queue1(pause_cmd, sizeof(pause_cmd)); | |
412 | } | |
413 | EXPORT_SYMBOL(audpp_pause); | |
414 | ||
415 | int audpp_flush(unsigned id) | |
416 | { | |
417 | u16 flush_cmd[AUDPP_CMD_DEC_CTRL_LEN / sizeof(unsigned short)]; | |
418 | ||
419 | if (id >= CH_COUNT) | |
420 | return -EINVAL; | |
421 | ||
422 | memset(flush_cmd, 0, sizeof(flush_cmd)); | |
423 | ||
424 | flush_cmd[0] = AUDPP_CMD_DEC_CTRL; | |
425 | flush_cmd[1 + id] = AUDPP_CMD_UPDATE_V | AUDPP_CMD_FLUSH_V; | |
426 | ||
427 | return audpp_send_queue1(flush_cmd, sizeof(flush_cmd)); | |
428 | } | |
429 | EXPORT_SYMBOL(audpp_flush); |