|
@@ -25,6 +25,7 @@
|
|
|
#include <linux/input.h>
|
|
|
#include <linux/firmware.h>
|
|
|
#include <linux/completion.h>
|
|
|
+#include <linux/soc/qcom/fsa4480-i2c.h>
|
|
|
#include <sound/soc.h>
|
|
|
#include <sound/jack.h>
|
|
|
#include "msm-cdc-pinctrl.h"
|
|
@@ -958,6 +959,9 @@ static void wcd_mbhc_swch_irq_handler(struct wcd_mbhc *mbhc)
|
|
|
mbhc->extn_cable_hph_rem = false;
|
|
|
wcd_mbhc_report_plug(mbhc, 0, jack_type);
|
|
|
|
|
|
+ if (mbhc->mbhc_cfg->enable_usbc_analog)
|
|
|
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_L_DET_EN, 0);
|
|
|
+
|
|
|
} else if (!detection_type) {
|
|
|
/* Disable external voltage source to micbias if present */
|
|
|
if (mbhc->mbhc_cb->enable_mb_source)
|
|
@@ -1291,8 +1295,8 @@ static int wcd_mbhc_initialise(struct wcd_mbhc *mbhc)
|
|
|
* by an external source
|
|
|
*/
|
|
|
if (mbhc->mbhc_cfg->enable_usbc_analog) {
|
|
|
- mbhc->hphl_swh = 1;
|
|
|
- mbhc->gnd_swh = 1;
|
|
|
+ mbhc->hphl_swh = 0;
|
|
|
+ mbhc->gnd_swh = 0;
|
|
|
|
|
|
if (mbhc->mbhc_cb->hph_pull_up_control_v2)
|
|
|
mbhc->mbhc_cb->hph_pull_up_control_v2(codec,
|
|
@@ -1310,7 +1314,16 @@ static int wcd_mbhc_initialise(struct wcd_mbhc *mbhc)
|
|
|
if (mbhc->mbhc_cfg->gnd_det_en && mbhc->mbhc_cb->mbhc_gnd_det_ctrl)
|
|
|
mbhc->mbhc_cb->mbhc_gnd_det_ctrl(codec, true);
|
|
|
WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_HS_L_DET_PULL_UP_COMP_CTRL, 1);
|
|
|
- WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_L_DET_EN, 1);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Disable L_DET for USB-C analog audio to avoid spurious interrupts
|
|
|
+ * when a non-audio accessory is inserted. L_DET_EN sets to 1 when FSA
|
|
|
+ * I2C driver notifies that ANALOG_AUDIO_ADAPTER is inserted
|
|
|
+ */
|
|
|
+ if (mbhc->mbhc_cfg->enable_usbc_analog)
|
|
|
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_L_DET_EN, 0);
|
|
|
+ else
|
|
|
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_L_DET_EN, 1);
|
|
|
|
|
|
if (mbhc->mbhc_cfg->enable_usbc_analog) {
|
|
|
/* Insertion debounce set to 48ms */
|
|
@@ -1481,207 +1494,26 @@ static int wcd_mbhc_set_keycode(struct wcd_mbhc *mbhc)
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-static int wcd_mbhc_usb_c_analog_setup_gpios(struct wcd_mbhc *mbhc,
|
|
|
- bool active)
|
|
|
+static int wcd_mbhc_usbc_ana_event_handler(struct notifier_block *nb,
|
|
|
+ unsigned long mode, void *ptr)
|
|
|
{
|
|
|
- int rc = 0;
|
|
|
- struct usbc_ana_audio_config *config =
|
|
|
- &mbhc->mbhc_cfg->usbc_analog_cfg;
|
|
|
- union power_supply_propval pval;
|
|
|
-
|
|
|
- dev_dbg(mbhc->codec->dev, "%s: setting GPIOs active = %d\n",
|
|
|
- __func__, active);
|
|
|
+ struct wcd_mbhc *mbhc = container_of(nb, struct wcd_mbhc, fsa_nb);
|
|
|
|
|
|
- memset(&pval, 0, sizeof(pval));
|
|
|
-
|
|
|
- if (active) {
|
|
|
- pval.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
|
|
|
- if (power_supply_set_property(mbhc->usb_psy,
|
|
|
- POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &pval))
|
|
|
- dev_info(mbhc->codec->dev, "%s: force PR_SOURCE mode unsuccessful\n",
|
|
|
- __func__);
|
|
|
- else
|
|
|
- mbhc->usbc_force_pr_mode = true;
|
|
|
-
|
|
|
- if (config->usbc_en1_gpio_p)
|
|
|
- rc = msm_cdc_pinctrl_select_active_state(
|
|
|
- config->usbc_en1_gpio_p);
|
|
|
- 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_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);
|
|
|
-
|
|
|
- if (mbhc->usbc_force_pr_mode) {
|
|
|
- pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
|
- if (power_supply_set_property(mbhc->usb_psy,
|
|
|
- POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &pval))
|
|
|
- dev_info(mbhc->codec->dev, "%s: force PR_DUAL mode unsuccessful\n",
|
|
|
- __func__);
|
|
|
-
|
|
|
- mbhc->usbc_force_pr_mode = false;
|
|
|
- }
|
|
|
-
|
|
|
- mbhc->usbc_mode = POWER_SUPPLY_TYPEC_NONE;
|
|
|
- if (mbhc->mbhc_cfg->swap_gnd_mic)
|
|
|
- mbhc->mbhc_cfg->swap_gnd_mic(mbhc->codec, false);
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
+ if (!mbhc)
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
- 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);
|
|
|
+ dev_dbg(mbhc->codec->dev, "%s: mode = %lu\n", __func__, mode);
|
|
|
|
|
|
- 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;
|
|
|
+ if (mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) {
|
|
|
+ /* insertion detected, enable L_DET_EN */
|
|
|
+ WCD_MBHC_REG_UPDATE_BITS(WCD_MBHC_L_DET_EN, 1);
|
|
|
}
|
|
|
-
|
|
|
- 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";
|
|
@@ -1689,7 +1521,6 @@ int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg)
|
|
|
if (!mbhc || !mbhc_cfg)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- config = &mbhc_cfg->usbc_analog_cfg;
|
|
|
codec = mbhc->codec;
|
|
|
card = codec->component.card;
|
|
|
|
|
@@ -1705,42 +1536,24 @@ int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg)
|
|
|
&mbhc_cfg->enable_usbc_analog);
|
|
|
}
|
|
|
if (mbhc_cfg->enable_usbc_analog == 0 || rc != 0) {
|
|
|
- dev_info(card->dev,
|
|
|
+ dev_dbg(card->dev,
|
|
|
"%s: %s in dt node is missing or false\n",
|
|
|
__func__, usb_c_dt);
|
|
|
- dev_info(card->dev,
|
|
|
+ dev_dbg(card->dev,
|
|
|
"%s: skipping USB c analog configuration\n", __func__);
|
|
|
}
|
|
|
|
|
|
- /* initialize GPIOs */
|
|
|
+ /* Parse fsa switch handle */
|
|
|
if (mbhc_cfg->enable_usbc_analog) {
|
|
|
dev_dbg(mbhc->codec->dev, "%s: usbc analog enabled\n",
|
|
|
__func__);
|
|
|
mbhc->swap_thr = GND_MIC_USBC_SWAP_THRESHOLD;
|
|
|
- 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;
|
|
|
-
|
|
|
- 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;
|
|
|
+ mbhc->fsa_np = of_parse_phandle(card->dev->of_node,
|
|
|
+ "fsa4480-i2c-handle", 0);
|
|
|
+ if (!mbhc->fsa_np) {
|
|
|
+ dev_err(card->dev, "%s: fsa4480 i2c node not found\n",
|
|
|
+ __func__);
|
|
|
+ rc = -EINVAL;
|
|
|
goto err;
|
|
|
}
|
|
|
}
|
|
@@ -1753,6 +1566,11 @@ int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg)
|
|
|
(mbhc->mbhc_cfg->read_fw_bin && mbhc->mbhc_fw) ||
|
|
|
(mbhc->mbhc_cfg->read_fw_bin && mbhc->mbhc_cal)) {
|
|
|
rc = wcd_mbhc_initialise(mbhc);
|
|
|
+ if (rc) {
|
|
|
+ dev_err(card->dev, "%s: wcd mbhc initialize failed\n",
|
|
|
+ __func__);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
} else {
|
|
|
if (!mbhc->mbhc_fw || !mbhc->mbhc_cal)
|
|
|
schedule_delayed_work(&mbhc->mbhc_firmware_dwork,
|
|
@@ -1762,24 +1580,14 @@ int wcd_mbhc_start(struct wcd_mbhc *mbhc, struct wcd_mbhc_config *mbhc_cfg)
|
|
|
__func__, mbhc->mbhc_fw, mbhc->mbhc_cal);
|
|
|
}
|
|
|
|
|
|
+ if (mbhc_cfg->enable_usbc_analog) {
|
|
|
+ mbhc->fsa_nb.notifier_call = wcd_mbhc_usbc_ana_event_handler;
|
|
|
+ mbhc->fsa_nb.priority = 0;
|
|
|
+ rc = fsa4480_reg_notifier(&mbhc->fsa_nb, mbhc->fsa_np);
|
|
|
+ }
|
|
|
+
|
|
|
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_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_force_gpio_p)
|
|
|
- of_node_put(config->usbc_force_gpio_p);
|
|
|
dev_dbg(mbhc->codec->dev, "%s: leave %d\n", __func__, rc);
|
|
|
return rc;
|
|
|
}
|
|
@@ -1787,8 +1595,6 @@ 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) {
|
|
@@ -1813,19 +1619,8 @@ void wcd_mbhc_stop(struct wcd_mbhc *mbhc)
|
|
|
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_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_force_gpio_p)
|
|
|
- of_node_put(config->usbc_force_gpio_p);
|
|
|
- }
|
|
|
+ if (mbhc->mbhc_cfg->enable_usbc_analog)
|
|
|
+ fsa4480_unreg_notifier(&mbhc->fsa_nb, mbhc->fsa_np);
|
|
|
|
|
|
pr_debug("%s: leave\n", __func__);
|
|
|
}
|