Unverified Commit 35c8c82f authored by Mark Brown's avatar Mark Brown
Browse files

Add support for jack detection to codec present in

Merge series from Ondřej Jirman <megi@xff.cz>:

This series adds support for jack detection to this codec. I used
and tested this on Pinephone. It works quite nicely. I tested it
against Android headset mic button resistor specification.

The patches are a rewritten and debugged version of the original
ones from Arnaud Ferraris and Samuel Holland, improved to better
handle headset button presses and with more robust plug-in/out
event debouncing, and to use set_jack API instead of sniffing
the sound card widget names, to detect the type of jack connector.
parents fc32f949 21fa98f4
Loading
Loading
Loading
Loading
+61 −12
Original line number Diff line number Diff line
@@ -115,9 +115,16 @@
#define SUN50I_ADDA_HS_MBIAS_CTRL	0x0e
#define SUN50I_ADDA_HS_MBIAS_CTRL_MMICBIASEN	7

#define SUN50I_ADDA_MDET_CTRL		0x1c
#define SUN50I_ADDA_MDET_CTRL_SELDETADC_FS	4
#define SUN50I_ADDA_MDET_CTRL_SELDETADC_DB	2
#define SUN50I_ADDA_MDET_CTRL_SELDETADC_BF	0

#define SUN50I_ADDA_JACK_MIC_CTRL	0x1d
#define SUN50I_ADDA_JACK_MIC_CTRL_JACKDETEN	7
#define SUN50I_ADDA_JACK_MIC_CTRL_INNERRESEN	6
#define SUN50I_ADDA_JACK_MIC_CTRL_HMICBIASEN	5
#define SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN	4

/* mixer controls */
static const struct snd_kcontrol_new sun50i_a64_codec_mixer_controls[] = {
@@ -296,6 +303,19 @@ static const struct snd_kcontrol_new sun50i_codec_earpiece_switch[] = {
			SUN50I_ADDA_EARPIECE_CTRL1_ESPPA_MUTE, 1, 0),
};

static int sun50i_codec_hbias_event(struct snd_soc_dapm_widget *w,
				    struct snd_kcontrol *kcontrol, int event)
{
	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
	u32 value = !!SND_SOC_DAPM_EVENT_ON(event);

	regmap_update_bits(component->regmap, SUN50I_ADDA_JACK_MIC_CTRL,
			   BIT(SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN),
			   value << SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN);

	return 0;
}

