Unverified Commit 7ed1b265 authored by Ivaylo Dimitrov's avatar Ivaylo Dimitrov Committed by Mark Brown
Browse files

ASoC: cpcap: Implement jack detection



cpcap has headphones/microphone and PTT button detection logic, implement
code to support it.

Signed-off-by: default avatarIvaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
Link: https://patch.msgid.link/20250122164129.807247-6-ivo.g.dimitrov.75@gmail.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 02d4a97c
Loading
Loading
Loading
Loading
+170 −1
Original line number Diff line number Diff line
@@ -14,9 +14,18 @@
#include <linux/regulator/consumer.h>
#include <linux/mfd/motorola-cpcap.h>
#include <sound/core.h>
#include <linux/input.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include <sound/tlv.h>

/* Register 8 - CPCAP_REG_INTS1  --- Interrupt Sense 1 */
#define CPCAP_BIT_HS_S                    9  /* Headset */
#define CPCAP_BIT_MB2_S                   10 /* Mic Bias2 */

/* Register 9 - CPCAP_REG_INTS2   --- Interrupt Sense 2 */
#define CPCAP_BIT_PTT_S                   11 /* Push To Talk */

/* Register 512 CPCAP_REG_VAUDIOC --- Audio Regulator and Bias Voltage */
#define CPCAP_BIT_AUDIO_LOW_PWR           6
#define CPCAP_BIT_AUD_LOWPWR_SPEED        5
@@ -262,6 +271,9 @@ struct cpcap_audio {
	int codec_freq;
	int codec_format;
	struct regulator *vaudio;
	int hsirq;
	int mb2irq;
	struct snd_soc_jack jack;
};

static int cpcap_st_workaround(struct snd_soc_dapm_widget *w,
@@ -1628,14 +1640,105 @@ static int cpcap_audio_reset(struct snd_soc_component *component,
	return 0;
}

static irqreturn_t cpcap_hs_irq_thread(int irq, void *data)
{
	struct snd_soc_component *component = data;
	struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cpcap->regmap;
	int status = 0;
	int mask = SND_JACK_HEADSET;
	int val;

	if (!regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_HS_S))) {
		val = BIT(CPCAP_BIT_MB_ON2) | BIT(CPCAP_BIT_PTT_CMP_EN);
		regmap_update_bits(regmap, CPCAP_REG_TXI, val, val);

		val = BIT(CPCAP_BIT_ST_HS_CP_EN);
		regmap_update_bits(regmap, CPCAP_REG_RXOA, val, val);

		regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_NORMAL);

		/* Give PTTS time to settle */
		msleep(20);

		if (!regmap_test_bits(regmap, CPCAP_REG_INTS2,
				      BIT(CPCAP_BIT_PTT_S))) {
			/* Headphones detected. (May also be a headset with the
			 * MFB pressed.)
			 */
			status = SND_JACK_HEADPHONE;
			dev_info(component->dev, "HP plugged in\n");
		} else if (regmap_test_bits(regmap, CPCAP_REG_INTS1,
					    BIT(CPCAP_BIT_MB2_S)) == 1) {
			status = SND_JACK_HEADSET;
			dev_info(component->dev, "HS plugged in\n");
		} else
			dev_info(component->dev, "Unsupported HS plugged in\n");
	} else {
		bool mic = cpcap->jack.status & SND_JACK_MICROPHONE;

		dev_info(component->dev, "H%s disconnect\n", mic ? "S" : "P");
		val = BIT(CPCAP_BIT_MB_ON2) | BIT(CPCAP_BIT_PTT_CMP_EN);
		regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, val, 0);

		val = BIT(CPCAP_BIT_ST_HS_CP_EN);
		regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, val, 0);

		regulator_set_mode(cpcap->vaudio, REGULATOR_MODE_STANDBY);

		mask |= SND_JACK_BTN_0;
	}

	snd_soc_jack_report(&cpcap->jack, status, mask);

	return IRQ_HANDLED;
}

static irqreturn_t cpcap_mb2_irq_thread(int irq, void *data)
{
	struct snd_soc_component *component = data;
	struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);
	struct regmap *regmap = cpcap->regmap;
	int status = 0;
	int mb2;
	int ptt;

	if (regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_HS_S)) == 1)
		return IRQ_HANDLED;

	mb2 = regmap_test_bits(regmap, CPCAP_REG_INTS1, BIT(CPCAP_BIT_MB2_S));
	ptt = regmap_test_bits(regmap, CPCAP_REG_INTS2, BIT(CPCAP_BIT_PTT_S));

	/* Initial detection might have been with MFB pressed */
	if (!(cpcap->jack.status & SND_JACK_MICROPHONE)) {
		if (ptt == 1 && mb2 == 1) {
			dev_info(component->dev, "MIC plugged in\n");
			snd_soc_jack_report(&cpcap->jack, SND_JACK_MICROPHONE,
					    SND_JACK_MICROPHONE);
		}

		return IRQ_HANDLED;
	}

	if (!mb2 || !ptt)
		status = SND_JACK_BTN_0;

	snd_soc_jack_report(&cpcap->jack, status, SND_JACK_BTN_0);

	return IRQ_HANDLED;
}

