| /* |
| * 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_update_bits(codec, DA7219_HP_L_CTRL, |
| DA7219_HP_L_AMP_OE_MASK | DA7219_HP_L_AMP_EN_MASK, |
| DA7219_HP_L_AMP_OE_MASK | DA7219_HP_L_AMP_EN_MASK); |
| snd_soc_update_bits(codec, DA7219_HP_R_CTRL, |
| DA7219_HP_R_AMP_OE_MASK | DA7219_HP_R_AMP_EN_MASK, |
| DA7219_HP_R_AMP_OE_MASK | DA7219_HP_R_AMP_EN_MASK); |
| msleep(DA7219_SETTLING_DELAY); |
| snd_soc_update_bits(codec, DA7219_HP_L_CTRL, |
| DA7219_HP_L_AMP_MUTE_EN_MASK | |
| DA7219_HP_L_AMP_MIN_GAIN_EN_MASK, 0); |
| snd_soc_update_bits(codec, DA7219_HP_R_CTRL, |
| DA7219_HP_R_AMP_MUTE_EN_MASK | |
| DA7219_HP_R_AMP_MIN_GAIN_EN_MASK, 0); |
| |
| /* |
| * 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); |
| msleep(DA7219_SETTLING_DELAY); |
| 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"); |