static const struct snd_soc_dapm_widget sun50i_a64_codec_widgets[] = {
	/* DAC */
	SND_SOC_DAPM_DAC("Left DAC", NULL, SUN50I_ADDA_MIX_DAC_CTRL,
@@ -367,7 +387,8 @@ static const struct snd_soc_dapm_widget sun50i_a64_codec_widgets[] = {
	/* Microphone Bias */
	SND_SOC_DAPM_SUPPLY("HBIAS", SUN50I_ADDA_JACK_MIC_CTRL,
			    SUN50I_ADDA_JACK_MIC_CTRL_HMICBIASEN,
			    0, NULL, 0),
			    0, sun50i_codec_hbias_event,
			    SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),

	/* Mic input path */
	SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN50I_ADDA_MIC2_CTRL,
@@ -471,17 +492,37 @@ static const struct snd_soc_dapm_route sun50i_a64_codec_routes[] = {
	{ "EARPIECE", NULL, "Earpiece Amp" },
};

static int sun50i_a64_codec_suspend(struct snd_soc_component *component)
static int sun50i_a64_codec_set_bias_level(struct snd_soc_component *component,
					   enum snd_soc_bias_level level)
{
	return regmap_update_bits(component->regmap, SUN50I_ADDA_HP_CTRL,
				  BIT(SUN50I_ADDA_HP_CTRL_PA_CLK_GATE),
	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
	int hbias;

	switch (level) {
	case SND_SOC_BIAS_OFF:
		regmap_clear_bits(component->regmap, SUN50I_ADDA_JACK_MIC_CTRL,
				   BIT(SUN50I_ADDA_JACK_MIC_CTRL_JACKDETEN) |
				   BIT(SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN));

		regmap_set_bits(component->regmap, SUN50I_ADDA_HP_CTRL,
				BIT(SUN50I_ADDA_HP_CTRL_PA_CLK_GATE));
		break;
	case SND_SOC_BIAS_STANDBY:
		regmap_clear_bits(component->regmap, SUN50I_ADDA_HP_CTRL,
				   BIT(SUN50I_ADDA_HP_CTRL_PA_CLK_GATE));

		hbias = snd_soc_dapm_get_pin_status(dapm, "HBIAS");
		regmap_update_bits(component->regmap, SUN50I_ADDA_JACK_MIC_CTRL,
				   BIT(SUN50I_ADDA_JACK_MIC_CTRL_JACKDETEN) |
				   BIT(SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN),
				   BIT(SUN50I_ADDA_JACK_MIC_CTRL_JACKDETEN) |
				   hbias << SUN50I_ADDA_JACK_MIC_CTRL_MICADCEN);
		break;
	default:
		break;
	}

static int sun50i_a64_codec_resume(struct snd_soc_component *component)
{
	return regmap_update_bits(component->regmap, SUN50I_ADDA_HP_CTRL,
				  BIT(SUN50I_ADDA_HP_CTRL_PA_CLK_GATE), 0);
	return 0;
}

static const struct snd_soc_component_driver sun50i_codec_analog_cmpnt_drv = {
@@ -491,8 +532,9 @@ static const struct snd_soc_component_driver sun50i_codec_analog_cmpnt_drv = {
	.num_dapm_widgets	= ARRAY_SIZE(sun50i_a64_codec_widgets),
	.dapm_routes		= sun50i_a64_codec_routes,
	.num_dapm_routes	= ARRAY_SIZE(sun50i_a64_codec_routes),
	.suspend		= sun50i_a64_codec_suspend,
	.resume			= sun50i_a64_codec_resume,
	.set_bias_level		= sun50i_a64_codec_set_bias_level,
	.idle_bias_on		= true,
	.suspend_bias_off	= true,
};

static const struct of_device_id sun50i_codec_analog_of_match[] = {
@@ -527,6 +569,13 @@ static int sun50i_codec_analog_probe(struct platform_device *pdev)
			   BIT(SUN50I_ADDA_JACK_MIC_CTRL_INNERRESEN),
			   enable << SUN50I_ADDA_JACK_MIC_CTRL_INNERRESEN);

	/* Select sample interval of the ADC sample to 16ms */
	regmap_update_bits(regmap, SUN50I_ADDA_MDET_CTRL,
			   0x7 << SUN50I_ADDA_MDET_CTRL_SELDETADC_FS |
			   0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_BF,
			   0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_FS |
			   0x3 << SUN50I_ADDA_MDET_CTRL_SELDETADC_BF);

	return devm_snd_soc_register_component(&pdev->dev,
					       &sun50i_codec_analog_cmpnt_drv,
					       NULL, 0);
+338 −8
Original line number Diff line number Diff line
@@ -12,12 +12,16 @@
#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/mutex.h>
#include <linux/of.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 +122,23 @@
#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_M				12
#define SUN8I_HMIC_CTRL1_HMIC_N				8
#define SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB		5
#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_MDATA_THRESHOLD		8
#define SUN8I_HMIC_CTRL2_HMIC_SF			6
#define SUN8I_HMIC_STS					0x118
#define SUN8I_HMIC_STS_MDATA_DISCARD			13
#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 +164,17 @@
#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_M_MASK		GENMASK(15, 12)
#define SUN8I_HMIC_CTRL1_HMIC_N_MASK		GENMASK(11, 8)
#define SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB_MASK GENMASK(6, 5)
#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|\
				 SND_JACK_BTN_3)

#define SUN8I_CODEC_PASSTHROUGH_SAMPLE_RATE 48000

@@ -177,15 +209,33 @@ struct sun8i_codec_aif {
};

struct sun8i_codec_quirks {
	bool	bus_clock	: 1;
	bool	jack_detection	: 1;
	bool	legacy_widgets	: 1;
	bool	lrck_inversion	: 1;
};

enum {
	SUN8I_JACK_STATUS_DISCONNECTED,
	SUN8I_JACK_STATUS_WAITING_HBIAS,
	SUN8I_JACK_STATUS_CONNECTED,
};

struct sun8i_codec {
	struct snd_soc_component	*component;
	struct regmap			*regmap;
	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_status;
	int				jack_last_sample;
	ktime_t				jack_hbias_ready;
	struct mutex			jack_mutex;
	int				last_hmic_irq;
	unsigned int			sysclk_rate;
	int				sysclk_refcnt;
};
@@ -197,6 +247,14 @@ static int sun8i_codec_runtime_resume(struct device *dev)
	struct sun8i_codec *scodec = dev_get_drvdata(dev);
	int ret;

	if (scodec->clk_bus) {
		ret = clk_prepare_enable(scodec->clk_bus);
		if (ret) {
			dev_err(dev, "Failed to enable the bus clock\n");
			return ret;
		}
	}

	regcache_cache_only(scodec->regmap, false);

	ret = regcache_sync(scodec->regmap);
@@ -215,6 +273,9 @@ static int sun8i_codec_runtime_suspend(struct device *dev)
	regcache_cache_only(scodec->regmap, true);
	regcache_mark_dirty(scodec->regmap);

	if (scodec->clk_bus)
		clk_disable_unprepare(scodec->clk_bus);

	return 0;
}

@@ -1232,6 +1293,8 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
	struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
	int ret;

	scodec->component = component;

	/* 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,
@@ -1268,6 +1331,251 @@ static int sun8i_codec_component_probe(struct snd_soc_component *component)
	return 0;
}

static void sun8i_codec_set_hmic_bias(struct sun8i_codec *scodec, bool enable)
{
	struct snd_soc_dapm_context *dapm = &scodec->component->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);

	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);
	unsigned int mdata;
	int type_mask = scodec->jack->jack->type;
	int type;

	guard(mutex)(&scodec->jack_mutex);

	if (scodec->jack_status == SUN8I_JACK_STATUS_DISCONNECTED) {
		if (scodec->last_hmic_irq != SUN8I_HMIC_STS_JACK_IN_IRQ_ST)
			return;

		scodec->jack_last_sample = -1;

		if (type_mask & SND_JACK_MICROPHONE) {
			/*
			 * If we were in disconnected state, we enable HBIAS and
			 * wait 600ms before reading initial HDATA value.
			 */
			scodec->jack_hbias_ready = ktime_add_ms(ktime_get(), 600);
			sun8i_codec_set_hmic_bias(scodec, true);
			queue_delayed_work(system_power_efficient_wq,
					   &scodec->jack_work,
					   msecs_to_jiffies(610));
			scodec->jack_status = SUN8I_JACK_STATUS_WAITING_HBIAS;
		} else {
			snd_soc_jack_report(scodec->jack, SND_JACK_HEADPHONE,
					    type_mask);
			scodec->jack_status = SUN8I_JACK_STATUS_CONNECTED;
		}
	} else if (scodec->jack_status == SUN8I_JACK_STATUS_WAITING_HBIAS) {
		/*
		 * If we're waiting for HBIAS to stabilize, and we get plug-out
		 * interrupt and nothing more for > 100ms, just cancel the
		 * initialization.
		 */
		if (scodec->last_hmic_irq == SUN8I_HMIC_STS_JACK_OUT_IRQ_ST) {
			scodec->jack_status = SUN8I_JACK_STATUS_DISCONNECTED;
			sun8i_codec_set_hmic_bias(scodec, false);
			return;
		}

		/*
		 * If we're not done waiting for HBIAS to stabilize, wait more.
		 */
		if (!ktime_after(ktime_get(), scodec->jack_hbias_ready)) {
			s64 msecs = ktime_ms_delta(scodec->jack_hbias_ready,
						   ktime_get());

			queue_delayed_work(system_power_efficient_wq,
					   &scodec->jack_work,
					   msecs_to_jiffies(msecs + 10));
			return;
		}

		/*
		 * Everything is stabilized, determine jack type and report it.
		 */
		regmap_read(scodec->regmap, SUN8I_HMIC_STS, &mdata);
		mdata &= SUN8I_HMIC_STS_HMIC_DATA_MASK;
		mdata >>= SUN8I_HMIC_STS_HMIC_DATA;

		regmap_write(scodec->regmap, SUN8I_HMIC_STS, 0);

		type = mdata < 16 ? SND_JACK_HEADPHONE : SND_JACK_HEADSET;
		if (type == SND_JACK_HEADPHONE)
			sun8i_codec_set_hmic_bias(scodec, false);

		snd_soc_jack_report(scodec->jack, type, type_mask);
		scodec->jack_status = SUN8I_JACK_STATUS_CONNECTED;
	} else if (scodec->jack_status == SUN8I_JACK_STATUS_CONNECTED) {
		if (scodec->last_hmic_irq != SUN8I_HMIC_STS_JACK_OUT_IRQ_ST)
			return;

		scodec->jack_status = SUN8I_JACK_STATUS_DISCONNECTED;
		if (type_mask & SND_JACK_MICROPHONE)
			sun8i_codec_set_hmic_bias(scodec, false);

		snd_soc_jack_report(scodec->jack, 0, type_mask);
	}
}

