diff options
author | Phani Kumar Uppalapati <phaniu@codeaurora.org> | 2016-12-02 11:44:10 -0800 |
---|---|---|
committer | Phani Kumar Uppalapati <phaniu@codeaurora.org> | 2017-01-18 09:39:24 -0800 |
commit | f42b8d16251385e76fd4bc4cc8500dc12e19b604 (patch) | |
tree | 88f8bb01fa7d550f651513040a161d24db27300c | |
parent | d9ac8efbd96e3d5106203f5faab87a38f4f87860 (diff) |
ASoC: wcd934x: Add support for USB Type-C analog audio
Add playback and capture support when an analog wired
accessory is inserted into USB Type-C receptacle through
an USB type-c to 3.5mm analog audio adapter.
CRs-Fixed: 1102048
Change-Id: I36126ecdb0d2683d27d78dea9256bee0be67c1a6
Signed-off-by: Phani Kumar Uppalapati <phaniu@codeaurora.org>
-rw-r--r-- | Documentation/devicetree/bindings/sound/qcom-audio-dev.txt | 9 | ||||
-rw-r--r-- | sound/soc/codecs/wcd-mbhc-v2.c | 322 | ||||
-rw-r--r-- | sound/soc/codecs/wcd-mbhc-v2.h | 17 | ||||
-rw-r--r-- | sound/soc/codecs/wcd934x/wcd934x-mbhc.c | 8 |
4 files changed, 351 insertions, 5 deletions
diff --git a/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt index f79f49608743..8c550ff70b2b 100644 --- a/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt +++ b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt @@ -2334,6 +2334,11 @@ Optional properties: - qcom,wsa-devs : List of phandles for all possible WSA881x devices supported for the target - qcom,wsa-aux-dev-prefix : Name prefix with Left/Right configuration for WSA881x device - qcom,wcn-btfm : Property to specify if WCN BT/FM chip is used for the target +- qcom,msm-mbhc-usbc-audio-supported : Property to specify if analog audio feature is + enabled or not. +- qcom,usbc-analog-en1_gpio : EN1 GPIO to enable USB type-C analog audio +- qcom,usbc-analog-en2_n_gpio : EN2 GPIO to enable USB type-C analog audio +- qcom,usbc-analog-force_detect_gpio : Force detect GPIO to enable USB type-C analog audio Example: @@ -2407,6 +2412,10 @@ Example: <&wsa881x_213>, <&wsa881x_214>; qcom,wsa-aux-dev-prefix = "SpkrRight", "SpkrLeft", "SpkrRight", "SpkrLeft"; + qcom,msm-mbhc-usbc-audio-supported = <1>; + qcom,usbc-analog-en1_gpio = <&wcd_usbc_analog_en1_gpio>; + qcom,usbc-analog-en2_n_gpio = <&wcd_usbc_analog_en2n_gpio>; + qcom,usbc-analog-force_detect_gpio = <&wcd_usbc_analog_f_gpio>; }; * MSMSTUB ASoC Machine driver diff --git a/sound/soc/codecs/wcd-mbhc-v2.c b/sound/soc/codecs/wcd-mbhc-v2.c index 51bb3387de16..5fe4a41cc1f4 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.c +++ b/sound/soc/codecs/wcd-mbhc-v2.c @@ -25,6 +25,7 @@ #include <linux/input.h> #include <linux/firmware.h> #include <linux/completion.h> +#include <linux/mfd/msm-cdc-pinctrl.h> #include <sound/soc.h> #include <sound/jack.h> #include "wcd-mbhc-v2.h" @@ -2132,6 +2133,22 @@ static int wcd_mbhc_initialise(struct wcd_mbhc *mbhc) if (mbhc->mbhc_cfg->moisture_en && mbhc->mbhc_cb->mbhc_moisture_config) mbhc->mbhc_cb->mbhc_moisture_config(mbhc); + /* + * For USB analog we need to override the switch configuration. + * Also, disable hph_l pull-up current source as HS_DET_L is driven + * by an external source + */ + if (mbhc->mbhc_cfg->enable_usbc_analog) { + mbhc->hphl_swh = 1; + mbhc->gnd_swh = 1; + + if (mbhc->mbhc_cb->hph_pull_up_control) + mbhc->mbhc_cb->hph_pull_up_control(codec, I_OFF); + else + WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_HS_L_DET_PULL_UP_CTRL, + 0); + } + WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_HPHL_PLUG_TYPE, mbhc->hphl_swh); WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_GND_PLUG_TYPE, mbhc->gnd_swh); WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_SW_HPH_LP_100K_TO_GND, 1); @@ -2304,15 +2321,263 @@ int wcd_mbhc_set_keycode(struct wcd_mbhc *mbhc) return result; } -int wcd_mbhc_start(struct wcd_mbhc *mbhc, - struct wcd_mbhc_config *mbhc_cfg) +static int wcd_mbhc_usb_c_analog_setup_gpios(struct wcd_mbhc *mbhc, + bool active) { int rc = 0; + struct usbc_ana_audio_config *config = + &mbhc->mbhc_cfg->usbc_analog_cfg; + + dev_dbg(mbhc->codec->dev, "%s: setting GPIOs active = %d\n", + __func__, active); + if (active) { + if (config->usbc_en1_gpio_p) { + rc = msm_cdc_pinctrl_select_active_state( + config->usbc_en1_gpio_p); + /* delay required to allow the hw to stabilize */ + usleep_range(1000, 1200); + } + if (rc == 0 && config->usbc_en2n_gpio_p) { + rc = msm_cdc_pinctrl_select_active_state( + config->usbc_en2n_gpio_p); + /* delay required to allow the hw to stabilize */ + usleep_range(1000, 1200); + } + if (rc == 0 && config->usbc_force_gpio_p) + rc = msm_cdc_pinctrl_select_active_state( + config->usbc_force_gpio_p); + mbhc->usbc_mode = POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; + } else { + /* no delay is required when disabling GPIOs */ + if (config->usbc_en2n_gpio_p) + msm_cdc_pinctrl_select_sleep_state( + config->usbc_en2n_gpio_p); + if (config->usbc_en1_gpio_p) + msm_cdc_pinctrl_select_sleep_state( + config->usbc_en1_gpio_p); + if (config->usbc_force_gpio_p) + msm_cdc_pinctrl_select_sleep_state( + config->usbc_force_gpio_p); + mbhc->usbc_mode = POWER_SUPPLY_TYPEC_NONE; + } + + return rc; +} + +/* workqueue */ +static void wcd_mbhc_usbc_analog_work_fn(struct work_struct *work) +{ + struct wcd_mbhc *mbhc = + container_of(work, struct wcd_mbhc, usbc_analog_work); + + wcd_mbhc_usb_c_analog_setup_gpios(mbhc, + mbhc->usbc_mode != POWER_SUPPLY_TYPEC_NONE); +} + +/* this callback function is used to process PMI notification */ +static int wcd_mbhc_usb_c_event_changed(struct notifier_block *nb, + unsigned long evt, void *ptr) +{ + int ret; + union power_supply_propval mode; + struct wcd_mbhc *mbhc = container_of(nb, struct wcd_mbhc, psy_nb); + struct snd_soc_codec *codec = mbhc->codec; + + if (ptr != mbhc->usb_psy || evt != PSY_EVENT_PROP_CHANGED) + return 0; + + ret = power_supply_get_property(mbhc->usb_psy, + POWER_SUPPLY_PROP_TYPEC_MODE, &mode); + if (ret) { + dev_err(codec->dev, "%s: Unable to read USB TYPEC_MODE: %d\n", + __func__, ret); + return ret; + } + + dev_dbg(codec->dev, "%s: USB change event received\n", + __func__); + dev_dbg(codec->dev, "%s: supply mode %d, expected %d\n", __func__, + mode.intval, POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER); + + switch (mode.intval) { + case POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER: + case POWER_SUPPLY_TYPEC_NONE: + dev_dbg(codec->dev, "%s: usbc_mode: %d; mode.intval: %d\n", + __func__, mbhc->usbc_mode, mode.intval); + + if (mbhc->usbc_mode == mode.intval) + break; /* filter notifications received before */ + mbhc->usbc_mode = mode.intval; + + dev_dbg(codec->dev, "%s: queueing usbc_analog_work\n", + __func__); + schedule_work(&mbhc->usbc_analog_work); + break; + default: + break; + } + return ret; +} + +/* PMI registration code */ +static int wcd_mbhc_usb_c_analog_init(struct wcd_mbhc *mbhc) +{ + int ret = 0; + struct snd_soc_codec *codec = mbhc->codec; + + dev_dbg(mbhc->codec->dev, "%s: usb-c analog setup start\n", __func__); + INIT_WORK(&mbhc->usbc_analog_work, wcd_mbhc_usbc_analog_work_fn); + + mbhc->usb_psy = power_supply_get_by_name("usb"); + if (IS_ERR_OR_NULL(mbhc->usb_psy)) { + dev_err(codec->dev, "%s: could not get USB psy info\n", + __func__); + ret = -EPROBE_DEFER; + if (IS_ERR(mbhc->usb_psy)) + ret = PTR_ERR(mbhc->usb_psy); + mbhc->usb_psy = NULL; + goto err; + } + + ret = wcd_mbhc_usb_c_analog_setup_gpios(mbhc, false); + if (ret) { + dev_err(codec->dev, "%s: error while setting USBC ana gpios\n", + __func__); + goto err; + } + + mbhc->psy_nb.notifier_call = wcd_mbhc_usb_c_event_changed; + mbhc->psy_nb.priority = 0; + ret = power_supply_reg_notifier(&mbhc->psy_nb); + if (ret) { + dev_err(codec->dev, "%s: power supply registration failed\n", + __func__); + goto err; + } + + /* + * as part of the init sequence check if there is a connected + * USB C analog adapter + */ + dev_dbg(mbhc->codec->dev, "%s: verify if USB adapter is already inserted\n", + __func__); + ret = wcd_mbhc_usb_c_event_changed(&mbhc->psy_nb, + PSY_EVENT_PROP_CHANGED, + mbhc->usb_psy); + +err: + return ret; +} + +static int wcd_mbhc_usb_c_analog_deinit(struct wcd_mbhc *mbhc) +{ + wcd_mbhc_usb_c_analog_setup_gpios(mbhc, false); + + /* deregister from PMI */ + power_supply_unreg_notifier(&mbhc->psy_nb); + + return 0; +} + +static int wcd_mbhc_init_gpio(struct wcd_mbhc *mbhc, + struct wcd_mbhc_config *mbhc_cfg, + const char *gpio_dt_str, + int *gpio, struct device_node **gpio_dn) +{ + int rc = 0; + struct snd_soc_codec *codec = mbhc->codec; + struct snd_soc_card *card = codec->component.card; + + dev_dbg(mbhc->codec->dev, "%s: gpio %s\n", __func__, gpio_dt_str); + + *gpio_dn = of_parse_phandle(card->dev->of_node, gpio_dt_str, 0); + + if (!(*gpio_dn)) { + *gpio = of_get_named_gpio(card->dev->of_node, gpio_dt_str, 0); + if (!gpio_is_valid(*gpio)) { + dev_err(card->dev, "%s, property %s not in node %s", + __func__, gpio_dt_str, + card->dev->of_node->full_name); + rc = -EINVAL; + } + } + + return rc; +} + +int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg) +{ + int rc = 0; + struct usbc_ana_audio_config *config; + struct snd_soc_codec *codec; + struct snd_soc_card *card; + const char *usb_c_dt = "qcom,msm-mbhc-usbc-audio-supported"; + + if (!mbhc || !mbhc_cfg) + return -EINVAL; + + config = &mbhc_cfg->usbc_analog_cfg; + codec = mbhc->codec; + card = codec->component.card; - pr_debug("%s: enter\n", __func__); /* update the mbhc config */ mbhc->mbhc_cfg = mbhc_cfg; + dev_dbg(mbhc->codec->dev, "%s: enter\n", __func__); + + /* check if USB C analog is defined on device tree */ + mbhc_cfg->enable_usbc_analog = 0; + if (of_find_property(card->dev->of_node, usb_c_dt, NULL)) { + rc = of_property_read_u32(card->dev->of_node, usb_c_dt, + &mbhc_cfg->enable_usbc_analog); + } + if (mbhc_cfg->enable_usbc_analog == 0 || rc != 0) { + dev_info(card->dev, + "%s: %s in dt node is missing or false\n", + __func__, usb_c_dt); + dev_info(card->dev, + "%s: skipping USB c analog configuration\n", __func__); + } + + /* initialize GPIOs */ + if (mbhc_cfg->enable_usbc_analog) { + dev_dbg(mbhc->codec->dev, "%s: usbc analog enabled\n", + __func__); + rc = wcd_mbhc_init_gpio(mbhc, mbhc_cfg, + "qcom,usbc-analog-en1_gpio", + &config->usbc_en1_gpio, + &config->usbc_en1_gpio_p); + if (rc) + goto err; + + rc = wcd_mbhc_init_gpio(mbhc, mbhc_cfg, + "qcom,usbc-analog-en2_n_gpio", + &config->usbc_en2n_gpio, + &config->usbc_en2n_gpio_p); + if (rc) + goto err; + + if (of_find_property(card->dev->of_node, + "qcom,usbc-analog-force_detect_gpio", + NULL)) { + rc = wcd_mbhc_init_gpio(mbhc, mbhc_cfg, + "qcom,usbc-analog-force_detect_gpio", + &config->usbc_force_gpio, + &config->usbc_force_gpio_p); + if (rc) + goto err; + } + + dev_dbg(mbhc->codec->dev, "%s: calling usb_c_analog_init\n", + __func__); + /* init PMI notifier */ + rc = wcd_mbhc_usb_c_analog_init(mbhc); + if (rc) { + rc = EPROBE_DEFER; + goto err; + } + } + /* Set btn key code */ if ((!mbhc->is_btn_already_regd) && wcd_mbhc_set_keycode(mbhc)) pr_err("Set btn key code error!!!\n"); @@ -2329,14 +2594,44 @@ int wcd_mbhc_start(struct wcd_mbhc *mbhc, pr_err("%s: Skipping to read mbhc fw, 0x%pK %pK\n", __func__, mbhc->mbhc_fw, mbhc->mbhc_cal); } - pr_debug("%s: leave %d\n", __func__, rc); + + return rc; +err: + if (config->usbc_en1_gpio > 0) { + dev_dbg(card->dev, "%s free usb en1 gpio %d\n", + __func__, config->usbc_en1_gpio); + gpio_free(config->usbc_en1_gpio); + config->usbc_en1_gpio = 0; + } + if (config->usbc_en2n_gpio > 0) { + dev_dbg(card->dev, "%s free usb_en2 gpio %d\n", + __func__, config->usbc_en2n_gpio); + gpio_free(config->usbc_en2n_gpio); + config->usbc_en2n_gpio = 0; + } + if (config->usbc_force_gpio > 0) { + dev_dbg(card->dev, "%s free usb_force gpio %d\n", + __func__, config->usbc_force_gpio); + gpio_free(config->usbc_force_gpio); + config->usbc_force_gpio = 0; + } + if (config->usbc_en1_gpio_p) + of_node_put(config->usbc_en1_gpio_p); + if (config->usbc_en2n_gpio_p) + of_node_put(config->usbc_en2n_gpio_p); + if (config->usbc_force_gpio_p) + of_node_put(config->usbc_force_gpio_p); + dev_dbg(mbhc->codec->dev, "%s: leave %d\n", __func__, rc); return rc; } EXPORT_SYMBOL(wcd_mbhc_start); void wcd_mbhc_stop(struct wcd_mbhc *mbhc) { + struct usbc_ana_audio_config *config = &mbhc->mbhc_cfg->usbc_analog_cfg; + pr_debug("%s: enter\n", __func__); + if (mbhc->current_plug != MBHC_PLUG_TYPE_NONE) { if (mbhc->mbhc_cb && mbhc->mbhc_cb->skip_imped_detect) mbhc->mbhc_cb->skip_imped_detect(mbhc->codec); @@ -2358,6 +2653,25 @@ void wcd_mbhc_stop(struct wcd_mbhc *mbhc) mbhc->mbhc_fw = NULL; mbhc->mbhc_cal = NULL; } + + if (mbhc->mbhc_cfg->enable_usbc_analog) { + wcd_mbhc_usb_c_analog_deinit(mbhc); + /* free GPIOs */ + if (config->usbc_en1_gpio > 0) + gpio_free(config->usbc_en1_gpio); + if (config->usbc_en2n_gpio > 0) + gpio_free(config->usbc_en2n_gpio); + if (config->usbc_force_gpio) + gpio_free(config->usbc_force_gpio); + + if (config->usbc_en1_gpio_p) + of_node_put(config->usbc_en1_gpio_p); + if (config->usbc_en2n_gpio_p) + of_node_put(config->usbc_en2n_gpio_p); + if (config->usbc_force_gpio_p) + of_node_put(config->usbc_force_gpio_p); + } + pr_debug("%s: leave\n", __func__); } EXPORT_SYMBOL(wcd_mbhc_stop); diff --git a/sound/soc/codecs/wcd-mbhc-v2.h b/sound/soc/codecs/wcd-mbhc-v2.h index 60473ce8dab0..6f23a2dcbff7 100644 --- a/sound/soc/codecs/wcd-mbhc-v2.h +++ b/sound/soc/codecs/wcd-mbhc-v2.h @@ -14,6 +14,7 @@ #include <linux/wait.h> #include <linux/stringify.h> +#include <linux/power_supply.h> #include "wcdcal-hwdep.h" #define TOMBAK_MBHC_NC 0 @@ -249,6 +250,15 @@ enum mbhc_moisture_rref { R_184_KOHM, }; +struct usbc_ana_audio_config { + int usbc_en1_gpio; + int usbc_en2n_gpio; + int usbc_force_gpio; + struct device_node *usbc_en1_gpio_p; /* used by pinctrl API */ + struct device_node *usbc_en2n_gpio_p; /* used by pinctrl API */ + struct device_node *usbc_force_gpio_p; /* used by pinctrl API */ +}; + struct wcd_mbhc_config { bool read_fw_bin; void *calibration; @@ -263,6 +273,8 @@ struct wcd_mbhc_config { int mbhc_micbias; int anc_micbias; bool enable_anc_mic_detect; + u32 enable_usbc_analog; + struct usbc_ana_audio_config usbc_analog_cfg; }; struct wcd_mbhc_intr { @@ -443,6 +455,11 @@ struct wcd_mbhc { unsigned long intr_status; bool is_hph_ocp_pending; + + int usbc_mode; + struct notifier_block psy_nb; + struct power_supply *usb_psy; + struct work_struct usbc_analog_work; }; #define WCD_MBHC_CAL_SIZE(buttons, rload) ( \ sizeof(struct wcd_mbhc_general_cfg) + \ diff --git a/sound/soc/codecs/wcd934x/wcd934x-mbhc.c b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c index f9b588e87e87..3d032f0a7115 100644 --- a/sound/soc/codecs/wcd934x/wcd934x-mbhc.c +++ b/sound/soc/codecs/wcd934x/wcd934x-mbhc.c @@ -772,13 +772,19 @@ static void tavil_mbhc_moisture_config(struct wcd_mbhc *mbhc) { struct snd_soc_codec *codec = mbhc->codec; - if (mbhc->moist_rref == R_OFF) + if ((mbhc->moist_rref == R_OFF) || + (mbhc->mbhc_cfg->enable_usbc_analog)) { + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_2, + 0x0C, R_OFF << 2); return; + } /* Donot enable moisture detection if jack type is NC */ if (!mbhc->hphl_swh) { dev_dbg(codec->dev, "%s: disable moisture detection for NC\n", __func__); + snd_soc_update_bits(codec, WCD934X_MBHC_NEW_CTL_2, + 0x0C, R_OFF << 2); return; } |