mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
ASoC: update the connected widgets functionality
ASoC core will scan for path, get the list of widgets when playback and capture is started.When a mixer command is issued it needs to scan only the path to find if back end or front end dai needs to be shutdown. Change ehances asoc core path finding functionality to provide support for different usecases. Signed-off-by: Gopikrishnaiah Anandan <agopik@codeaurora.org>
This commit is contained in:
parent
fffcf82ba1
commit
2f22f117c5
3 changed files with 241 additions and 61 deletions
|
@ -2,6 +2,7 @@
|
|||
* linux/sound/soc-dai.h -- ALSA SoC Layer
|
||||
*
|
||||
* Copyright: 2005-2008 Wolfson Microelectronics. PLC.
|
||||
* Copyright (c) 2012, Code Aurora Forum. 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 as
|
||||
|
|
|
@ -368,6 +368,8 @@ int snd_soc_dapm_weak_routes(struct snd_soc_dapm_context *dapm,
|
|||
const struct snd_soc_dapm_route *route, int num);
|
||||
|
||||
/* dapm events */
|
||||
void snd_soc_dapm_codec_stream_event(struct snd_soc_codec *codec,
|
||||
const char *stream, int event);
|
||||
int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
|
||||
const char *stream, int event);
|
||||
void snd_soc_dapm_rtd_stream_event(struct snd_soc_pcm_runtime *rtd,
|
||||
|
@ -403,6 +405,15 @@ void snd_soc_dapm_auto_nc_codec_pins(struct snd_soc_codec *codec);
|
|||
/* Mostly internal - should not normally be used */
|
||||
void dapm_mark_dirty(struct snd_soc_dapm_widget *w, const char *reason);
|
||||
|
||||
struct snd_soc_dapm_widget *snd_soc_get_codec_widget(struct snd_soc_card *card,
|
||||
struct snd_soc_codec *codec, const char *name);
|
||||
struct snd_soc_dapm_widget *snd_soc_get_platform_widget(struct snd_soc_card *card,
|
||||
struct snd_soc_platform *platform, const char *name);
|
||||
|
||||
/* dapm path query */
|
||||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
struct snd_soc_dapm_widget_list **list);
|
||||
|
||||
/* dapm widget types */
|
||||
enum snd_soc_dapm_type {
|
||||
snd_soc_dapm_input = 0, /* input pin */
|
||||
|
@ -449,8 +460,8 @@ struct snd_soc_dapm_route {
|
|||
|
||||
/* dapm audio path between two widgets */
|
||||
struct snd_soc_dapm_path {
|
||||
const char *name;
|
||||
const char *long_name;
|
||||
char *name;
|
||||
char *long_name;
|
||||
|
||||
/* source (input) and sink (output) widgets */
|
||||
struct snd_soc_dapm_widget *source;
|
||||
|
@ -574,4 +585,16 @@ struct snd_soc_dapm_stats {
|
|||
int neighbour_checks;
|
||||
};
|
||||
|
||||
/* Accessors for snd_soc_dapm_widget->private_data */
|
||||
static inline void *snd_soc_dapm_widget_get_pdata(struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
return w->private_data;
|
||||
}
|
||||
|
||||
static inline void snd_soc_dapm_widget_set_pdata(struct snd_soc_dapm_widget *w,
|
||||
void *data)
|
||||
{
|
||||
w->private_data = data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -173,19 +173,6 @@ static inline struct snd_soc_card *dapm_get_soc_card(
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void dapm_reset(struct snd_soc_card *card)
|
||||
{
|
||||
struct snd_soc_dapm_widget *w;
|
||||
|
||||
memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
|
||||
|
||||
list_for_each_entry(w, &card->widgets, list) {
|
||||
w->power_checked = false;
|
||||
w->inputs = -1;
|
||||
w->outputs = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int soc_widget_read(struct snd_soc_dapm_widget *w, int reg)
|
||||
{
|
||||
if (w->codec)
|
||||
|
@ -525,17 +512,17 @@ static int dapm_new_mixer(struct snd_soc_dapm_widget *w)
|
|||
* for widgets so cut the prefix off
|
||||
* the front of the widget name.
|
||||
*/
|
||||
snprintf((char *)path->long_name, name_len,
|
||||
"%s %s", w->name + prefix_len,
|
||||
snprintf(path->long_name, name_len, "%s %s",
|
||||
w->name + prefix_len,
|
||||
w->kcontrol_news[i].name);
|
||||
break;
|
||||
case snd_soc_dapm_mixer_named_ctl:
|
||||
snprintf((char *)path->long_name, name_len,
|
||||
"%s", w->kcontrol_news[i].name);
|
||||
snprintf(path->long_name, name_len, "%s",
|
||||
w->kcontrol_news[i].name);
|
||||
break;
|
||||
}
|
||||
|
||||
((char *)path->long_name)[name_len - 1] = '\0';
|
||||
path->long_name[name_len - 1] = '\0';
|
||||
|
||||
path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i],
|
||||
wlist, path->long_name,
|
||||
|
@ -651,15 +638,6 @@ static int dapm_new_pga(struct snd_soc_dapm_widget *w)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* reset 'walked' bit for each dapm path */
|
||||
static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm)
|
||||
{
|
||||
struct snd_soc_dapm_path *p;
|
||||
|
||||
list_for_each_entry(p, &dapm->card->paths, list)
|
||||
p->walked = 0;
|
||||
}
|
||||
|
||||
/* We implement power down on suspend by checking the power state of
|
||||
* the ALSA card - when we are suspending the ALSA state for the card
|
||||
* is set to D3.
|
||||
|
@ -680,11 +658,60 @@ static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget)
|
|||
}
|
||||
}
|
||||
|
||||
/* reset 'walked' bit for each dapm path */
|
||||
static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm)
|
||||
{
|
||||
struct snd_soc_dapm_path *p;
|
||||
|
||||
list_for_each_entry(p, &dapm->card->paths, list)
|
||||
p->walked = 0;
|
||||
}
|
||||
|
||||
/* add widget to list if it's not already in the list */
|
||||
static int dapm_list_add_widget(struct snd_soc_dapm_widget_list **list,
|
||||
struct snd_soc_dapm_widget *w)
|
||||
{
|
||||
struct snd_soc_dapm_widget_list *wlist;
|
||||
int wlistsize, wlistentries, i;
|
||||
|
||||
if (*list == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
wlist = *list;
|
||||
|
||||
/* is this widget already in the list */
|
||||
for (i = 0; i < wlist->num_widgets; i++) {
|
||||
if (wlist->widgets[i] == w)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* allocate some new space */
|
||||
wlistentries = wlist->num_widgets + 1;
|
||||
wlistsize = sizeof(struct snd_soc_dapm_widget_list) +
|
||||
wlistentries * sizeof(struct snd_soc_dapm_widget *);
|
||||
*list = krealloc(wlist, wlistsize, GFP_KERNEL);
|
||||
if (*list == NULL) {
|
||||
dev_err(w->dapm->dev, "can't allocate widget list for %s\n",
|
||||
w->name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
wlist = *list;
|
||||
|
||||
/* insert the widget */
|
||||
dev_dbg(w->dapm->dev, "added %s in widget list pos %d\n",
|
||||
w->name, wlist->num_widgets);
|
||||
|
||||
wlist->widgets[wlist->num_widgets] = w;
|
||||
wlist->num_widgets++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recursively check for a completed path to an active or physically connected
|
||||
* output widget. Returns number of complete paths.
|
||||
*/
|
||||
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
|
||||
static int is_connected_output_ep(struct snd_soc_dapm_widget *widget,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
struct snd_soc_dapm_path *path;
|
||||
int con = 0;
|
||||
|
@ -734,9 +761,26 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
|
|||
if (path->walked)
|
||||
continue;
|
||||
|
||||
dev_vdbg(widget->dapm->dev," %c : %s -> %s -> %s : %c\n",
|
||||
path->sink && path->connect ? '*' : ' ',
|
||||
widget->name, path->name, path->sink->name,
|
||||
path->weak ? 'w': ' ');
|
||||
|
||||
if (path->sink && path->connect) {
|
||||
path->walked = 1;
|
||||
con += is_connected_output_ep(path->sink);
|
||||
|
||||
/* do we need to add this widget to the list ? */
|
||||
if (list) {
|
||||
int err;
|
||||
err = dapm_list_add_widget(list, path->sink);
|
||||
if (err < 0) {
|
||||
dev_err(widget->dapm->dev, "could not add widget %s\n",
|
||||
widget->name);
|
||||
return con;
|
||||
}
|
||||
}
|
||||
|
||||
con += is_connected_output_ep(path->sink, list);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -749,7 +793,8 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget)
|
|||
* Recursively check for a completed path to an active or physically connected
|
||||
* input widget. Returns number of complete paths.
|
||||
*/
|
||||
static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
|
||||
static int is_connected_input_ep(struct snd_soc_dapm_widget *widget,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
struct snd_soc_dapm_path *path;
|
||||
int con = 0;
|
||||
|
@ -805,6 +850,12 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
|
|||
list_for_each_entry(path, &widget->sources, list_sink) {
|
||||
DAPM_UPDATE_STAT(widget, neighbour_checks);
|
||||
|
||||
if (path->source)
|
||||
dev_vdbg(widget->dapm->dev," %c : %s <- %s <- %s : %c\n",
|
||||
path->source && path->connect ? '*' : ' ',
|
||||
widget->name, path->name, path->source->name,
|
||||
path->weak ? 'w': ' ');
|
||||
|
||||
if (path->weak)
|
||||
continue;
|
||||
|
||||
|
@ -813,13 +864,27 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget)
|
|||
|
||||
if (path->source && path->connect) {
|
||||
path->walked = 1;
|
||||
con += is_connected_input_ep(path->source);
|
||||
|
||||
/* do we need to add this widget to the list ? */
|
||||
if (list) {
|
||||
int err;
|
||||
err = dapm_list_add_widget(list, path->sink);
|
||||
if (err < 0) {
|
||||
dev_err(widget->dapm->dev, "could not add widget %s\n",
|
||||
widget->name);
|
||||
return con;
|
||||
}
|
||||
}
|
||||
|
||||
con += is_connected_input_ep(path->source, list);
|
||||
}
|
||||
}
|
||||
|
||||
widget->inputs = con;
|
||||
|
||||
return con;
|
||||
}
|
||||
|
||||
/* Get the DAPM AIF widget for thie DAI stream */
|
||||
struct snd_soc_dapm_widget *snd_soc_get_codec_widget(struct snd_soc_card *card,
|
||||
struct snd_soc_codec *codec, const char *name)
|
||||
|
@ -861,6 +926,77 @@ struct snd_soc_dapm_widget *snd_soc_get_platform_widget(struct snd_soc_card *car
|
|||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snd_soc_get_platform_widget);
|
||||
|
||||
static int dapm_get_playback_paths(struct snd_soc_dapm_context *dapm,
|
||||
struct snd_soc_dapm_widget *root,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
int paths;
|
||||
|
||||
if (!root) {
|
||||
dev_err(dapm->dev, "no root widget for path discovery\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(dapm->dev, "Playback: checking paths from %s\n",root->name);
|
||||
paths = is_connected_output_ep(root, list);
|
||||
dev_dbg(dapm->dev, "Playback: found %d paths from %s\n", paths, root->name);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static int dapm_get_capture_paths(struct snd_soc_dapm_context *dapm,
|
||||
struct snd_soc_dapm_widget *root,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
int paths;
|
||||
|
||||
if (!root) {
|
||||
dev_err(dapm->dev, "no root widget for path discovery\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_dbg(dapm->dev, "Capture: checking paths from %s\n",root->name);
|
||||
paths = is_connected_input_ep(root, list);
|
||||
dev_dbg(dapm->dev, "Capture: found %d paths from %s\n", paths, root->name);
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_get_connected_widgets - query audio path and it's widgets.
|
||||
* @dai: the soc DAI.
|
||||
* @stream: stream direction.
|
||||
* @list: list of active widgets for this stream.
|
||||
*
|
||||
* Queries DAPM graph as to whether an valid audio stream path exists for
|
||||
* the initial stream specified by name. This takes into account
|
||||
* current mixer and mux kcontrol settings. Creates list of valid widgets.
|
||||
*
|
||||
* Returns the number of valid paths or negative error.
|
||||
*/
|
||||
int snd_soc_dapm_dai_get_connected_widgets(struct snd_soc_dai *dai, int stream,
|
||||
struct snd_soc_dapm_widget_list **list)
|
||||
{
|
||||
struct snd_soc_card *card = dai->card;
|
||||
struct snd_soc_dapm_widget *w;
|
||||
int paths;
|
||||
|
||||
memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
|
||||
|
||||
list_for_each_entry(w, &card->widgets, list) {
|
||||
w->power_checked = false;
|
||||
w->inputs = -1;
|
||||
w->outputs = -1;
|
||||
}
|
||||
|
||||
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
|
||||
paths = dapm_get_playback_paths(&card->dapm, dai->playback_aif, list);
|
||||
else
|
||||
paths = dapm_get_capture_paths(&card->dapm, dai->capture_aif, list);
|
||||
|
||||
dapm_clear_walk(&card->dapm);
|
||||
return paths;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -906,9 +1042,9 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w)
|
|||
|
||||
DAPM_UPDATE_STAT(w, power_checks);
|
||||
|
||||
in = is_connected_input_ep(w);
|
||||
in = is_connected_input_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
out = is_connected_output_ep(w);
|
||||
out = is_connected_output_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
return out != 0 && in != 0;
|
||||
}
|
||||
|
@ -921,7 +1057,7 @@ static int dapm_adc_check_power(struct snd_soc_dapm_widget *w)
|
|||
DAPM_UPDATE_STAT(w, power_checks);
|
||||
|
||||
if (w->active) {
|
||||
in = is_connected_input_ep(w);
|
||||
in = is_connected_input_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
return in != 0;
|
||||
} else {
|
||||
|
@ -937,7 +1073,7 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w)
|
|||
DAPM_UPDATE_STAT(w, power_checks);
|
||||
|
||||
if (w->active) {
|
||||
out = is_connected_output_ep(w);
|
||||
out = is_connected_output_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
return out != 0;
|
||||
} else {
|
||||
|
@ -1440,7 +1576,13 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
|||
}
|
||||
}
|
||||
|
||||
dapm_reset(card);
|
||||
memset(&card->dapm_stats, 0, sizeof(card->dapm_stats));
|
||||
|
||||
list_for_each_entry(w, &card->widgets, list) {
|
||||
w->power_checked = false;
|
||||
w->inputs = -1;
|
||||
w->outputs = -1;
|
||||
}
|
||||
|
||||
/* Check which widgets we need to power and store them in
|
||||
* lists indicating if they should be powered up or down. We
|
||||
|
@ -1536,12 +1678,6 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
|||
&async_domain);
|
||||
async_synchronize_full_domain(&async_domain);
|
||||
|
||||
/* do we need to notify any clients that DAPM event is complete */
|
||||
list_for_each_entry(d, &card->dapm_list, list) {
|
||||
if (d->stream_event)
|
||||
d->stream_event(d, event);
|
||||
}
|
||||
|
||||
pop_dbg(dapm->dev, card->pop_time,
|
||||
"DAPM sequencing finished, waiting %dms\n", card->pop_time);
|
||||
pop_wait(card->pop_time);
|
||||
|
@ -1552,6 +1688,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int dapm_widget_power_open_file(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t dapm_widget_power_read_file(struct file *file,
|
||||
char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
|
@ -1566,14 +1708,13 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
|
|||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
in = is_connected_input_ep(w);
|
||||
in = is_connected_input_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
out = is_connected_output_ep(w);
|
||||
out = is_connected_output_ep(w, NULL);
|
||||
dapm_clear_walk(w->dapm);
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s: %s%s in %d out %d",
|
||||
w->name, w->power ? "On" : "Off",
|
||||
w->force ? " (forced)" : "", in, out);
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d",
|
||||
w->name, w->power ? "On" : "Off", in, out);
|
||||
|
||||
if (w->reg >= 0)
|
||||
ret += snprintf(buf + ret, PAGE_SIZE - ret,
|
||||
|
@ -1615,11 +1756,17 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
|
|||
}
|
||||
|
||||
static const struct file_operations dapm_widget_power_fops = {
|
||||
.open = simple_open,
|
||||
.open = dapm_widget_power_open_file,
|
||||
.read = dapm_widget_power_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
||||
static int dapm_bias_open_file(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
|
@ -1650,7 +1797,7 @@ static ssize_t dapm_bias_read_file(struct file *file, char __user *user_buf,
|
|||
}
|
||||
|
||||
static const struct file_operations dapm_bias_fops = {
|
||||
.open = simple_open,
|
||||
.open = dapm_bias_open_file,
|
||||
.read = dapm_bias_read_file,
|
||||
.llseek = default_llseek,
|
||||
};
|
||||
|
@ -2680,15 +2827,15 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_info_pin_switch);
|
|||
int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
const char *pin = (const char *)kcontrol->private_value;
|
||||
|
||||
mutex_lock(&card->mutex);
|
||||
mutex_lock(&codec->mutex);
|
||||
|
||||
ucontrol->value.integer.value[0] =
|
||||
snd_soc_dapm_get_pin_status(&card->dapm, pin);
|
||||
snd_soc_dapm_get_pin_status(&codec->dapm, pin);
|
||||
|
||||
mutex_unlock(&card->mutex);
|
||||
mutex_unlock(&codec->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2703,19 +2850,19 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_pin_switch);
|
|||
int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol,
|
||||
struct snd_ctl_elem_value *ucontrol)
|
||||
{
|
||||
struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
|
||||
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
||||
const char *pin = (const char *)kcontrol->private_value;
|
||||
|
||||
mutex_lock(&card->mutex);
|
||||
mutex_lock(&codec->mutex);
|
||||
|
||||
if (ucontrol->value.integer.value[0])
|
||||
snd_soc_dapm_enable_pin(&card->dapm, pin);
|
||||
snd_soc_dapm_enable_pin(&codec->dapm, pin);
|
||||
else
|
||||
snd_soc_dapm_disable_pin(&card->dapm, pin);
|
||||
snd_soc_dapm_disable_pin(&codec->dapm, pin);
|
||||
|
||||
snd_soc_dapm_sync(&card->dapm);
|
||||
snd_soc_dapm_sync(&codec->dapm);
|
||||
|
||||
mutex_unlock(&card->mutex);
|
||||
mutex_unlock(&codec->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2952,6 +3099,15 @@ int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
|
|||
return 0;
|
||||
}
|
||||
|
||||
void snd_soc_dapm_codec_stream_event(struct snd_soc_codec *codec,
|
||||
const char *stream, int event)
|
||||
{
|
||||
// mutex_lock(&codec->card->dapm_mutex);
|
||||
soc_dapm_stream_event(&codec->dapm, stream, event);
|
||||
// mutex_unlock(&codec->card->dapm_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL(snd_soc_dapm_codec_stream_event);
|
||||
|
||||
/**
|
||||
* snd_soc_dapm_enable_pin - enable pin.
|
||||
* @dapm: DAPM context
|
||||
|
|
Loading…
Reference in a new issue