static irqreturn_t sun8i_codec_jack_irq(int irq, void *dev_id)
{
	struct sun8i_codec *scodec = dev_id;
	int type = SND_JACK_HEADSET;
	unsigned int status, value;

	guard(mutex)(&scodec->jack_mutex);

	regmap_read(scodec->regmap, SUN8I_HMIC_STS, &status);
	regmap_write(scodec->regmap, SUN8I_HMIC_STS, status);

	/*
	 * De-bounce in/out interrupts via a delayed work re-scheduling to
	 * 100ms after each interrupt..
	 */
	if (status & BIT(SUN8I_HMIC_STS_JACK_OUT_IRQ_ST)) {
		/*
		 * Out interrupt has priority over in interrupt so that if
		 * we get both, we assume the disconnected state, which is
		 * safer.
		 */
		scodec->last_hmic_irq = SUN8I_HMIC_STS_JACK_OUT_IRQ_ST;
		mod_delayed_work(system_power_efficient_wq, &scodec->jack_work,
				 msecs_to_jiffies(100));
	} else if (status & BIT(SUN8I_HMIC_STS_JACK_IN_IRQ_ST)) {
		scodec->last_hmic_irq = SUN8I_HMIC_STS_JACK_IN_IRQ_ST;
		mod_delayed_work(system_power_efficient_wq, &scodec->jack_work,
				 msecs_to_jiffies(100));
	} else if (status & BIT(SUN8I_HMIC_STS_HMIC_DATA_IRQ_ST)) {
		/*
		 * Ignore data interrupts until jack status turns to connected
		 * state, which is after HMIC enable stabilization is completed.
		 * Until then tha data are bogus.
		 */
		if (scodec->jack_status != SUN8I_JACK_STATUS_CONNECTED)
			return IRQ_HANDLED;

		value = (status & SUN8I_HMIC_STS_HMIC_DATA_MASK) >>
			SUN8I_HMIC_STS_HMIC_DATA;

		/*
		 * Assumes 60 mV per ADC LSB increment, 2V bias voltage, 2.2kOhm
		 * bias resistor.
		 */
		if (value == 0)
			type |= SND_JACK_BTN_0;
		else if (value == 1)
			type |= SND_JACK_BTN_3;
		else if (value <= 3)
			type |= SND_JACK_BTN_1;
		else if (value <= 8)
			type |= SND_JACK_BTN_2;

		/*
		 * De-bounce. Only report button after two consecutive A/D
		 * samples are identical.
		 */
		if (scodec->jack_last_sample >= 0 &&
		    scodec->jack_last_sample == value)
			snd_soc_jack_report(scodec->jack, type,
					    scodec->jack->jack->type);

		scodec->jack_last_sample = value;
	}

	return IRQ_HANDLED;
}

