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

From: Xing Zheng

Date: Sun Nov 01 2026 - 00:04:34 EST


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@xxxxxxxxxxxxxx>
---
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)