[PATCH v1 5/6] ASoC: codecs: rk3308: report B/C headphone detection through set_jack

Xing Zheng zhengxing at rock-chips.com
Sat Oct 31 21:01:24 PDT 2026


Use the detection GRF interrupt to report headphone insertion and
removal on codec revisions B and C. Keep detection optional for existing
device trees and retain index-based IRQ lookup for trees without
interrupt names.

Enable the detector only while a machine driver has registered a jack.
Cancel its work and disable its IRQ before releasing the jack. Keep the
revision-C DAC clock running while monitoring, and restore normal DAPM
clock ownership when monitoring stops.

Report jack status without changing codec routes from the worker: jack
pin associations and output policy belong to the machine driver.

Signed-off-by: Xing Zheng <zhengxing at rock-chips.com>
---
 sound/soc/codecs/rk3308_codec.c | 225 ++++++++++++++++++++++++++++++++
 1 file changed, 225 insertions(+)

diff --git a/sound/soc/codecs/rk3308_codec.c b/sound/soc/codecs/rk3308_codec.c
index 319b2391dd9b..4e3e38a8571f 100644
--- a/sound/soc/codecs/rk3308_codec.c
+++ b/sound/soc/codecs/rk3308_codec.c
@@ -10,15 +10,19 @@
 #include <linux/device.h>
 #include <linux/delay.h>
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/mfd/syscon.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/regmap.h>
 #include <linux/reset.h>
 #include <linux/util_macros.h>
+#include <linux/workqueue.h>
 #include <sound/core.h>
+#include <sound/jack.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
@@ -31,6 +35,14 @@
 #define GRF_SOC_CON1			0x304
 #define GRF_CHIP_ID			0x800
 