static int sun8i_codec_enable_jack_detect(struct snd_soc_component *component,
					  struct snd_soc_jack *jack, void *data)
{
	struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);
	struct platform_device *pdev = to_platform_device(component->dev);
	int ret;

	if (!scodec->quirks->jack_detection)
		return 0;

	scodec->jack = jack;

	scodec->jack_irq = platform_get_irq(pdev, 0);
	if (scodec->jack_irq < 0)
		return scodec->jack_irq;

	/* Reserved value required for jack IRQs to trigger. */
	regmap_write(scodec->regmap, SUN8I_HMIC_CTRL1,
			   0xf << SUN8I_HMIC_CTRL1_HMIC_N |
			   0x0 << SUN8I_HMIC_CTRL1_MDATA_THRESHOLD_DB |
			   0x4 << SUN8I_HMIC_CTRL1_HMIC_M);

	/* Sample the ADC at 128 Hz; bypass smooth filter. */
	regmap_write(scodec->regmap, SUN8I_HMIC_CTRL2,
			   0x0 << SUN8I_HMIC_CTRL2_HMIC_SAMPLE |
			   0x17 << SUN8I_HMIC_CTRL2_HMIC_MDATA_THRESHOLD |
			   0x0 << SUN8I_HMIC_CTRL2_HMIC_SF);

	/* Do not discard any MDATA, enable user written MDATA threshold. */
	regmap_write(scodec->regmap, SUN8I_HMIC_STS, 0);

	regmap_set_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
			BIT(SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN) |
			BIT(SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN));

	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;

	return 0;
}

