if (WARN_ON(sig_org == 0 || sig_cros == 0)) return0;
sig_org = nau8825_intlog10_dec3(sig_org);
sig_cros = nau8825_intlog10_dec3(sig_cros); if (sig_org >= sig_cros)
gain = (sig_org - sig_cros) * 20 + GAIN_AUGMENT; else
gain = (sig_cros - sig_org) * 20 + GAIN_AUGMENT;
sidetone = SIDETONE_BASE - gain * 2;
sidetone /= 1000;
return sidetone;
}
staticint nau8825_xtalk_baktab_index_by_reg(unsignedint reg)
{ int index;
for (index = 0; index < ARRAY_SIZE(nau8825_xtalk_baktab); index++) if (nau8825_xtalk_baktab[index].reg == reg) return index; return -EINVAL;
}
staticvoid nau8825_xtalk_backup(struct nau8825 *nau8825)
{ int i;
if (nau8825->xtalk_baktab_initialized) return;
/* Backup some register values to backup table */ for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++)
regmap_read(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
&nau8825_xtalk_baktab[i].def);
nau8825->xtalk_baktab_initialized = true;
}
staticvoid nau8825_xtalk_restore(struct nau8825 *nau8825, bool cause_cancel)
{ int i, volume;
if (!nau8825->xtalk_baktab_initialized) return;
/* Restore register values from backup table; When the driver restores *theheadphonevolumeinXTALK_DONEstate,itneedsrecoverto *originallevelgraduallywith3dBperstepforlesspopnoise. *Otherwise,therestoreshoulddoASAP.
*/ for (i = 0; i < ARRAY_SIZE(nau8825_xtalk_baktab); i++) { if (!cause_cancel && nau8825_xtalk_baktab[i].reg ==
NAU8825_REG_HSVOL_CTRL) { /* Ramping up the volume change to reduce pop noise */
volume = nau8825_xtalk_baktab[i].def &
NAU8825_HPR_VOL_MASK;
nau8825_hpvol_ramp(nau8825, 0, volume, 3); continue;
}
regmap_write(nau8825->regmap, nau8825_xtalk_baktab[i].reg,
nau8825_xtalk_baktab[i].def);
}
nau8825->xtalk_baktab_initialized = false;
}
staticvoid nau8825_xtalk_prepare_dac(struct nau8825 *nau8825)
{ /* Enable power of DAC path */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
NAU8825_ENABLE_ADC | NAU8825_ENABLE_ADC_CLK |
NAU8825_ENABLE_DAC_CLK, NAU8825_ENABLE_DACR |
NAU8825_ENABLE_DACL | NAU8825_ENABLE_ADC |
NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK); /* Prevent startup click by letting charge pump to ramp up and *changebumpenable
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN); /* Enable clock sync of DAC and DAC clock */
regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN |
NAU8825_RDAC_FS_BCLK_ENB,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN); /* Power up output driver with 2 stage */
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L);
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L); /* HP outputs not shouted to ground */
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, 0); /* Enable HP boost driver */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
NAU8825_HP_BOOST_DIS, NAU8825_HP_BOOST_DIS); /* Enable class G compare path to supply 1.8V or 0.9V. */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLASSG_CTRL,
NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN,
NAU8825_CLASSG_LDAC_EN | NAU8825_CLASSG_RDAC_EN);
}
staticvoid nau8825_xtalk_prepare_adc(struct nau8825 *nau8825)
{ /* Power up left ADC and raise 5dB than Vmid for Vref */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_VMID_PLUS_0_5DB);
}
staticvoid nau8825_xtalk_clock(struct nau8825 *nau8825)
{ /* Recover FLL default value */
regmap_write(nau8825->regmap, NAU8825_REG_FLL1, 0x0);
regmap_write(nau8825->regmap, NAU8825_REG_FLL2, 0x3126);
regmap_write(nau8825->regmap, NAU8825_REG_FLL3, 0x0008);
regmap_write(nau8825->regmap, NAU8825_REG_FLL4, 0x0010);
regmap_write(nau8825->regmap, NAU8825_REG_FLL5, 0x0);
regmap_write(nau8825->regmap, NAU8825_REG_FLL6, 0x6000); /* Enable internal VCO clock for detection signal generated */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL6, NAU8825_DCO_EN,
NAU8825_DCO_EN); /* Given specific clock frequency of internal clock to *generatesignal.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0xf);
regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1,
NAU8825_FLL_RATIO_MASK, 0x10);
}
staticvoid nau8825_xtalk_prepare(struct nau8825 *nau8825)
{ int volume, index;
/* Backup those registers changed by cross talk detection */
nau8825_xtalk_backup(nau8825); /* Config IIS as master to output signal by codec */
regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK | NAU8825_I2S_LRC_DIV_MASK |
NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_MASTER |
(0x2 << NAU8825_I2S_LRC_DIV_SFT) | 0x1); /* Ramp up headphone volume to 0dB to get better performance and *avoidpopnoiseinheadphone.
*/
index = nau8825_xtalk_baktab_index_by_reg(NAU8825_REG_HSVOL_CTRL); if (index != -EINVAL) {
volume = nau8825_xtalk_baktab[index].def &
NAU8825_HPR_VOL_MASK;
nau8825_hpvol_ramp(nau8825, volume, 0, 3);
}
nau8825_xtalk_clock(nau8825);
nau8825_xtalk_prepare_dac(nau8825);
nau8825_xtalk_prepare_adc(nau8825); /* Config channel path and digital gain */
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACL_CTRL,
NAU8825_DACL_CH_SEL_MASK | NAU8825_DACL_CH_VOL_MASK,
NAU8825_DACL_CH_SEL_L | 0xab);
regmap_update_bits(nau8825->regmap, NAU8825_REG_DACR_CTRL,
NAU8825_DACR_CH_SEL_MASK | NAU8825_DACR_CH_VOL_MASK,
NAU8825_DACR_CH_SEL_R | 0xab); /* Config cross talk parameters and generate the 23Hz sine wave with *1/16fullscaleofsignallevelforimpedancemeasurement.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
NAU8825_IMM_THD_MASK | NAU8825_IMM_GEN_VOL_MASK |
NAU8825_IMM_CYC_MASK | NAU8825_IMM_DAC_SRC_MASK,
(0x9 << NAU8825_IMM_THD_SFT) | NAU8825_IMM_GEN_VOL_1_16th |
NAU8825_IMM_CYC_8192 | NAU8825_IMM_DAC_SRC_SIN); /* RMS intrruption enable */
regmap_update_bits(nau8825->regmap,
NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0); /* Power up left and right DAC */ if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825)
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); else
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL);
}
staticvoid nau8825_xtalk_clean_dac(struct nau8825 *nau8825)
{ /* Disable HP boost driver */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST,
NAU8825_HP_BOOST_DIS, 0); /* HP outputs shouted to ground */
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSD_CTRL,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); /* Power down left and right DAC */ if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825)
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); else
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0);
/* Enable the TESTDAC and disable L/R HP impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP |
NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); /* Power down output driver with 2 stage */
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_HP_DRV_R | NAU8825_POWERUP_HP_DRV_L, 0);
regmap_update_bits(nau8825->regmap, NAU8825_REG_POWER_UP_CONTROL,
NAU8825_POWERUP_INTEGR_R | NAU8825_POWERUP_INTEGR_L |
NAU8825_POWERUP_DRV_IN_R | NAU8825_POWERUP_DRV_IN_L, 0); /* Disable clock sync of DAC and DAC clock */
regmap_update_bits(nau8825->regmap, NAU8825_REG_RDAC,
NAU8825_RDAC_EN | NAU8825_RDAC_CLK_EN, 0); /* Disable charge pump ramp up function and change bump */
regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP,
NAU8825_JAMNODCLOW | NAU8825_CHANRGE_PUMP_EN, 0); /* Disable power of DAC path */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_DACR | NAU8825_ENABLE_DACL |
NAU8825_ENABLE_ADC_CLK | NAU8825_ENABLE_DAC_CLK, 0); if (!nau8825->irq)
regmap_update_bits(nau8825->regmap,
NAU8825_REG_ENA_CTRL, NAU8825_ENABLE_ADC, 0);
}
staticvoid nau8825_xtalk_clean_adc(struct nau8825 *nau8825)
{ /* Power down left ADC and restore voltage to Vmid */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ANALOG_ADC_2,
NAU8825_POWERUP_ADCL | NAU8825_ADC_VREFSEL_MASK, 0);
}
staticvoid nau8825_xtalk_clean(struct nau8825 *nau8825, bool cause_cancel)
{ /* Enable internal VCO needed for interruptions */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0);
nau8825_xtalk_clean_dac(nau8825);
nau8825_xtalk_clean_adc(nau8825); /* Clear cross talk parameters and disable */
regmap_write(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL, 0); /* RMS intrruption disable */
regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_RMS_EN, NAU8825_IRQ_RMS_EN); /* Recover default value for IIS */
regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK | NAU8825_I2S_LRC_DIV_MASK |
NAU8825_I2S_BLK_DIV_MASK, NAU8825_I2S_MS_SLAVE); /* Restore value of specific register for cross talk */
nau8825_xtalk_restore(nau8825, cause_cancel);
}
staticvoid nau8825_xtalk_imm_start(struct nau8825 *nau8825, int vol)
{ /* Apply ADC volume for better cross talk performance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ADC_DGAIN_CTRL,
NAU8825_ADC_DIG_VOL_MASK, vol); /* Disables JKTIP(HPL) DAC channel for right to left measurement. *Doitbeforesendingsignalinordertoerasepopnoise.
*/
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_TESTDACR_EN | NAU8825_BIAS_TESTDACL_EN,
NAU8825_BIAS_TESTDACL_EN); switch (nau8825->xtalk_state) { case NAU8825_XTALK_HPR_R2L: /* Enable right headphone impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
NAU8825_BIAS_HPR_IMP); break; case NAU8825_XTALK_HPL_R2L: /* Enable left headphone impedance */
regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ,
NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP,
NAU8825_BIAS_HPL_IMP); break; default: break;
}
msleep(100); /* Impedance measurement mode enable */
regmap_update_bits(nau8825->regmap, NAU8825_REG_IMM_MODE_CTRL,
NAU8825_IMM_EN, NAU8825_IMM_EN);
}
nau8825_xtalk_measure(nau8825); /* To determine the cross talk side tone gain when reach *theimpedancemeasurestate.
*/ if (nau8825->xtalk_state == NAU8825_XTALK_IMM)
nau8825_xtalk_measure(nau8825);
/* Delay jack report until cross talk detection process *completed.Itcanavoidapplicationtodoplayback *preparationbeforecrosstalkdetectionisstillworking. *Meanwhile,theprotectionofthecrosstalkdetection *isreleased.
*/ if (nau8825->xtalk_state == NAU8825_XTALK_DONE) {
snd_soc_jack_report(nau8825->jack, nau8825->xtalk_event,
nau8825->xtalk_event_mask);
nau8825_sema_release(nau8825);
nau8825->xtalk_protect = false;
}
}
staticvoid nau8825_xtalk_cancel(struct nau8825 *nau8825)
{ /* If the crosstalk is eanbled and the process is on going, *thedriverforcestocancelthecrosstalktaskand *restorestheconfigurationtooriginalstatus.
*/ if (nau8825->xtalk_enable && nau8825->xtalk_state !=
NAU8825_XTALK_DONE) {
cancel_work_sync(&nau8825->xtalk_work);
nau8825_xtalk_clean(nau8825, true);
} /* Reset parameters for cross talk suppression function */
nau8825_sema_reset(nau8825);
nau8825->xtalk_state = NAU8825_XTALK_DONE;
nau8825->xtalk_protect = false;
}
staticbool nau8825_readable_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case NAU8825_REG_ENA_CTRL ... NAU8825_REG_FLL_VCO_RSV: case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: case NAU8825_REG_INTERRUPT_MASK ... NAU8825_REG_KEYDET_CTRL: case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: case NAU8825_REG_IMM_MODE_CTRL ... NAU8825_REG_IMM_RMS_R: case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: case NAU8825_REG_MISC_CTRL: case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_FLL2_UPPER: case NAU8825_REG_BIAS_ADJ: case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_GENERAL_STATUS: returntrue; default: returnfalse;
}
}
staticbool nau8825_writeable_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case NAU8825_REG_RESET ... NAU8825_REG_FLL_VCO_RSV: case NAU8825_REG_HSD_CTRL ... NAU8825_REG_JACK_DET_CTRL: case NAU8825_REG_INTERRUPT_MASK: case NAU8825_REG_INT_CLR_KEY_STATUS ... NAU8825_REG_KEYDET_CTRL: case NAU8825_REG_VDET_THRESHOLD_1 ... NAU8825_REG_DACR_CTRL: case NAU8825_REG_ADC_DRC_KNEE_IP12 ... NAU8825_REG_ADC_DRC_ATKDCY: case NAU8825_REG_DAC_DRC_KNEE_IP12 ... NAU8825_REG_DAC_DRC_ATKDCY: case NAU8825_REG_IMM_MODE_CTRL: case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: case NAU8825_REG_MISC_CTRL: case NAU8825_REG_FLL2_LOWER ... NAU8825_REG_FLL2_UPPER: case NAU8825_REG_BIAS_ADJ: case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: case NAU8825_REG_BOOST ... NAU8825_REG_FEPGA: case NAU8825_REG_POWER_UP_CONTROL ... NAU8825_REG_CHARGE_PUMP: returntrue; default: returnfalse;
}
}
staticbool nau8825_volatile_reg(struct device *dev, unsignedint reg)
{ switch (reg) { case NAU8825_REG_RESET: case NAU8825_REG_IRQ_STATUS: case NAU8825_REG_INT_CLR_KEY_STATUS: case NAU8825_REG_IMM_RMS_L: case NAU8825_REG_IMM_RMS_R: case NAU8825_REG_I2C_DEVICE_ID: case NAU8825_REG_SARDOUT_RAM_STATUS: case NAU8825_REG_CHARGE_PUMP_INPUT_READ: case NAU8825_REG_GENERAL_STATUS: case NAU8825_REG_BIQ_CTRL ... NAU8825_REG_BIQ_COF10: returntrue; default: returnfalse;
}
}
if (SND_SOC_DAPM_EVENT_OFF(event)) {
dev_dbg(nau8825->dev, "system clock control : POWER OFF\n"); /* Set clock source to disable or internal clock before the *playbackorcaptureend.CodecneedsclockforJack *detectionandbuttonpressifjackinserted;otherwise, *theclockshouldbeclosed.
*/ if (nau8825_is_jack_inserted(regmap)) {
nau8825_configure_sysclk(nau8825,
NAU8825_CLK_INTERNAL, 0);
} else {
nau8825_configure_sysclk(nau8825, NAU8825_CLK_DIS, 0);
}
}
/* ADC for button press detection. A dapm supply widget is used to *preventdapm_power_widgetskeepingthecodecatSND_SOC_BIAS_ON *duringsuspend.
*/
SND_SOC_DAPM_SUPPLY("SAR", NAU8825_REG_SAR_CTRL,
NAU8825_SAR_ADC_EN_SFT, 0, NULL, 0),
if (slots != 4 && slots != 8) {
dev_err(nau8825->dev, "Only support 4 or 8 slots!\n"); return -EINVAL;
}
/* The driver is limited to 1-channel for ADC, and 2-channel for DAC on TDM mode */ if (hweight_long((unsignedlong) tx_mask) != 1 ||
hweight_long((unsignedlong) rx_mask) != 2) {
dev_err(nau8825->dev, "The limitation is 1-channel for ADC, and 2-channel for DAC on TDM mode.\n"); return -EINVAL;
}
if (((tx_mask & 0xf) && (tx_mask & 0xf0)) ||
((rx_mask & 0xf) && (rx_mask & 0xf0)) ||
((tx_mask & 0xf) && (rx_mask & 0xf0)) ||
((rx_mask & 0xf) && (tx_mask & 0xf0))) {
dev_err(nau8825->dev, "Slot assignment of DAC and ADC need to set same interval.\n"); return -EINVAL;
}
regmap_read(regmap, NAU8825_REG_JACK_DET_CTRL, &jkdet);
active_high = jkdet & NAU8825_JACK_POLARITY;
regmap_read(regmap, NAU8825_REG_I2C_DEVICE_ID, &status);
is_high = status & NAU8825_GPIO2JD1; /* return jack connection status according to jack insertion logic *activehighoractivelow.
*/ return active_high == is_high;
}
staticvoid nau8825_restart_jack_detection(struct regmap *regmap)
{ /* this will restart the entire jack detection process including MIC/GND *switchingandcreateinterrupts.Wehavetogofrom0to1andback *to0torestart.
*/
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_RESTART, NAU8825_JACK_DET_RESTART);
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_RESTART, 0);
}
staticvoid nau8825_int_status_clear_all(struct regmap *regmap)
{ int active_irq, clear_irq, i;
/* Reset the intrruption status from rightmost bit if the corres- *pondingirqeventoccurs.
*/
regmap_read(regmap, NAU8825_REG_IRQ_STATUS, &active_irq); for (i = 0; i < NAU8825_REG_DATA_LEN; i++) {
clear_irq = (0x1 << i); if (active_irq & clear_irq)
regmap_write(regmap,
NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
}
}
/* Enable HSD function */
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL,
NAU8825_HSD_AUTO_MODE, NAU8825_HSD_AUTO_MODE);
/* Enable headset jack type detection complete interruption and *jackejectioninterruption.
*/
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_HEADSET_COMPLETE_EN | NAU8825_IRQ_EJECT_EN, 0);
/* Enable internal VCO needed for interruptions */
nau8825_configure_sysclk(nau8825, NAU8825_CLK_INTERNAL, 0); /* Raise up the internal clock for jack detection */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0);
staticint nau8825_jack_insert(struct nau8825 *nau8825)
{ struct regmap *regmap = nau8825->regmap; struct snd_soc_dapm_context *dapm = nau8825->dapm; int jack_status_reg, mic_detected; int type = 0;
regmap_read(regmap, NAU8825_REG_GENERAL_STATUS, &jack_status_reg);
mic_detected = (jack_status_reg >> 10) & 3; /* The JKSLV and JKR2 all detected in high impedance headset */ if (mic_detected == 0x3)
nau8825->high_imped = true; else
nau8825->high_imped = false;
switch (mic_detected) { case0: /* no mic */
type = SND_JACK_HEADPHONE; break; case1:
dev_dbg(nau8825->dev, "OMTP (micgnd1) mic connected\n");
type = SND_JACK_HEADSET;
snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
snd_soc_dapm_force_enable_pin(dapm, "SAR");
snd_soc_dapm_sync(dapm); break; case3: /* Detection failure case */
dev_warn(nau8825->dev, "Detection failure. Try the manually mechanism for jack type checking.\n"); if (!nau8825_high_imped_detection(nau8825)) {
type = SND_JACK_HEADSET;
snd_soc_dapm_force_enable_pin(dapm, "MICBIAS");
snd_soc_dapm_force_enable_pin(dapm, "SAR");
snd_soc_dapm_sync(dapm);
} else
type = SND_JACK_HEADPHONE; break;
}
/* Update to the default divider of internal clock for power saving */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0xf);
/* Disable HSD function */
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, NAU8825_HSD_AUTO_MODE, 0);
/* Leaving HPOL/R grounded after jack insert by default. They will be *ungroundedaspartofthewidgetpowerupsequenceatthebeginning *ofplaybacktoreducepop.
*/ return type;
}
/* upper 8 bits of the register are for short pressed keys, *lower8bits-forlongpressedbuttons
*/
nau8825->button_pressed = nau8825_button_decode(
key_status >> 8);
event |= nau8825->button_pressed;
event_mask |= NAU8825_BUTTONS;
clear_irq = NAU8825_KEY_SHORT_PRESS_IRQ;
} elseif (active_irq & NAU8825_KEY_RELEASE_IRQ) {
event_mask = NAU8825_BUTTONS;
clear_irq = NAU8825_KEY_RELEASE_IRQ;
} elseif (active_irq & NAU8825_HEADSET_COMPLETION_IRQ) { if (nau8825_is_jack_inserted(regmap)) {
event |= nau8825_jack_insert(nau8825); if (nau8825->xtalk_enable && !nau8825->high_imped) { /* Apply the cross talk suppression in the *headsetwithouthighimpedance.
*/ if (!nau8825->xtalk_protect) { /* Raise protection for cross talk de- *tectionifnoprotectionbefore. *Thedriverhastocancelthepro- *cessandrestorechangesifprocess *isongoingwhenejection.
*/ int ret;
nau8825->xtalk_protect = true;
ret = nau8825_sema_acquire(nau8825, 0); if (ret)
nau8825->xtalk_protect = false;
} /* Startup cross talk detection process */ if (nau8825->xtalk_protect) {
nau8825->xtalk_state =
NAU8825_XTALK_PREPARE;
schedule_work(&nau8825->xtalk_work);
}
} else { /* The cross talk suppression shouldn't apply *intheheadsetwithhighimpedance.Thus, *relievetheprotectionraisedbefore.
*/ if (nau8825->xtalk_protect) {
nau8825_sema_release(nau8825);
nau8825->xtalk_protect = false;
}
}
} else {
dev_warn(nau8825->dev, "Headset completion IRQ fired but no headset connected\n");
nau8825_eject_jack(nau8825);
}
event_mask |= SND_JACK_HEADSET;
clear_irq = NAU8825_HEADSET_COMPLETION_IRQ; /* Record the interruption report event for driver to report *theeventlater.Thejackreportwilldelayuntilcross *talkdetectionprocessisdone.
*/ if (nau8825->xtalk_state == NAU8825_XTALK_PREPARE) {
nau8825->xtalk_event = event;
nau8825->xtalk_event_mask = event_mask;
}
} elseif (active_irq & NAU8825_IMPEDANCE_MEAS_IRQ) { /* crosstalk detection enable and process on going */ if (nau8825->xtalk_enable && nau8825->xtalk_protect)
schedule_work(&nau8825->xtalk_work);
clear_irq = NAU8825_IMPEDANCE_MEAS_IRQ;
} elseif ((active_irq & NAU8825_JACK_INSERTION_IRQ_MASK) ==
NAU8825_JACK_INSERTION_DETECTED) { /* One more step to check GPIO status directly. Thus, the *drivercanconfirmtherealinsertioninterruptionbecause *theintrruptionatmanualmodehasbypasseddebounce *circuitwhichcangetridofunstablestatus.
*/ if (nau8825_is_jack_inserted(regmap)) { /* Turn off insertion interruption at manual mode */
regmap_update_bits(regmap,
NAU8825_REG_INTERRUPT_DIS_CTRL,
NAU8825_IRQ_INSERT_DIS,
NAU8825_IRQ_INSERT_DIS);
regmap_update_bits(regmap, NAU8825_REG_INTERRUPT_MASK,
NAU8825_IRQ_INSERT_EN, NAU8825_IRQ_INSERT_EN); /* Enable interruption for jack type detection at audo *modewhichcandetectmicrophoneandjacktype.
*/
nau8825_setup_auto_irq(nau8825);
}
}
if (!clear_irq)
clear_irq = active_irq; /* clears the rightmost interruption */
regmap_write(regmap, NAU8825_REG_INT_CLR_KEY_STATUS, clear_irq);
/* Delay jack report until cross talk detection is done. It can avoid *applicationtodoplaybackpreparationwhencrosstalkdetection *processisstillworking.Otherwise,theresourcelikeclockand *powerwillbeissuedbythematthesametimeandconflicthappens.
*/ if (event_mask && nau8825->xtalk_state == NAU8825_XTALK_DONE)
snd_soc_jack_report(nau8825->jack, event, event_mask);
/* Ensure the reference clock frequency (FREF) is <= 13.5MHz by dividing *freq_inby1,2,4,or8usingFLLpre-scalar. *FREF=freq_in/NAU8825_FLL_REF_DIV_MASK
*/ for (i = 0; i < ARRAY_SIZE(fll_pre_scalar); i++) {
fref = fll_in / fll_pre_scalar[i].param; if (fref <= NAU_FREF_MAX) break;
} if (i == ARRAY_SIZE(fll_pre_scalar)) return -EINVAL;
fll_param->clk_ref_div = fll_pre_scalar[i].val;
/* Choose the FLL ratio based on FREF */ for (i = 0; i < ARRAY_SIZE(fll_ratio); i++) { if (fref >= fll_ratio[i].param) break;
} if (i == ARRAY_SIZE(fll_ratio)) return -EINVAL;
fll_param->ratio = fll_ratio[i].val;
/* Calculate the frequency of DCO (FDCO) given freq_out = 256 * Fs. *FDCOmustbewithinthe90MHz-124MHzortheFFLcannotbe *guaranteedacrossthefullrangeofoperation. *FDCO=freq_out*2*mclk_src_scaling
*/
fvco_max = 0;
fvco_sel = ARRAY_SIZE(mclk_src_scaling); for (i = 0; i < ARRAY_SIZE(mclk_src_scaling); i++) {
fvco = 256ULL * fs * 2 * mclk_src_scaling[i].param; if (fvco > NAU_FVCO_MIN && fvco < NAU_FVCO_MAX &&
fvco_max < fvco) {
fvco_max = fvco;
fvco_sel = i;
}
} if (ARRAY_SIZE(mclk_src_scaling) == fvco_sel) return -EINVAL;
fll_param->mclk_src = mclk_src_scaling[fvco_sel].val;
/* freq_out must be 256*Fs in order to achieve the best performance */ staticint nau8825_set_pll(struct snd_soc_component *component, int pll_id, int source, unsignedint freq_in, unsignedint freq_out)
{ struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); struct nau8825_fll fll_param; int ret, fs;
staticint nau8825_mclk_prepare(struct nau8825 *nau8825, unsignedint freq)
{ int ret;
nau8825->mclk = devm_clk_get(nau8825->dev, "mclk"); if (IS_ERR(nau8825->mclk)) {
dev_info(nau8825->dev, "No 'mclk' clock found, assume MCLK is managed externally"); return0;
}
if (!nau8825->mclk_freq) {
ret = clk_prepare_enable(nau8825->mclk); if (ret) {
dev_err(nau8825->dev, "Unable to prepare codec mclk\n"); return ret;
}
}
if (nau8825->mclk_freq != freq) {
freq = clk_round_rate(nau8825->mclk, freq);
ret = clk_set_rate(nau8825->mclk, freq); if (ret) {
dev_err(nau8825->dev, "Unable to set mclk rate\n"); return ret;
}
nau8825->mclk_freq = freq;
}
return0;
}
staticvoid nau8825_configure_mclk_as_sysclk(struct regmap *regmap)
{
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_MCLK);
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_DCO_EN, 0); /* Make DSP operate as default setting for power saving. */
regmap_update_bits(regmap, NAU8825_REG_FLL1,
NAU8825_ICTRL_LATCH_MASK, 0);
}
staticint nau8825_configure_sysclk(struct nau8825 *nau8825, int clk_id, unsignedint freq)
{ struct regmap *regmap = nau8825->regmap; int ret;
switch (clk_id) { case NAU8825_CLK_DIS: /* Clock provided externally and disable internal VCO clock */
nau8825_configure_mclk_as_sysclk(regmap); if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break; case NAU8825_CLK_MCLK: /* Acquire the semaphore to synchronize the playback and *interrupthandler.Inordertoavoidtheplaybackinter- *feredbycrosstalkprocess,thedrivermaketheplayback *preparationhalteduntilcrosstalkprocessfinish.
*/
nau8825_sema_acquire(nau8825, 3 * HZ);
nau8825_configure_mclk_as_sysclk(regmap); /* MCLK not changed by clock tree */
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0); /* Release the semaphore. */
nau8825_sema_release(nau8825);
ret = nau8825_mclk_prepare(nau8825, freq); if (ret) return ret;
break; case NAU8825_CLK_INTERNAL: if (nau8825_is_jack_inserted(nau8825->regmap)) {
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_DCO_EN, NAU8825_DCO_EN);
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_SRC_MASK, NAU8825_CLK_SRC_VCO); /* Decrease the VCO frequency and make DSP operate *asdefaultsettingforpowersaving.
*/
regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_MCLK_SRC_MASK, 0xf);
regmap_update_bits(regmap, NAU8825_REG_FLL1,
NAU8825_ICTRL_LATCH_MASK |
NAU8825_FLL_RATIO_MASK, 0x10);
regmap_update_bits(regmap, NAU8825_REG_FLL6,
NAU8825_SDM_EN, NAU8825_SDM_EN);
} else { /* The clock turns off intentionally for power saving *whennoheadsetconnected.
*/
nau8825_configure_mclk_as_sysclk(regmap);
dev_warn(nau8825->dev, "Disable clock for power saving when no headset connected\n");
} if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break; case NAU8825_CLK_FLL_MCLK: /* Acquire the semaphore to synchronize the playback and *interrupthandler.Inordertoavoidtheplaybackinter- *feredbycrosstalkprocess,thedrivermaketheplayback *preparationhalteduntilcrosstalkprocessfinish.
*/
nau8825_sema_acquire(nau8825, 3 * HZ); /* Higher FLL reference input frequency can only set lower *gainerror,suchas0000forinputreferencefromMCLK *12.288Mhz.
*/
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK,
NAU8825_FLL_CLK_SRC_MCLK | 0); /* Release the semaphore. */
nau8825_sema_release(nau8825);
ret = nau8825_mclk_prepare(nau8825, freq); if (ret) return ret;
break; case NAU8825_CLK_FLL_BLK: /* Acquire the semaphore to synchronize the playback and *interrupthandler.Inordertoavoidtheplaybackinter- *feredbycrosstalkprocess,thedrivermaketheplayback *preparationhalteduntilcrosstalkprocessfinish.
*/
nau8825_sema_acquire(nau8825, 3 * HZ); /* If FLL reference input is from low frequency source, *highererrorgaincanapplysuchas0xfwhichhas *themostsensitivegainerrorcorrectionthreshold, *Therefore,FLLhasthemostaccurateDCOto *targetfrequency.
*/
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK,
NAU8825_FLL_CLK_SRC_BLK |
(0xf << NAU8825_GAIN_ERR_SFT)); /* Release the semaphore. */
nau8825_sema_release(nau8825);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
break; case NAU8825_CLK_FLL_FS: /* Acquire the semaphore to synchronize the playback and *interrupthandler.Inordertoavoidtheplaybackinter- *feredbycrosstalkprocess,thedrivermaketheplayback *preparationhalteduntilcrosstalkprocessfinish.
*/
nau8825_sema_acquire(nau8825, 3 * HZ); /* If FLL reference input is from low frequency source, *highererrorgaincanapplysuchas0xfwhichhas *themostsensitivegainerrorcorrectionthreshold, *Therefore,FLLhasthemostaccurateDCOto *targetfrequency.
*/
regmap_update_bits(regmap, NAU8825_REG_FLL3,
NAU8825_FLL_CLK_SRC_MASK | NAU8825_GAIN_ERR_MASK,
NAU8825_FLL_CLK_SRC_FS |
(0xf << NAU8825_GAIN_ERR_SFT)); /* Release the semaphore. */
nau8825_sema_release(nau8825);
if (nau8825->mclk_freq) {
clk_disable_unprepare(nau8825->mclk);
nau8825->mclk_freq = 0;
}
case SND_SOC_BIAS_STANDBY: if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) { if (nau8825->mclk_freq) {
ret = clk_prepare_enable(nau8825->mclk); if (ret) {
dev_err(nau8825->dev, "Unable to prepare component mclk\n"); return ret;
}
} /* Setup codec configuration after resume */
nau8825_resume_setup(nau8825);
} break;
case SND_SOC_BIAS_OFF: /* Reset the configuration of jack type for detection */ /* Detach 2kOhm Resistors from MICBIAS to MICGND1/2 */
regmap_update_bits(nau8825->regmap, NAU8825_REG_MIC_BIAS,
NAU8825_MICBIAS_JKSLV | NAU8825_MICBIAS_JKR2, 0); /* ground HPL/HPR, MICGRND1/2 */
regmap_update_bits(nau8825->regmap,
NAU8825_REG_HSD_CTRL, 0xf, 0xf); /* Cancel and reset cross talk detection funciton */
nau8825_xtalk_cancel(nau8825); /* Turn off all interruptions before system shutdown. Keep the *interruptionquietbeforeresumesetupcompletes.
*/
regmap_write(nau8825->regmap,
NAU8825_REG_INTERRUPT_DIS_CTRL, 0xffff); /* Disable ADC needed for interruptions at audo mode */
regmap_update_bits(nau8825->regmap, NAU8825_REG_ENA_CTRL,
NAU8825_ENABLE_ADC, 0); if (nau8825->mclk_freq)
clk_disable_unprepare(nau8825->mclk); break;
} return0;
}
dev_dbg(dev, "sar-threshold-num: %d\n", nau8825->sar_threshold_num); for (i = 0; i < nau8825->sar_threshold_num; i++)
dev_dbg(dev, "sar-threshold[%d]=%d\n", i,
nau8825->sar_threshold[i]);
staticint nau8825_read_device_properties(struct device *dev, struct nau8825 *nau8825) { int ret;
nau8825->jkdet_enable = device_property_read_bool(dev, "nuvoton,jkdet-enable");
nau8825->jkdet_pull_enable = device_property_read_bool(dev, "nuvoton,jkdet-pull-enable");
nau8825->jkdet_pull_up = device_property_read_bool(dev, "nuvoton,jkdet-pull-up");
ret = device_property_read_u32(dev, "nuvoton,jkdet-polarity",
&nau8825->jkdet_polarity); if (ret)
nau8825->jkdet_polarity = 1;
ret = device_property_read_u32(dev, "nuvoton,micbias-voltage",
&nau8825->micbias_voltage); if (ret)
nau8825->micbias_voltage = 6;
ret = device_property_read_u32(dev, "nuvoton,vref-impedance",
&nau8825->vref_impedance); if (ret)
nau8825->vref_impedance = 2;
ret = device_property_read_u32(dev, "nuvoton,sar-threshold-num",
&nau8825->sar_threshold_num); if (ret)
nau8825->sar_threshold_num = 4;
ret = device_property_read_u32_array(dev, "nuvoton,sar-threshold",
nau8825->sar_threshold, nau8825->sar_threshold_num); if (ret) {
nau8825->sar_threshold[0] = 0x08;
nau8825->sar_threshold[1] = 0x12;
nau8825->sar_threshold[2] = 0x26;
nau8825->sar_threshold[3] = 0x73;
}
ret = device_property_read_u32(dev, "nuvoton,sar-hysteresis",
&nau8825->sar_hysteresis); if (ret)
nau8825->sar_hysteresis = 0;
ret = device_property_read_u32(dev, "nuvoton,sar-voltage",
&nau8825->sar_voltage); if (ret)
nau8825->sar_voltage = 6;
ret = device_property_read_u32(dev, "nuvoton,sar-compare-time",
&nau8825->sar_compare_time); if (ret)
nau8825->sar_compare_time = 1;
ret = device_property_read_u32(dev, "nuvoton,sar-sampling-time",
&nau8825->sar_sampling_time); if (ret)
nau8825->sar_sampling_time = 1;
ret = device_property_read_u32(dev, "nuvoton,short-key-debounce",
&nau8825->key_debounce); if (ret)
nau8825->key_debounce = 3;
ret = device_property_read_u32(dev, "nuvoton,jack-insert-debounce",
&nau8825->jack_insert_debounce); if (ret)
nau8825->jack_insert_debounce = 7;
ret = device_property_read_u32(dev, "nuvoton,jack-eject-debounce",
&nau8825->jack_eject_debounce); if (ret)
nau8825->jack_eject_debounce = 0;
nau8825->xtalk_enable = device_property_read_bool(dev, "nuvoton,crosstalk-enable");
nau8825->adcout_ds = device_property_read_bool(dev, "nuvoton,adcout-drive-strong");
ret = device_property_read_u32(dev, "nuvoton,adc-delay-ms", &nau8825->adc_delay); if (ret)
nau8825->adc_delay = 125; if (nau8825->adc_delay < 125 || nau8825->adc_delay > 500)
dev_warn(dev, "Please set the suitable delay time!\n");
nau8825->mclk = devm_clk_get_optional(dev, "mclk"); if (IS_ERR(nau8825->mclk)) return PTR_ERR(nau8825->mclk); if (!nau8825->mclk) /* The MCLK is managed externally or not used at all */
dev_info(dev, "No 'mclk' clock found, assume MCLK is managed externally");
return0;
}
staticint nau8825_setup_irq(struct nau8825 *nau8825)
{ int ret;
if (!nau8825) {
nau8825 = devm_kzalloc(dev, sizeof(*nau8825), GFP_KERNEL); if (!nau8825) return -ENOMEM;
ret = nau8825_read_device_properties(dev, nau8825); if (ret) return ret;
}
i2c_set_clientdata(i2c, nau8825);
nau8825->regmap = devm_regmap_init_i2c(i2c, &nau8825_regmap_config); if (IS_ERR(nau8825->regmap)) return PTR_ERR(nau8825->regmap);
nau8825->dev = dev;
nau8825->irq = i2c->irq; /* Initiate parameters, semaphore and work queue which are needed in *crosstalksuppressionmeasurmentfunction.
*/
nau8825->xtalk_state = NAU8825_XTALK_DONE;
nau8825->xtalk_protect = false;
nau8825->xtalk_baktab_initialized = false;
sema_init(&nau8825->xtalk_sem, 1);
INIT_WORK(&nau8825->xtalk_work, nau8825_xtalk_work);
nau8825_print_device_properties(nau8825);
nau8825_reset_chip(nau8825->regmap);
ret = regmap_read(nau8825->regmap, NAU8825_REG_I2C_DEVICE_ID, &value); if (ret < 0) {
dev_err(dev, "Failed to read device id from the NAU8825: %d\n",
ret); return ret;
}
nau8825->sw_id = value & NAU8825_SOFTWARE_ID_MASK; switch (nau8825->sw_id) { case NAU8825_SOFTWARE_ID_NAU8825: break; case NAU8825_SOFTWARE_ID_NAU8825C:
ret = regmap_register_patch(nau8825->regmap, nau8825_regmap_patch,
ARRAY_SIZE(nau8825_regmap_patch)); if (ret) {
dev_err(dev, "Failed to register Rev C patch: %d\n", ret); return ret;
} break; default:
dev_err(dev, "Not a NAU8825 chip\n"); return -ENODEV;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.