+#define RK3308_HPDET_COUNTER		0x0030
+#define RK3308_HPDET_CON			0x0034
+#define RK3308_HPDET_STATUS		0x0038
+#define RK3308_HPDET_STATUS_CLR		0x003c
+#define RK3308_HPDET_EDGES		(BIT(0) | BIT(1))
+/* 200 ms at the detection block's 100 MHz clock. */
+#define RK3308_HPDET_FILTER_CYCLES	20000000
+
 enum {
 	ACODEC_VERSION_A = 'A',
 	ACODEC_VERSION_B,
@@ -49,6 +61,17 @@ struct rk3308_codec_priv {
 	unsigned char codec_ver;
 	u32 micbias_level;
 	u32 adc_grps[ADC_LR_GROUP_MAX];
+	struct regmap *detect_grf;
+	/* Serialize detector configuration and jack lifetime changes. */
+	struct mutex hpdet_lock;
+	struct snd_soc_jack *hpdet_jack;
+	struct delayed_work hpdet_work;
+	unsigned int hpdet_status;
+	int hpdet_irq;
+	bool hpdet_enabled;
+	bool hpdet_seen_irq;
+	bool hpdet_clock_forced;
+	bool hp_jack_reversed;
 };
 
 static struct clk_bulk_data rk3308_codec_clocks[] = {
@@ -1015,7 +1038,205 @@ static int rk3308_codec_set_bias_level(struct snd_soc_component *component,
 	return 0;
 }
 
+static bool rk3308_codec_hpdet_inserted(struct rk3308_codec_priv *rk3308,
+					unsigned int status)
+{
+	status &= RK3308_HPDET_EDGES;
+	if (rk3308->hp_jack_reversed)
+		return status == 0 || status == BIT(1);
+
+	return status == BIT(0);
+}
+
+static void rk3308_codec_hpdet_work(struct work_struct *work)
+{
+	struct rk3308_codec_priv *rk3308 =
+		container_of(to_delayed_work(work), struct rk3308_codec_priv, hpdet_work);
+	unsigned int status;
+	int ret;
+
+	if (!rk3308->component->card->instantiated) {
+		mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+				 msecs_to_jiffies(100));
+		return;
+	}
+
+	if (READ_ONCE(rk3308->hpdet_seen_irq)) {
+		status = READ_ONCE(rk3308->hpdet_status);
+	} else {
+		ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+		if (ret)
+			return;
+	}
+
+	/* The machine driver owns the jack pins and output-routing policy. */
+	snd_soc_jack_report(rk3308->hpdet_jack,
+			    rk3308_codec_hpdet_inserted(rk3308, status) ?
+			    SND_JACK_HEADPHONE : 0, SND_JACK_HEADPHONE);
+}
+
+static irqreturn_t rk3308_codec_hpdet_irq(int irq, void *data)
+{
+	struct rk3308_codec_priv *rk3308 = data;
+	unsigned int status;
+	int ret;
+
+	ret = regmap_read(rk3308->detect_grf, RK3308_HPDET_STATUS, &status);
+	if (ret || !(status & RK3308_HPDET_EDGES))
+		return IRQ_NONE;
+
+	ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+			   status & RK3308_HPDET_EDGES);
+	if (ret)
+		return IRQ_HANDLED;
+
+	WRITE_ONCE(rk3308->hpdet_status, status);
+	WRITE_ONCE(rk3308->hpdet_seen_irq, true);
+	mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+			 msecs_to_jiffies(10));
+
+	return IRQ_HANDLED;
+}
+
+static int rk3308_codec_hpdet_clock(struct rk3308_codec_priv *rk3308, bool on)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(rk3308->component);
+	int ret;
+
+	if (rk3308->codec_ver != ACODEC_VERSION_C || on == rk3308->hpdet_clock_forced)
+		return 0;
+
+	/* Revision C needs the DAC clock while the detector is monitoring. */
+	snd_soc_dapm_mutex_lock(dapm);
+	if (on) {
+		ret = snd_soc_dapm_force_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+	} else {
+		ret = snd_soc_dapm_disable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+		if (!ret)
+			ret = snd_soc_dapm_enable_pin_unlocked(dapm, "DAC_MCLK_GATE");
+	}
+	if (!ret) {
+		rk3308->hpdet_clock_forced = on;
+		ret = snd_soc_dapm_sync_unlocked(dapm);
+	}
+	snd_soc_dapm_mutex_unlock(dapm);
+
+	return ret;
+}
+
+static int rk3308_codec_set_jack(struct snd_soc_component *component,
+				 struct snd_soc_jack *jack, void *data)
+{
+	struct rk3308_codec_priv *rk3308 = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (!rk3308->detect_grf)
+		return jack ? -EOPNOTSUPP : 0;
+
+	mutex_lock(&rk3308->hpdet_lock);
+	if (rk3308->hpdet_enabled) {
+		disable_irq(rk3308->hpdet_irq);
+		rk3308->hpdet_enabled = false;
+	}
+	cancel_delayed_work_sync(&rk3308->hpdet_work);
+	rk3308->hpdet_jack = NULL;
+
+	ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+			   RK3308_HPDET_EDGES << 16);
+	if (ret)
+		goto disable;
+	ret = regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+				RK3308_DAC_HEADPHONE_DET_EN);
+	if (ret || !jack)
+		goto disable;
+
+	ret = rk3308_codec_hpdet_clock(rk3308, true);
+	if (ret)
+		goto disable;
+	ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_COUNTER,
+			   RK3308_HPDET_FILTER_CYCLES);
+	if (ret)
+		goto disable;
+	ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_STATUS_CLR,
+			   RK3308_HPDET_EDGES);
+	if (ret)
+		goto disable;
+	ret = regmap_set_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+			      RK3308_DAC_HEADPHONE_DET_EN);
+	if (ret)
+		goto disable;
+	ret = regmap_write(rk3308->detect_grf, RK3308_HPDET_CON,
+			   (RK3308_HPDET_EDGES << 16) | RK3308_HPDET_EDGES);
+	if (ret)
+		goto disable;
+
+	rk3308->hpdet_jack = jack;
+	WRITE_ONCE(rk3308->hpdet_seen_irq, false);
+	rk3308->hpdet_enabled = true;
+	/* Wait for the 200 ms hardware filter before the initial report. */
+	mod_delayed_work(system_power_efficient_wq, &rk3308->hpdet_work,
+			 msecs_to_jiffies(210));
+	enable_irq(rk3308->hpdet_irq);
+	mutex_unlock(&rk3308->hpdet_lock);
+
+	return 0;
+
+disable:
+	regmap_write(rk3308->detect_grf, RK3308_HPDET_CON, RK3308_HPDET_EDGES << 16);
+	regmap_clear_bits(rk3308->regmap, RK3308_DAC_ANA_CON00,
+			  RK3308_DAC_HEADPHONE_DET_EN);
+	if (ret)
+		rk3308_codec_hpdet_clock(rk3308, false);
+	else
+		ret = rk3308_codec_hpdet_clock(rk3308, false);
+	mutex_unlock(&rk3308->hpdet_lock);
+
+	return ret;
+}
+
+static void rk3308_codec_remove(struct snd_soc_component *component)
+{
+	rk3308_codec_set_jack(component, NULL, NULL);
+}
+
+static int rk3308_codec_hpdet_init(struct platform_device *pdev,
+				   struct rk3308_codec_priv *rk3308)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	if (rk3308->codec_ver == ACODEC_VERSION_A ||
+	    of_property_read_bool(np, "rockchip,no-hp-det") ||
+	    !of_find_property(np, "rockchip,detect-grf", NULL))
+		return 0;
+
+	rk3308->detect_grf = syscon_regmap_lookup_by_phandle(np, "rockchip,detect-grf");
+	if (IS_ERR(rk3308->detect_grf))
+		return dev_err_probe(dev, PTR_ERR(rk3308->detect_grf),
+				     "Failed to get detection GRF\n");
+
+	if (of_find_property(np, "interrupt-names", NULL))
+		rk3308->hpdet_irq = platform_get_irq_byname(pdev, "hpdet");
+	else
+		rk3308->hpdet_irq = platform_get_irq(pdev, 1);
+	if (rk3308->hpdet_irq < 0)
+		return rk3308->hpdet_irq;
+
+	mutex_init(&rk3308->hpdet_lock);
+	INIT_DELAYED_WORK(&rk3308->hpdet_work, rk3308_codec_hpdet_work);
+	rk3308->hp_jack_reversed = of_property_read_bool(np, "rockchip,hp-jack-reversed");
+	ret = devm_request_threaded_irq(dev, rk3308->hpdet_irq, NULL, rk3308_codec_hpdet_irq,
+					IRQF_ONESHOT | IRQF_NO_AUTOEN, "rk3308-hpdet", rk3308);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request headphone IRQ\n");
+
+	return 0;
+}
+
 static const struct snd_soc_component_driver rk3308_codec_component_driver = {
+	.remove = rk3308_codec_remove,
+	.set_jack = rk3308_codec_set_jack,
 	.probe = rk3308_codec_probe,
 	.set_bias_level = rk3308_codec_set_bias_level,
 	.controls = rk3308_codec_controls,
@@ -1175,6 +1396,10 @@ static int rk3308_codec_platform_probe(struct platform_device *pdev)
 	if (err)
 		return err;
 
+	err = rk3308_codec_hpdet_init(pdev, rk3308);
+	if (err)
+		return err;
+
 	err = devm_snd_soc_register_component(dev, &rk3308_codec_component_driver,
 					      &rk3308_codec_dai_driver, 1);
 	if (err)



More information about the linux-arm-kernel mailing list