Commit 6339d232 authored by Vaibhav Agarwal's avatar Vaibhav Agarwal Committed by Greg Kroah-Hartman

greybus: audio: Add topology parser for GB codec

For each GB codec module inserted, DAPM widgets, kcontrols, routes
and DAIs can be fetched through greybus in a binary chunk and parsed
locally to create & populate DAPM graph for the specific module.

It is required by each codec module to populate a minimum set of
kcontrols with fixed names to support basic audio usecase.
To support advanced features of codec module, the same can be polpulated
with existing topology parser. However, to use them for different usecase
separate mechanism (may be via MSP) is required to inform userspace about
their configuration value & enable/disable sequence.

ToDos:
Currently, support for enumerated kcontrol/dapm control is hardcoded.
Need to add complete logic within the parser.
Signed-off-by: default avatarVaibhav Agarwal <vaibhav.agarwal@linaro.org>
Signed-off-by: default avatarMark Greer <mgreer@animalcreek.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 2a70e49f
......@@ -29,7 +29,7 @@ gb-raw-y := raw.o
gb-hid-y := hid.o
gb-es2-y := es2.o
gb-arche-y := arche-platform.o arche-apb-ctrl.o
gb-audio-codec-y := audio_codec.o
gb-audio-codec-y := audio_codec.o audio_topology.o
gb-audio-gb-y := audio_gb.o
gb-audio-apbridgea-y := audio_apbridgea.o
gb-audio-manager-y += audio_manager.o
......
......@@ -19,124 +19,6 @@
static DEFINE_MUTEX(gb_codec_list_lock);
static LIST_HEAD(gb_codec_list);
static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
/* Ensure GB speaker is connected */
return 0;
}
static int gbcodec_event_hp(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
/* Ensure GB module supports jack slot */
return 0;
}
static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *k, int event)
{
/* Ensure GB module supports jack slot */
return 0;
}
static const struct snd_kcontrol_new gbcodec_snd_controls[] = {
SOC_DOUBLE("Playback Mute", GBCODEC_MUTE_REG, 0, 1, 1, 1),
SOC_DOUBLE("Capture Mute", GBCODEC_MUTE_REG, 4, 5, 1, 1),
SOC_DOUBLE_R("Playback Volume", GBCODEC_PB_LVOL_REG,
GBCODEC_PB_RVOL_REG, 0, 127, 0),
SOC_DOUBLE_R("Capture Volume", GBCODEC_CAP_LVOL_REG,
GBCODEC_CAP_RVOL_REG, 0, 127, 0),
};
static const struct snd_kcontrol_new spk_amp_ctl =
SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 0, 1, 0);
static const struct snd_kcontrol_new hp_amp_ctl =
SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 1, 1, 0);
static const struct snd_kcontrol_new mic_adc_ctl =
SOC_DAPM_SINGLE("Switch", GBCODEC_CTL_REG, 4, 1, 0);
/* APB1-GBSPK source */
static const char * const gbcodec_apb1_src[] = {"Stereo", "Left", "Right"};
static const SOC_ENUM_SINGLE_DECL(
gbcodec_apb1_rx_enum, GBCODEC_APB1_MUX_REG, 0, gbcodec_apb1_src);
static const struct snd_kcontrol_new gbcodec_apb1_rx_mux =
SOC_DAPM_ENUM("APB1 source", gbcodec_apb1_rx_enum);
static const SOC_ENUM_SINGLE_DECL(
gbcodec_mic_enum, GBCODEC_APB1_MUX_REG, 4, gbcodec_apb1_src);
static const struct snd_kcontrol_new gbcodec_mic_mux =
SOC_DAPM_ENUM("MIC source", gbcodec_mic_enum);
static const struct snd_soc_dapm_widget gbcodec_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Spk", gbcodec_event_spk),
SND_SOC_DAPM_SPK("HP", gbcodec_event_hp),
SND_SOC_DAPM_MIC("Int Mic", gbcodec_event_int_mic),
SND_SOC_DAPM_OUTPUT("SPKOUT"),
SND_SOC_DAPM_OUTPUT("HPOUT"),
SND_SOC_DAPM_INPUT("MIC"),
SND_SOC_DAPM_INPUT("HSMIC"),
SND_SOC_DAPM_SWITCH("SPK Amp", SND_SOC_NOPM, 0, 0, &spk_amp_ctl),
SND_SOC_DAPM_SWITCH("HP Amp", SND_SOC_NOPM, 0, 0, &hp_amp_ctl),
SND_SOC_DAPM_SWITCH("MIC ADC", SND_SOC_NOPM, 0, 0, &mic_adc_ctl),
SND_SOC_DAPM_PGA("SPK DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_PGA("HP DAC", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("SPK Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MIXER("APB1_TX Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
SND_SOC_DAPM_MUX("APB1_RX Mux", SND_SOC_NOPM, 0, 0,
&gbcodec_apb1_rx_mux),
SND_SOC_DAPM_MUX("MIC Mux", SND_SOC_NOPM, 0, 0, &gbcodec_mic_mux),
SND_SOC_DAPM_AIF_IN("APB1RX", "APBridgeA1 Playback", 0, SND_SOC_NOPM, 0,
0),
SND_SOC_DAPM_AIF_OUT("APB1TX", "APBridgeA1 Capture", 0, SND_SOC_NOPM, 0,
0),
};
static const struct snd_soc_dapm_route gbcodec_dapm_routes[] = {
/* Playback path */
{"Spk", NULL, "SPKOUT"},
{"SPKOUT", NULL, "SPK Amp"},
{"SPK Amp", "Switch", "SPK DAC"},
{"SPK DAC", NULL, "SPK Mixer"},
{"HP", NULL, "HPOUT"},
{"HPOUT", NULL, "HP Amp"},
{"HP Amp", "Switch", "HP DAC"},
{"HP DAC", NULL, "HP Mixer"},
{"SPK Mixer", NULL, "APB1_RX Mux"},
{"HP Mixer", NULL, "APB1_RX Mux"},
{"APB1_RX Mux", "Left", "APB1RX"},
{"APB1_RX Mux", "Right", "APB1RX"},
{"APB1_RX Mux", "Stereo", "APB1RX"},
/* Capture path */
{"MIC", NULL, "Int Mic"},
{"MIC", NULL, "MIC Mux"},
{"MIC Mux", "Left", "MIC ADC"},
{"MIC Mux", "Right", "MIC ADC"},
{"MIC Mux", "Stereo", "MIC ADC"},
{"MIC ADC", "Switch", "APB1_TX Mixer"},
{"APB1_TX Mixer", NULL, "APB1TX"}
};
static int gbcodec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
......@@ -180,24 +62,9 @@ static struct snd_soc_dai_ops gbcodec_dai_ops = {
.digital_mute = gbcodec_digital_mute,
};
static struct snd_soc_dai_driver gbcodec_dai = {
.playback = {
.stream_name = "APBridgeA1 Playback",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "APBridgeA1 Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &gbcodec_dai_ops,
};
/*
* codec driver ops
*/
static int gbcodec_probe(struct snd_soc_codec *codec)
{
/* Empty function for now */
......@@ -261,13 +128,6 @@ static struct snd_soc_codec_driver soc_codec_dev_gbcodec = {
.reg_word_size = 1,
.idle_bias_off = true,
.controls = gbcodec_snd_controls,
.num_controls = ARRAY_SIZE(gbcodec_snd_controls),
.dapm_widgets = gbcodec_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(gbcodec_dapm_widgets),
.dapm_routes = gbcodec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(gbcodec_dapm_routes),
};
/*
......@@ -369,6 +229,9 @@ static struct gbaudio_codec_info *gbaudio_get_codec(struct device *dev,
mutex_init(&gbcodec->lock);
INIT_LIST_HEAD(&gbcodec->dai_list);
INIT_LIST_HEAD(&gbcodec->widget_list);
INIT_LIST_HEAD(&gbcodec->codec_ctl_list);
INIT_LIST_HEAD(&gbcodec->widget_ctl_list);
gbcodec->dev_id = dev_id;
dev_set_drvdata(dev, gbcodec);
gbcodec->dev = dev;
......@@ -412,8 +275,9 @@ struct device_driver gb_codec_driver = {
static int gbaudio_codec_probe(struct gb_connection *connection)
{
int ret;
int ret, i;
struct gbaudio_codec_info *gbcodec;
struct gb_audio_topology *topology;
struct device *dev = &connection->bundle->dev;
int dev_id = connection->bundle->id;
......@@ -425,8 +289,35 @@ static int gbaudio_codec_probe(struct gb_connection *connection)
gbcodec->mgmt_connection = connection;
/* fetch topology data */
ret = gb_audio_gb_get_topology(connection, &topology);
if (ret) {
dev_err(gbcodec->dev,
"%d:Error while fetching topology\n", ret);
goto base_error;
}
/* process topology data */
ret = gbaudio_tplg_parse_data(gbcodec, topology);
if (ret) {
dev_err(dev, "%d:Error while parsing topology data\n",
ret);
goto topology_error;
}
gbcodec->topology = topology;
/* update codec info */
soc_codec_dev_gbcodec.controls = gbcodec->kctls;
soc_codec_dev_gbcodec.num_controls = gbcodec->num_kcontrols;
soc_codec_dev_gbcodec.dapm_widgets = gbcodec->widgets;
soc_codec_dev_gbcodec.num_dapm_widgets = gbcodec->num_dapm_widgets;
soc_codec_dev_gbcodec.dapm_routes = gbcodec->routes;
soc_codec_dev_gbcodec.num_dapm_routes = gbcodec->num_dapm_routes;
/* update DAI info */
gbcodec->dais = &gbcodec_dai;
for (i = 0; i < gbcodec->num_dais; i++)
gbcodec->dais[i].ops = &gbcodec_dai_ops;
/* FIXME */
dev->driver = &gb_codec_driver;
......@@ -435,7 +326,7 @@ static int gbaudio_codec_probe(struct gb_connection *connection)
gbcodec->dais, 1);
if (ret) {
dev_err(dev, "%d:Failed to register codec\n", ret);
goto base_error;
goto parse_error;
}
/* update DAI links in response to this codec */
......@@ -455,8 +346,13 @@ static int gbaudio_codec_probe(struct gb_connection *connection)
codec_reg_error:
snd_soc_unregister_codec(dev);
base_error:
dev->driver = NULL;
parse_error:
gbaudio_tplg_release(gbcodec);
gbcodec->topology = NULL;
topology_error:
kfree(topology);
base_error:
gbcodec->mgmt_connection = NULL;
return ret;
}
......@@ -480,6 +376,8 @@ static void gbaudio_codec_remove(struct gb_connection *connection)
snd_soc_unregister_codec(dev);
dev->driver = NULL;
gbaudio_tplg_release(gbcodec);
kfree(gbcodec->topology);
gbcodec->mgmt_connection = NULL;
mutex_lock(&gbcodec->lock);
gbcodec->codec_registered = 0;
......@@ -509,68 +407,6 @@ static struct gb_protocol gb_audio_mgmt_protocol = {
.request_recv = gbaudio_codec_report_event_recv,
};
static struct gbaudio_dai *gbaudio_allocate_dai(struct gbaudio_codec_info *gb,
int data_cport,
struct gb_connection *connection,
const char *name)
{
struct gbaudio_dai *dai;
mutex_lock(&gb->lock);
dai = devm_kzalloc(gb->dev, sizeof(*dai), GFP_KERNEL);
if (!dai) {
dev_err(gb->dev, "%s:DAI Malloc failure\n", name);
mutex_unlock(&gb->lock);
return NULL;
}
dai->data_cport = data_cport;
dai->connection = connection;
/* update name */
if (name)
strlcpy(dai->name, name, NAME_SIZE);
list_add(&dai->list, &gb->dai_list);
dev_dbg(gb->dev, "%d:%s: DAI added\n", data_cport, dai->name);
mutex_unlock(&gb->lock);
return dai;
}
struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec,
int data_cport,
struct gb_connection *connection,
const char *name)
{
struct gbaudio_dai *dai, *_dai;
/* FIXME need to take care for multiple DAIs */
mutex_lock(&gbcodec->lock);
if (list_empty(&gbcodec->dai_list)) {
mutex_unlock(&gbcodec->lock);
return gbaudio_allocate_dai(gbcodec, data_cport, connection,
name);
}
list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
if (dai->data_cport == data_cport) {
if (connection)
dai->connection = connection;
if (name)
strlcpy(dai->name, name, NAME_SIZE);
dev_dbg(gbcodec->dev, "%d:%s: DAI updated\n",
data_cport, dai->name);
mutex_unlock(&gbcodec->lock);
return dai;
}
}
dev_err(gbcodec->dev, "%s:DAI not found\n", name);
mutex_unlock(&gbcodec->lock);
return NULL;
}
static int gbaudio_dai_probe(struct gb_connection *connection)
{
struct gbaudio_dai *dai;
......
......@@ -68,6 +68,19 @@ static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = {
GBCODEC_APB2_MUX_REG_DEFAULT,
};
struct gbaudio_widget {
__u8 id;
const char *name;
struct list_head list;
};
struct gbaudio_control {
__u8 id;
char *name;
const char * const *texts;
struct list_head list;
};
struct gbaudio_dai {
__le16 data_cport;
char name[NAME_SIZE];
......@@ -87,6 +100,7 @@ struct gbaudio_codec_info {
char vstr[NAME_SIZE];
char pstr[NAME_SIZE];
struct list_head list;
struct gb_audio_topology *topology;
char name[NAME_SIZE];
/* soc related data */
......@@ -105,6 +119,10 @@ struct gbaudio_codec_info {
int num_kcontrols;
int num_dapm_widgets;
int num_dapm_routes;
unsigned long dai_offset;
unsigned long widget_offset;
unsigned long control_offset;
unsigned long route_offset;
struct snd_kcontrol_new *kctls;
struct snd_soc_dapm_widget *widgets;
struct snd_soc_dapm_route *routes;
......@@ -112,10 +130,23 @@ struct gbaudio_codec_info {
/* lists */
struct list_head dai_list;
struct list_head widget_list;
struct list_head codec_ctl_list;
struct list_head widget_ctl_list;
struct mutex lock;
};
struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec,
int data_cport,
struct gb_connection *connection,
const char *name);
int gbaudio_tplg_parse_data(struct gbaudio_codec_info *gbcodec,
struct gb_audio_topology *tplg_data);
void gbaudio_tplg_release(struct gbaudio_codec_info *gbcodec);
/* protocol related */
extern int gb_audio_gb_get_topology(struct gb_connection *connection,
struct gb_audio_topology **topology);
extern int gb_audio_gb_get_control(struct gb_connection *connection,
uint8_t control_id, uint8_t index,
struct gb_audio_ctl_elem_value *value);
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment