aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Purdie <rpurdie@rpsys.net>2006-10-06 12:32:18 -0400
committerJaroslav Kysela <perex@suse.cz>2007-02-09 03:00:18 -0500
commit2b97eabc09f42d0f63e8053636e34e1afa0d604e (patch)
treebca3579abc48891378776a2a5bf548b46be47be8
parentdb2a416556af0313db028147e4a22fef6f214f2f (diff)
[ALSA] ASoC: dynamic audio power management (DAPM)
This patch adds Dynamic Audio Power Management (DAPM) to ASoC. Dynamic Audio Power Management (DAPM) is designed to allow portable and handheld Linux devices to use the minimum amount of power within the audio subsystem at all times. It is independent of other kernel PM and as such, can easily co-exist with the other PM systems. DAPM is also completely transparent to all user space applications as all power switching is done within the ASoC core. No code changes or recompiling are required for user space applications. DAPM makes power switching decisions based upon any audio stream (capture/playback) activity and audio mixer settings within the device. DAPM spans the whole machine. It covers power control within the entire audio subsystem, this includes internal codec power blocks and machine level power systems. There are 4 power domains within DAPM:- 1. Codec domain - VREF, VMID (core codec and audio power) Usually controlled at codec probe/remove and suspend/resume, although can be set at stream time if power is not needed for sidetone, etc. 2. Platform/Machine domain - physically connected inputs and outputs Is platform/machine and user action specific, is configured by the machine driver and responds to asynchronous events e.g when HP are inserted 3. Path domain - audio subsystem signal paths Automatically set when mixer and mux settings are changed by the user. e.g. alsamixer, amixer. 4. Stream domain - DAC's and ADC's. Enabled and disabled when stream playback/capture is started and stopped respectively. e.g. aplay, arecord. All DAPM power switching decisions are made automatically by consulting an audio routing map of the whole machine. This map is specific to each machine and consists of the interconnections between every audio component (including internal codec components). Signed-off-by: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Liam Girdwood <liam.girdwood@wolfsonmicro.com> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
-rw-r--r--sound/soc/soc-dapm.c1327
1 files changed, 1327 insertions, 0 deletions
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
new file mode 100644
index 000000000000..2c2c27f4e9c0
--- /dev/null
+++ b/sound/soc/soc-dapm.c
@@ -0,0 +1,1327 @@
1/*
2 * soc-dapm.c -- ALSA SoC Dynamic Audio Power Management
3 *
4 * Copyright 2005 Wolfson Microelectronics PLC.
5 * Author: Liam Girdwood
6 * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version.
12 *
13 * Revision history
14 * 12th Aug 2005 Initial version.
15 * 25th Oct 2005 Implemented path power domain.
16 * 18th Dec 2005 Implemented machine and stream level power domain.
17 *
18 * Features:
19 * o Changes power status of internal codec blocks depending on the
20 * dynamic configuration of codec internal audio paths and active
21 * DAC's/ADC's.
22 * o Platform power domain - can support external components i.e. amps and
23 * mic/meadphone insertion events.
24 * o Automatic Mic Bias support
25 * o Jack insertion power event initiation - e.g. hp insertion will enable
26 * sinks, dacs, etc
27 * o Delayed powerdown of audio susbsytem to reduce pops between a quick
28 * device reopen.
29 *
30 * Todo:
31 * o DAPM power change sequencing - allow for configurable per
32 * codec sequences.
33 * o Support for analogue bias optimisation.
34 * o Support for reduced codec oversampling rates.
35 * o Support for reduced codec bias currents.
36 */
37
38#include <linux/module.h>
39#include <linux/moduleparam.h>
40#include <linux/init.h>
41#include <linux/delay.h>
42#include <linux/pm.h>
43#include <linux/bitops.h>
44#include <linux/platform_device.h>
45#include <linux/jiffies.h>
46#include <sound/driver.h>
47#include <sound/core.h>
48#include <sound/pcm.h>
49#include <sound/pcm_params.h>
50#include <sound/soc-dapm.h>
51#include <sound/initval.h>
52
53/* debug */
54#define DAPM_DEBUG 0
55#if DAPM_DEBUG
56#define dump_dapm(codec, action) dbg_dump_dapm(codec, action)
57#define dbg(format, arg...) printk(format, ## arg)
58#else
59#define dump_dapm(codec, action)
60#define dbg(format, arg...)
61#endif
62
63#define POP_DEBUG 0
64#if POP_DEBUG
65#define POP_TIME 500 /* 500 msecs - change if pop debug is too fast */
66#define pop_wait(time) schedule_timeout_interruptible(msecs_to_jiffies(time))
67#define pop_dbg(format, arg...) printk(format, ## arg); pop_wait(POP_TIME)
68#else
69#define pop_dbg(format, arg...)
70#define pop_wait(time)
71#endif
72
73/* dapm power sequences - make this per codec in the future */
74static int dapm_up_seq[] = {
75 snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic,
76 snd_soc_dapm_mux, snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_pga,
77 snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post
78};
79static int dapm_down_seq[] = {
80 snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk,
81 snd_soc_dapm_pga, snd_soc_dapm_mixer, snd_soc_dapm_dac, snd_soc_dapm_mic,
82 snd_soc_dapm_micbias, snd_soc_dapm_mux, snd_soc_dapm_post
83};
84
85static int dapm_status = 1;
86module_param(dapm_status, int, 0);
87MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries");
88
89/* create a new dapm widget */
90static struct snd_soc_dapm_widget *dapm_cnew_widget(
91 const struct snd_soc_dapm_widget *_widget)
92{
93 struct snd_soc_dapm_widget* widget;
94 widget = kmalloc(sizeof(struct snd_soc_dapm_widget), GFP_KERNEL);
95 if (!widget)
96 return NULL;
97
98 memcpy(widget, _widget, sizeof(struct snd_soc_dapm_widget));
99 return widget;
100}
101
102/* set up initial codec paths */
103static void dapm_set_path_status(struct snd_soc_dapm_widget *w,
104 struct snd_soc_dapm_path *p, int i)
105{
106 switch (w->id) {
107 case snd_soc_dapm_switch:
108 case snd_soc_dapm_mixer: {
109 int val;
110 int reg = w->kcontrols[i].private_value & 0xff;
111 int shift = (w->kcontrols[i].private_value >> 8) & 0x0f;
112 int mask = (w->kcontrols[i].private_value >> 16) & 0xff;
113 int invert = (w->kcontrols[i].private_value >> 24) & 0x01;
114
115 val = snd_soc_read(w->codec, reg);
116 val = (val >> shift) & mask;
117
118 if ((invert && !val) || (!invert && val))
119 p->connect = 1;
120 else
121 p->connect = 0;
122 }
123 break;
124 case snd_soc_dapm_mux: {
125 struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value;
126 int val, item, bitmask;
127
128 for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
129 ;
130 val = snd_soc_read(w->codec, e->reg);
131 item = (val >> e->shift_l) & (bitmask - 1);
132
133 p->connect = 0;
134 for (i = 0; i < e->mask; i++) {
135 if (!(strcmp(p->name, e->texts[i])) && item == i)
136 p->connect = 1;
137 }
138 }
139 break;
140 /* does not effect routing - always connected */
141 case snd_soc_dapm_pga:
142 case snd_soc_dapm_output:
143 case snd_soc_dapm_adc:
144 case snd_soc_dapm_input:
145 case snd_soc_dapm_dac:
146 case snd_soc_dapm_micbias:
147 case snd_soc_dapm_vmid:
148 p->connect = 1;
149 break;
150 /* does effect routing - dynamically connected */
151 case snd_soc_dapm_hp:
152 case snd_soc_dapm_mic:
153 case snd_soc_dapm_spk:
154 case snd_soc_dapm_line:
155 case snd_soc_dapm_pre:
156 case snd_soc_dapm_post:
157 p->connect = 0;
158 break;
159 }
160}
161
162/* connect mux widget to it's interconnecting audio paths */
163static int dapm_connect_mux(struct snd_soc_codec *codec,
164 struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
165 struct snd_soc_dapm_path *path, const char *control_name,
166 const struct snd_kcontrol_new *kcontrol)
167{
168 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
169 int i;
170
171 for (i = 0; i < e->mask; i++) {
172 if (!(strcmp(control_name, e->texts[i]))) {
173 list_add(&path->list, &codec->dapm_paths);
174 list_add(&path->list_sink, &dest->sources);
175 list_add(&path->list_source, &src->sinks);
176 path->name = (char*)e->texts[i];
177 dapm_set_path_status(dest, path, 0);
178 return 0;
179 }
180 }
181
182 return -ENODEV;
183}
184
185/* connect mixer widget to it's interconnecting audio paths */
186static int dapm_connect_mixer(struct snd_soc_codec *codec,
187 struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest,
188 struct snd_soc_dapm_path *path, const char *control_name)
189{
190 int i;
191
192 /* search for mixer kcontrol */
193 for (i = 0; i < dest->num_kcontrols; i++) {
194 if (!strcmp(control_name, dest->kcontrols[i].name)) {
195 list_add(&path->list, &codec->dapm_paths);
196 list_add(&path->list_sink, &dest->sources);
197 list_add(&path->list_source, &src->sinks);
198 path->name = dest->kcontrols[i].name;
199 dapm_set_path_status(dest, path, i);
200 return 0;
201 }
202 }
203 return -ENODEV;
204}
205
206/* update dapm codec register bits */
207static int dapm_update_bits(struct snd_soc_dapm_widget *widget)
208{
209 int change, power;
210 unsigned short old, new;
211 struct snd_soc_codec *codec = widget->codec;
212
213 /* check for valid widgets */
214 if (widget->reg < 0 || widget->id == snd_soc_dapm_input ||
215 widget->id == snd_soc_dapm_output ||
216 widget->id == snd_soc_dapm_hp ||
217 widget->id == snd_soc_dapm_mic ||
218 widget->id == snd_soc_dapm_line ||
219 widget->id == snd_soc_dapm_spk)
220 return 0;
221
222 power = widget->power;
223 if (widget->invert)
224 power = (power ? 0:1);
225
226 old = snd_soc_read(codec, widget->reg);
227 new = (old & ~(0x1 << widget->shift)) | (power << widget->shift);
228
229 change = old != new;
230 if (change) {
231 pop_dbg("pop test %s : %s in %d ms\n", widget->name,
232 widget->power ? "on" : "off", POP_TIME);
233 snd_soc_write(codec, widget->reg, new);
234 pop_wait(POP_TIME);
235 }
236 dbg("reg old %x new %x change %d\n", old, new, change);
237 return change;
238}
239
240/* ramps the volume up or down to minimise pops before or after a
241 * DAPM power event */
242static int dapm_set_pga(struct snd_soc_dapm_widget *widget, int power)
243{
244 const struct snd_kcontrol_new *k = widget->kcontrols;
245
246 if (widget->muted && !power)
247 return 0;
248 if (!widget->muted && power)
249 return 0;
250
251 if (widget->num_kcontrols && k) {
252 int reg = k->private_value & 0xff;
253 int shift = (k->private_value >> 8) & 0x0f;
254 int mask = (k->private_value >> 16) & 0xff;
255 int invert = (k->private_value >> 24) & 0x01;
256
257 if (power) {
258 int i;
259 /* power up has happended, increase volume to last level */
260 if (invert) {
261 for (i = mask; i > widget->saved_value; i--)
262 snd_soc_update_bits(widget->codec, reg, mask, i);
263 } else {
264 for (i = 0; i < widget->saved_value; i++)
265 snd_soc_update_bits(widget->codec, reg, mask, i);
266 }
267 widget->muted = 0;
268 } else {
269 /* power down is about to occur, decrease volume to mute */
270 int val = snd_soc_read(widget->codec, reg);
271 int i = widget->saved_value = (val >> shift) & mask;
272 if (invert) {
273 for (; i < mask; i++)
274 snd_soc_update_bits(widget->codec, reg, mask, i);
275 } else {
276 for (; i > 0; i--)
277 snd_soc_update_bits(widget->codec, reg, mask, i);
278 }
279 widget->muted = 1;
280 }
281 }
282 return 0;
283}
284
285/* create new dapm mixer control */
286static int dapm_new_mixer(struct snd_soc_codec *codec,
287 struct snd_soc_dapm_widget *w)
288{
289 int i, ret = 0;
290 char name[32];
291 struct snd_soc_dapm_path *path;
292
293 /* add kcontrol */
294 for (i = 0; i < w->num_kcontrols; i++) {
295
296 /* match name */
297 list_for_each_entry(path, &w->sources, list_sink) {
298
299 /* mixer/mux paths name must match control name */
300 if (path->name != (char*)w->kcontrols[i].name)
301 continue;
302
303 /* add dapm control with long name */
304 snprintf(name, 32, "%s %s", w->name, w->kcontrols[i].name);
305 path->long_name = kstrdup (name, GFP_KERNEL);
306 if (path->long_name == NULL)
307 return -ENOMEM;
308
309 path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w,
310 path->long_name);
311 ret = snd_ctl_add(codec->card, path->kcontrol);
312 if (ret < 0) {
313 printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n",
314 path->long_name);
315 kfree(path->long_name);
316 path->long_name = NULL;
317 return ret;
318 }
319 }
320 }
321 return ret;
322}
323
324/* create new dapm mux control */
325static int dapm_new_mux(struct snd_soc_codec *codec,
326 struct snd_soc_dapm_widget *w)
327{
328 struct snd_soc_dapm_path *path = NULL;
329 struct snd_kcontrol *kcontrol;
330 int ret = 0;
331
332 if (!w->num_kcontrols) {
333 printk(KERN_ERR "asoc: mux %s has no controls\n", w->name);
334 return -EINVAL;
335 }
336
337 kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
338 ret = snd_ctl_add(codec->card, kcontrol);
339 if (ret < 0)
340 goto err;
341
342 list_for_each_entry(path, &w->sources, list_sink)
343 path->kcontrol = kcontrol;
344
345 return ret;
346
347err:
348 printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);
349 return ret;
350}
351
352/* create new dapm volume control */
353static int dapm_new_pga(struct snd_soc_codec *codec,
354 struct snd_soc_dapm_widget *w)
355{
356 struct snd_kcontrol *kcontrol;
357 int ret = 0;
358
359 if (!w->num_kcontrols)
360 return -EINVAL;
361
362 kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
363 ret = snd_ctl_add(codec->card, kcontrol);
364 if (ret < 0) {
365 printk(KERN_ERR "asoc: failed to add kcontrol %s\n", w->name);
366 return ret;
367 }
368
369 return ret;
370}
371
372/* reset 'walked' bit for each dapm path */
373static inline void dapm_clear_walk(struct snd_soc_codec *codec)
374{
375 struct snd_soc_dapm_path *p;
376
377 list_for_each_entry(p, &codec->dapm_paths, list)
378 p->walked = 0;
379}
380
381/*
382 * Recursively check for a completed path to an active or physically connected
383 * output widget. Returns number of complete paths.
384 */
385static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
386{
387 struct snd_soc_dapm_path *path;
388 int con = 0;
389
390 if (widget->id == snd_soc_dapm_adc && widget->active)
391 return 1;
392
393 if (widget->connected) {
394 /* connected pin ? */
395 if (widget->id == snd_soc_dapm_output && !widget->ext)
396 return 1;
397
398 /* connected jack or spk ? */
399 if (widget->id == snd_soc_dapm_hp || widget->id == snd_soc_dapm_spk ||
400 widget->id == snd_soc_dapm_line)
401 return 1;
402 }
403
404 list_for_each_entry(path, &widget->sinks, list_source) {
405 if (path->walked)
406 continue;
407
408 if (path->sink && path->connect) {
409 path->walked = 1;
410 con += is_connected_output_ep(path->sink);
411 }
412 }
413
414 return con;
415}
416
417/*
418 * Recursively check for a completed path to an active or physically connected
419 * input widget. Returns number of complete paths.
420 */
421static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
422{
423 struct snd_soc_dapm_path *path;
424 int con = 0;
425
426 /* active stream ? */
427 if (widget->id == snd_soc_dapm_dac && widget->active)
428 return 1;
429
430 if (widget->connected) {
431 /* connected pin ? */
432 if (widget->id == snd_soc_dapm_input && !widget->ext)
433 return 1;
434
435 /* connected VMID/Bias for lower pops */
436 if (widget->id == snd_soc_dapm_vmid)
437 return 1;
438
439 /* connected jack ? */
440 if (widget->id == snd_soc_dapm_mic || widget->id == snd_soc_dapm_line)
441 return 1;
442 }
443
444 list_for_each_entry(path, &widget->sources, list_sink) {
445 if (path->walked)
446 continue;
447
448 if (path->source && path->connect) {
449 path->walked = 1;
450 con += is_connected_input_ep(path->source);
451 }
452 }
453
454 return con;
455}
456
457/*
458 * Scan each dapm widget for complete audio path.
459 * A complete path is a route that has valid endpoints i.e.:-
460 *
461 * o DAC to output pin.
462 * o Input Pin to ADC.
463 * o Input pin to Output pin (bypass, sidetone)
464 * o DAC to ADC (loopback).
465 */
466int dapm_power_widgets(struct snd_soc_codec *codec, int event)
467{
468 struct snd_soc_dapm_widget *w;
469 int in, out, i, c = 1, *seq = NULL, ret = 0, power_change, power;
470
471 /* do we have a sequenced stream event */
472 if (event == SND_SOC_DAPM_STREAM_START) {
473 c = ARRAY_SIZE(dapm_up_seq);
474 seq = dapm_up_seq;
475 } else if (event == SND_SOC_DAPM_STREAM_STOP) {
476 c = ARRAY_SIZE(dapm_down_seq);
477 seq = dapm_down_seq;
478 }
479
480 for(i = 0; i < c; i++) {
481 list_for_each_entry(w, &codec->dapm_widgets, list) {
482
483 /* is widget in stream order */
484 if (seq && seq[i] && w->id != seq[i])
485 continue;
486
487 /* vmid - no action */
488 if (w->id == snd_soc_dapm_vmid)
489 continue;
490
491 /* active ADC */
492 if (w->id == snd_soc_dapm_adc && w->active) {
493 in = is_connected_input_ep(w);
494 dapm_clear_walk(w->codec);
495 w->power = (in != 0) ? 1 : 0;
496 dapm_update_bits(w);
497 continue;
498 }
499
500 /* active DAC */
501 if (w->id == snd_soc_dapm_dac && w->active) {
502 out = is_connected_output_ep(w);
503 dapm_clear_walk(w->codec);
504 w->power = (out != 0) ? 1 : 0;
505 dapm_update_bits(w);
506 continue;
507 }
508
509 /* programmable gain/attenuation */
510 if (w->id == snd_soc_dapm_pga) {
511 int on;
512 in = is_connected_input_ep(w);
513 dapm_clear_walk(w->codec);
514 out = is_connected_output_ep(w);
515 dapm_clear_walk(w->codec);
516 w->power = on = (out != 0 && in != 0) ? 1 : 0;
517
518 if (!on)
519 dapm_set_pga(w, on); /* lower volume to reduce pops */
520 dapm_update_bits(w);
521 if (on)
522 dapm_set_pga(w, on); /* restore volume from zero */
523
524 continue;
525 }
526
527 /* pre and post event widgets */
528 if (w->id == snd_soc_dapm_pre) {
529 if (!w->event)
530 continue;
531
532 if (event == SND_SOC_DAPM_STREAM_START) {
533 ret = w->event(w, SND_SOC_DAPM_PRE_PMU);
534 if (ret < 0)
535 return ret;
536 } else if (event == SND_SOC_DAPM_STREAM_STOP) {
537 ret = w->event(w, SND_SOC_DAPM_PRE_PMD);
538 if (ret < 0)
539 return ret;
540 }
541 continue;
542 }
543 if (w->id == snd_soc_dapm_post) {
544 if (!w->event)
545 continue;
546
547 if (event == SND_SOC_DAPM_STREAM_START) {
548 ret = w->event(w, SND_SOC_DAPM_POST_PMU);
549 if (ret < 0)
550 return ret;
551 } else if (event == SND_SOC_DAPM_STREAM_STOP) {
552 ret = w->event(w, SND_SOC_DAPM_POST_PMD);
553 if (ret < 0)
554 return ret;
555 }
556 continue;
557 }
558
559 /* all other widgets */
560 in = is_connected_input_ep(w);
561 dapm_clear_walk(w->codec);
562 out = is_connected_output_ep(w);
563 dapm_clear_walk(w->codec);
564 power = (out != 0 && in != 0) ? 1 : 0;
565 power_change = (w->power == power) ? 0: 1;
566 w->power = power;
567
568 /* call any power change event handlers */
569 if (power_change) {
570 if (w->event) {
571 dbg("power %s event for %s flags %x\n",
572 w->power ? "on" : "off", w->name, w->event_flags);
573 if (power) {
574 /* power up event */
575 if (w->event_flags & SND_SOC_DAPM_PRE_PMU) {
576 ret = w->event(w, SND_SOC_DAPM_PRE_PMU);
577 if (ret < 0)
578 return ret;
579 }
580 dapm_update_bits(w);
581 if (w->event_flags & SND_SOC_DAPM_POST_PMU){
582 ret = w->event(w, SND_SOC_DAPM_POST_PMU);
583 if (ret < 0)
584 return ret;
585 }
586 } else {
587 /* power down event */
588 if (w->event_flags & SND_SOC_DAPM_PRE_PMD) {
589 ret = w->event(w, SND_SOC_DAPM_PRE_PMD);
590 if (ret < 0)
591 return ret;
592 }
593 dapm_update_bits(w);
594 if (w->event_flags & SND_SOC_DAPM_POST_PMD) {
595 ret = w->event(w, SND_SOC_DAPM_POST_PMD);
596 if (ret < 0)
597 return ret;
598 }
599 }
600 } else
601 /* no event handler */
602 dapm_update_bits(w);
603 }
604 }
605 }
606
607 return ret;
608}
609
610#if DAPM_DEBUG
611static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action)
612{
613 struct snd_soc_dapm_widget *w;
614 struct snd_soc_dapm_path *p = NULL;
615 int in, out;
616
617 printk("DAPM %s %s\n", codec->name, action);
618
619 list_for_each_entry(w, &codec->dapm_widgets, list) {
620
621 /* only display widgets that effect routing */
622 switch (w->id) {
623 case snd_soc_dapm_pre:
624 case snd_soc_dapm_post:
625 case snd_soc_dapm_vmid:
626 continue;
627 case snd_soc_dapm_mux:
628 case snd_soc_dapm_output:
629 case snd_soc_dapm_input:
630 case snd_soc_dapm_switch:
631 case snd_soc_dapm_hp:
632 case snd_soc_dapm_mic:
633 case snd_soc_dapm_spk:
634 case snd_soc_dapm_line:
635 case snd_soc_dapm_micbias:
636 case snd_soc_dapm_dac:
637 case snd_soc_dapm_adc:
638 case snd_soc_dapm_pga:
639 case snd_soc_dapm_mixer:
640 if (w->name) {
641 in = is_connected_input_ep(w);
642 dapm_clear_walk(w->codec);
643 out = is_connected_output_ep(w);
644 dapm_clear_walk(w->codec);
645 printk("%s: %s in %d out %d\n", w->name,
646 w->power ? "On":"Off",in, out);
647
648 list_for_each_entry(p, &w->sources, list_sink) {
649 if (p->connect)
650 printk(" in %s %s\n", p->name ? p->name : "static",
651 p->source->name);
652 }
653 list_for_each_entry(p, &w->sinks, list_source) {
654 p = list_entry(lp, struct snd_soc_dapm_path, list_source);
655 if (p->connect)
656 printk(" out %s %s\n", p->name ? p->name : "static",
657 p->sink->name);
658 }
659 }
660 break;
661 }
662 }
663}
664#endif
665
666/* test and update the power status of a mux widget */
667int dapm_mux_update_power(struct snd_soc_dapm_widget *widget,
668 struct snd_kcontrol *kcontrol, int mask, int val, struct soc_enum* e)
669{
670 struct snd_soc_dapm_path *path;
671 int found = 0;
672
673 if (widget->id != snd_soc_dapm_mux)
674 return -ENODEV;
675
676 if (!snd_soc_test_bits(widget->codec, e->reg, mask, val))
677 return 0;
678
679 /* find dapm widget path assoc with kcontrol */
680 list_for_each_entry(path, &widget->codec->dapm_paths, list) {
681 if (path->kcontrol != kcontrol)
682 continue;
683
684 if (!path->name || ! e->texts[val])
685 continue;
686
687 found = 1;
688 /* we now need to match the string in the enum to the path */
689 if (!(strcmp(path->name, e->texts[val])))
690 path->connect = 1; /* new connection */
691 else
692 path->connect = 0; /* old connection must be powered down */
693 }
694
695 if (found)
696 dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
697
698 return 0;
699}
700EXPORT_SYMBOL_GPL(dapm_mux_update_power);
701
702/* test and update the power status of a mixer widget */
703int dapm_mixer_update_power(struct snd_soc_dapm_widget *widget,
704 struct snd_kcontrol *kcontrol, int reg, int val_mask, int val, int invert)
705{
706 struct snd_soc_dapm_path *path;
707 int found = 0;
708
709 if (widget->id != snd_soc_dapm_mixer)
710 return -ENODEV;
711
712 if (!snd_soc_test_bits(widget->codec, reg, val_mask, val))
713 return 0;
714
715 /* find dapm widget path assoc with kcontrol */
716 list_for_each_entry(path, &widget->codec->dapm_paths, list) {
717 if (path->kcontrol != kcontrol)
718 continue;
719
720 /* found, now check type */
721 found = 1;
722 if (val)
723 /* new connection */
724 path->connect = invert ? 0:1;
725 else
726 /* old connection must be powered down */
727 path->connect = invert ? 1:0;
728 break;
729 }
730
731 if (found)
732 dapm_power_widgets(widget->codec, SND_SOC_DAPM_STREAM_NOP);
733
734 return 0;
735}
736EXPORT_SYMBOL_GPL(dapm_mixer_update_power);
737
738/* show dapm widget status in sys fs */
739static ssize_t dapm_widget_show(struct device *dev,
740 struct device_attribute *attr, char *buf)
741{
742 struct snd_soc_device *devdata = dev_get_drvdata(dev);
743 struct snd_soc_codec *codec = devdata->codec;
744 struct snd_soc_dapm_widget *w;
745 int count = 0;
746 char *state = "not set";
747
748 list_for_each_entry(w, &codec->dapm_widgets, list) {
749
750 /* only display widgets that burnm power */
751 switch (w->id) {
752 case snd_soc_dapm_hp:
753 case snd_soc_dapm_mic:
754 case snd_soc_dapm_spk:
755 case snd_soc_dapm_line:
756 case snd_soc_dapm_micbias:
757 case snd_soc_dapm_dac:
758 case snd_soc_dapm_adc:
759 case snd_soc_dapm_pga:
760 case snd_soc_dapm_mixer:
761 if (w->name)
762 count += sprintf(buf + count, "%s: %s\n",
763 w->name, w->power ? "On":"Off");
764 break;
765 default:
766 break;
767 }
768 }
769
770 switch(codec->dapm_state){
771 case SNDRV_CTL_POWER_D0:
772 state = "D0";
773 break;
774 case SNDRV_CTL_POWER_D1:
775 state = "D1";
776 break;
777 case SNDRV_CTL_POWER_D2:
778 state = "D2";
779 break;
780 case SNDRV_CTL_POWER_D3hot:
781 state = "D3hot";
782 break;
783 case SNDRV_CTL_POWER_D3cold:
784 state = "D3cold";
785 break;
786 }
787 count += sprintf(buf + count, "PM State: %s\n", state);
788
789 return count;
790}
791
792static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL);
793
794int snd_soc_dapm_sys_add(struct device *dev)
795{
796 int ret = 0;
797
798 if (dapm_status)
799 ret = device_create_file(dev, &dev_attr_dapm_widget);
800
801 return ret;
802}
803
804static void snd_soc_dapm_sys_remove(struct device *dev)
805{
806 if (dapm_status)
807 device_remove_file(dev, &dev_attr_dapm_widget);
808}
809
810/* free all dapm widgets and resources */
811void dapm_free_widgets(struct snd_soc_codec *codec)
812{
813 struct snd_soc_dapm_widget *w, *next_w;
814 struct snd_soc_dapm_path *p, *next_p;
815
816 list_for_each_entry_safe(w, next_w, &codec->dapm_widgets, list) {
817 list_del(&w->list);
818 kfree(w);
819 }
820
821 list_for_each_entry_safe(p, next_p, &codec->dapm_paths, list) {
822 list_del(&p->list);
823 kfree(p->long_name);
824 kfree(p);
825 }
826}
827
828/**
829 * snd_soc_dapm_sync_endpoints - scan and power dapm paths
830 * @codec: audio codec
831 *
832 * Walks all dapm audio paths and powers widgets according to their
833 * stream or path usage.
834 *
835 * Returns 0 for success.
836 */
837int snd_soc_dapm_sync_endpoints(struct snd_soc_codec *codec)
838{
839 return dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
840}
841EXPORT_SYMBOL_GPL(snd_soc_dapm_sync_endpoints);
842
843/**
844 * snd_soc_dapm_connect_input - connect dapm widgets
845 * @codec: audio codec
846 * @sink: name of target widget
847 * @control: mixer control name
848 * @source: name of source name
849 *
850 * Connects 2 dapm widgets together via a named audio path. The sink is
851 * the widget receiving the audio signal, whilst the source is the sender
852 * of the audio signal.
853 *
854 * Returns 0 for success else error.
855 */
856int snd_soc_dapm_connect_input(struct snd_soc_codec *codec, const char *sink,
857 const char * control, const char *source)
858{
859 struct snd_soc_dapm_path *path;
860 struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w;
861 int ret = 0;
862
863 /* find src and dest widgets */
864 list_for_each_entry(w, &codec->dapm_widgets, list) {
865
866 if (!wsink && !(strcmp(w->name, sink))) {
867 wsink = w;
868 continue;
869 }
870 if (!wsource && !(strcmp(w->name, source))) {
871 wsource = w;
872 }
873 }
874
875 if (wsource == NULL || wsink == NULL)
876 return -ENODEV;
877
878 path = kzalloc(sizeof(struct snd_soc_dapm_path), GFP_KERNEL);
879 if (!path)
880 return -ENOMEM;
881
882 path->source = wsource;
883 path->sink = wsink;
884 INIT_LIST_HEAD(&path->list);
885 INIT_LIST_HEAD(&path->list_source);
886 INIT_LIST_HEAD(&path->list_sink);
887
888 /* check for external widgets */
889 if (wsink->id == snd_soc_dapm_input) {
890 if (wsource->id == snd_soc_dapm_micbias ||
891 wsource->id == snd_soc_dapm_mic ||
892 wsink->id == snd_soc_dapm_line)
893 wsink->ext = 1;
894 }
895 if (wsource->id == snd_soc_dapm_output) {
896 if (wsink->id == snd_soc_dapm_spk ||
897 wsink->id == snd_soc_dapm_hp ||
898 wsink->id == snd_soc_dapm_line)
899 wsource->ext = 1;
900 }
901
902 /* connect static paths */
903 if (control == NULL) {
904 list_add(&path->list, &codec->dapm_paths);
905 list_add(&path->list_sink, &wsink->sources);
906 list_add(&path->list_source, &wsource->sinks);
907 path->connect = 1;
908 return 0;
909 }
910
911 /* connect dynamic paths */
912 switch(wsink->id) {
913 case snd_soc_dapm_adc:
914 case snd_soc_dapm_dac:
915 case snd_soc_dapm_pga:
916 case snd_soc_dapm_input:
917 case snd_soc_dapm_output:
918 case snd_soc_dapm_micbias:
919 case snd_soc_dapm_vmid:
920 case snd_soc_dapm_pre:
921 case snd_soc_dapm_post:
922 list_add(&path->list, &codec->dapm_paths);
923 list_add(&path->list_sink, &wsink->sources);
924 list_add(&path->list_source, &wsource->sinks);
925 path->connect = 1;
926 return 0;
927 case snd_soc_dapm_mux:
928 ret = dapm_connect_mux(codec, wsource, wsink, path, control,
929 &wsink->kcontrols[0]);
930 if (ret != 0)
931 goto err;
932 break;
933 case snd_soc_dapm_switch:
934 case snd_soc_dapm_mixer:
935 ret = dapm_connect_mixer(codec, wsource, wsink, path, control);
936 if (ret != 0)
937 goto err;
938 break;
939 case snd_soc_dapm_hp:
940 case snd_soc_dapm_mic:
941 case snd_soc_dapm_line:
942 case snd_soc_dapm_spk:
943 list_add(&path->list, &codec->dapm_paths);
944 list_add(&path->list_sink, &wsink->sources);
945 list_add(&path->list_source, &wsource->sinks);
946 path->connect = 0;
947 return 0;
948 }
949 return 0;
950
951err:
952 printk(KERN_WARNING "asoc: no dapm match for %s --> %s --> %s\n", source,
953 control, sink);
954 kfree(path);
955 return ret;
956}
957EXPORT_SYMBOL_GPL(snd_soc_dapm_connect_input);
958
959/**
960 * snd_soc_dapm_new_widgets - add new dapm widgets
961 * @codec: audio codec
962 *
963 * Checks the codec for any new dapm widgets and creates them if found.
964 *
965 * Returns 0 for success.
966 */
967int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec)
968{
969 struct snd_soc_dapm_widget *w;
970
971 mutex_lock(&codec->mutex);
972 list_for_each_entry(w, &codec->dapm_widgets, list)
973 {
974 if (w->new)
975 continue;
976
977 switch(w->id) {
978 case snd_soc_dapm_switch:
979 case snd_soc_dapm_mixer:
980 dapm_new_mixer(codec, w);
981 break;
982 case snd_soc_dapm_mux:
983 dapm_new_mux(codec, w);
984 break;
985 case snd_soc_dapm_adc:
986 case snd_soc_dapm_dac:
987 case snd_soc_dapm_pga:
988 dapm_new_pga(codec, w);
989 break;
990 case snd_soc_dapm_input:
991 case snd_soc_dapm_output:
992 case snd_soc_dapm_micbias:
993 case snd_soc_dapm_spk:
994 case snd_soc_dapm_hp:
995 case snd_soc_dapm_mic:
996 case snd_soc_dapm_line:
997 case snd_soc_dapm_vmid:
998 case snd_soc_dapm_pre:
999 case snd_soc_dapm_post:
1000 break;
1001 }
1002 w->new = 1;
1003 }
1004
1005 dapm_power_widgets(codec, SND_SOC_DAPM_STREAM_NOP);
1006 mutex_unlock(&codec->mutex);
1007 return 0;
1008}
1009EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets);
1010
1011/**
1012 * snd_soc_dapm_get_volsw - dapm mixer get callback
1013 * @kcontrol: mixer control
1014 * @uinfo: control element information
1015 *
1016 * Callback to get the value of a dapm mixer control.
1017 *
1018 * Returns 0 for success.
1019 */
1020int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol,
1021 struct snd_ctl_elem_value *ucontrol)
1022{
1023 struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
1024 int reg = kcontrol->private_value & 0xff;
1025 int shift = (kcontrol->private_value >> 8) & 0x0f;
1026 int rshift = (kcontrol->private_value >> 12) & 0x0f;
1027 int mask = (kcontrol->private_value >> 16) & 0xff;
1028 int invert = (kcontrol->private_value >> 24) & 0x01;
1029
1030 /* return the saved value if we are powered down */
1031 if (widget->id == snd_soc_dapm_pga && !widget->power) {
1032 ucontrol->value.integer.value[0] = widget->saved_value;
1033 return 0;
1034 }
1035
1036 ucontrol->value.integer.value[0] =
1037 (snd_soc_read(widget->codec, reg) >> shift) & mask;
1038 if (shift != rshift)
1039 ucontrol->value.integer.value[1] =
1040 (snd_soc_read(widget->codec, reg) >> rshift) & mask;
1041 if (invert) {
1042 ucontrol->value.integer.value[0] =
1043 mask - ucontrol->value.integer.value[0];
1044 if (shift != rshift)
1045 ucontrol->value.integer.value[1] =
1046 mask - ucontrol->value.integer.value[1];
1047 }
1048
1049 return 0;
1050}
1051EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw);
1052
1053/**
1054 * snd_soc_dapm_put_volsw - dapm mixer set callback
1055 * @kcontrol: mixer control
1056 * @uinfo: control element information
1057 *
1058 * Callback to set the value of a dapm mixer control.
1059 *
1060 * Returns 0 for success.
1061 */
1062int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol,
1063 struct snd_ctl_elem_value *ucontrol)
1064{
1065 struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
1066 int reg = kcontrol->private_value & 0xff;
1067 int shift = (kcontrol->private_value >> 8) & 0x0f;
1068 int rshift = (kcontrol->private_value >> 12) & 0x0f;
1069 int mask = (kcontrol->private_value >> 16) & 0xff;
1070 int invert = (kcontrol->private_value >> 24) & 0x01;
1071 unsigned short val, val2, val_mask;
1072 int ret;
1073
1074 val = (ucontrol->value.integer.value[0] & mask);
1075
1076 if (invert)
1077 val = mask - val;
1078 val_mask = mask << shift;
1079 val = val << shift;
1080 if (shift != rshift) {
1081 val2 = (ucontrol->value.integer.value[1] & mask);
1082 if (invert)
1083 val2 = mask - val2;
1084 val_mask |= mask << rshift;
1085 val |= val2 << rshift;
1086 }
1087
1088 mutex_lock(&widget->codec->mutex);
1089 widget->value = val;
1090
1091 /* save volume value if the widget is powered down */
1092 if (widget->id == snd_soc_dapm_pga && !widget->power) {
1093 widget->saved_value = val;
1094 mutex_unlock(&widget->codec->mutex);
1095 return 1;
1096 }
1097
1098 dapm_mixer_update_power(widget, kcontrol, reg, val_mask, val, invert);
1099 if (widget->event) {
1100 if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
1101 ret = widget->event(widget, SND_SOC_DAPM_PRE_REG);
1102 if (ret < 0)
1103 goto out;
1104 }
1105 ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
1106 if (widget->event_flags & SND_SOC_DAPM_POST_REG)
1107 ret = widget->event(widget, SND_SOC_DAPM_POST_REG);
1108 } else
1109 ret = snd_soc_update_bits(widget->codec, reg, val_mask, val);
1110
1111out:
1112 mutex_unlock(&widget->codec->mutex);
1113 return ret;
1114}
1115EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw);
1116
1117/**
1118 * snd_soc_dapm_get_enum_double - dapm enumerated double mixer get callback
1119 * @kcontrol: mixer control
1120 * @uinfo: control element information
1121 *
1122 * Callback to get the value of a dapm enumerated double mixer control.
1123 *
1124 * Returns 0 for success.
1125 */
1126int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol,
1127 struct snd_ctl_elem_value *ucontrol)
1128{
1129 struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
1130 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
1131 unsigned short val, bitmask;
1132
1133 for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
1134 ;
1135 val = snd_soc_read(widget->codec, e->reg);
1136 ucontrol->value.enumerated.item[0] = (val >> e->shift_l) & (bitmask - 1);
1137 if (e->shift_l != e->shift_r)
1138 ucontrol->value.enumerated.item[1] =
1139 (val >> e->shift_r) & (bitmask - 1);
1140
1141 return 0;
1142}
1143EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double);
1144
1145/**
1146 * snd_soc_dapm_put_enum_double - dapm enumerated double mixer set callback
1147 * @kcontrol: mixer control
1148 * @uinfo: control element information
1149 *
1150 * Callback to set the value of a dapm enumerated double mixer control.
1151 *
1152 * Returns 0 for success.
1153 */
1154int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol,
1155 struct snd_ctl_elem_value *ucontrol)
1156{
1157 struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
1158 struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
1159 unsigned short val, mux;
1160 unsigned short mask, bitmask;
1161 int ret = 0;
1162
1163 for (bitmask = 1; bitmask < e->mask; bitmask <<= 1)
1164 ;
1165 if (ucontrol->value.enumerated.item[0] > e->mask - 1)
1166 return -EINVAL;
1167 mux = ucontrol->value.enumerated.item[0];
1168 val = mux << e->shift_l;
1169 mask = (bitmask - 1) << e->shift_l;
1170 if (e->shift_l != e->shift_r) {
1171 if (ucontrol->value.enumerated.item[1] > e->mask - 1)
1172 return -EINVAL;
1173 val |= ucontrol->value.enumerated.item[1] << e->shift_r;
1174 mask |= (bitmask - 1) << e->shift_r;
1175 }
1176
1177 mutex_lock(&widget->codec->mutex);
1178 widget->value = val;
1179 dapm_mux_update_power(widget, kcontrol, mask, mux, e);
1180 if (widget->event) {
1181 if (widget->event_flags & SND_SOC_DAPM_PRE_REG) {
1182 ret = widget->event(widget, SND_SOC_DAPM_PRE_REG);
1183 if (ret < 0)
1184 goto out;
1185 }
1186 ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
1187 if (widget->event_flags & SND_SOC_DAPM_POST_REG)
1188 ret = widget->event(widget, SND_SOC_DAPM_POST_REG);
1189 } else
1190 ret = snd_soc_update_bits(widget->codec, e->reg, mask, val);
1191
1192out:
1193 mutex_unlock(&widget->codec->mutex);
1194 return ret;
1195}
1196EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double);
1197
1198/**
1199 * snd_soc_dapm_new_control - create new dapm control
1200 * @codec: audio codec
1201 * @widget: widget template
1202 *
1203 * Creates a new dapm control based upon the template.
1204 *
1205 * Returns 0 for success else error.
1206 */
1207int snd_soc_dapm_new_control(struct snd_soc_codec *codec,
1208 const struct snd_soc_dapm_widget *widget)
1209{
1210 struct snd_soc_dapm_widget *w;
1211
1212 if ((w = dapm_cnew_widget(widget)) == NULL)
1213 return -ENOMEM;
1214
1215 w->codec = codec;
1216 INIT_LIST_HEAD(&w->sources);
1217 INIT_LIST_HEAD(&w->sinks);
1218 INIT_LIST_HEAD(&w->list);
1219 list_add(&w->list, &codec->dapm_widgets);
1220
1221 /* machine layer set ups unconnected pins and insertions */
1222 w->connected = 1;
1223 return 0;
1224}
1225EXPORT_SYMBOL_GPL(snd_soc_dapm_new_control);
1226
1227/**
1228 * snd_soc_dapm_stream_event - send a stream event to the dapm core
1229 * @codec: audio codec
1230 * @stream: stream name
1231 * @event: stream event
1232 *
1233 * Sends a stream event to the dapm core. The core then makes any
1234 * necessary widget power changes.
1235 *
1236 * Returns 0 for success else error.
1237 */
1238int snd_soc_dapm_stream_event(struct snd_soc_codec *codec,
1239 char *stream, int event)
1240{
1241 struct snd_soc_dapm_widget *w;
1242
1243 mutex_lock(&codec->mutex);
1244 list_for_each_entry(w, &codec->dapm_widgets, list)
1245 {
1246 if (!w->sname)
1247 continue;
1248 dbg("widget %s\n %s stream %s event %d\n", w->name, w->sname,
1249 stream, event);
1250 if (strstr(w->sname, stream)) {
1251 switch(event) {
1252 case SND_SOC_DAPM_STREAM_START:
1253 w->active = 1;
1254 break;
1255 case SND_SOC_DAPM_STREAM_STOP:
1256 w->active = 0;
1257 break;
1258 case SND_SOC_DAPM_STREAM_SUSPEND:
1259 if (w->active)
1260 w->suspend = 1;
1261 w->active = 0;
1262 break;
1263 case SND_SOC_DAPM_STREAM_RESUME:
1264 if (w->suspend) {
1265 w->active = 1;
1266 w->suspend = 0;
1267 }
1268 break;
1269 case SND_SOC_DAPM_STREAM_PAUSE_PUSH:
1270 break;
1271 case SND_SOC_DAPM_STREAM_PAUSE_RELEASE:
1272 break;
1273 }
1274 }
1275 }
1276 mutex_unlock(&codec->mutex);
1277
1278 dapm_power_widgets(codec, event);
1279 dump_dapm(codec, __FUNCTION__);
1280 return 0;
1281}
1282EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
1283
1284/**
1285 * snd_soc_dapm_set_endpoint - set audio endpoint status
1286 * @codec: audio codec
1287 * @endpoint: audio signal endpoint (or start point)
1288 * @status: point status
1289 *
1290 * Set audio endpoint status - connected or disconnected.
1291 *
1292 * Returns 0 for success else error.
1293 */
1294int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec,
1295 char *endpoint, int status)
1296{
1297 struct snd_soc_dapm_widget *w;
1298
1299 list_for_each_entry(w, &codec->dapm_widgets, list) {
1300 if (!strcmp(w->name, endpoint)) {
1301 w->connected = status;
1302 }
1303 }
1304
1305 return 0;
1306}
1307EXPORT_SYMBOL_GPL(snd_soc_dapm_set_endpoint);
1308
1309/**
1310 * snd_soc_dapm_free - free dapm resources
1311 * @socdev: SoC device
1312 *
1313 * Free all dapm widgets and resources.
1314 */
1315void snd_soc_dapm_free(struct snd_soc_device *socdev)
1316{
1317 struct snd_soc_codec *codec = socdev->codec;
1318
1319 snd_soc_dapm_sys_remove(socdev->dev);
1320 dapm_free_widgets(codec);
1321}
1322EXPORT_SYMBOL_GPL(snd_soc_dapm_free);
1323
1324/* Module information */
1325MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
1326MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC");
1327MODULE_LICENSE("GPL");