static void sun8i_codec_disable_jack_detect(struct snd_soc_component *component)
{
	struct sun8i_codec *scodec = snd_soc_component_get_drvdata(component);

	if (!scodec->quirks->jack_detection)
		return;

	devm_free_irq(component->dev, scodec->jack_irq, scodec);

	cancel_delayed_work_sync(&scodec->jack_work);

	regmap_clear_bits(scodec->regmap, SUN8I_HMIC_CTRL1,
			  BIT(SUN8I_HMIC_CTRL1_JACK_OUT_IRQ_EN) |
			  BIT(SUN8I_HMIC_CTRL1_JACK_IN_IRQ_EN) |
			  BIT(SUN8I_HMIC_CTRL1_HMIC_DATA_IRQ_EN));

	scodec->jack = NULL;
}

static int sun8i_codec_component_set_jack(struct snd_soc_component *component,
					  struct snd_soc_jack *jack, void *data)
{
	int ret = 0;

	if (jack)
		ret = sun8i_codec_enable_jack_detect(component, jack, data);
	else
		sun8i_codec_disable_jack_detect(component);

	return ret;
}

static const struct snd_soc_component_driver sun8i_soc_component = {
	.controls		= sun8i_codec_controls,
	.num_controls		= ARRAY_SIZE(sun8i_codec_controls),
@@ -1275,15 +1583,23 @@ static const struct snd_soc_component_driver sun8i_soc_component = {
	.num_dapm_widgets	= ARRAY_SIZE(sun8i_codec_dapm_widgets),
	.dapm_routes		= sun8i_codec_dapm_routes,
	.num_dapm_routes	= ARRAY_SIZE(sun8i_codec_dapm_routes),
	.set_jack		= sun8i_codec_component_set_jack,
	.probe			= sun8i_codec_component_probe,
	.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,
@@ -1299,6 +1615,20 @@ static int sun8i_codec_probe(struct platform_device *pdev)
	if (!scodec)
		return -ENOMEM;

	scodec->quirks = of_device_get_match_data(&pdev->dev);
	INIT_DELAYED_WORK(&scodec->jack_work, sun8i_codec_jack_work);
	mutex_init(&scodec->jack_mutex);

	platform_set_drvdata(pdev, scodec);

	if (scodec->quirks->bus_clock) {
		scodec->clk_bus = devm_clk_get(&pdev->dev, "bus");
		if (IS_ERR(scodec->clk_bus)) {
			dev_err(&pdev->dev, "Failed to get the bus clock\n");
			return PTR_ERR(scodec->clk_bus);
		}
	}

	scodec->clk_module = devm_clk_get(&pdev->dev, "mod");
	if (IS_ERR(scodec->clk_module)) {
		dev_err(&pdev->dev, "Failed to get the module clock\n");
@@ -1311,17 +1641,14 @@ static int sun8i_codec_probe(struct platform_device *pdev)
		return PTR_ERR(base);
	}

	scodec->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "bus", base,
	scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
					       &sun8i_codec_regmap_config);
	if (IS_ERR(scodec->regmap)) {
		dev_err(&pdev->dev, "Failed to create our regmap\n");
		return PTR_ERR(scodec->regmap);
	}

	scodec->quirks = of_device_get_match_data(&pdev->dev);

	platform_set_drvdata(pdev, scodec);

	regcache_cache_only(scodec->regmap, true);
	pm_runtime_enable(&pdev->dev);
	if (!pm_runtime_enabled(&pdev->dev)) {
		ret = sun8i_codec_runtime_resume(&pdev->dev);
@@ -1357,11 +1684,14 @@ static void sun8i_codec_remove(struct platform_device *pdev)
}

static const struct sun8i_codec_quirks sun8i_a33_quirks = {
	.bus_clock	= true,
	.legacy_widgets	= true,
	.lrck_inversion	= true,
};

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[] = {