| /* | |
| * da7219-aad.c - Dialog DA7219 ALSA SoC AAD Driver | |
| * | |
| * Copyright (c) 2015 Dialog Semiconductor Ltd. | |
| * | |
| * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License as published by the | |
| * Free Software Foundation; either version 2 of the License, or (at your | |
| * option) any later version. | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/clk.h> | |
| #include <linux/i2c.h> | |
| #include <linux/property.h> | |
| #include <linux/pm_wakeirq.h> | |
| #include <linux/slab.h> | |
| #include <linux/delay.h> | |
| #include <linux/workqueue.h> | |
| #include <sound/soc.h> | |
| #include <sound/jack.h> | |
| #include <sound/da7219.h> | |
| #include "da7219.h" | |
| #include "da7219-aad.h" | |
| /* | |
| * Detection control | |
| */ | |
| void da7219_aad_jack_det(struct snd_soc_codec *codec, struct snd_soc_jack *jack) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| da7219->aad->jack = jack; | |
| da7219->aad->jack_inserted = false; | |
| /* Send an initial empty report */ | |
| snd_soc_jack_report(jack, 0, DA7219_AAD_REPORT_ALL_MASK); | |
| /* Enable/Disable jack detection */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_ACCDET_EN_MASK, | |
| (jack ? DA7219_ACCDET_EN_MASK : 0)); | |
| } | |
| EXPORT_SYMBOL_GPL(da7219_aad_jack_det); | |
| /* | |
| * Button/HPTest work | |
| */ | |
| static void da7219_aad_btn_det_work(struct work_struct *work) | |
| { | |
| struct da7219_aad_priv *da7219_aad = | |
| container_of(work, struct da7219_aad_priv, btn_det_work); | |
| struct snd_soc_codec *codec = da7219_aad->codec; | |
| struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); | |
| u8 statusa, micbias_ctrl; | |
| bool micbias_up = false; | |
| int retries = 0; | |
| /* Drive headphones/lineout */ | |
| snd_soc_update_bits(codec, DA7219_HP_L_CTRL, | |
| DA7219_HP_L_AMP_OE_MASK, | |
| DA7219_HP_L_AMP_OE_MASK); | |
| snd_soc_update_bits(codec, DA7219_HP_R_CTRL, | |
| DA7219_HP_R_AMP_OE_MASK, | |
| DA7219_HP_R_AMP_OE_MASK); | |
| /* Make sure mic bias is up */ | |
| snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); | |
| snd_soc_dapm_sync(dapm); | |
| do { | |
| statusa = snd_soc_read(codec, DA7219_ACCDET_STATUS_A); | |
| if (statusa & DA7219_MICBIAS_UP_STS_MASK) | |
| micbias_up = true; | |
| else if (retries++ < DA7219_AAD_MICBIAS_CHK_RETRIES) | |
| msleep(DA7219_AAD_MICBIAS_CHK_DELAY); | |
| } while ((!micbias_up) && (retries < DA7219_AAD_MICBIAS_CHK_RETRIES)); | |
| if (retries >= DA7219_AAD_MICBIAS_CHK_RETRIES) | |
| dev_warn(codec->dev, "Mic bias status check timed out"); | |
| /* | |
| * Mic bias pulse required to enable mic, must be done before enabling | |
| * button detection to prevent erroneous button readings. | |
| */ | |
| if (da7219_aad->micbias_pulse_lvl && da7219_aad->micbias_pulse_time) { | |
| /* Pulse higher level voltage */ | |
| micbias_ctrl = snd_soc_read(codec, DA7219_MICBIAS_CTRL); | |
| snd_soc_update_bits(codec, DA7219_MICBIAS_CTRL, | |
| DA7219_MICBIAS1_LEVEL_MASK, | |
| da7219_aad->micbias_pulse_lvl); | |
| msleep(da7219_aad->micbias_pulse_time); | |
| snd_soc_write(codec, DA7219_MICBIAS_CTRL, micbias_ctrl); | |
| } | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_BUTTON_CONFIG_MASK, | |
| da7219_aad->btn_cfg); | |
| } | |
| static void da7219_aad_hptest_work(struct work_struct *work) | |
| { | |
| struct da7219_aad_priv *da7219_aad = | |
| container_of(work, struct da7219_aad_priv, hptest_work); | |
| struct snd_soc_codec *codec = da7219_aad->codec; | |
| struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| u16 tonegen_freq_hptest; | |
| u8 pll_srm_sts, gain_ramp_ctrl, accdet_cfg8; | |
| int report = 0, ret = 0; | |
| /* Lock DAPM and any Kcontrols that are affected by this test */ | |
| snd_soc_dapm_mutex_lock(dapm); | |
| mutex_lock(&da7219->lock); | |
| /* Ensure MCLK is available for HP test procedure */ | |
| if (da7219->mclk) { | |
| ret = clk_prepare_enable(da7219->mclk); | |
| if (ret) { | |
| dev_err(codec->dev, "Failed to enable mclk - %d\n", ret); | |
| mutex_unlock(&da7219->lock); | |
| snd_soc_dapm_mutex_unlock(dapm); | |
| return; | |
| } | |
| } | |
| /* | |
| * If MCLK not present, then we're using the internal oscillator and | |
| * require different frequency settings to achieve the same result. | |
| */ | |
| pll_srm_sts = snd_soc_read(codec, DA7219_PLL_SRM_STS); | |
| if (pll_srm_sts & DA7219_PLL_SRM_STS_MCLK) | |
| tonegen_freq_hptest = cpu_to_le16(DA7219_AAD_HPTEST_RAMP_FREQ); | |
| else | |
| tonegen_freq_hptest = cpu_to_le16(DA7219_AAD_HPTEST_RAMP_FREQ_INT_OSC); | |
| /* Ensure gain ramping at fastest rate */ | |
| gain_ramp_ctrl = snd_soc_read(codec, DA7219_GAIN_RAMP_CTRL); | |
| snd_soc_write(codec, DA7219_GAIN_RAMP_CTRL, DA7219_GAIN_RAMP_RATE_X8); | |
| /* Bypass cache so it saves current settings */ | |
| regcache_cache_bypass(da7219->regmap, true); | |
| /* Make sure Tone Generator is disabled */ | |
| snd_soc_write(codec, DA7219_TONE_GEN_CFG1, 0); | |
| /* Enable HPTest block, 1KOhms check */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_8, | |
| DA7219_HPTEST_EN_MASK | DA7219_HPTEST_RES_SEL_MASK, | |
| DA7219_HPTEST_EN_MASK | | |
| DA7219_HPTEST_RES_SEL_1KOHMS); | |
| /* Set gains to 0db */ | |
| snd_soc_write(codec, DA7219_DAC_L_GAIN, DA7219_DAC_DIGITAL_GAIN_0DB); | |
| snd_soc_write(codec, DA7219_DAC_R_GAIN, DA7219_DAC_DIGITAL_GAIN_0DB); | |
| snd_soc_write(codec, DA7219_HP_L_GAIN, DA7219_HP_AMP_GAIN_0DB); | |
| snd_soc_write(codec, DA7219_HP_R_GAIN, DA7219_HP_AMP_GAIN_0DB); | |
| /* Disable DAC filters, EQs and soft mute */ | |
| snd_soc_update_bits(codec, DA7219_DAC_FILTERS1, DA7219_HPF_MODE_MASK, | |
| 0); | |
| snd_soc_update_bits(codec, DA7219_DAC_FILTERS4, DA7219_DAC_EQ_EN_MASK, | |
| 0); | |
| snd_soc_update_bits(codec, DA7219_DAC_FILTERS5, | |
| DA7219_DAC_SOFTMUTE_EN_MASK, 0); | |
| /* Enable HP left & right paths */ | |
| snd_soc_update_bits(codec, DA7219_CP_CTRL, DA7219_CP_EN_MASK, | |
| DA7219_CP_EN_MASK); | |
| snd_soc_update_bits(codec, DA7219_DIG_ROUTING_DAC, | |
| DA7219_DAC_L_SRC_MASK | DA7219_DAC_R_SRC_MASK, | |
| DA7219_DAC_L_SRC_TONEGEN | | |
| DA7219_DAC_R_SRC_TONEGEN); | |
| snd_soc_update_bits(codec, DA7219_DAC_L_CTRL, | |
| DA7219_DAC_L_EN_MASK | DA7219_DAC_L_MUTE_EN_MASK, | |
| DA7219_DAC_L_EN_MASK); | |
| snd_soc_update_bits(codec, DA7219_DAC_R_CTRL, | |
| DA7219_DAC_R_EN_MASK | DA7219_DAC_R_MUTE_EN_MASK, | |
| DA7219_DAC_R_EN_MASK); | |
| snd_soc_update_bits(codec, DA7219_MIXOUT_L_SELECT, | |
| DA7219_MIXOUT_L_MIX_SELECT_MASK, | |
| DA7219_MIXOUT_L_MIX_SELECT_MASK); | |
| snd_soc_update_bits(codec, DA7219_MIXOUT_R_SELECT, | |
| DA7219_MIXOUT_R_MIX_SELECT_MASK, | |
| DA7219_MIXOUT_R_MIX_SELECT_MASK); | |
| snd_soc_update_bits(codec, DA7219_DROUTING_ST_OUTFILT_1L, | |
| DA7219_OUTFILT_ST_1L_SRC_MASK, | |
| DA7219_DMIX_ST_SRC_OUTFILT1L); | |
| snd_soc_update_bits(codec, DA7219_DROUTING_ST_OUTFILT_1R, | |
| DA7219_OUTFILT_ST_1R_SRC_MASK, | |
| DA7219_DMIX_ST_SRC_OUTFILT1R); | |
| snd_soc_update_bits(codec, DA7219_MIXOUT_L_CTRL, | |
| DA7219_MIXOUT_L_AMP_EN_MASK, | |
| DA7219_MIXOUT_L_AMP_EN_MASK); | |
| snd_soc_update_bits(codec, DA7219_MIXOUT_R_CTRL, | |
| DA7219_MIXOUT_R_AMP_EN_MASK, | |
| DA7219_MIXOUT_R_AMP_EN_MASK); | |
| snd_soc_write(codec, DA7219_HP_L_CTRL, | |
| DA7219_HP_L_AMP_OE_MASK | DA7219_HP_L_AMP_EN_MASK); | |
| snd_soc_write(codec, DA7219_HP_R_CTRL, | |
| DA7219_HP_R_AMP_OE_MASK | DA7219_HP_R_AMP_EN_MASK); | |
| /* | |
| * If we're running from the internal oscillator then give audio paths | |
| * time to settle before running test. | |
| */ | |
| if (!(pll_srm_sts & DA7219_PLL_SRM_STS_MCLK)) | |
| msleep(DA7219_AAD_HPTEST_INT_OSC_PATH_DELAY); | |
| /* Configure & start Tone Generator */ | |
| snd_soc_write(codec, DA7219_TONE_GEN_ON_PER, DA7219_BEEP_ON_PER_MASK); | |
| regmap_raw_write(da7219->regmap, DA7219_TONE_GEN_FREQ1_L, | |
| &tonegen_freq_hptest, sizeof(tonegen_freq_hptest)); | |
| snd_soc_update_bits(codec, DA7219_TONE_GEN_CFG2, | |
| DA7219_SWG_SEL_MASK | DA7219_TONE_GEN_GAIN_MASK, | |
| DA7219_SWG_SEL_SRAMP | | |
| DA7219_TONE_GEN_GAIN_MINUS_15DB); | |
| snd_soc_write(codec, DA7219_TONE_GEN_CFG1, DA7219_START_STOPN_MASK); | |
| msleep(DA7219_AAD_HPTEST_PERIOD); | |
| /* Grab comparator reading */ | |
| accdet_cfg8 = snd_soc_read(codec, DA7219_ACCDET_CONFIG_8); | |
| if (accdet_cfg8 & DA7219_HPTEST_COMP_MASK) | |
| report |= SND_JACK_HEADPHONE; | |
| else | |
| report |= SND_JACK_LINEOUT; | |
| /* Stop tone generator */ | |
| snd_soc_write(codec, DA7219_TONE_GEN_CFG1, 0); | |
| msleep(DA7219_AAD_HPTEST_PERIOD); | |
| /* Restore original settings from cache */ | |
| regcache_mark_dirty(da7219->regmap); | |
| regcache_sync_region(da7219->regmap, DA7219_HP_L_CTRL, | |
| DA7219_HP_R_CTRL); | |
| regcache_sync_region(da7219->regmap, DA7219_MIXOUT_L_CTRL, | |
| DA7219_MIXOUT_R_CTRL); | |
| regcache_sync_region(da7219->regmap, DA7219_DROUTING_ST_OUTFILT_1L, | |
| DA7219_DROUTING_ST_OUTFILT_1R); | |
| regcache_sync_region(da7219->regmap, DA7219_MIXOUT_L_SELECT, | |
| DA7219_MIXOUT_R_SELECT); | |
| regcache_sync_region(da7219->regmap, DA7219_DAC_L_CTRL, | |
| DA7219_DAC_R_CTRL); | |
| regcache_sync_region(da7219->regmap, DA7219_DIG_ROUTING_DAC, | |
| DA7219_DIG_ROUTING_DAC); | |
| regcache_sync_region(da7219->regmap, DA7219_CP_CTRL, DA7219_CP_CTRL); | |
| regcache_sync_region(da7219->regmap, DA7219_DAC_FILTERS5, | |
| DA7219_DAC_FILTERS5); | |
| regcache_sync_region(da7219->regmap, DA7219_DAC_FILTERS4, | |
| DA7219_DAC_FILTERS1); | |
| regcache_sync_region(da7219->regmap, DA7219_HP_L_GAIN, | |
| DA7219_HP_R_GAIN); | |
| regcache_sync_region(da7219->regmap, DA7219_DAC_L_GAIN, | |
| DA7219_DAC_R_GAIN); | |
| regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_ON_PER, | |
| DA7219_TONE_GEN_ON_PER); | |
| regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_FREQ1_L, | |
| DA7219_TONE_GEN_FREQ1_U); | |
| regcache_sync_region(da7219->regmap, DA7219_TONE_GEN_CFG1, | |
| DA7219_TONE_GEN_CFG2); | |
| regcache_cache_bypass(da7219->regmap, false); | |
| /* Disable HPTest block */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_8, | |
| DA7219_HPTEST_EN_MASK, 0); | |
| /* | |
| * If we're running from the internal oscillator then give audio paths | |
| * time to settle before allowing headphones to be driven as required. | |
| */ | |
| if (!(pll_srm_sts & DA7219_PLL_SRM_STS_MCLK)) | |
| msleep(DA7219_AAD_HPTEST_INT_OSC_PATH_DELAY); | |
| /* Restore gain ramping rate */ | |
| snd_soc_write(codec, DA7219_GAIN_RAMP_CTRL, gain_ramp_ctrl); | |
| /* Drive Headphones/lineout */ | |
| snd_soc_update_bits(codec, DA7219_HP_L_CTRL, DA7219_HP_L_AMP_OE_MASK, | |
| DA7219_HP_L_AMP_OE_MASK); | |
| snd_soc_update_bits(codec, DA7219_HP_R_CTRL, DA7219_HP_R_AMP_OE_MASK, | |
| DA7219_HP_R_AMP_OE_MASK); | |
| /* Remove MCLK, if previously enabled */ | |
| if (da7219->mclk) | |
| clk_disable_unprepare(da7219->mclk); | |
| mutex_unlock(&da7219->lock); | |
| snd_soc_dapm_mutex_unlock(dapm); | |
| /* | |
| * Only send report if jack hasn't been removed during process, | |
| * otherwise it's invalid and we drop it. | |
| */ | |
| if (da7219_aad->jack_inserted) | |
| snd_soc_jack_report(da7219_aad->jack, report, | |
| SND_JACK_HEADSET | SND_JACK_LINEOUT); | |
| } | |
| /* | |
| * IRQ | |
| */ | |
| static irqreturn_t da7219_aad_irq_thread(int irq, void *data) | |
| { | |
| struct da7219_aad_priv *da7219_aad = data; | |
| struct snd_soc_codec *codec = da7219_aad->codec; | |
| struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| u8 events[DA7219_AAD_IRQ_REG_MAX]; | |
| u8 statusa; | |
| int i, report = 0, mask = 0; | |
| /* Read current IRQ events */ | |
| regmap_bulk_read(da7219->regmap, DA7219_ACCDET_IRQ_EVENT_A, | |
| events, DA7219_AAD_IRQ_REG_MAX); | |
| if (!events[DA7219_AAD_IRQ_REG_A] && !events[DA7219_AAD_IRQ_REG_B]) | |
| return IRQ_NONE; | |
| /* Read status register for jack insertion & type status */ | |
| statusa = snd_soc_read(codec, DA7219_ACCDET_STATUS_A); | |
| /* Clear events */ | |
| regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_EVENT_A, | |
| events, DA7219_AAD_IRQ_REG_MAX); | |
| dev_dbg(codec->dev, "IRQ events = 0x%x|0x%x, status = 0x%x\n", | |
| events[DA7219_AAD_IRQ_REG_A], events[DA7219_AAD_IRQ_REG_B], | |
| statusa); | |
| if (statusa & DA7219_JACK_INSERTION_STS_MASK) { | |
| /* Jack Insertion */ | |
| if (events[DA7219_AAD_IRQ_REG_A] & | |
| DA7219_E_JACK_INSERTED_MASK) { | |
| report |= SND_JACK_MECHANICAL; | |
| mask |= SND_JACK_MECHANICAL; | |
| da7219_aad->jack_inserted = true; | |
| } | |
| /* Jack type detection */ | |
| if (events[DA7219_AAD_IRQ_REG_A] & | |
| DA7219_E_JACK_DETECT_COMPLETE_MASK) { | |
| /* | |
| * If 4-pole, then enable button detection, else perform | |
| * HP impedance test to determine output type to report. | |
| * | |
| * We schedule work here as the tasks themselves can | |
| * take time to complete, and in particular for hptest | |
| * we want to be able to check if the jack was removed | |
| * during the procedure as this will invalidate the | |
| * result. By doing this as work, the IRQ thread can | |
| * handle a removal, and we can check at the end of | |
| * hptest if we have a valid result or not. | |
| */ | |
| if (statusa & DA7219_JACK_TYPE_STS_MASK) { | |
| report |= SND_JACK_HEADSET; | |
| mask |= SND_JACK_HEADSET | SND_JACK_LINEOUT; | |
| schedule_work(&da7219_aad->btn_det_work); | |
| } else { | |
| schedule_work(&da7219_aad->hptest_work); | |
| } | |
| } | |
| /* Button support for 4-pole jack */ | |
| if (statusa & DA7219_JACK_TYPE_STS_MASK) { | |
| for (i = 0; i < DA7219_AAD_MAX_BUTTONS; ++i) { | |
| /* Button Press */ | |
| if (events[DA7219_AAD_IRQ_REG_B] & | |
| (DA7219_E_BUTTON_A_PRESSED_MASK << i)) { | |
| report |= SND_JACK_BTN_0 >> i; | |
| mask |= SND_JACK_BTN_0 >> i; | |
| } | |
| } | |
| snd_soc_jack_report(da7219_aad->jack, report, mask); | |
| for (i = 0; i < DA7219_AAD_MAX_BUTTONS; ++i) { | |
| /* Button Release */ | |
| if (events[DA7219_AAD_IRQ_REG_B] & | |
| (DA7219_E_BUTTON_A_RELEASED_MASK >> i)) { | |
| report &= ~(SND_JACK_BTN_0 >> i); | |
| mask |= SND_JACK_BTN_0 >> i; | |
| } | |
| } | |
| } | |
| } else { | |
| /* Jack removal */ | |
| if (events[DA7219_AAD_IRQ_REG_A] & DA7219_E_JACK_REMOVED_MASK) { | |
| report = 0; | |
| mask |= DA7219_AAD_REPORT_ALL_MASK; | |
| da7219_aad->jack_inserted = false; | |
| /* Un-drive headphones/lineout */ | |
| snd_soc_update_bits(codec, DA7219_HP_R_CTRL, | |
| DA7219_HP_R_AMP_OE_MASK, 0); | |
| snd_soc_update_bits(codec, DA7219_HP_L_CTRL, | |
| DA7219_HP_L_AMP_OE_MASK, 0); | |
| /* Ensure button detection disabled */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_BUTTON_CONFIG_MASK, 0); | |
| /* Disable mic bias */ | |
| snd_soc_dapm_disable_pin(dapm, "Mic Bias"); | |
| snd_soc_dapm_sync(dapm); | |
| /* Cancel any pending work */ | |
| cancel_work_sync(&da7219_aad->btn_det_work); | |
| cancel_work_sync(&da7219_aad->hptest_work); | |
| } | |
| } | |
| snd_soc_jack_report(da7219_aad->jack, report, mask); | |
| return IRQ_HANDLED; | |
| } | |
| /* | |
| * DT/ACPI to pdata conversion | |
| */ | |
| static enum da7219_aad_micbias_pulse_lvl | |
| da7219_aad_fw_micbias_pulse_lvl(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 2800: | |
| return DA7219_AAD_MICBIAS_PULSE_LVL_2_8V; | |
| case 2900: | |
| return DA7219_AAD_MICBIAS_PULSE_LVL_2_9V; | |
| default: | |
| dev_warn(codec->dev, "Invalid micbias pulse level"); | |
| return DA7219_AAD_MICBIAS_PULSE_LVL_OFF; | |
| } | |
| } | |
| static enum da7219_aad_btn_cfg | |
| da7219_aad_fw_btn_cfg(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 2: | |
| return DA7219_AAD_BTN_CFG_2MS; | |
| case 5: | |
| return DA7219_AAD_BTN_CFG_5MS; | |
| case 10: | |
| return DA7219_AAD_BTN_CFG_10MS; | |
| case 50: | |
| return DA7219_AAD_BTN_CFG_50MS; | |
| case 100: | |
| return DA7219_AAD_BTN_CFG_100MS; | |
| case 200: | |
| return DA7219_AAD_BTN_CFG_200MS; | |
| case 500: | |
| return DA7219_AAD_BTN_CFG_500MS; | |
| default: | |
| dev_warn(codec->dev, "Invalid button config"); | |
| return DA7219_AAD_BTN_CFG_10MS; | |
| } | |
| } | |
| static enum da7219_aad_mic_det_thr | |
| da7219_aad_fw_mic_det_thr(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 200: | |
| return DA7219_AAD_MIC_DET_THR_200_OHMS; | |
| case 500: | |
| return DA7219_AAD_MIC_DET_THR_500_OHMS; | |
| case 750: | |
| return DA7219_AAD_MIC_DET_THR_750_OHMS; | |
| case 1000: | |
| return DA7219_AAD_MIC_DET_THR_1000_OHMS; | |
| default: | |
| dev_warn(codec->dev, "Invalid mic detect threshold"); | |
| return DA7219_AAD_MIC_DET_THR_500_OHMS; | |
| } | |
| } | |
| static enum da7219_aad_jack_ins_deb | |
| da7219_aad_fw_jack_ins_deb(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 5: | |
| return DA7219_AAD_JACK_INS_DEB_5MS; | |
| case 10: | |
| return DA7219_AAD_JACK_INS_DEB_10MS; | |
| case 20: | |
| return DA7219_AAD_JACK_INS_DEB_20MS; | |
| case 50: | |
| return DA7219_AAD_JACK_INS_DEB_50MS; | |
| case 100: | |
| return DA7219_AAD_JACK_INS_DEB_100MS; | |
| case 200: | |
| return DA7219_AAD_JACK_INS_DEB_200MS; | |
| case 500: | |
| return DA7219_AAD_JACK_INS_DEB_500MS; | |
| case 1000: | |
| return DA7219_AAD_JACK_INS_DEB_1S; | |
| default: | |
| dev_warn(codec->dev, "Invalid jack insert debounce"); | |
| return DA7219_AAD_JACK_INS_DEB_20MS; | |
| } | |
| } | |
| static enum da7219_aad_jack_det_rate | |
| da7219_aad_fw_jack_det_rate(struct snd_soc_codec *codec, const char *str) | |
| { | |
| if (!strcmp(str, "32ms_64ms")) { | |
| return DA7219_AAD_JACK_DET_RATE_32_64MS; | |
| } else if (!strcmp(str, "64ms_128ms")) { | |
| return DA7219_AAD_JACK_DET_RATE_64_128MS; | |
| } else if (!strcmp(str, "128ms_256ms")) { | |
| return DA7219_AAD_JACK_DET_RATE_128_256MS; | |
| } else if (!strcmp(str, "256ms_512ms")) { | |
| return DA7219_AAD_JACK_DET_RATE_256_512MS; | |
| } else { | |
| dev_warn(codec->dev, "Invalid jack detect rate"); | |
| return DA7219_AAD_JACK_DET_RATE_256_512MS; | |
| } | |
| } | |
| static enum da7219_aad_jack_rem_deb | |
| da7219_aad_fw_jack_rem_deb(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 1: | |
| return DA7219_AAD_JACK_REM_DEB_1MS; | |
| case 5: | |
| return DA7219_AAD_JACK_REM_DEB_5MS; | |
| case 10: | |
| return DA7219_AAD_JACK_REM_DEB_10MS; | |
| case 20: | |
| return DA7219_AAD_JACK_REM_DEB_20MS; | |
| default: | |
| dev_warn(codec->dev, "Invalid jack removal debounce"); | |
| return DA7219_AAD_JACK_REM_DEB_1MS; | |
| } | |
| } | |
| static enum da7219_aad_btn_avg | |
| da7219_aad_fw_btn_avg(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 1: | |
| return DA7219_AAD_BTN_AVG_1; | |
| case 2: | |
| return DA7219_AAD_BTN_AVG_2; | |
| case 4: | |
| return DA7219_AAD_BTN_AVG_4; | |
| case 8: | |
| return DA7219_AAD_BTN_AVG_8; | |
| default: | |
| dev_warn(codec->dev, "Invalid button average value"); | |
| return DA7219_AAD_BTN_AVG_2; | |
| } | |
| } | |
| static enum da7219_aad_adc_1bit_rpt | |
| da7219_aad_fw_adc_1bit_rpt(struct snd_soc_codec *codec, u32 val) | |
| { | |
| switch (val) { | |
| case 1: | |
| return DA7219_AAD_ADC_1BIT_RPT_1; | |
| case 2: | |
| return DA7219_AAD_ADC_1BIT_RPT_2; | |
| case 4: | |
| return DA7219_AAD_ADC_1BIT_RPT_4; | |
| case 8: | |
| return DA7219_AAD_ADC_1BIT_RPT_8; | |
| default: | |
| dev_warn(codec->dev, "Invalid ADC 1-bit repeat value"); | |
| return DA7219_AAD_ADC_1BIT_RPT_1; | |
| } | |
| } | |
| static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct snd_soc_codec *codec) | |
| { | |
| struct device *dev = codec->dev; | |
| struct i2c_client *i2c = to_i2c_client(dev); | |
| struct fwnode_handle *aad_np; | |
| struct da7219_aad_pdata *aad_pdata; | |
| const char *fw_str; | |
| u32 fw_val32; | |
| aad_np = device_get_named_child_node(dev, "da7219_aad"); | |
| if (!aad_np) | |
| return NULL; | |
| aad_pdata = devm_kzalloc(codec->dev, sizeof(*aad_pdata), GFP_KERNEL); | |
| if (!aad_pdata) | |
| return NULL; | |
| aad_pdata->irq = i2c->irq; | |
| if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-lvl", | |
| &fw_val32) >= 0) | |
| aad_pdata->micbias_pulse_lvl = | |
| da7219_aad_fw_micbias_pulse_lvl(codec, fw_val32); | |
| else | |
| aad_pdata->micbias_pulse_lvl = DA7219_AAD_MICBIAS_PULSE_LVL_OFF; | |
| if (fwnode_property_read_u32(aad_np, "dlg,micbias-pulse-time", | |
| &fw_val32) >= 0) | |
| aad_pdata->micbias_pulse_time = fw_val32; | |
| if (fwnode_property_read_u32(aad_np, "dlg,btn-cfg", &fw_val32) >= 0) | |
| aad_pdata->btn_cfg = da7219_aad_fw_btn_cfg(codec, fw_val32); | |
| else | |
| aad_pdata->btn_cfg = DA7219_AAD_BTN_CFG_10MS; | |
| if (fwnode_property_read_u32(aad_np, "dlg,mic-det-thr", &fw_val32) >= 0) | |
| aad_pdata->mic_det_thr = | |
| da7219_aad_fw_mic_det_thr(codec, fw_val32); | |
| else | |
| aad_pdata->mic_det_thr = DA7219_AAD_MIC_DET_THR_500_OHMS; | |
| if (fwnode_property_read_u32(aad_np, "dlg,jack-ins-deb", &fw_val32) >= 0) | |
| aad_pdata->jack_ins_deb = | |
| da7219_aad_fw_jack_ins_deb(codec, fw_val32); | |
| else | |
| aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS; | |
| if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str)) | |
| aad_pdata->jack_det_rate = | |
| da7219_aad_fw_jack_det_rate(codec, fw_str); | |
| else | |
| aad_pdata->jack_det_rate = DA7219_AAD_JACK_DET_RATE_256_512MS; | |
| if (fwnode_property_read_u32(aad_np, "dlg,jack-rem-deb", &fw_val32) >= 0) | |
| aad_pdata->jack_rem_deb = | |
| da7219_aad_fw_jack_rem_deb(codec, fw_val32); | |
| else | |
| aad_pdata->jack_rem_deb = DA7219_AAD_JACK_REM_DEB_1MS; | |
| if (fwnode_property_read_u32(aad_np, "dlg,a-d-btn-thr", &fw_val32) >= 0) | |
| aad_pdata->a_d_btn_thr = (u8) fw_val32; | |
| else | |
| aad_pdata->a_d_btn_thr = 0xA; | |
| if (fwnode_property_read_u32(aad_np, "dlg,d-b-btn-thr", &fw_val32) >= 0) | |
| aad_pdata->d_b_btn_thr = (u8) fw_val32; | |
| else | |
| aad_pdata->d_b_btn_thr = 0x16; | |
| if (fwnode_property_read_u32(aad_np, "dlg,b-c-btn-thr", &fw_val32) >= 0) | |
| aad_pdata->b_c_btn_thr = (u8) fw_val32; | |
| else | |
| aad_pdata->b_c_btn_thr = 0x21; | |
| if (fwnode_property_read_u32(aad_np, "dlg,c-mic-btn-thr", &fw_val32) >= 0) | |
| aad_pdata->c_mic_btn_thr = (u8) fw_val32; | |
| else | |
| aad_pdata->c_mic_btn_thr = 0x3E; | |
| if (fwnode_property_read_u32(aad_np, "dlg,btn-avg", &fw_val32) >= 0) | |
| aad_pdata->btn_avg = da7219_aad_fw_btn_avg(codec, fw_val32); | |
| else | |
| aad_pdata->btn_avg = DA7219_AAD_BTN_AVG_2; | |
| if (fwnode_property_read_u32(aad_np, "dlg,adc-1bit-rpt", &fw_val32) >= 0) | |
| aad_pdata->adc_1bit_rpt = | |
| da7219_aad_fw_adc_1bit_rpt(codec, fw_val32); | |
| else | |
| aad_pdata->adc_1bit_rpt = DA7219_AAD_ADC_1BIT_RPT_1; | |
| return aad_pdata; | |
| } | |
| static void da7219_aad_handle_pdata(struct snd_soc_codec *codec) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| struct da7219_aad_priv *da7219_aad = da7219->aad; | |
| struct da7219_pdata *pdata = da7219->pdata; | |
| if ((pdata) && (pdata->aad_pdata)) { | |
| struct da7219_aad_pdata *aad_pdata = pdata->aad_pdata; | |
| u8 cfg, mask; | |
| da7219_aad->irq = aad_pdata->irq; | |
| switch (aad_pdata->micbias_pulse_lvl) { | |
| case DA7219_AAD_MICBIAS_PULSE_LVL_2_8V: | |
| case DA7219_AAD_MICBIAS_PULSE_LVL_2_9V: | |
| da7219_aad->micbias_pulse_lvl = | |
| (aad_pdata->micbias_pulse_lvl << | |
| DA7219_MICBIAS1_LEVEL_SHIFT); | |
| break; | |
| default: | |
| break; | |
| } | |
| da7219_aad->micbias_pulse_time = aad_pdata->micbias_pulse_time; | |
| switch (aad_pdata->btn_cfg) { | |
| case DA7219_AAD_BTN_CFG_2MS: | |
| case DA7219_AAD_BTN_CFG_5MS: | |
| case DA7219_AAD_BTN_CFG_10MS: | |
| case DA7219_AAD_BTN_CFG_50MS: | |
| case DA7219_AAD_BTN_CFG_100MS: | |
| case DA7219_AAD_BTN_CFG_200MS: | |
| case DA7219_AAD_BTN_CFG_500MS: | |
| da7219_aad->btn_cfg = (aad_pdata->btn_cfg << | |
| DA7219_BUTTON_CONFIG_SHIFT); | |
| } | |
| cfg = 0; | |
| mask = 0; | |
| switch (aad_pdata->mic_det_thr) { | |
| case DA7219_AAD_MIC_DET_THR_200_OHMS: | |
| case DA7219_AAD_MIC_DET_THR_500_OHMS: | |
| case DA7219_AAD_MIC_DET_THR_750_OHMS: | |
| case DA7219_AAD_MIC_DET_THR_1000_OHMS: | |
| cfg |= (aad_pdata->mic_det_thr << | |
| DA7219_MIC_DET_THRESH_SHIFT); | |
| mask |= DA7219_MIC_DET_THRESH_MASK; | |
| } | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, mask, cfg); | |
| cfg = 0; | |
| mask = 0; | |
| switch (aad_pdata->jack_ins_deb) { | |
| case DA7219_AAD_JACK_INS_DEB_5MS: | |
| case DA7219_AAD_JACK_INS_DEB_10MS: | |
| case DA7219_AAD_JACK_INS_DEB_20MS: | |
| case DA7219_AAD_JACK_INS_DEB_50MS: | |
| case DA7219_AAD_JACK_INS_DEB_100MS: | |
| case DA7219_AAD_JACK_INS_DEB_200MS: | |
| case DA7219_AAD_JACK_INS_DEB_500MS: | |
| case DA7219_AAD_JACK_INS_DEB_1S: | |
| cfg |= (aad_pdata->jack_ins_deb << | |
| DA7219_JACKDET_DEBOUNCE_SHIFT); | |
| mask |= DA7219_JACKDET_DEBOUNCE_MASK; | |
| } | |
| switch (aad_pdata->jack_det_rate) { | |
| case DA7219_AAD_JACK_DET_RATE_32_64MS: | |
| case DA7219_AAD_JACK_DET_RATE_64_128MS: | |
| case DA7219_AAD_JACK_DET_RATE_128_256MS: | |
| case DA7219_AAD_JACK_DET_RATE_256_512MS: | |
| cfg |= (aad_pdata->jack_det_rate << | |
| DA7219_JACK_DETECT_RATE_SHIFT); | |
| mask |= DA7219_JACK_DETECT_RATE_MASK; | |
| } | |
| switch (aad_pdata->jack_rem_deb) { | |
| case DA7219_AAD_JACK_REM_DEB_1MS: | |
| case DA7219_AAD_JACK_REM_DEB_5MS: | |
| case DA7219_AAD_JACK_REM_DEB_10MS: | |
| case DA7219_AAD_JACK_REM_DEB_20MS: | |
| cfg |= (aad_pdata->jack_rem_deb << | |
| DA7219_JACKDET_REM_DEB_SHIFT); | |
| mask |= DA7219_JACKDET_REM_DEB_MASK; | |
| } | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_2, mask, cfg); | |
| snd_soc_write(codec, DA7219_ACCDET_CONFIG_3, | |
| aad_pdata->a_d_btn_thr); | |
| snd_soc_write(codec, DA7219_ACCDET_CONFIG_4, | |
| aad_pdata->d_b_btn_thr); | |
| snd_soc_write(codec, DA7219_ACCDET_CONFIG_5, | |
| aad_pdata->b_c_btn_thr); | |
| snd_soc_write(codec, DA7219_ACCDET_CONFIG_6, | |
| aad_pdata->c_mic_btn_thr); | |
| cfg = 0; | |
| mask = 0; | |
| switch (aad_pdata->btn_avg) { | |
| case DA7219_AAD_BTN_AVG_1: | |
| case DA7219_AAD_BTN_AVG_2: | |
| case DA7219_AAD_BTN_AVG_4: | |
| case DA7219_AAD_BTN_AVG_8: | |
| cfg |= (aad_pdata->btn_avg << | |
| DA7219_BUTTON_AVERAGE_SHIFT); | |
| mask |= DA7219_BUTTON_AVERAGE_MASK; | |
| } | |
| switch (aad_pdata->adc_1bit_rpt) { | |
| case DA7219_AAD_ADC_1BIT_RPT_1: | |
| case DA7219_AAD_ADC_1BIT_RPT_2: | |
| case DA7219_AAD_ADC_1BIT_RPT_4: | |
| case DA7219_AAD_ADC_1BIT_RPT_8: | |
| cfg |= (aad_pdata->adc_1bit_rpt << | |
| DA7219_ADC_1_BIT_REPEAT_SHIFT); | |
| mask |= DA7219_ADC_1_BIT_REPEAT_MASK; | |
| } | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_7, mask, cfg); | |
| } | |
| } | |
| /* | |
| * Suspend/Resume | |
| */ | |
| void da7219_aad_suspend(struct snd_soc_codec *codec) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| struct da7219_aad_priv *da7219_aad = da7219->aad; | |
| struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); | |
| u8 micbias_ctrl; | |
| if (da7219_aad->jack) { | |
| /* Disable jack detection during suspend */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_ACCDET_EN_MASK, 0); | |
| /* | |
| * If we have a 4-pole jack inserted, then micbias will be | |
| * enabled. We can disable micbias here, and keep a note to | |
| * re-enable it on resume. If jack removal occurred during | |
| * suspend then this will be dealt with through the IRQ handler. | |
| */ | |
| if (da7219_aad->jack_inserted) { | |
| micbias_ctrl = snd_soc_read(codec, DA7219_MICBIAS_CTRL); | |
| if (micbias_ctrl & DA7219_MICBIAS1_EN_MASK) { | |
| snd_soc_dapm_disable_pin(dapm, "Mic Bias"); | |
| snd_soc_dapm_sync(dapm); | |
| da7219_aad->micbias_resume_enable = true; | |
| } | |
| } | |
| } | |
| } | |
| void da7219_aad_resume(struct snd_soc_codec *codec) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| struct da7219_aad_priv *da7219_aad = da7219->aad; | |
| struct snd_soc_dapm_context *dapm = snd_soc_codec_get_dapm(codec); | |
| if (da7219_aad->jack) { | |
| /* Re-enable micbias if previously enabled for 4-pole jack */ | |
| if (da7219_aad->jack_inserted && | |
| da7219_aad->micbias_resume_enable) { | |
| snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); | |
| snd_soc_dapm_sync(dapm); | |
| da7219_aad->micbias_resume_enable = false; | |
| } | |
| /* Re-enable jack detection */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_ACCDET_EN_MASK, | |
| DA7219_ACCDET_EN_MASK); | |
| } | |
| } | |
| /* | |
| * Init/Exit | |
| */ | |
| int da7219_aad_init(struct snd_soc_codec *codec) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| struct da7219_aad_priv *da7219_aad; | |
| u8 mask[DA7219_AAD_IRQ_REG_MAX]; | |
| int ret; | |
| da7219_aad = devm_kzalloc(codec->dev, sizeof(*da7219_aad), GFP_KERNEL); | |
| if (!da7219_aad) | |
| return -ENOMEM; | |
| da7219->aad = da7219_aad; | |
| da7219_aad->codec = codec; | |
| /* Handle any DT/ACPI/platform data */ | |
| if (da7219->pdata && !da7219->pdata->aad_pdata) | |
| da7219->pdata->aad_pdata = da7219_aad_fw_to_pdata(codec); | |
| da7219_aad_handle_pdata(codec); | |
| /* Disable button detection */ | |
| snd_soc_update_bits(codec, DA7219_ACCDET_CONFIG_1, | |
| DA7219_BUTTON_CONFIG_MASK, 0); | |
| INIT_WORK(&da7219_aad->btn_det_work, da7219_aad_btn_det_work); | |
| INIT_WORK(&da7219_aad->hptest_work, da7219_aad_hptest_work); | |
| ret = request_threaded_irq(da7219_aad->irq, NULL, | |
| da7219_aad_irq_thread, | |
| IRQF_TRIGGER_LOW | IRQF_ONESHOT, | |
| "da7219-aad", da7219_aad); | |
| if (ret) { | |
| dev_err(codec->dev, "Failed to request IRQ: %d\n", ret); | |
| return ret; | |
| } | |
| /* Unmask AAD IRQs */ | |
| memset(mask, 0, DA7219_AAD_IRQ_REG_MAX); | |
| regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_MASK_A, | |
| &mask, DA7219_AAD_IRQ_REG_MAX); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(da7219_aad_init); | |
| void da7219_aad_exit(struct snd_soc_codec *codec) | |
| { | |
| struct da7219_priv *da7219 = snd_soc_codec_get_drvdata(codec); | |
| struct da7219_aad_priv *da7219_aad = da7219->aad; | |
| u8 mask[DA7219_AAD_IRQ_REG_MAX]; | |
| /* Mask off AAD IRQs */ | |
| memset(mask, DA7219_BYTE_MASK, DA7219_AAD_IRQ_REG_MAX); | |
| regmap_bulk_write(da7219->regmap, DA7219_ACCDET_IRQ_MASK_A, | |
| mask, DA7219_AAD_IRQ_REG_MAX); | |
| free_irq(da7219_aad->irq, da7219_aad); | |
| cancel_work_sync(&da7219_aad->btn_det_work); | |
| cancel_work_sync(&da7219_aad->hptest_work); | |
| } | |
| EXPORT_SYMBOL_GPL(da7219_aad_exit); | |
| MODULE_DESCRIPTION("ASoC DA7219 AAD Driver"); | |
| MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>"); | |
| MODULE_LICENSE("GPL"); |