build/patch/kernel/archive/sunxi-6.1/patches.megous/ASoC-sun8i-codec-Implement-jack-and-accessory-detection.patch

405 lines
12 KiB
Diff
Raw Normal View History

From 74c2e4efbcaef85b832fedca004ede62d0058b85 Mon Sep 17 00:00:00 2001
From: Arnaud Ferraris <arnaud.ferraris@collabora.com>
Date: Fri, 25 Sep 2020 20:28:14 -0500
Subject: [PATCH 253/389] ASoC: sun8i-codec: Implement jack and accessory
detection
Add support for the jack detection functionality in the A64 variant,
which uses a pair of IRQs; and microphone accessory (button) detection,
which uses an ADC with an IRQ trigger.
Since not all users of this component have a headphone or headset jack,
only enable jack pins where the corresponding widget exists. Similarly,
only enable microphone accessory detection when the microphone is hooked
up to a headset jack.
IRQs will only be triggered if the JACKDETEN, HMICBIASEN, and MICADCEN
bits are set appropriately in the analog codec component, but there is
no direct software dependency between the two components.
Signed-off-by: Arnaud Ferraris <arnaud.ferraris@collabora.com>
[Samuel: Decouple from analog codec, automatic pin detection, fixes]
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
sound/soc/sunxi/sun8i-codec.c | 269 ++++++++++++++++++++++++++++++++++
1 file changed, 269 insertions(+)
diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c
index 6c125e3e0976..4b6b7336da06 100644
--- a/sound/soc/sunxi/sun8i-codec.c
+++ b/sound/soc/sunxi/sun8i-codec.c
@@ -12,12 +12,15 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/clk.h>
+#include <linux/input.h>
#include <linux/io.h>
+#include <linux/irq.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/log2.h>
+#include <sound/jack.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
@@ -118,6 +121,19 @@
#define SUN8I_ADC_VOL_CTRL 0x104
#define SUN8I_ADC_VOL_CTRL_ADCL_VOL 8
#define SUN8I_ADC_VOL_CTRL_ADCR_VOL 0
+#define SUN8I_HMIC_CTRL1 0x110
+#define SUN8I_HMIC_CTRL1_HMIC_N 8
+#define SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN 4
+#define SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN 3
+#define SUN8I_HMIC_CTRL1_HMIC_DATA_IRQ_EN 0
+#define SUN8I_HMIC_CTRL2 0x114
+#define SUN8I_HMIC_CTRL2_HMIC_SAMPLE 14
+#define SUN8I_HMIC_CTRL2_HMIC_SF 6
+#define SUN8I_HMIC_STS 0x118
+#define SUN8I_HMIC_STS_HMIC_DATA 8
+#define SUN8I_HMIC_STS_JACK_OUT_IRQ_ST 4
+#define SUN8I_HMIC_STS_JACK_IN_IRQ_ST 3
+#define SUN8I_HMIC_STS_HMIC_DATA_IRQ_ST 0
#define SUN8I_DAC_DIG_CTRL 0x120
#define SUN8I_DAC_DIG_CTRL_ENDA 15
#define SUN8I_DAC_VOL_CTRL 0x124
@@ -143,6 +159,14 @@
#define SUN8I_AIF_CLK_CTRL_WORD_SIZ_MASK GENMASK(5, 4)
#define SUN8I_AIF_CLK_CTRL_DATA_FMT_MASK GENMASK(3, 2)
#define SUN8I_AIF3_CLK_CTRL_AIF3_CLK_SRC_MASK GENMASK(1, 0)
+#define SUN8I_HMIC_CTRL1_HMIC_N_MASK GENMASK(11, 8)
+#define SUN8I_HMIC_CTRL2_HMIC_SAMPLE_MASK GENMASK(15, 14)
+#define SUN8I_HMIC_CTRL2_HMIC_SF_MASK GENMASK(7, 6)
+#define SUN8I_HMIC_STS_HMIC_DATA_MASK GENMASK(12, 8)
+
+#define SUN8I_CODEC_BUTTONS (SND_JACK_BTN_0|\
+ SND_JACK_BTN_1|\
+ SND_JACK_BTN_2)
#define SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE 48000
@@ -178,16 +202,23 @@ struct sun8i_codec_aif {
struct sun8i_codec_quirks {
bool bus_clock : 1;
+ bool jack_detection : 1;
bool legacy_widgets : 1;
bool lrck_inversion : 1;
};
struct sun8i_codec {
struct regmap *regmap;
+ struct snd_soc_card *card;
struct clk *clk_bus;
struct clk *clk_module;
const struct sun8i_codec_quirks *quirks;
struct sun8i_codec_aif aifs[SUN8I_CODEC_NAIFS];
+ struct snd_soc_jack jack;
+ struct delayed_work jack_work;
+ int jack_irq;
+ int jack_pending;
+ int jack_type;
unsigned int sysclk_rate;
int sysclk_refcnt;
};
@@ -1239,12 +1270,78 @@ static const struct snd_soc_dapm_route sun8i_codec_legacy_routes[] = {
{ "AIF1 Slot 0 Right", NULL, "DACR" },
};
+static struct snd_soc_jack_pin sun8i_codec_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Microphone",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int sun8i_codec_jack_init(struct sun8i_codec *scodec)
+{
+ int pins = 0;
+ int type = 0;
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(sun8i_codec_jack_pins); ++i) {
+ struct snd_soc_jack_pin *pin = &sun8i_codec_jack_pins[i];
+ struct snd_soc_dapm_widget *w;
+
+ for_each_card_widgets(scodec->card, w) {
+ if (!strcmp(pin->pin, w->name)) {
+ pins |= BIT(i);
+ type |= pin->mask;
+ break;
+ }
+ }
+ }
+
+ if (!type)
+ return 0;
+
+ if (type & SND_JACK_MICROPHONE)
+ type |= SUN8I_CODEC_BUTTONS;
+
+ ret = snd_soc_card_jack_new(scodec->card, "Headset Jack", type,
+ &scodec->jack);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(sun8i_codec_jack_pins); ++i) {
+ struct snd_soc_jack_pin *pin = &sun8i_codec_jack_pins[i];
+
+ if (pins & BIT(i)) {
+ ret = snd_soc_jack_add_pins(&scodec->jack, 1, pin);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (type & SND_JACK_MICROPHONE) {
+ struct snd_jack *jack = scodec->jack.jack;
+
+ snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOLUMEUP);
+ snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN);
+ }
+
+ scodec->jack_type = type;
+
+ return 0;
+}
+
static int sun8i_codec_component_probe(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
int ret;
+ scodec->card = component->card;
+
/* Add widgets for backward compatibility with old device trees. */
if (scodec->quirks->legacy_widgets) {
ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_legacy_widgets,
@@ -1278,9 +1375,54 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
/* Program the default sample rate. */
sun8i_codec_update_sample_rate(scodec);
+ if (scodec->quirks->jack_detection) {
+ ret = sun8i_codec_jack_init(scodec);
+ if (ret)
+ return ret;
+ }
+
+ if (scodec->jack_type) {
+ int irq_mask = BIT(SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN) |
+ BIT(SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN);
+
+ /* Reserved value required for jack IRQs to trigger. */
+ regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
+ SUN8I_HMIC_CTRL1_HMIC_N_MASK,
+ 0xf << SUN8I_HMIC_CTRL1_HMIC_N);
+
+ /* Sample the ADC at 64 Hz; average across 2 samples. */
+ regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL2,
+ SUN8I_HMIC_CTRL2_HMIC_SAMPLE_MASK |
+ SUN8I_HMIC_CTRL2_HMIC_SF_MASK,
+ 0x1 << SUN8I_HMIC_CTRL2_HMIC_SAMPLE |
+ 0x1 << SUN8I_HMIC_CTRL2_HMIC_SF);
+
+ regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
+ irq_mask, irq_mask);
+
+ enable_irq(scodec->jack_irq);
+ }
+
return 0;
}
+static void sun8i_codec_component_remove(struct snd_soc_component *component)
+{
+ struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
+
+ if (scodec->jack_type) {
+ int irq_mask = BIT(SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN) |
+ BIT(SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN) |
+ BIT(SUN8I_HMIC_CTRL1_HMIC_DATA_IRQ_EN);
+
+ disable_irq(scodec->jack_irq);
+ cancel_delayed_work_sync(&scodec->jack_work);
+
+ regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
+ irq_mask, 0);
+ }
+}
+
static const struct snd_soc_component_driver sun8i_soc_component = {
.controls = sun8i_codec_controls,
.num_controls = ARRAY_SIZE(sun8i_codec_controls),
@@ -1289,20 +1431,130 @@ static const struct snd_soc_component_driver sun8i_soc_component = {
.dapm_routes = sun8i_codec_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(sun8i_codec_dapm_routes),
.probe = sun8i_codec_component_probe,
+ .remove = sun8i_codec_component_remove,
.idle_bias_on = 1,
.suspend_bias_off = 1,
.endianness = 1,
};
+static bool sun8i_codec_volatile_reg(struct device *dev, unsigned int reg)
+{
+ return reg == SUN8I_HMIC_STS;
+}
+
static const struct regmap_config sun8i_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
+ .volatile_reg = sun8i_codec_volatile_reg,
.max_register = SUN8I_DAC_MXR_SRC,
.cache_type = REGCACHE_FLAT,
};
+static int sun8i_codec_read_hmic_button(struct sun8i_codec *scodec)
+{
+ unsigned int value;
+ int button;
+
+ regmap_read(scodec->regmap, SUN8I_HMIC_STS, &value);
+ value &= SUN8I_HMIC_STS_HMIC_DATA_MASK;
+ value >>= SUN8I_HMIC_STS_HMIC_DATA;
+
+ if (value < 0x2)
+ button = SND_JACK_BTN_0;
+ else if (value < 0x7)
+ button = SND_JACK_BTN_1;
+ else if (value < 0x10)
+ button = SND_JACK_BTN_2;
+ else
+ button = 0;
+
+ dev_dbg(scodec->card->dev, "HMIC ADC read %u => %#x\n", value, button);
+
+ return button;
+}
+
+static void sun8i_codec_set_hmic_bias(struct sun8i_codec *scodec, bool enable)
+{
+ struct snd_soc_dapm_context *dapm = &scodec->card->dapm;
+ int irq_mask = BIT(SUN8I_HMIC_CTRL1_HMIC_DATA_IRQ_EN);
+
+ if (enable)
+ snd_soc_dapm_force_enable_pin(dapm, "HBIAS");
+ else
+ snd_soc_dapm_disable_pin(dapm, "HBIAS");
+ snd_soc_dapm_sync(dapm);
+
+ dev_dbg(scodec->card->dev, "HMIC bias %s\n", enable ? "on" : "off");
+
+ regmap_update_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
+ irq_mask, enable ? irq_mask : 0);
+}
+
+static void sun8i_codec_jack_work(struct work_struct *work)
+{
+ struct sun8i_codec *scodec = container_of(work, struct sun8i_codec,
+ jack_work.work);
+ int type;
+
+ /* Prevent a well-timed button press from affecting detection. */
+ synchronize_irq(scodec->jack_irq);
+ if (!scodec->jack_pending)
+ return;
+
+ if (sun8i_codec_read_hmic_button(scodec)) {
+ sun8i_codec_set_hmic_bias(scodec, false);
+ type = SND_JACK_HEADPHONE;
+ } else {
+ type = SND_JACK_HEADSET;
+ }
+
+ snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+}
+
+static irqreturn_t sun8i_codec_jack_irq(int irq, void *dev_id)
+{
+ struct sun8i_codec *scodec = dev_id;
+ unsigned int status;
+
+ regmap_read(scodec->regmap, SUN8I_HMIC_STS, &status);
+
+ if (status & BIT(SUN8I_HMIC_STS_JACK_OUT_IRQ_ST)) {
+ if (scodec->jack_type & SND_JACK_MICROPHONE) {
+ sun8i_codec_set_hmic_bias(scodec, false);
+ scodec->jack_pending = false;
+ }
+
+ dev_dbg(scodec->card->dev, "jack out\n");
+ snd_soc_jack_report(&scodec->jack, 0, scodec->jack_type);
+ } else if (status & BIT(SUN8I_HMIC_STS_JACK_IN_IRQ_ST)) {
+ int type = SND_JACK_HEADPHONE;
+
+ if (scodec->jack_type & SND_JACK_MICROPHONE) {
+ sun8i_codec_set_hmic_bias(scodec, true);
+ scodec->jack_pending = true;
+ queue_delayed_work(system_power_efficient_wq,
+ &scodec->jack_work,
+ msecs_to_jiffies(600));
+ }
+
+ dev_dbg(scodec->card->dev, "jack in\n");
+ snd_soc_jack_report(&scodec->jack, type, type);
+ } else if (status & BIT(SUN8I_HMIC_STS_HMIC_DATA_IRQ_ST)) {
+ int type = SND_JACK_HEADSET | sun8i_codec_read_hmic_button(scodec);
+
+ scodec->jack_pending = false;
+ snd_soc_jack_report(&scodec->jack, type, scodec->jack_type);
+ } else {
+ return IRQ_NONE;
+ }
+
+ regmap_write(scodec->regmap, SUN8I_HMIC_STS, status);
+
+ return IRQ_HANDLED;
+}
+
static int sun8i_codec_probe(struct platform_device *pdev)
{
struct sun8i_codec *scodec;
@@ -1344,6 +1596,22 @@ static int sun8i_codec_probe(struct platform_device *pdev)
return PTR_ERR(scodec->regmap);
}
+ if (scodec->quirks->jack_detection) {
+ scodec->jack_irq = platform_get_irq(pdev, 0);
+ if (scodec->jack_irq < 0)
+ return scodec->jack_irq;
+
+ irq_set_status_flags(scodec->jack_irq, IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(&pdev->dev, scodec->jack_irq,
+ NULL, sun8i_codec_jack_irq,
+ IRQF_ONESHOT,
+ dev_name(&pdev->dev), scodec);
+ if (ret)
+ return ret;
+
+ INIT_DELAYED_WORK(&scodec->jack_work, sun8i_codec_jack_work);
+ }
+
regcache_cache_only(scodec->regmap, true);
pm_runtime_enable(&pdev->dev);
if (!pm_runtime_enabled(&pdev->dev)) {
@@ -1389,6 +1657,7 @@ static const struct sun8i_codec_quirks sun8i_a33_quirks = {
static const struct sun8i_codec_quirks sun50i_a64_quirks = {
.bus_clock = true,
+ .jack_detection = true,
};
static const struct of_device_id sun8i_codec_of_match[] = {
--
2.35.3