/** * nau8825_sema_acquire - acquire the semaphore of nau88l25 * @nau8825: component to register the codec private data with * @timeout: how long in jiffies to wait before failure or zero to wait * until release * * Attempts to acquire the semaphore with number of jiffies. If no more * tasks are allowed to acquire the semaphore, calling this function will * put the task to sleep. If the semaphore is not released within the * specified number of jiffies, this function returns. * If the semaphore is not released within the specified number of jiffies, * this function returns -ETIME. If the sleep is interrupted by a signal, * this function will return -EINTR. It returns 0 if the semaphore was * acquired successfully. * * Acquires the semaphore without jiffies. Try to acquire the semaphore * atomically. Returns 0 if the semaphore has been acquired successfully * or 1 if it cannot be acquired.
*/ staticint nau8825_sema_acquire(struct nau8825 *nau8825, long timeout)
{ int ret;
if (timeout) {
ret = down_timeout(&nau8825->xtalk_sem, timeout); if (ret < 0)
dev_warn(nau8825->dev, "Acquire semaphore timeout\n");
} else {
ret = down_trylock(&nau8825->xtalk_sem); if (ret)
dev_warn(nau8825->dev, "Acquire semaphore fail\n");
}
return ret;
}
/** * nau8825_sema_release - release the semaphore of nau88l25 * @nau8825: component to register the codec private data with * * Release the semaphore which may be called from any context and * even by tasks which have never called down().
*/ staticinlinevoid nau8825_sema_release(struct nau8825 *nau8825)
{
up(&nau8825->xtalk_sem);
}
/** * nau8825_sema_reset - reset the semaphore for nau88l25 * @nau8825: component to register the codec private data with * * Reset the counter of the semaphore. Call this function to restart * a new round task management.
*/ staticinlinevoid nau8825_sema_reset(struct nau8825 *nau8825)
{
nau8825->xtalk_sem.count = 1;
}
/** * nau8825_hpvol_ramp - Ramp up the headphone volume change gradually to target level. * * @nau8825: component to register the codec private data with * @vol_from: the volume to start up * @vol_to: the target volume * @step: the volume span to move on * * The headphone volume is from 0dB to minimum -54dB and -1dB per step. * If the volume changes sharp, there is a pop noise heard in headphone. We * provide the function to ramp up the volume up or down by delaying 10ms * per step.
*/ staticvoid nau8825_hpvol_ramp(struct nau8825 *nau8825, unsignedint vol_from, unsignedint vol_to, unsignedint step)
{ unsignedint value, volume, ramp_up, from, to;
if (vol_from == vol_to || step == 0) { return;
} elseif (vol_from < vol_to) {
ramp_up = true;
from = vol_from;
to = vol_to;
} else {
ramp_up = false;
from = vol_to;
to = vol_from;
} /* only handle volume from 0dB to minimum -54dB */ if (to > NAU8825_HP_VOL_MIN)
to = NAU8825_HP_VOL_MIN;
for (volume = from; volume < to; volume += step) { if (ramp_up)
value = volume; else
value = to - volume + from;
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
(value << NAU8825_HPL_VOL_SFT) | value);
usleep_range(10000, 10500);
} if (ramp_up)
value = to; else
value = from;
regmap_update_bits(nau8825->regmap, NAU8825_REG_HSVOL_CTRL,
NAU8825_HPL_VOL_MASK | NAU8825_HPR_VOL_MASK,
(value << NAU8825_HPL_VOL_SFT) | value);
}
/** * nau8825_intlog10_dec3 - Computes log10 of a value, rounding the result to 3 decimal places. * @value: input for log10 * * return log10(value) * 1000
*/ static u32 nau8825_intlog10_dec3(u32 value)
{ return intlog10(value) / ((1 << 24) / 1000);
}
/** * nau8825_xtalk_sidetone - computes cross talk suppression sidetone gain. * * @sig_org: orignal signal level * @sig_cros: cross talk signal level * * The orignal and cross talk signal vlues need to be characterized. * Once these values have been characterized, this sidetone value * can be converted to decibel with the equation below. * sidetone = 20 * log (original signal level / crosstalk signal level) * * return cross talk sidetone gain
*/ static u32 nau8825_xtalk_sidetone(u32 sig_org, u32 sig_cros)
{
u32 gain, sidetone;
if (WARN_ON(sig_org == 0 || sig_cros == 0)) return 0;
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 * the headphone volume in XTALK_DONE state, it needs recover to * original level gradually with 3dB per step for less pop noise. * Otherwise, the restore should do ASAP.
*/ 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 * change bump enable
*/
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 * generate signal.
*/
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 * avoid pop noise in headphone.
*/
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/16 full scale of signal level for impedance measurement.
*/
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. * Do it before sending signal in order to erase pop noise.
*/
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);
}
/* The cross talk measurement function can reduce cross talk across the * JKTIP(HPL) and JKR1(HPR) outputs which measures the cross talk signal * level to determine what cross talk reduction gain is. This system works by * sending a 23Hz -24dBV sine wave into the headset output DAC and through * the PGA. The output of the PGA is then connected to an internal current * sense which measures the attenuated 23Hz signal and passing the output to * an ADC which converts the measurement to a binary code. With two separated * measurement, one for JKR1(HPR) and the other JKTIP(HPL), measurement data * can be separated read in IMM_RMS_L for HSR and HSL after each measurement. * Thus, the measurement function has four states to complete whole sequence. * 1. Prepare state : Prepare the resource for detection and transfer to HPR * IMM stat to make JKR1(HPR) impedance measure. * 2. HPR IMM state : Read out orignal signal level of JKR1(HPR) and transfer * to HPL IMM state to make JKTIP(HPL) impedance measure. * 3. HPL IMM state : Read out cross talk signal level of JKTIP(HPL) and * transfer to IMM state to determine suppression sidetone gain. * 4. IMM state : Computes cross talk suppression sidetone gain with orignal * and cross talk signal level. Apply this gain and then restore codec * configuration. Then transfer to Done state for ending.
*/ staticvoid nau8825_xtalk_measure(struct nau8825 *nau8825)
{
u32 sidetone;
switch (nau8825->xtalk_state) { case NAU8825_XTALK_PREPARE: /* In prepare state, set up clock, intrruption, DAC path, ADC * path and cross talk detection parameters for preparation.
*/
nau8825_xtalk_prepare(nau8825);
msleep(280); /* Trigger right headphone impedance detection */
nau8825->xtalk_state = NAU8825_XTALK_HPR_R2L;
nau8825_xtalk_imm_start(nau8825, 0x00d2); break; case NAU8825_XTALK_HPR_R2L: /* In right headphone IMM state, read out right headphone * impedance measure result, and then start up left side.
*/
regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
&nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]);
dev_dbg(nau8825->dev, "HPR_R2L imm: %x\n",
nau8825->imp_rms[NAU8825_XTALK_HPR_R2L]); /* Disable then re-enable IMM mode to update */
nau8825_xtalk_imm_stop(nau8825); /* Trigger left headphone impedance detection */
nau8825->xtalk_state = NAU8825_XTALK_HPL_R2L;
nau8825_xtalk_imm_start(nau8825, 0x00ff); break; case NAU8825_XTALK_HPL_R2L: /* In left headphone IMM state, read out left headphone * impedance measure result, and delay some time to wait * detection sine wave output finish. Then, we can calculate * the cross talk suppresstion side tone according to the L/R * headphone imedance.
*/
regmap_read(nau8825->regmap, NAU8825_REG_IMM_RMS_L,
&nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
dev_dbg(nau8825->dev, "HPL_R2L imm: %x\n",
nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
nau8825_xtalk_imm_stop(nau8825);
msleep(150);
nau8825->xtalk_state = NAU8825_XTALK_IMM; break; case NAU8825_XTALK_IMM: /* In impedance measure state, the orignal and cross talk * signal level vlues are ready. The side tone gain is deter- * mined with these signal level. After all, restore codec * configuration.
*/
sidetone = nau8825_xtalk_sidetone(
nau8825->imp_rms[NAU8825_XTALK_HPR_R2L],
nau8825->imp_rms[NAU8825_XTALK_HPL_R2L]);
dev_dbg(nau8825->dev, "cross talk sidetone: %x\n", sidetone);
regmap_write(nau8825->regmap, NAU8825_REG_DAC_DGAIN_CTRL,
(sidetone << 8) | sidetone);
nau8825_xtalk_clean(nau8825, false);
nau8825->xtalk_state = NAU8825_XTALK_DONE; break; default: break;
}
}
nau8825_xtalk_measure(nau8825); /* To determine the cross talk side tone gain when reach * the impedance measure state.
*/ if (nau8825->xtalk_state == NAU8825_XTALK_IMM)
nau8825_xtalk_measure(nau8825);
/* Delay jack report until cross talk detection process * completed. It can avoid application to do playback * preparation before cross talk detection is still working. * Meanwhile, the protection of the cross talk detection * is released.
*/ 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, * the driver forces to cancel the crosstalk task and * restores the configuration to original status.
*/ 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 * playback or capture end. Codec needs clock for Jack * detection and button press if jack inserted; otherwise, * the clock should be closed.
*/ 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 * prevent dapm_power_widgets keeping the codec at SND_SOC_BIAS_ON * during suspend.
*/
SND_SOC_DAPM_SUPPLY("SAR", NAU8825_REG_SAR_CTRL,
NAU8825_SAR_ADC_EN_SFT, 0, NULL, 0),
/* CLK_DAC or CLK_ADC = OSR * FS * DAC or ADC clock frequency is defined as Over Sampling Rate (OSR) * multiplied by the audio sample rate (Fs). Note that the OSR and Fs * values must be selected such that the maximum frequency is less * than 6.144 MHz.
*/
osr = nau8825_get_osr(nau8825, substream->stream); if (!osr || !osr->osr) goto error; if (params_rate(params) * osr->osr > CLK_DA_AD_MAX) goto error; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_DAC_SRC_MASK,
osr->clk_src << NAU8825_CLK_DAC_SRC_SFT); else
regmap_update_bits(nau8825->regmap, NAU8825_REG_CLK_DIVIDER,
NAU8825_CLK_ADC_SRC_MASK,
osr->clk_src << NAU8825_CLK_ADC_SRC_SFT);
/* make BCLK and LRC divde configuration if the codec as master. */
regmap_read(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2, &ctrl_val); if (ctrl_val & NAU8825_I2S_MS_MASTER) { /* get the bclk and fs ratio */
bclk_fs = snd_soc_params_to_bclk(params) / params_rate(params); if (bclk_fs <= 32)
bclk_div = 2; elseif (bclk_fs <= 64)
bclk_div = 1; elseif (bclk_fs <= 128)
bclk_div = 0; else goto error;
regmap_update_bits(nau8825->regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_LRC_DIV_MASK | NAU8825_I2S_BLK_DIV_MASK,
((bclk_div + 1) << NAU8825_I2S_LRC_DIV_SFT) | bclk_div);
}
switch (params_width(params)) { case 16:
val_len |= NAU8825_I2S_DL_16; break; case 20:
val_len |= NAU8825_I2S_DL_20; break; case 24:
val_len |= NAU8825_I2S_DL_24; break; case 32:
val_len |= NAU8825_I2S_DL_32; break; default: goto error;
}
/* Release the semaphore. */
nau8825_sema_release(nau8825);
return 0;
}
/** * nau8825_set_tdm_slot - configure DAI TDM. * @dai: DAI * @tx_mask: bitmask representing active TX slots. * @rx_mask: bitmask representing active RX slots. * @slots: Number of slots in use. * @slot_width: Width in bits for each slot. * * Configures a DAI for TDM operation. Support TDM 4/8 slots. * The limitation is DAC and ADC need shift 4 slots at 8 slots mode.
*/ staticint nau8825_set_tdm_slot(struct snd_soc_dai *dai, unsignedint tx_mask, unsignedint rx_mask, int slots, int slot_width)
{ struct snd_soc_component *component = dai->component; struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); unsignedint ctrl_val = 0, ctrl_offset = 0, value = 0, dac_s, adc_s;
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;
}
/** * nau8825_enable_jack_detect - Specify a jack for event reporting * * @component: component to register the jack with * @jack: jack to use to report headset and button events on * * After this function has been called the headset insert/remove and button * events will be routed to the given jack. Jack can be null to stop * reporting.
*/ int nau8825_enable_jack_detect(struct snd_soc_component *component, struct snd_soc_jack *jack)
{ struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); struct regmap *regmap = nau8825->regmap;
nau8825->jack = jack;
if (!nau8825->jack) {
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R |
NAU8825_SPKR_DWN1L, 0); return 0;
} /* Ground HP Outputs[1:0], needed for headset auto detection * Enable Automatic Mic/Gnd switching reading on insert interrupt[6]
*/
regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L,
NAU8825_HSD_AUTO_MODE | NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L);
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 * active high or active low.
*/ return active_high == is_high;
}
staticvoid nau8825_restart_jack_detection(struct regmap *regmap)
{ /* this will restart the entire jack detection process including MIC/GND * switching and create interrupts. We have to go from 0 to 1 and back * to 0 to restart.
*/
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- * ponding irq event occurs.
*/
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 * jack ejection interruption.
*/
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);
/* Chip needs one FSCLK cycle in order to generate interruptions, * as we cannot guarantee one will be provided by the system. Turning * master mode on then off enables us to generate that FSCLK cycle * with a minimum of contention on the clock bus.
*/
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_MASTER);
regmap_update_bits(regmap, NAU8825_REG_I2S_PCM_CTRL2,
NAU8825_I2S_MS_MASK, NAU8825_I2S_MS_SLAVE);
/* Not bypass de-bounce circuit */
regmap_update_bits(regmap, NAU8825_REG_JACK_DET_CTRL,
NAU8825_JACK_DET_DB_BYPASS, 0);
/* Unmask all interruptions */
regmap_write(regmap, NAU8825_REG_INTERRUPT_DIS_CTRL, 0);
/* Restart the jack detection process at auto mode */
nau8825_restart_jack_detection(regmap);
}
staticint nau8825_button_decode(int value)
{ int buttons = 0;
/* The chip supports up to 8 buttons, but ALSA defines only 6 buttons */ if (value & BIT(0))
buttons |= SND_JACK_BTN_0; if (value & BIT(1))
buttons |= SND_JACK_BTN_1; if (value & BIT(2))
buttons |= SND_JACK_BTN_2; if (value & BIT(3))
buttons |= SND_JACK_BTN_3; if (value & BIT(4))
buttons |= SND_JACK_BTN_4; if (value & BIT(5))
buttons |= SND_JACK_BTN_5;
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) { case 0: /* no mic */
type = SND_JACK_HEADPHONE; break; case 1:
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; case 3: /* 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 * ungrounded as part of the widget power up sequence at the beginning * of playback to reduce pop.
*/ return type;
}
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.