406 lines
12 KiB
Diff
406 lines
12 KiB
Diff
From 7ddc99c30e086ed9e5c632474827d4b3cd733599 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 322/351] 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 afb14986c03f..6128d861df90 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, NULL, 0);
|
|
+ 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,21 +1431,131 @@ 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,
|
|
.non_legacy_dai_naming = 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;
|
|
@@ -1345,6 +1597,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)) {
|
|
@@ -1390,6 +1658,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.34.0
|
|
|