asoc: wcd-mbhc: add support for usbc analog audio on msmnile

Add support for usbc analog audio for msmnile platform
by adding fsa driver calls in mbhc driver to make
any switching orientation changes of gnd/mic.

Change-Id: Iab7511907325a24345734402c10a4cf5f8ebfa23
Signed-off-by: Phani Kumar Uppalapati <phaniu@codeaurora.org>
Цей коміт міститься в:
Phani Kumar Uppalapati
2018-01-17 18:42:52 -08:00
зафіксовано Gerrit - the friendly Code Review server
джерело d6a2b8187f
коміт 8fe02478b7
2 змінених файлів з 49 додано та 268 видалено

Переглянути файл

@@ -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;
struct wcd_mbhc *mbhc = container_of(nb, struct wcd_mbhc, fsa_nb);
dev_dbg(mbhc->codec->dev, "%s: setting GPIOs active = %d\n",
__func__, active);
if (!mbhc)
return -EINVAL;
memset(&pval, 0, sizeof(pval));
dev_dbg(mbhc->codec->dev, "%s: mode = %lu\n", __func__, mode);
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);
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);
}
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";
@@ -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__);
}