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.
This commit is contained in:
commit
35c8c82fa0
@ -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),
|
||||
BIT(SUN50I_ADDA_HP_CTRL_PA_CLK_GATE));
|
||||
}
|
||||
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
|
||||
int hbias;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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 legacy_widgets : 1;
|
||||
bool lrck_inversion : 1;
|
||||
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,
|
||||
&sun8i_codec_regmap_config);
|
||||
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[] = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user