static int cpcap_soc_probe(struct snd_soc_component *component)
{
	struct platform_device *pdev = to_platform_device(component->dev);
	struct snd_soc_card *card = component->card;
	struct cpcap_audio *cpcap;
	int err;

	cpcap = devm_kzalloc(component->dev, sizeof(*cpcap), GFP_KERNEL);
	if (!cpcap)
		return -ENOMEM;

	snd_soc_component_set_drvdata(component, cpcap);
	cpcap->component = component;

@@ -1644,6 +1747,16 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
		return dev_err_probe(component->dev, PTR_ERR(cpcap->vaudio),
				     "Cannot get VAUDIO regulator\n");

	err = snd_soc_card_jack_new(card, "Headphones",
				    SND_JACK_HEADSET | SND_JACK_BTN_0,
				    &cpcap->jack);
	if (err < 0) {
		dev_err(component->dev, "Cannot create HS jack: %i\n", err);
		return err;
	}

	snd_jack_set_key(cpcap->jack.jack, SND_JACK_BTN_0, KEY_MEDIA);

	cpcap->regmap = dev_get_regmap(component->dev->parent, NULL);
	if (!cpcap->regmap)
		return -ENODEV;
@@ -1653,7 +1766,58 @@ static int cpcap_soc_probe(struct snd_soc_component *component)
	if (err)
		return err;

	return cpcap_audio_reset(component, false);
	cpcap->hsirq = platform_get_irq_byname(pdev, "hs");
	if (cpcap->hsirq < 0)
		return cpcap->hsirq;

	err = devm_request_threaded_irq(component->dev, cpcap->hsirq, NULL,
					cpcap_hs_irq_thread,
					IRQF_TRIGGER_RISING |
					IRQF_TRIGGER_FALLING |
					IRQF_ONESHOT,
					"cpcap-codec-hs",
					component);
	if (err) {
		dev_warn(component->dev, "no HS irq%i: %i\n",
			 cpcap->hsirq, err);
		return err;
	}

	cpcap->mb2irq = platform_get_irq_byname(pdev, "mb2");
	if (cpcap->mb2irq < 0)
		return cpcap->mb2irq;

	err = devm_request_threaded_irq(component->dev, cpcap->mb2irq, NULL,
					cpcap_mb2_irq_thread,
					IRQF_TRIGGER_RISING |
					IRQF_TRIGGER_FALLING |
					IRQF_ONESHOT,
					"cpcap-codec-mb2",
					component);
	if (err) {
		dev_warn(component->dev, "no MB2 irq%i: %i\n",
			 cpcap->mb2irq, err);
		return err;
	}

	err = cpcap_audio_reset(component, false);
	if (err)
		return err;

	cpcap_hs_irq_thread(cpcap->hsirq, component);

	enable_irq_wake(cpcap->hsirq);
	enable_irq_wake(cpcap->mb2irq);

	return 0;
}

static void cpcap_soc_remove(struct snd_soc_component *component)
{
	struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);

	disable_irq_wake(cpcap->hsirq);
	disable_irq_wake(cpcap->mb2irq);
}

static int cpcap_set_bias_level(struct snd_soc_component *component,
@@ -1661,6 +1825,10 @@ static int cpcap_set_bias_level(struct snd_soc_component *component,
{
	struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);

	/* VAIDIO should be kept in normal mode in order MIC/PTT to work */
	if (cpcap->jack.status & SND_JACK_MICROPHONE)
		return 0;

	switch (level) {
	case SND_SOC_BIAS_OFF:
		break;
@@ -1679,6 +1847,7 @@ static int cpcap_set_bias_level(struct snd_soc_component *component,

static const struct snd_soc_component_driver soc_codec_dev_cpcap = {
	.probe			= cpcap_soc_probe,
	.remove			= cpcap_soc_remove,
	.controls		= cpcap_snd_controls,
	.num_controls		= ARRAY_SIZE(cpcap_snd_controls),
	.dapm_widgets		= cpcap_dapm_widgets,