From 64a7e2cceb75ccabaec713944a95511605751b29 Mon Sep 17 00:00:00 2001 From: Vaibhav Agarwal Date: Tue, 29 Mar 2016 16:32:36 +0530 Subject: [PATCH] greybus: audio: Added jack support to audio module Register jack with ASoC sound card in case audio module populates it via codec FW. Currently, only a single jack with 4 buttons can be registered for each module. Signed-off-by: Vaibhav Agarwal Reviewed-by: Mark Greer Signed-off-by: Greg Kroah-Hartman --- drivers/staging/greybus/audio_codec.c | 98 ++++++++++++++++ drivers/staging/greybus/audio_codec.h | 15 +++ drivers/staging/greybus/audio_module.c | 142 ++++++++++++++++++++++- drivers/staging/greybus/audio_topology.c | 1 + 4 files changed, 251 insertions(+), 5 deletions(-) diff --git a/drivers/staging/greybus/audio_codec.c b/drivers/staging/greybus/audio_codec.c index 803bab20cf0c..66a954806cf5 100644 --- a/drivers/staging/greybus/audio_codec.c +++ b/drivers/staging/greybus/audio_codec.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "audio_codec.h" #include "audio_apbridgea.h" @@ -735,10 +736,80 @@ static struct snd_soc_dai_ops gbcodec_dai_ops = { .digital_mute = gbcodec_digital_mute, }; +static int gbaudio_init_jack(struct gbaudio_module_info *module, + struct snd_soc_codec *codec) +{ + int ret; + + if (!module->num_jacks) + return 0; + + /* register jack(s) in case any */ + if (module->num_jacks > 1) { + dev_err(module->dev, "Currently supports max=1 jack\n"); + return -EINVAL; + } + + snprintf(module->jack_name, NAME_SIZE, "GB %d Headset Jack", + module->dev_id); + ret = snd_soc_jack_new(codec, module->jack_name, GBCODEC_JACK_MASK, + &module->headset_jack); + if (ret) { + dev_err(module->dev, "Failed to create new jack\n"); + return ret; + } + + snprintf(module->button_name, NAME_SIZE, "GB %d Button Jack", + module->dev_id); + ret = snd_soc_jack_new(codec, module->button_name, + GBCODEC_JACK_BUTTON_MASK, &module->button_jack); + if (ret) { + dev_err(module->dev, "Failed to create button jack\n"); + return ret; + } + + ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_0, + KEY_MEDIA); + if (ret) { + dev_err(module->dev, "Failed to set BTN_0\n"); + return ret; + } + + ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_1, + KEY_VOICECOMMAND); + if (ret) { + dev_err(module->dev, "Failed to set BTN_1\n"); + return ret; + } + + ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_2, + KEY_VOLUMEUP); + if (ret) { + dev_err(module->dev, "Failed to set BTN_2\n"); + return ret; + } + + ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_3, + KEY_VOLUMEDOWN); + if (ret) { + dev_err(module->dev, "Failed to set BTN_0\n"); + return ret; + } + + /* FIXME + * verify if this is really required + set_bit(INPUT_PROP_NO_DUMMY_RELEASE, + module->button_jack.jack->input_dev->propbit); + */ + + return 0; +} + int gbaudio_register_module(struct gbaudio_module_info *module) { int ret; struct snd_soc_codec *codec; + struct snd_soc_jack *jack = NULL; if (!gbcodec) { dev_err(module->dev, "GB Codec not yet probed\n"); @@ -756,6 +827,12 @@ int gbaudio_register_module(struct gbaudio_module_info *module) return -EINVAL; } + ret = gbaudio_init_jack(module, codec); + if (ret) { + mutex_unlock(&gbcodec->lock); + return ret; + } + if (module->dapm_widgets) snd_soc_dapm_new_controls(&codec->dapm, module->dapm_widgets, module->num_dapm_widgets); @@ -774,6 +851,15 @@ int gbaudio_register_module(struct gbaudio_module_info *module) &codec->dapm); } +#ifdef CONFIG_SND_JACK + /* register jack devices for this module from codec->jack_list */ + list_for_each_entry(jack, &codec->jack_list, list) { + if ((jack == &module->headset_jack) + || (jack == &module->button_jack)) + snd_device_register(codec->card->snd_card, jack->jack); + } +#endif + list_add(&module->list, &gbcodec->module_list); dev_dbg(codec->dev, "Registered %s module\n", module->name); @@ -851,6 +937,7 @@ void gbaudio_unregister_module(struct gbaudio_module_info *module) { struct snd_soc_codec *codec = gbcodec->codec; struct snd_card *card = codec->card->snd_card; + struct snd_soc_jack *jack, *next_j; dev_dbg(codec->dev, "Unregister %s module\n", module->name); @@ -862,6 +949,17 @@ void gbaudio_unregister_module(struct gbaudio_module_info *module) dev_dbg(codec->dev, "Process Unregister %s module\n", module->name); mutex_lock(&module->lock); +#ifdef CONFIG_SND_JACK + /* free jack devices for this module from codec->jack_list */ + list_for_each_entry_safe(jack, next_j, &codec->jack_list, list) { + if ((jack == &module->headset_jack) + || (jack == &module->button_jack)) { + snd_device_free(codec->card->snd_card, jack->jack); + list_del(&jack->list); + } + } +#endif + gbaudio_codec_cleanup(module); module->is_connected = 0; diff --git a/drivers/staging/greybus/audio_codec.h b/drivers/staging/greybus/audio_codec.h index a2697dd62949..165b3595dae9 100644 --- a/drivers/staging/greybus/audio_codec.h +++ b/drivers/staging/greybus/audio_codec.h @@ -10,6 +10,7 @@ #define __LINUX_GBAUDIO_CODEC_H #include +#include #include "greybus.h" #include "greybus_protocols.h" @@ -57,6 +58,11 @@ enum gbcodec_reg_index { #define GBCODEC_APB1_MUX_REG_DEFAULT 0x00 #define GBCODEC_APB2_MUX_REG_DEFAULT 0x00 +#define GBCODEC_JACK_MASK (SND_JACK_HEADSET | SND_JACK_LINEOUT | \ + SND_JACK_LINEIN | SND_JACK_UNSUPPORTED) +#define GBCODEC_JACK_BUTTON_MASK (SND_JACK_BTN_0 | SND_JACK_BTN_1 | \ + SND_JACK_BTN_2 | SND_JACK_BTN_3) + static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = { GBCODEC_CTL_REG_DEFAULT, GBCODEC_MUTE_REG_DEFAULT, @@ -139,6 +145,15 @@ struct gbaudio_module_info { int manager_id; char name[NAME_SIZE]; + /* jack related */ + char jack_name[NAME_SIZE]; + char button_name[NAME_SIZE]; + int num_jacks; + int jack_type; + int button_status; + struct snd_soc_jack headset_jack; + struct snd_soc_jack button_jack; + /* used by codec_ops */ struct mutex lock; int is_connected; diff --git a/drivers/staging/greybus/audio_module.c b/drivers/staging/greybus/audio_module.c index 94fdadc67c18..9039aa63e040 100644 --- a/drivers/staging/greybus/audio_module.c +++ b/drivers/staging/greybus/audio_module.c @@ -22,18 +22,150 @@ static LIST_HEAD(gb_codec_list); * gb_snd management functions */ -static int gbaudio_codec_request_handler(struct gb_operation *op) +static int gbaudio_request_jack(struct gbaudio_module_info *module, + struct gb_audio_jack_event_request *req) { - struct gb_connection *connection = op->connection; - struct gb_audio_streaming_event_request *req = op->request->payload; + int report, button_status; - dev_warn(&connection->bundle->dev, - "Audio Event received: cport: %u, event: %u\n", + dev_warn(module->dev, "Jack Event received: type: %u, event: %u\n", + req->widget_type, req->event); + + mutex_lock(&module->lock); + if (req->event == GB_AUDIO_JACK_EVENT_REMOVAL) { + module->jack_type = 0; + button_status = module->button_status; + module->button_status = 0; + mutex_unlock(&module->lock); + if (button_status) + snd_soc_jack_report(&module->button_jack, 0, + GBCODEC_JACK_BUTTON_MASK); + snd_soc_jack_report(&module->headset_jack, 0, + GBCODEC_JACK_MASK); + return 0; + } + + report &= ~GBCODEC_JACK_MASK; + /* currently supports Headphone, Headset & Lineout only */ + if (req->widget_type && GB_AUDIO_WIDGET_TYPE_HP) + report |= SND_JACK_HEADPHONE & GBCODEC_JACK_MASK; + + if (req->widget_type && GB_AUDIO_WIDGET_TYPE_MIC) + report = SND_JACK_MICROPHONE & GBCODEC_JACK_MASK; + + if (req->widget_type && GB_AUDIO_WIDGET_TYPE_LINE) + report = (report & GBCODEC_JACK_MASK) | + SND_JACK_LINEOUT | SND_JACK_LINEIN; + + if (module->jack_type) + dev_warn(module->dev, "Modifying jack from %d to %d\n", + module->jack_type, report); + + module->jack_type = report; + mutex_unlock(&module->lock); + snd_soc_jack_report(&module->headset_jack, report, GBCODEC_JACK_MASK); + + return 0; +} + +static int gbaudio_request_button(struct gbaudio_module_info *module, + struct gb_audio_button_event_request *req) +{ + int soc_button_id, report; + + dev_warn(module->dev, "Button Event received: id: %u, event: %u\n", + req->button_id, req->event); + + /* currently supports 4 buttons only */ + mutex_lock(&module->lock); + if (!module->jack_type) { + dev_err(module->dev, "Jack not present. Bogus event!!\n"); + mutex_unlock(&module->lock); + return -EINVAL; + } + + report = module->button_status & GBCODEC_JACK_BUTTON_MASK; + + switch (req->button_id) { + case 1: + soc_button_id = SND_JACK_BTN_0; + break; + + case 2: + soc_button_id = SND_JACK_BTN_1; + break; + + case 3: + soc_button_id = SND_JACK_BTN_2; + break; + + case 4: + soc_button_id = SND_JACK_BTN_3; + break; + default: + dev_err(module->dev, "Invalid button request received\n"); + return -EINVAL; + } + + if (req->event == GB_AUDIO_BUTTON_EVENT_PRESS) + report = report | soc_button_id; + else + report = report & ~soc_button_id; + + module->button_status = report; + + mutex_unlock(&module->lock); + + snd_soc_jack_report(&module->button_jack, report, + GBCODEC_JACK_BUTTON_MASK); + + return 0; +} + +static int gbaudio_request_stream(struct gbaudio_module_info *module, + struct gb_audio_streaming_event_request *req) +{ + dev_warn(module->dev, "Audio Event received: cport: %u, event: %u\n", req->data_cport, req->event); return 0; } +static int gbaudio_codec_request_handler(struct gb_operation *op) +{ + struct gb_connection *connection = op->connection; + struct gbaudio_module_info *module = + greybus_get_drvdata(connection->bundle); + struct gb_operation_msg_hdr *header = op->request->header; + struct gb_audio_streaming_event_request *stream_req; + struct gb_audio_jack_event_request *jack_req; + struct gb_audio_button_event_request *button_req; + int ret; + + switch (header->type) { + case GB_AUDIO_TYPE_STREAMING_EVENT: + stream_req = op->request->payload; + ret = gbaudio_request_stream(module, stream_req); + break; + + case GB_AUDIO_TYPE_JACK_EVENT: + jack_req = op->request->payload; + ret = gbaudio_request_jack(module, jack_req); + break; + + case GB_AUDIO_TYPE_BUTTON_EVENT: + button_req = op->request->payload; + ret = gbaudio_request_button(module, button_req); + break; + + default: + dev_err(&connection->bundle->dev, + "Invalid Audio Event received\n"); + return -EINVAL; + } + + return ret; +} + static int gbaudio_data_connection_request_handler(struct gb_operation *op) { struct gb_connection *connection = op->connection; diff --git a/drivers/staging/greybus/audio_topology.c b/drivers/staging/greybus/audio_topology.c index 9e36fb27faf8..79161c1b74ce 100644 --- a/drivers/staging/greybus/audio_topology.c +++ b/drivers/staging/greybus/audio_topology.c @@ -669,6 +669,7 @@ static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module, case snd_soc_dapm_hp: *dw = (struct snd_soc_dapm_widget) SND_SOC_DAPM_HP(w->name, gbcodec_event_hp); + module->num_jacks++; break; case snd_soc_dapm_mic: *dw = (struct snd_soc_dapm_widget)