// SPDX-License-Identifier: GPL-2.0-only // // The NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver. // // Copyright (C) 2026 Nuvoton Technology Corp. // Author: David Lin // Seven Lee // John Hsu // Neo Chang #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nau8360-dsp.h" #include "nau8360.h" /* range of Master Clock MCLK (Hz) */ #define MASTER_CLK_MIN 11025000 #define MASTER_CLK_MAX 24576000 /* DSP Optimal Clock Range 120MHz~126M(Hz) */ #define DSP_OP_CLK48 122880000 #define DSP_OP_CLK44 112896000 /* the maximum frequency of DAC and IV sense clock */ #define CLK_DA_IVSNS_MAX 6144000 #define ADSP_SR_48000 48000 #define ADSP_SR_44100 44100 static const int ivsns_clk_div[] = { 1, 2, 4, 5, 8, 10 }; static const int dac_clk_div[] = { 1, 2, 4, 8 }; static const char * const nau8360_rx_func_names[] = { [NAU8360_TDM_DACL] = "DAC_L", [NAU8360_TDM_DACR] = "DAC_R", [NAU8360_TDM_ANCL] = "ANC_L", [NAU8360_TDM_ANCR] = "ANC_R", }; static const char * const nau8360_tx_func_names[] = { [NAU8360_TDM_AECL] = "AEC_L", [NAU8360_TDM_AECR] = "AEC_R", [NAU8360_TDM_ISNSL] = "ISNS_L", [NAU8360_TDM_ISNSR] = "ISNS_R", [NAU8360_TDM_VSNSL] = "VSNS_L", [NAU8360_TDM_VSNSR] = "VSNS_R", [NAU8360_TDM_TJ] = "TJ", [NAU8360_TDM_VBAT] = "VBAT", }; /* PLL threshold */ #define PLL_FREQ_MIN 1000000 #define PLL_FREQ_MAX 32000000 #define PLL_FOUT_MIN 12500000 #define PLL_FOUT_MAX 125000000 #define PLL_FREF_MAX 8000000 #define PLL_FVCO_MIN 50000000 #define MSEL_MAX 32 #define RSEL_MAX 4 static const struct reg_default nau8360_reg_defaults[] = { { NAU8360_R02_I2C_ADDR, 0x0000 }, { NAU8360_R03_CLK_CTRL0, 0x0000 }, { NAU8360_R04_CLK_CTRL1, 0x0000 }, { NAU8360_R05_INTERRUPT_CTRL, 0x40ff }, { NAU8360_R07_GP_CTRL, 0xaa30 }, { NAU8360_R08_GP_CTRL0, 0x1e1e }, { NAU8360_R09_GP_CTRL1, 0x1e1e }, { NAU8360_R0A_GP_CTRL2, 0x0000 }, { NAU8360_R0B_I2S_PCM_CTRL1, 0x0702 }, { NAU8360_R0C_I2S_PCM_CTRL2, 0x0a10 }, { NAU8360_R0D_I2S_PCM_CTRL3, 0x1300 }, { NAU8360_R0E_I2S_DATA_CTRL1, 0x0304 }, { NAU8360_R0F_I2S_DATA_CTRL2, 0x080b }, { NAU8360_R10_I2S_DATA_CTRL3, 0x0014 }, { NAU8360_R11_I2S_DATA_CTRL4, 0x0014 }, { NAU8360_R12_PATH_CTRL, 0x0400 }, { NAU8360_R17_I2S0_DATA_CTRL5, 0x0014 }, { NAU8360_R1A_DSP_CORE_CTRL2, 0x0010 }, { NAU8360_R2C_ALC_CTRL1, 0x2000 }, { NAU8360_R2D_ALC_CTRL2, 0x8400 }, { NAU8360_R2E_ALC_CTRL3, 0x2083 }, { NAU8360_R31_UVLOP_CTRL1, 0x0000 }, { NAU8360_R32_UVLOP_CTRL2, 0x8400 }, { NAU8360_R33_UVLOP_CTRL3, 0x0000 }, { NAU8360_R40_CLK_DET_CTRL, 0xca60 }, { NAU8360_R41_CLK_CTL2, 0xc400 }, { NAU8360_R5D_SINC_CFG, 0x0010 }, { NAU8360_R5F_ANA_TRIM_CFG1, 0x8400 }, { NAU8360_R60_RST, 0x0010 }, { NAU8360_R67_ANALOG_CONTROL_0, 0x0160 }, { NAU8360_R68_ANALOG_CONTROL_1, 0x00d4 }, { NAU8360_R6A_SARADC_CFG0, 0x5c09 }, { NAU8360_R6B_SARADC_CFG1, 0x0008 }, { NAU8360_R6C_IVSNS_CFG0, 0xf040 }, { NAU8360_R6D_IVSNS_CFG1, 0x3555 }, { NAU8360_R6E_DAC_CFG0, 0x0ed5 }, { NAU8360_R71_CLK_DIV_CFG, 0x0211 }, { NAU8360_R72_PLL_CFG0, 0xccc4 }, { NAU8360_R73_PLL_CFG1, 0x0101 }, { NAU8360_R74_PLL_CFG2, 0x0000 }, { NAU8360_R7A_DAC_TRIM_CFG2, 0x0000 }, { NAU8360_R7B_IVSNS_TRIM_CFG, 0x0000 }, { NAU8360_R7C_MISC_TRIM_CFG, 0x72d5 }, { NAU8360_R86_HW3_CTL0, 0x2000 }, { NAU8360_R88_ALC_CTRL6, 0x0000 }, { NAU8360_R8A_HW3_VL_CTL7, 0xc000 }, { NAU8360_R8B_HW3_VR_CTL8, 0xc000 }, { NAU8360_R8C_HW3_CTL6, 0x0000 }, { NAU8360_R8D_HW3_IL_CTL7, 0xc000 }, { NAU8360_R8E_HW3_IR_CTL8, 0xc000 }, { NAU8360_R8F_HW3_CTL9, 0x0000 }, { NAU8360_R90_HW2_CTL0, 0x2000 }, { NAU8360_R96_HW2_CTL6, 0x0000 }, { NAU8360_R97_HW2_CTL7, 0xc000 }, { NAU8360_R98_HW2_CTL8, 0xc000 }, { NAU8360_R99_HW2_CTL9, 0x0000 }, { NAU8360_R9A_HW1_CTL0, 0xc000 }, { NAU8360_R9B_HW1_CTL1, 0xc000 }, { NAU8360_R9C_HW1_CTL2, 0x0800 }, { NAU8360_R9D_PEQ_CTL, 0x0001 }, { NAU8360_RA0_LEFT_XODRC_CTRL, 0x0000 }, { NAU8360_RA2_RIGHT_XODRC_CTRL, 0x0000 }, { NAU8360_RA4_ANA_REG_0, 0x7f86 }, { NAU8360_RA5_ANA_REG_1, 0x276e }, }; static bool nau8360_readable_reg(struct device *dev, unsigned int reg) { switch (reg) { case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: case NAU8360_R17_I2S0_DATA_CTRL5: case NAU8360_R1A_DSP_CORE_CTRL2: case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: case NAU8360_R46_I2C_DEVICE_ID: case NAU8360_R5D_SINC_CFG: case NAU8360_R5F_ANA_TRIM_CFG1 ... NAU8360_R60_RST: case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R6E_DAC_CFG0: case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: case NAU8360_R7E_CLK_GATED_EN: case NAU8360_R86_HW3_CTL0: case NAU8360_R88_ALC_CTRL6: case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: case NAU8360_RA0_LEFT_XODRC_CTRL: case NAU8360_RA2_RIGHT_XODRC_CTRL: case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_RF000_DSP_COMM: case NAU8360_RF002_DSP_COMM: return true; default: return false; } } static bool nau8360_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { case NAU8360_R00_SOFTWARE_RST ... NAU8360_R12_PATH_CTRL: case NAU8360_R17_I2S0_DATA_CTRL5: case NAU8360_R1A_DSP_CORE_CTRL2: case NAU8360_R2C_ALC_CTRL1 ... NAU8360_R2E_ALC_CTRL3: case NAU8360_R31_UVLOP_CTRL1 ... NAU8360_R33_UVLOP_CTRL3: case NAU8360_R40_CLK_DET_CTRL ... NAU8360_R41_CLK_CTL2: case NAU8360_R5D_SINC_CFG: case NAU8360_R5F_ANA_TRIM_CFG1: case NAU8360_R60_RST: case NAU8360_R67_ANALOG_CONTROL_0 ... NAU8360_R68_ANALOG_CONTROL_1: case NAU8360_R6A_SARADC_CFG0 ... NAU8360_R6E_DAC_CFG0: case NAU8360_R71_CLK_DIV_CFG ... NAU8360_R74_PLL_CFG2: case NAU8360_R77_SOFT_SD ... NAU8360_R7C_MISC_TRIM_CFG: case NAU8360_R7E_CLK_GATED_EN: case NAU8360_R86_HW3_CTL0: case NAU8360_R88_ALC_CTRL6: case NAU8360_R8A_HW3_VL_CTL7 ... NAU8360_R90_HW2_CTL0: case NAU8360_R96_HW2_CTL6 ... NAU8360_R9D_PEQ_CTL: case NAU8360_RA4_ANA_REG_0 ... NAU8360_RA5_ANA_REG_1: case NAU8360_R100_LEFT_BIQ0_COE ... (NAU8360_R100_LEFT_BIQ0_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R10C_LEFT_BIQ1_COE ... (NAU8360_R10C_LEFT_BIQ1_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R118_LEFT_BIQ2_COE ... (NAU8360_R118_LEFT_BIQ2_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R124_LEFT_BIQ3_COE ... (NAU8360_R124_LEFT_BIQ3_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R130_LEFT_BIQ4_COE ... (NAU8360_R130_LEFT_BIQ4_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R13C_LEFT_BIQ5_COE ... (NAU8360_R13C_LEFT_BIQ5_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R148_LEFT_BIQ6_COE ... (NAU8360_R148_LEFT_BIQ6_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R154_LEFT_BIQ7_COE ... (NAU8360_R154_LEFT_BIQ7_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R160_LEFT_BIQ8_COE ... (NAU8360_R160_LEFT_BIQ8_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R16C_LEFT_BIQ9_COE ... (NAU8360_R16C_LEFT_BIQ9_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R178_LEFT_BIQ10_COE ... (NAU8360_R178_LEFT_BIQ10_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R184_LEFT_BIQ11_COE ... (NAU8360_R184_LEFT_BIQ11_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R190_LEFT_BIQ12_COE ... (NAU8360_R190_LEFT_BIQ12_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R19C_LEFT_BIQ13_COE ... (NAU8360_R19C_LEFT_BIQ13_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R1A8_LEFT_BIQ14_COE ... (NAU8360_R1A8_LEFT_BIQ14_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R200_RIGHT_BIQ0_COE ... (NAU8360_R200_RIGHT_BIQ0_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R20C_RIGHT_BIQ1_COE ... (NAU8360_R20C_RIGHT_BIQ1_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R218_RIGHT_BIQ2_COE ... (NAU8360_R218_RIGHT_BIQ2_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R224_RIGHT_BIQ3_COE ... (NAU8360_R224_RIGHT_BIQ3_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R230_RIGHT_BIQ4_COE ... (NAU8360_R230_RIGHT_BIQ4_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R23C_RIGHT_BIQ5_COE ... (NAU8360_R23C_RIGHT_BIQ5_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R248_RIGHT_BIQ6_COE ... (NAU8360_R248_RIGHT_BIQ6_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R254_RIGHT_BIQ7_COE ... (NAU8360_R254_RIGHT_BIQ7_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R260_RIGHT_BIQ8_COE ... (NAU8360_R260_RIGHT_BIQ8_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R26C_RIGHT_BIQ9_COE ... (NAU8360_R26C_RIGHT_BIQ9_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R278_RIGHT_BIQ10_COE ... (NAU8360_R278_RIGHT_BIQ10_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R284_RIGHT_BIQ11_COE ... (NAU8360_R284_RIGHT_BIQ11_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R290_RIGHT_BIQ12_COE ... (NAU8360_R290_RIGHT_BIQ12_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R29C_RIGHT_BIQ13_COE ... (NAU8360_R29C_RIGHT_BIQ13_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_R2A8_RIGHT_BIQ14_COE ... (NAU8360_R2A8_RIGHT_BIQ14_COE + NAU8360_PEQ_REG_WIDTH): case NAU8360_RF000_DSP_COMM: case NAU8360_RF002_DSP_COMM: return true; default: return false; } } static bool nau8360_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case NAU8360_R00_SOFTWARE_RST ... NAU8360_R02_I2C_ADDR: case NAU8360_R06_INT_CLR_STATUS: case NAU8360_R21_VBAT_READOUT ... NAU8360_R22_TEMP_READOUT: case NAU8360_R41_CLK_CTL2: case NAU8360_R46_I2C_DEVICE_ID: case NAU8360_R69_ANALOG_CONTROL_3: case NAU8360_R77_SOFT_SD ... NAU8360_R79_EN_HIRC48M: case NAU8360_R7E_CLK_GATED_EN: case NAU8360_R9D_PEQ_CTL: case NAU8360_RF000_DSP_COMM: case NAU8360_RF002_DSP_COMM: return true; default: return false; } } static int nau8360_get_tdm_chan_len(struct nau8360 *nau8360) { int val = 0; regmap_read(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, &val); val = (val & NAU8360_TDM_CLEN_MASK) >> NAU8360_TDM_CLEN_SFT; return (val << 3) + 16; } static inline bool nau8360_dsp_active(struct snd_soc_component *comp) { return (snd_soc_component_read(comp, NAU8360_R12_PATH_CTRL) & NAU8360_DAC_SEL_DSP); } static int nau8360_anc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); int ret, value = NAU8360_PEQ_BAND_8; mutex_lock(&nau8360->lock); ret = snd_soc_put_volsw(kcontrol, ucontrol); /* update anc flag if return value 1 and register value changed */ if (ret != 1) goto unlock; nau8360->anc_enable = ucontrol->value.integer.value[0]; if (nau8360_dsp_active(cp)) value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; regmap_update_bits(nau8360->regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, value << NAU8360_PEQ_BAND_SFT); unlock: mutex_unlock(&nau8360->lock); return ret; } static inline void nau8360_peq_mem_enable(struct regmap *regmap, bool enable) { regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_TEST, enable ? NAU8360_HW1_MEM_TEST : 0); } static inline int nau8360_peq_regaddr(const char *id_name) { int reg, band_num, dsp_addr = NAU8360_DSP_ADDR_BYNAME(id_name); char *band = strstr(id_name, "BIQ"); if (!band || kstrtoint((band + 3), 10, &band_num)) return -EINVAL; reg = dsp_addr == NAU8360_RF000_DSP_COMM ? NAU8360_R100_LEFT_BIQ0_COE : NAU8360_R200_RIGHT_BIQ0_COE; reg += band_num * NAU8360_TOT_BAND_COE_RANGE; return reg; } static int nau8360_peq_coeff_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); struct soc_bytes_ext *params = (void *)kcontrol->private_value; int i, value, reg, ret = 0; __be16 *val = (__be16 *)ucontrol->value.bytes.data; /* Use the DAPM lock to prevent race conditions during DAPM power-up * state transitions, and check component active status to prohibit * PEQ access during active audio streams (playback and capture). */ snd_soc_dapm_mutex_lock(dapm); if (snd_soc_component_active(cp)) { dev_dbg(nau8360->dev, "PEQ coefficient access is ignored during audio is active"); ret = -EBUSY; goto unlock_dapm; } reg = nau8360_peq_regaddr(kcontrol->id.name); if (reg < 0) { ret = reg; goto unlock_dapm; } mutex_lock(&nau8360->lock); nau8360_peq_mem_enable(nau8360->regmap, true); for (i = 0; i < params->max / sizeof(u16); i++) { value = snd_soc_component_read(cp, reg + i); *(val + i) = cpu_to_be16(value); } nau8360_peq_mem_enable(nau8360->regmap, false); mutex_unlock(&nau8360->lock); unlock_dapm: snd_soc_dapm_mutex_unlock(dapm); return ret; } static int nau8360_peq_coeff_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *cp = snd_kcontrol_chip(kcontrol); struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(cp); struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); struct soc_bytes_ext *params = (void *)kcontrol->private_value; int i, reg, ret = 0; __be16 *data = NULL; bool changed = false; /* Use the DAPM lock to prevent race conditions during DAPM power-up * state transitions, and check component active status to prohibit * PEQ access during active audio streams (playback and capture). */ snd_soc_dapm_mutex_lock(dapm); if (snd_soc_component_active(cp)) { dev_dbg(nau8360->dev, "PEQ coefficient access is ignored during audio is active"); ret = -EBUSY; goto unlock_dapm; } reg = nau8360_peq_regaddr(kcontrol->id.name); if (reg < 0) { ret = reg; goto unlock_dapm; } data = kmemdup(ucontrol->value.bytes.data, params->max, GFP_KERNEL); if (!data) { ret = -ENOMEM; goto unlock_dapm; } mutex_lock(&nau8360->lock); nau8360_peq_mem_enable(nau8360->regmap, true); for (i = 0; i < params->max / sizeof(u16); i++) { if (snd_soc_component_read(cp, reg + i) != be16_to_cpu(*(data + i))) { snd_soc_component_write(cp, reg + i, be16_to_cpu(*(data + i))); changed = true; } } nau8360_peq_mem_enable(nau8360->regmap, false); mutex_unlock(&nau8360->lock); ret = changed ? 1 : 0; unlock_dapm: snd_soc_dapm_mutex_unlock(dapm); kfree(data); return ret; } #define NAU8360_PEQ_COEF_BYTES_EXT(ch, band) \ SND_SOC_BYTES_EXT(ch " PEQ Coefficients " band, 20, nau8360_peq_coeff_get, \ nau8360_peq_coeff_put) static const struct snd_kcontrol_new nau8360_snd_controls[] = { NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ0"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ1"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ2"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ3"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ4"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ5"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ6"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ7"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ8"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ9"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ10"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ11"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ12"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ13"), NAU8360_PEQ_COEF_BYTES_EXT("Left", "BIQ14"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ0"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ1"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ2"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ3"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ4"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ5"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ6"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ7"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ8"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ9"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ10"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ11"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ12"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ13"), NAU8360_PEQ_COEF_BYTES_EXT("Right", "BIQ14"), SOC_SINGLE("Low Latency Switch", NAU8360_R96_HW2_CTL6, NAU8360_HW2_LATENCY_SFT, 1, 0), SOC_SINGLE_EXT("ANC Path Switch", NAU8360_R96_HW2_CTL6, NAU8360_HW1_ANC_EN_SFT, 1, 0, snd_soc_get_volsw, nau8360_anc_put), }; static int nau8360_adci_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_ON(event)) snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, 0); else if (SND_SOC_DAPM_EVENT_OFF(event)) snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD, NAU8360_PD_ISNS_R_PMD | NAU8360_PD_ISNS_L_PMD); return 0; } static int nau8360_adcv_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_ON(event)) snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, 0); else if (SND_SOC_DAPM_EVENT_OFF(event)) snd_soc_component_update_bits(component, NAU8360_R6C_IVSNS_CFG0, NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD, NAU8360_PD_VSNS_R_PMD | NAU8360_PD_VSNS_L_PMD); return 0; } static int nau8360_aif_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_OFF(event)) snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_HV_EN, 0); return 0; } static int nau8360_hw1_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_OFF(event)) snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_ANA_MUTE, NAU8360_ANA_MUTE); return 0; } static int nau8360_hw1_mux_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_OFF(event)) { snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, 0); snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW); msleep(20); } return 0; } static int nau8360_hw2_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_ON(event)) { snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, NAU8360_HW2_CH_MUTE, 0); snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, NAU8360_HW1_CH_MUTE, 0); } return 0; } static int nau8360_dac_power_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *cp = snd_soc_dapm_to_component(w->dapm); unsigned int mask = 1 << w->shift; if (SND_SOC_DAPM_EVENT_ON(event)) snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, 0); else if (SND_SOC_DAPM_EVENT_OFF(event)) snd_soc_component_update_bits(cp, NAU8360_R6E_DAC_CFG0, mask, mask); return 0; } static int nau8360_hv_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); if (SND_SOC_DAPM_EVENT_ON(event)) { /* enable Class D HV power after DAC power is stable */ snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_HV_EN, NAU8360_HV_EN); msleep(50); /* Class D modulator input short setting for mute and de-pop purpose. * Restore normal after initiation. Set segment driver as full driving * strength. */ snd_soc_component_update_bits(component, NAU8360_RA5_ANA_REG_1, NAU8360_CLASSD_SHT_IN | NAU8360_HVEN_SYNC_SAW, 0); snd_soc_component_update_bits(component, NAU8360_R68_ANALOG_CONTROL_1, NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL); snd_soc_component_update_bits(component, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_ANA_MUTE, 0); } else if (SND_SOC_DAPM_EVENT_OFF(event)) { snd_soc_component_update_bits(component, NAU8360_R9C_HW1_CTL2, NAU8360_HW1_CH_MUTE, NAU8360_HW1_CH_MUTE); snd_soc_component_update_bits(component, NAU8360_R99_HW2_CTL9, NAU8360_HW2_CH_MUTE, NAU8360_HW2_CH_MUTE); } return 0; } static inline void nau8360_dsp_enable(struct regmap *regmap, bool enable) { regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_STALL, enable ? 0 : NAU8360_HW3_STALL); regmap_update_bits(regmap, NAU8360_R1A_DSP_CORE_CTRL2, NAU8360_DSP_RUNSTALL, enable ? 0 : NAU8360_DSP_RUNSTALL); } static void nau8360_dsp_switch(struct snd_soc_component *component, bool enable) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); struct regmap *regmap = nau8360->regmap; int value = NAU8360_PEQ_BAND_8; mutex_lock(&nau8360->lock); /* If DSP is enabled, unstall HW3 engine and DSP, loading DSP firmware, * and configure PEQ after dsp reset. */ if (enable) { value = nau8360->anc_enable ? NAU8360_PEQ_BAND_15 : NAU8360_PEQ_BAND_12; nau8360_dsp_enable(regmap, true); } else { dev_dbg(nau8360->dev, "Bypass DSP path"); nau8360_dsp_enable(regmap, false); } regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, value << NAU8360_PEQ_BAND_SFT); mutex_unlock(&nau8360->lock); } static int nau8360_dac_mux_put_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); struct snd_soc_component *component = snd_soc_dapm_to_component(dapm); struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int *item = ucontrol->value.enumerated.item; int ret = 0; snd_soc_dapm_mutex_lock(dapm); if (snd_soc_dapm_get_bias_level(dapm) > SND_SOC_BIAS_STANDBY) { dev_warn_ratelimited(nau8360->dev, "changing path is not allowed during playback"); snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } if (item[0] == NAU8360_DAC_SRC_DSP && !nau8360->load_fw_done) { dev_warn_ratelimited(nau8360->dev, "Cannot enable DSP: Firmware not ready or disabled\n"); snd_soc_dapm_mutex_unlock(dapm); return -EBUSY; } snd_soc_dapm_mutex_unlock(dapm); ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); if (ret <= 0) return ret; nau8360_dsp_switch(component, snd_soc_enum_item_to_val(e, item[0])); return ret; } /* Select HW1 output source (enables PEQ bypass) */ static const char *const nau8360_hw1out_src[] = { "Audio", "PEQ" }; static SOC_ENUM_SINGLE_DECL(nau8360_hw1out_enum, NAU8360_R12_PATH_CTRL, NAU8360_SEL_HW1_SFT, nau8360_hw1out_src); static const struct snd_kcontrol_new nau8360_hw1out_mux = SOC_DAPM_ENUM("HW1 Output Source", nau8360_hw1out_enum); /* Select DAC input source (enables DSP bypass) */ static const char *const nau8360_dac_src[] = { "HW1", "DSP" }; static SOC_ENUM_SINGLE_DECL(nau8360_dac_enum, NAU8360_R12_PATH_CTRL, NAU8360_DAC_SEL_SFT, nau8360_dac_src); static const struct snd_kcontrol_new nau8360_dac_mux = SOC_DAPM_ENUM_EXT("DAC Source", nau8360_dac_enum, snd_soc_dapm_get_enum_double, nau8360_dac_mux_put_enum); static const struct snd_soc_dapm_widget nau8360_dapm_widgets[] = { SND_SOC_DAPM_SIGGEN("Sense"), SND_SOC_DAPM_ADC_E("ADC_I", NULL, SND_SOC_NOPM, 0, 0, nau8360_adci_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_ADC_E("ADC_V", NULL, SND_SOC_NOPM, 0, 0, nau8360_adcv_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA("DSP", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_PGA("HW3 Engine", NAU8360_R8C_HW3_CTL6, 3, 0, NULL, 0), SND_SOC_DAPM_AIF_IN_E("AIFRX", "Playback", 0, SND_SOC_NOPM, 0, 0, nau8360_aif_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_S("HW1 Engine", 0, NAU8360_R9D_PEQ_CTL, 0, 1, nau8360_hw1_event, SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX_E("HW1 Mux", SND_SOC_NOPM, 0, 0, &nau8360_hw1out_mux, nau8360_hw1_mux_event, SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, &nau8360_dac_mux), SND_SOC_DAPM_PGA_S("HW2 Engine", 1, NAU8360_R96_HW2_CTL6, 5, 0, nau8360_hw2_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY("DAC Clock", NAU8360_R71_CLK_DIV_CFG, 9, 1, NULL, 0), SND_SOC_DAPM_DAC_E("DACL", NULL, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_DAC_E("DACR", NULL, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, nau8360_dac_power_event, SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_S("ADACL", 2, SND_SOC_NOPM, NAU8360_PD_DACL_SFT, 0, nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_S("ADACR", 2, SND_SOC_NOPM, NAU8360_PD_DACR_SFT, 0, nau8360_dac_power_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_PGA_S("Class D", 3, SND_SOC_NOPM, 0, 0, nau8360_hv_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_OUTPUT("OUTL"), SND_SOC_DAPM_OUTPUT("OUTR"), }; static const struct snd_soc_dapm_route nau8360_dapm_routes[] = { { "ADC_I", NULL, "Sense" }, { "ADC_V", NULL, "Sense" }, { "HW3 Engine", NULL, "ADC_I" }, { "HW3 Engine", NULL, "ADC_V" }, { "Capture", NULL, "HW3 Engine" }, { "HW1 Engine", NULL, "AIFRX" }, { "HW1 Mux", "Audio", "AIFRX" }, { "HW1 Mux", "PEQ", "HW1 Engine" }, { "DSP", NULL, "HW1 Mux" }, { "DAC Mux", "HW1", "HW1 Mux" }, { "DAC Mux", "DSP", "DSP" }, { "HW2 Engine", NULL, "DAC Mux" }, { "DACL", NULL, "HW2 Engine" }, { "DACR", NULL, "HW2 Engine" }, { "DACL", NULL, "DAC Clock" }, { "DACR", NULL, "DAC Clock" }, { "ADACL", NULL, "DACL" }, { "ADACR", NULL, "DACR" }, { "Class D", NULL, "ADACL" }, { "Class D", NULL, "ADACR" }, { "OUTL", NULL, "Class D" }, { "OUTR", NULL, "Class D" }, }; static int nau8360_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_component *component = dai->component; struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); unsigned int i2s_mask = NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK; unsigned int i2s_fmt = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; int val = 0; flush_work(&nau8360->load_fw_work); if (nau8360_dsp_active(component) && !nau8360->load_fw_done) { dev_warn(nau8360->dev, "DSP firmware is not ready yet!"); return -EBUSY; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_read(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, &val); if ((val & i2s_mask) == i2s_fmt) regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, NAU8360_EN_TDM_RX, NAU8360_EN_TDM_RX); if (nau8360_dsp_active(component)) snd_soc_dapm_enable_pin(nau8360->dapm, "Sense"); } return 0; } static void nau8360_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_component *component = dai->component; struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); unsigned int tdm_mask; tdm_mask = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? NAU8360_EN_TDM_RX : NAU8360_EN_TDM_TX; regmap_update_bits(nau8360->regmap, NAU8360_R0B_I2S_PCM_CTRL1, tdm_mask, 0); if (nau8360_dsp_active(component) && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); } static int nau8360_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct snd_soc_component *component = dai->component; struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); unsigned int val_len, val_srate; int dlen = params_width(params); if (dlen > nau8360_get_tdm_chan_len(nau8360)) { dev_err(nau8360->dev, "Invalid data length"); return -EINVAL; } switch (dlen) { case 16: val_len = NAU8360_TDM_DLEN_16; break; case 20: val_len = NAU8360_TDM_DLEN_20; break; case 24: val_len = NAU8360_TDM_DLEN_24; break; case 32: val_len = NAU8360_TDM_DLEN_32; break; default: return -EINVAL; } switch (params_rate(params)) { case 16000: val_srate = NAU8360_SRATE_16000; break; case 32000: val_srate = NAU8360_SRATE_32000; break; case 44100: case 48000: val_srate = NAU8360_SRATE_48000; break; case 88200: case 96000: val_srate = NAU8360_SRATE_96000; break; case 176400: case 192000: val_srate = NAU8360_SRATE_192000; break; default: return -EINVAL; } regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, NAU8360_TDM_DLEN_MASK, val_len); regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, NAU8360_SRATE_MASK, val_srate); return 0; } static int nau8360_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; unsigned int ctrl_val, ctrl1_val; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; default: return -EINVAL; } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: ctrl_val = NAU8360_FRAME_START_H2L | NAU8360_RX_OFFSET_I2S; ctrl1_val = NAU8360_TX_OFFSET_I2S; break; case SND_SOC_DAIFMT_LEFT_J: ctrl_val = NAU8360_RX_OFFSET_LEFT | NAU8360_RX_LEFT_JUSTIFY; ctrl1_val = NAU8360_TX_OFFSET_LEFT; break; case SND_SOC_DAIFMT_RIGHT_J: ctrl_val = NAU8360_RX_OFFSET_RIGHT | NAU8360_RX_RIGHT_JUSTIFY; ctrl1_val = NAU8360_TX_OFFSET_RIGHT; break; case SND_SOC_DAIFMT_DSP_A: ctrl_val = NAU8360_RX_OFFSET_PCM_A; ctrl1_val = NAU8360_TX_OFFSET_PCM_A; break; case SND_SOC_DAIFMT_DSP_B: ctrl_val = NAU8360_RX_OFFSET_PCM_B; ctrl1_val = NAU8360_TX_OFFSET_PCM_B; break; default: return -EINVAL; } snd_soc_component_update_bits(component, NAU8360_R0B_I2S_PCM_CTRL1, NAU8360_FRAME_START_MASK | NAU8360_RX_OFFSET_MASK | NAU8360_RX_JUSTIFY_MASK, ctrl_val); snd_soc_component_update_bits(component, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_TX_OFFSET_MASK, ctrl1_val); return 0; } static void nau8360_set_tdm_rx_slot(struct snd_soc_component *cp, int type, int slot) { switch (type) { case NAU8360_TDM_DACL: snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, NAU8360_RX_DACL_MASK, slot); break; case NAU8360_TDM_DACR: snd_soc_component_update_bits(cp, NAU8360_R0C_I2S_PCM_CTRL2, NAU8360_RX_DACR_MASK, (slot << NAU8360_RX_DACR_SFT)); break; case NAU8360_TDM_ANCL: snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, NAU8360_RX_ANC_L_MASK, (slot << NAU8360_RX_ANC_L_SFT)); break; case NAU8360_TDM_ANCR: snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, NAU8360_RX_ANC_R_MASK, (slot << NAU8360_RX_ANC_R_SFT)); break; } } static void nau8360_set_tdm_tx_slot(struct snd_soc_component *cp, int type, int slot) { switch (type) { case NAU8360_TDM_AECL: snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, NAU8360_AEC_L_SLOT_MASK, slot << NAU8360_AEC_L_SLOT_SFT); break; case NAU8360_TDM_AECR: snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, NAU8360_AEC_R_SLOT_MASK, slot); break; case NAU8360_TDM_ISNSL: snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, NAU8360_ISNS_L_SLOT_MASK, slot << NAU8360_ISNS_L_SLOT_SFT); break; case NAU8360_TDM_ISNSR: snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, NAU8360_ISNS_R_SLOT_MASK, slot); break; case NAU8360_TDM_VSNSL: snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_VSNS_L_SLOT_MASK, slot); break; case NAU8360_TDM_VSNSR: snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, NAU8360_VSNS_R_SLOT_MASK, slot << NAU8360_VSNS_R_SLOT_SFT); break; case NAU8360_TDM_TJ: snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, NAU8360_TEMP_SLOT_MASK, slot); break; case NAU8360_TDM_VBAT: snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, NAU8360_VBAT_SLOT_MASK, slot << NAU8360_VBAT_SLOT_SFT); break; } } static void nau8360_set_tdm_tx_func(struct snd_soc_component *cp, int type, bool enable) { switch (type) { case NAU8360_TDM_AECL: snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, NAU8360_AEC_L_EN, enable ? NAU8360_AEC_L_EN : 0); break; case NAU8360_TDM_AECR: snd_soc_component_update_bits(cp, NAU8360_R17_I2S0_DATA_CTRL5, NAU8360_AEC_R_EN, enable ? NAU8360_AEC_R_EN : 0); break; case NAU8360_TDM_ISNSL: snd_soc_component_update_bits(cp, NAU8360_R0E_I2S_DATA_CTRL1, NAU8360_ISNS_L_TX_EN, enable ? NAU8360_ISNS_L_TX_EN : 0); break; case NAU8360_TDM_ISNSR: snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, NAU8360_ISNS_R_TX_EN, enable ? NAU8360_ISNS_R_TX_EN : 0); break; case NAU8360_TDM_VSNSL: snd_soc_component_update_bits(cp, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_VSNS_L_TX_EN, enable ? NAU8360_VSNS_L_TX_EN : 0); break; case NAU8360_TDM_VSNSR: snd_soc_component_update_bits(cp, NAU8360_R11_I2S_DATA_CTRL4, NAU8360_VSNS_R_TX_EN, enable ? NAU8360_VSNS_R_TX_EN : 0); break; case NAU8360_TDM_TJ: snd_soc_component_update_bits(cp, NAU8360_R10_I2S_DATA_CTRL3, NAU8360_TEMP_TX_EN, enable ? NAU8360_TEMP_TX_EN : 0); break; case NAU8360_TDM_VBAT: snd_soc_component_update_bits(cp, NAU8360_R0F_I2S_DATA_CTRL2, NAU8360_VBAT_TX_EN, enable ? NAU8360_VBAT_TX_EN : 0); break; } } static int nau8360_validate_tdm_slots(struct device *dev, unsigned int mask, const u32 *func_slots, const char * const *func_names, int num_funcs, const char *dir, unsigned int *slot_used) { int i; unsigned int func_slot; *slot_used = 0; if (!mask) return 0; for (i = 0; i < num_funcs; i++) { func_slot = func_slots[i]; if (func_slot == TDM_SLOT_NONE) { dev_warn(dev, "%s %s slot disabled", dir, func_names[i]); continue; } if (!(mask & BIT(func_slot))) { dev_warn(dev, "%s %s mapped to slot %d, but disabled by mask!", dir, func_names[i], func_slot); continue; } if (*slot_used & BIT(func_slot)) { dev_err(dev, "%s %s slot %d collision!", dir, func_names[i], func_slot); return -EINVAL; } *slot_used |= BIT(func_slot); } return 0; } static void nau8360_enable_tdm_channels(struct snd_soc_component *cp, int rx_slot_used, int tx_slot_used) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); int i, slot; unsigned int val = 0; bool enable; for (i = 0; i < NAU8360_TDM_TXN; i++) { slot = nau8360->tdm_tx_func_slot[i]; enable = (slot != TDM_SLOT_NONE) && (tx_slot_used & BIT(slot)); nau8360_set_tdm_tx_func(cp, i, enable); } if (rx_slot_used) val |= NAU8360_EN_TDM_RX; if (tx_slot_used) val |= NAU8360_EN_TDM_TX; snd_soc_component_update_bits(cp, NAU8360_R0B_I2S_PCM_CTRL1, NAU8360_EN_TDM_RX | NAU8360_EN_TDM_TX, val); } static void nau8360_tdm_apply(struct snd_soc_component *cp, int slot_width) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); int i, chan_tx; for (i = 0; i < NAU8360_TDM_TXN; i++) { if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) continue; /* compute the slot location in bytes according to slot/chan width */ chan_tx = (slot_width >> 3) * nau8360->tdm_tx_func_slot[i]; nau8360_set_tdm_tx_slot(cp, i, chan_tx); } regmap_update_bits(nau8360->regmap, NAU8360_R0C_I2S_PCM_CTRL2, NAU8360_TDM_CLEN_MASK, ((slot_width - 16) >> 3) << NAU8360_TDM_CLEN_SFT); } static int nau8360_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_component *cp = dai->component; struct device *dev = cp->dev; struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); unsigned int tx_slot_used = 0, rx_slot_used = 0; int ret = 0; if (!slots || !slot_width) { nau8360_enable_tdm_channels(cp, 0, 0); return ret; } if (slots > NAU8360_TDM_MAX_CHAN) { dev_err(dev, "Invalid TDM slots: %d", slots); return -EINVAL; } if (slot_width != 16 && slot_width != 24 && slot_width != 32) { dev_err(dev, "Invalid TDM channel length: %d", slot_width); return -EINVAL; } ret = nau8360_validate_tdm_slots(cp->dev, rx_mask, nau8360->tdm_rx_func_slot, nau8360_rx_func_names, NAU8360_TDM_RXN, "RX", &rx_slot_used); if (ret < 0) goto err; ret = nau8360_validate_tdm_slots(cp->dev, tx_mask, nau8360->tdm_tx_func_slot, nau8360_tx_func_names, NAU8360_TDM_TXN, "TX", &tx_slot_used); if (ret < 0) goto err; if (nau8360_get_tdm_chan_len(nau8360) != slot_width) nau8360_tdm_apply(cp, slot_width); nau8360_enable_tdm_channels(cp, rx_slot_used, tx_slot_used); dev_dbg(dev, "TDM: tx_mask 0x%x, rx_mask 0x%x", tx_mask, rx_mask); err: return ret; } static inline int dyn_clk_div(int div_max, int fin, int fout) { int clk_div; if (!fin || !fout) return -EINVAL; for (clk_div = 0; clk_div <= div_max; clk_div++) if (fin / (clk_div + 1) <= fout) break; if (clk_div > div_max) return -EINVAL; return clk_div; } static inline int tab_clk_div(const int clk_div[], int num_div, int fin) { int i; if (!fin) return -EINVAL; for (i = 0; i < num_div; i++) if ((fin / clk_div[i]) <= CLK_DA_IVSNS_MAX) break; if (i == num_div) return -EINVAL; return i; } static int nau8360_set_sysclk_output(struct nau8360 *nau8360, unsigned int freq) { struct device *dev = nau8360->dev; int ratio = 0, mclk_rate; if (freq < MASTER_CLK_MIN || freq > MASTER_CLK_MAX) { dev_err(dev, "system clock %d Hz exceed range", freq); return -EINVAL; } if (freq % ADSP_SR_48000 == 0) ratio = freq / ADSP_SR_48000; else if (freq % ADSP_SR_44100 == 0) ratio = freq / ADSP_SR_44100; switch (ratio) { case NAU8360_MCLK_FS_RATIO_250: mclk_rate = NAU8360_MCLK_RATE_12000; break; case NAU8360_MCLK_FS_RATIO_256: mclk_rate = NAU8360_MCLK_RATE_12288; break; case NAU8360_MCLK_FS_RATIO_400: mclk_rate = NAU8360_MCLK_RATE_19200; break; case NAU8360_MCLK_FS_RATIO_500: mclk_rate = NAU8360_MCLK_RATE_24000; break; case NAU8360_MCLK_FS_RATIO_512: mclk_rate = NAU8360_MCLK_RATE_24576; break; default: dev_err(dev, "invalid sysclk %d", freq); return -EINVAL; } dev_dbg(dev, "sysclk %d Hz, ratio %d", freq, ratio); nau8360->sys_clk = freq; regmap_update_bits(nau8360->regmap, NAU8360_R40_CLK_DET_CTRL, NAU8360_MCLK_RATE_MASK, mclk_rate); return 0; } static int nau8360_dig_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) { struct device *dev = nau8360->dev; struct regmap *regmap = nau8360->regmap; int value, mclk_div; switch (source) { case NAU8360_CLK_SRC_MCLK: value = NAU8360_MCLK_SEL_MCLK; break; case NAU8360_CLK_SRC_PLL: value = NAU8360_MCLK_SEL_PLL; break; default: return -EINVAL; } regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_MCLK_SEL_MASK, value); mclk_div = dyn_clk_div(NAU8360_MCLK_DIV_MAX, freq, nau8360->sys_clk); if (mclk_div < 0) { dev_err(dev, "mclk_div (%d -> %d): error", freq, nau8360->sys_clk); return -EINVAL; } regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_MCLK_DIV_MASK, mclk_div << NAU8360_MCLK_DIV_SFT); dev_dbg(dev, " mclk_div (%d -> %d): %d", freq, nau8360->sys_clk, mclk_div + 1); return 0; } static int nau8360_ana_sys_clk(struct nau8360 *nau8360, int source, unsigned int freq) { struct device *dev = nau8360->dev; struct regmap *regmap = nau8360->regmap; struct nau8360_pll *pll = &nau8360->pll; int value, pclk_div, ivdiv_sel, ddiv_sel; switch (source) { case NAU8360_CLK_SRC_PLL: value = NAU8360_CLK_ANA_SEL_PLL; break; case NAU8360_CLK_SRC_MCLK: value = NAU8360_CLK_ANA_SEL_MCLK; break; case NAU8360_CLK_SRC_BCLK: value = NAU8360_CLK_ANA_SEL_BCLK; break; default: return -EINVAL; } if (source == NAU8360_CLK_SRC_PLL) { freq = pll->output; pclk_div = dyn_clk_div(NAU8360_PLLOUT_DIV_MAX, freq, nau8360->sys_clk); if (pclk_div < 0) { dev_err(dev, "pll_div (%d -> %d): error", freq, nau8360->sys_clk); return -EINVAL; } dev_dbg(dev, " pll_div (%d -> %d): %d", freq, nau8360->sys_clk, pclk_div + 1); regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PLLOUT_DIV_MASK, pclk_div << NAU8360_PLLOUT_DIV_SFT); freq /= (pclk_div + 1); } ivdiv_sel = tab_clk_div(ivsns_clk_div, ARRAY_SIZE(ivsns_clk_div), freq); if (ivdiv_sel < 0) { dev_err(dev, "iv_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); return -EINVAL; } value |= ivdiv_sel << NAU8360_IVSNS_CLK_DIV_SFT; ddiv_sel = tab_clk_div(dac_clk_div, ARRAY_SIZE(dac_clk_div), freq); if (ddiv_sel < 0) { dev_err(dev, "dac_div (%d -> %d): error", freq, CLK_DA_IVSNS_MAX); return -EINVAL; } value |= ddiv_sel << NAU8360_DAC_CLK_DIV_SFT; dev_dbg(dev, " clk_div (%d -> %d): ivsns %d, dac %d", freq, CLK_DA_IVSNS_MAX, ivsns_clk_div[ivdiv_sel], dac_clk_div[ddiv_sel]); regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, NAU8360_CLK_ANA_SEL_MASK | NAU8360_IVSNS_CLK_DIV_MASK | NAU8360_DAC_CLK_DIV_MASK, value); return 0; } static int nau8360_dsp_hw_clk(struct nau8360 *nau8360, int source, unsigned int freq) { struct device *dev = nau8360->dev; struct regmap *regmap = nau8360->regmap; int val_dsp, val_hw, clk_div, dsp_clk; if (freq % ADSP_SR_48000 == 0) dsp_clk = DSP_OP_CLK48; else if (freq % ADSP_SR_44100 == 0) dsp_clk = DSP_OP_CLK44; else return -EINVAL; clk_div = dyn_clk_div(NAU8360_DSP_CLK_DIV_MAX, freq, dsp_clk); if (clk_div < 0) { dev_err(dev, "dsp/hw clk_div (%d -> %d): error", freq, dsp_clk); return -EINVAL; } val_dsp = clk_div << NAU8360_DSP_CLK_DIV_SFT; val_hw = clk_div << NAU8360_HW_CLK_DIV_SFT; if (source == NAU8360_CLK_SRC_MCLK) { val_dsp |= NAU8360_DSP_CLK_SEL_MCLK; val_hw |= NAU8360_HW_CLK_SEL_MCLK; } else if (source == NAU8360_CLK_SRC_PLL) { val_dsp |= NAU8360_DSP_CLK_SEL_PLL; val_hw |= NAU8360_HW_CLK_SEL_PLL; } else return -EINVAL; dev_dbg(dev, " dsp/hw clk_div (%d -> %d): %d", freq, dsp_clk, clk_div + 1); regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_DSP_CLK_SEL_MASK | NAU8360_DSP_CLK_DIV_MASK, val_dsp); regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_HW_CLK_SEL_MASK | NAU8360_HW_CLK_DIV_MASK, val_hw); return 0; } static int nau8360_set_sysclk(struct snd_soc_component *cp, int clk_id, int source, unsigned int freq, int dir) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); struct regmap *regmap = nau8360->regmap; struct device *dev = nau8360->dev; static const char * const idtab[] = { "DIG", "ANA", "Internal" }; static const char * const srctab[] = { "MCLK", "PLL", "HIRC48M", "BCLK" }; int ret; if (clk_id < 0 || clk_id >= ARRAY_SIZE(idtab)) return -EINVAL; if (source < 0 || source >= ARRAY_SIZE(srctab)) return -EINVAL; if (dir == SND_SOC_CLOCK_OUT) { dev_dbg(dev, "sysclk: freq %d (out)", freq); return nau8360_set_sysclk_output(nau8360, freq); } switch (clk_id) { case NAU8360_CLK_ID_INT: source = NAU8360_CLK_SRC_ICLK; dev_dbg(dev, "sysclk: id %d (%s), src %d (%s) (in)", clk_id, idtab[clk_id], source, srctab[source]); regmap_update_bits(regmap, NAU8360_R03_CLK_CTRL0, NAU8360_DSP_CLK_SEL_MASK, NAU8360_DSP_CLK_SEL_HIRC48M); regmap_update_bits(regmap, NAU8360_R04_CLK_CTRL1, NAU8360_HW_CLK_SEL_MASK | NAU8360_MCLK_SEL_MASK, NAU8360_HW_CLK_SEL_HIRC48M | NAU8360_MCLK_SEL_HIRC48M); regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); break; case NAU8360_CLK_ID_DIG: dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", clk_id, idtab[clk_id], source, srctab[source], freq); if (source == NAU8360_CLK_SRC_BCLK) return -EINVAL; if (source == NAU8360_CLK_SRC_MCLK) regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); ret = nau8360_dig_sys_clk(nau8360, source, freq); if (ret) return -EINVAL; ret = nau8360_dsp_hw_clk(nau8360, source, freq); if (ret) return -EINVAL; break; case NAU8360_CLK_ID_ANA: dev_dbg(dev, "sysclk: id %d (%s), src %d (%s), freq %d (in)", clk_id, idtab[clk_id], source, srctab[source], freq); if (source == NAU8360_CLK_SRC_MCLK || source == NAU8360_CLK_SRC_BCLK) regmap_update_bits(regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_DIS); ret = nau8360_ana_sys_clk(nau8360, source, freq); if (ret) return -EINVAL; break; default: return -EINVAL; } return 0; } static int nau8360_calc_pll(struct nau8360 *nau8360) { struct nau8360_pll *pll = &nau8360->pll; u64 fref = 0ULL, fvco = 0ULL, ratio; int fr, fv; if (pll->src != NAU8360_PLL_INTERNAL && (pll->input > PLL_FREQ_MAX || pll->input < PLL_FREQ_MIN)) return -EINVAL; if (pll->output > PLL_FOUT_MAX || pll->output < PLL_FOUT_MIN) return -EINVAL; for (pll->msel = 1; pll->msel <= MSEL_MAX; pll->msel++) { fr = pll->input / pll->msel; if (fr <= PLL_FREF_MAX) { fref = fr; break; } } if (!fref) return -ERANGE; for (pll->rsel = 1; pll->rsel <= RSEL_MAX; pll->rsel++) { fv = pll->output * pll->rsel; if (fv >= PLL_FVCO_MIN) { fvco = fv; break; } } if (!fvco) return -ERANGE; dev_dbg(nau8360->dev, "fref(1-8MHz): %lld, fvco(50-125MHz): %lld", fref, fvco); /* Calculate the PLL 8-bit integer input and 12-bit fractional */ ratio = div_u64(fvco << 12, fref); pll->nsel = (ratio >> 12) & 0xff; pll->xsel = ratio & 0xfff; return 0; } static int nau8360_set_pll(struct snd_soc_component *cp, int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); struct device *dev = nau8360->dev; struct nau8360_pll *pll = &nau8360->pll; int ctrl_val, ret; switch (source) { case NAU8360_PLL_MCLK: ctrl_val = NAU8360_PLL_CLK_SEL_MCLK; break; case NAU8360_PLL_BCLK: ctrl_val = NAU8360_PLL_CLK_SEL_BCLK; break; case NAU8360_PLL_INTERNAL: ctrl_val = NAU8360_PLL_CLK_SEL_HIRC; break; default: return -EINVAL; } pll->src = source; pll->input = freq_in; pll->output = freq_out; ret = nau8360_calc_pll(nau8360); if (ret) { dev_err(dev, "clock error input %d output %d", freq_in, freq_out); return ret; } dev_dbg(dev, "src:%d, input:%d, output:%d, M:%d, R:%d, N:%d, X:%d", pll->src, pll->input, pll->output, pll->msel, pll->rsel, pll->nsel, pll->xsel); regmap_update_bits(nau8360->regmap, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK | NAU8360_PLL_CLK_SEL_MASK, NAU8360_PD_PLL_EN | ctrl_val); regmap_write_bits(nau8360->regmap, NAU8360_R73_PLL_CFG1, NAU8360_RSEL_MASK | NAU8360_MSEL_MASK | NAU8360_NSEL_MASK, (pll->rsel - 1) << NAU8360_RSEL_SFT | (pll->msel - 1) << NAU8360_MSEL_SFT | (pll->nsel - 1)); regmap_write_bits(nau8360->regmap, NAU8360_R74_PLL_CFG2, NAU8360_XSEL_MASK, pll->xsel); return 0; } static void nau8360_coeff_set_def(struct nau8360 *nau8360) { struct regmap *regmap = nau8360->regmap; int i; mutex_lock(&nau8360->lock); regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, NAU8360_HW1_MEM_CLEAR); regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_HW1_MEM_CLEAR, 0); nau8360_peq_mem_enable(regmap, true); for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { regmap_write(regmap, NAU8360_R100_LEFT_BIQ0_COE + 1 + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); regmap_write(regmap, NAU8360_R200_RIGHT_BIQ0_COE + 1 + i * NAU8360_TOT_BAND_COE_RANGE, 0x20); } nau8360_peq_mem_enable(regmap, false); regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_BAND_MASK, NAU8360_PEQ_BAND_8 << NAU8360_PEQ_BAND_SFT); mutex_unlock(&nau8360->lock); } static inline int nau8360_vbat_level(struct regmap *regmap, int *vbat) { struct device *dev = regmap_get_device(regmap); int value = 0, ret; ret = regmap_read(regmap, NAU8360_R21_VBAT_READOUT, &value); if (ret) return ret; /* multiple 100 on value scale */ *vbat = (value * 100 + NAU8360_VBAT_BASE) / NAU8360_VBAT_STEP; if (*vbat < NAU8360_VBAT_MIN || *vbat > NAU8360_VBAT_MAX) { dev_err(dev, "VBAT %dV is out of valid range (%dV-%dV)", *vbat, NAU8360_VBAT_MIN, NAU8360_VBAT_MAX); return -ERANGE; } return 0; } static inline void nau8360_sawtooth_params(int vbat, int *vsaw_level, int *vsaw_slope) { if (vbat < NAU8360_VBAT_MID_THRES) { *vsaw_level = 0x1; *vsaw_slope = 0x0; } else if (vbat < NAU8360_VBAT_HIGH_THRES) { *vsaw_level = 0x2; *vsaw_slope = 0x1; } else { *vsaw_level = 0x3; *vsaw_slope = 0x2; } } static inline void nau8360_dsp_software_reset(struct snd_soc_component *component) { /* Enable PLL for successful DSP reset. After DSP is alive, * system clock switches to internal clock and disable PLL. */ snd_soc_component_update_bits(component, NAU8360_R72_PLL_CFG0, NAU8360_PD_PLL_MASK, NAU8360_PD_PLL_EN); msleep(50); snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0x5a5a); snd_soc_component_write(component, NAU8360_R01_DSP_SOFTWARE_RST, 0xa5a5); snd_soc_component_set_sysclk(component, NAU8360_CLK_ID_INT, 0, 0, SND_SOC_CLOCK_IN); } static void nau8360_dsp_bootup(struct snd_soc_component *component) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); struct regmap *regmap = nau8360->regmap; regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, 0); nau8360_dsp_software_reset(component); nau8360_dsp_enable(regmap, true); } static void nau8360_tdm_function_config(struct snd_soc_component *cp) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(cp); int i, chan_tx, tdm_chan_len; for (i = 0; i < NAU8360_TDM_RXN; i++) { if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) continue; nau8360_set_tdm_rx_slot(cp, i, nau8360->tdm_rx_func_slot[i]); } tdm_chan_len = nau8360_get_tdm_chan_len(nau8360) >> 3; for (i = 0; i < NAU8360_TDM_TXN; i++) { if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) continue; /* compute the slot location in bytes according to slot/chan width */ chan_tx = (tdm_chan_len * nau8360->tdm_tx_func_slot[i]); nau8360_set_tdm_tx_slot(cp, i, chan_tx); } } static void nau8360_dsp_fw_load(struct nau8360 *nau8360) { nau8360->load_fw_done = false; schedule_work(&nau8360->load_fw_work); } static void nau8360_load_fw_work(struct work_struct *work) { struct nau8360 *nau8360 = container_of(work, struct nau8360, load_fw_work); struct snd_soc_component *cp = snd_soc_dapm_to_component(nau8360->dapm); int ret; ret = nau8360_dsp_init(cp); if (ret) { dev_err(nau8360->dev, "Failed to initialize DSP: %d\n", ret); nau8360_dsp_enable(nau8360->regmap, false); return; } nau8360->load_fw_done = true; } static int nau8360_codec_probe(struct snd_soc_component *component) { struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); struct regmap *regmap = nau8360->regmap; struct device *dev = nau8360->dev; int ret, vbat, vsaw_level, vsaw_slope; nau8360->dapm = dapm; nau8360_dsp_bootup(component); nau8360_dsp_fw_load(nau8360); ret = nau8360_dsp_setup_controls(component); if (ret) { nau8360_dsp_enable(regmap, false); dev_err(dev, "DSP setup controls failed(%d)", ret); goto err; } nau8360_tdm_function_config(component); /* default disable Sense signal after booting */ snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); snd_soc_dapm_sync(nau8360->dapm); nau8360_coeff_set_def(nau8360); /* VBAT is sensed by chip. */ ret = nau8360_vbat_level(regmap, &vbat); if (ret) { dev_err(dev, "Failed to get valid VBAT level: %d", ret); goto err; } dev_dbg(dev, "VBAT %dV for nau8360", vbat); /* Config sawtooth clock according to VBAT. Class D modulator input short setting * for mute and de-pop purpose. Restore normal after initiation. */ nau8360_sawtooth_params(vbat, &vsaw_level, &vsaw_slope); regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_VSAW_LV_MASK | NAU8360_KVCO_SAW_MASK, (vsaw_level << NAU8360_VSAW_LV_SFT) | (vsaw_slope << NAU8360_KVCO_SAW_SFT)); return 0; err: cancel_work_sync(&nau8360->load_fw_work); return ret; } static int __maybe_unused nau8360_suspend(struct snd_soc_component *component) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); cancel_work_sync(&nau8360->load_fw_work); regmap_update_bits(nau8360->regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, NAU8360_HW2_STALL); nau8360_dsp_enable(nau8360->regmap, false); regcache_cache_only(nau8360->regmap, true); regcache_mark_dirty(nau8360->regmap); return 0; } static int __maybe_unused nau8360_resume(struct snd_soc_component *component) { struct nau8360 *nau8360 = snd_soc_component_get_drvdata(component); struct regmap *regmap = nau8360->regmap; regcache_cache_only(regmap, false); nau8360_dsp_bootup(component); nau8360_peq_mem_enable(regmap, true); regcache_sync(regmap); nau8360_peq_mem_enable(regmap, false); /* disable Sense at standby */ snd_soc_dapm_disable_pin(nau8360->dapm, "Sense"); snd_soc_dapm_sync(nau8360->dapm); nau8360_dsp_fw_load(nau8360); return 0; } static const struct snd_soc_component_driver soc_comp_dev_nau8360 = { .probe = nau8360_codec_probe, .set_sysclk = nau8360_set_sysclk, .set_pll = nau8360_set_pll, .suspend = nau8360_suspend, .resume = nau8360_resume, .controls = nau8360_snd_controls, .num_controls = ARRAY_SIZE(nau8360_snd_controls), .dapm_widgets = nau8360_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(nau8360_dapm_widgets), .dapm_routes = nau8360_dapm_routes, .num_dapm_routes = ARRAY_SIZE(nau8360_dapm_routes), .suspend_bias_off = 1, .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, }; static const struct snd_soc_dai_ops nau8360_dai_ops = { .startup = nau8360_startup, .shutdown = nau8360_shutdown, .hw_params = nau8360_hw_params, .set_fmt = nau8360_set_fmt, .set_tdm_slot = nau8360_set_tdm_slot, }; #define NAU8360_RATES (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000) #define NAU8360_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_driver nau8360_dai = { .name = NAU8360_CODEC_DAI, .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 4, .rates = NAU8360_RATES, .formats = NAU8360_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 8, .rates = NAU8360_RATES, .formats = NAU8360_FORMATS, }, .ops = &nau8360_dai_ops, .symmetric_rate = 1, }; static int nau8360_reg_write(void *context, unsigned int reg, unsigned int value) { struct i2c_client *client = context; struct nau8360 *nau8360 = i2c_get_clientdata(client); int ret, count = 0; put_unaligned_be16(reg, &nau8360->i2c_write_buf[count]); count += 2; if (NAU8360_IS_DSP_REG(reg)) { /* format for DSP, 4 bytes value and native */ put_unaligned_le32(value, &nau8360->i2c_write_buf[count]); count += 4; } else { /* format for Codec, 2 bytes value and endian big */ put_unaligned_be16(value, &nau8360->i2c_write_buf[count]); count += 2; } ret = i2c_master_send(client, nau8360->i2c_write_buf, count); if (ret == count) return 0; if (ret < 0) return ret; return -EIO; } static int nau8360_reg_read(void *context, unsigned int reg, unsigned int *value) { struct i2c_client *client = context; struct nau8360 *nau8360 = i2c_get_clientdata(client); struct i2c_msg xfer[2]; int ret; put_unaligned_be16(reg, &nau8360->i2c_read_reg); xfer[0].addr = client->addr; xfer[0].len = 2; xfer[0].buf = nau8360->i2c_read_reg; xfer[0].flags = 0; xfer[1].addr = client->addr; xfer[1].len = (NAU8360_IS_DSP_REG(reg)) ? 4 : 2; xfer[1].buf = nau8360->i2c_read_buf; xfer[1].flags = I2C_M_RD; ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer)); if (ret < 0) return ret; if (ret != ARRAY_SIZE(xfer)) return -EIO; if (NAU8360_IS_DSP_REG(reg)) *value = get_unaligned_le32(nau8360->i2c_read_buf); else *value = get_unaligned_be16(nau8360->i2c_read_buf); return 0; } static const struct regmap_config nau8360_regmap_config = { .reg_bits = NAU8360_REG_ADDR_LEN, .val_bits = NAU8360_REG_DATA_LEN, .max_register = NAU8360_REG_MAX, .readable_reg = nau8360_readable_reg, .writeable_reg = nau8360_writeable_reg, .volatile_reg = nau8360_volatile_reg, .reg_read = nau8360_reg_read, .reg_write = nau8360_reg_write, .cache_type = REGCACHE_MAPLE, .reg_defaults = nau8360_reg_defaults, .num_reg_defaults = ARRAY_SIZE(nau8360_reg_defaults), }; static inline void nau8360_reset_chip(struct regmap *regmap) { regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0x5a5a); regmap_write(regmap, NAU8360_R00_SOFTWARE_RST, 0xa5a5); } static void nau8360_init_regs(struct nau8360 *nau8360) { struct regmap *regmap = nau8360->regmap; /* Enable Digital LDO */ regmap_write(regmap, NAU8360_R78_PD_SW_DLDO, NAU8360_PD_SW_DLDO_EN); /* Enable Software Shutdown Mode */ regmap_write(regmap, NAU8360_R77_SOFT_SD, NAU8360_SOFT_SD_EN); /* Stall HW1 PEQ Engine and Clear DRAM to Zero */ regmap_update_bits(regmap, NAU8360_R9D_PEQ_CTL, NAU8360_PEQ_STALL, NAU8360_PEQ_STALL); /* Stall HW2 engine */ regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_STALL, NAU8360_HW2_STALL); /* Stall HW3 engine and DSP processor */ nau8360_dsp_enable(regmap, false); /* Enable Clock Gate */ regmap_write(regmap, NAU8360_R7E_CLK_GATED_EN, NAU8360_CLK_GATED_EN); /* Latch I2C LSB Value */ regmap_write(regmap, NAU8360_R02_I2C_ADDR, 0x0001); /* ANC Left/Right Channel as Slot 2/3 and switch */ regmap_update_bits(regmap, NAU8360_R10_I2S_DATA_CTRL3, NAU8360_RX_ANC_R_MASK | NAU8360_RX_ANC_L_MASK, 0x3 << NAU8360_RX_ANC_R_SFT | 0x2 << NAU8360_RX_ANC_L_SFT); /* Set DAC Clock Divider as 2 and Chopper Divider as 16 */ regmap_update_bits(regmap, NAU8360_R71_CLK_DIV_CFG, NAU8360_DAC_CLK_DIV_MASK | NAU8360_DAC_CHOP_CLK_DIV_MASK, NAU8360_DAC_CLK_DIV_2 | NAU8360_DAC_CHOP_CLK_DIV_16); /* Set DAC SINC OSR as 128 and IVSENSE BS OSR as 32 */ regmap_update_bits(regmap, NAU8360_R5D_SINC_CFG, NAU8360_DAC_SINC_OSR_MASK | NAU8360_IVSENSE_BS_OSR_MASK, NAU8360_DAC_SINC_OSR_128 | NAU8360_IVSENSE_BS_OSR_32); /* Set Trim Bit Control of DLDO and GVDD to The Highest Voltage */ regmap_write(regmap, NAU8360_R5F_ANA_TRIM_CFG1, 0xf407); /* Disable Data Det and Clock Detection Settings*/ regmap_update_bits(regmap, NAU8360_R40_CLK_DET_CTRL, NAU8360_APWRUPEN | NAU8360_CLKPWRUPEN | NAU8360_FS_MCLK_DET | NAU8360_FS_BCLK_DET, 0); /* Set HW3 Droop and Filter Sample as 192K */ regmap_update_bits(regmap, NAU8360_R8C_HW3_CTL6, NAU8360_HW3_DROOP_MASK | NAU8360_HW3_FS_MASK, NAU8360_HW3_DROOP_192K | NAU8360_HW3_FS_192K); /* Set HW1 Mute, Mute Interval as 699ms and HW1 Zero THD as 0xff */ regmap_update_bits(regmap, NAU8360_R9C_HW1_CTL2, NAU8360_MUTE_INTRVL_MASK | NAU8360_HW1_CH_MUTE | NAU8360_HW1_ZERO_THD_MASK, NAU8360_MUTE_INTRVL_699MS | NAU8360_HW1_CH_MUTE | 0xff); /* set HW2 droop with 192K*/ regmap_update_bits(regmap, NAU8360_R96_HW2_CTL6, NAU8360_HW2_DROOP_SEL_MASK | NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_MASK, NAU8360_HW2_DROOP_SEL_LARGE | NAU8360_HW2_DROOP_EN | NAU8360_HW2_FS_192K); /* Set HW2 default volume */ regmap_write(regmap, NAU8360_R97_HW2_CTL7, 0xbf66); regmap_write(regmap, NAU8360_R98_HW2_CTL8, 0xbf66); /* Set HW2 Mute and Threshold of Zero Crossing */ regmap_update_bits(regmap, NAU8360_R99_HW2_CTL9, NAU8360_HW2_CH_MUTE | NAU8360_HW2_ZERO_THD_MASK, NAU8360_HW2_CH_MUTE | 0xff); /* According to DSP enabled or not, set stream path as * HW1->DSP->HW2->SINC->HW3 or HW1->HW2->SINC->HW3 */ regmap_update_bits(regmap, NAU8360_R12_PATH_CTRL, NAU8360_SEL_HW1_MASK | NAU8360_AUD_SEL_MASK | NAU8360_SEL_HW2_MASK | NAU8360_SEL_HW3_MASK | NAU8360_DAC_SEL_MASK, NAU8360_SEL_HW1_OUT | NAU8360_AUD_SEL_SINCOUT | NAU8360_SEL_HW2_OUT | NAU8360_SEL_HW3_OUT); /* Set Input Status as Low and Input/Output Mode Disable for All GPIO */ regmap_write(regmap, NAU8360_R07_GP_CTRL, 0x0000); /* enable SARADC with extra average filter of VBAT */ regmap_update_bits(regmap, NAU8360_R6A_SARADC_CFG0, NAU8360_SARADC_EN | NAU8360_VBAT_AVG_EN, NAU8360_SARADC_EN | NAU8360_VBAT_AVG_EN); /* Enable Temperature Sensor, VBATDIV and VRF. * Set Average Filter Data as 512 for VTEMP and VBAT. */ regmap_update_bits(regmap, NAU8360_R6B_SARADC_CFG1, NAU8360_VTEMP_EN | NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | NAU8360_VTEMP_AVG_N_MASK | NAU8360_VBAT_AVG_N_MASK, NAU8360_VTEMP_EN | NAU8360_VBATDIV_EN | NAU8360_VREF_EN | NAU8360_PRELOAD_VREF_EN | NAU8360_VTEMP_AVG_N_512 | NAU8360_VBAT_AVG_N_512); /* Enable IV sense internal LDO */ regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, NAU8360_PD_LDO_SDM_IVSNS_PMD, 0); /* Enable Bias Current and Reference Voltage for IVSENSE. Set VSNS Gain * as 1/12 and ISNS Gain as x16(24dB). Set PGA Current as 2'b11 and SDN * Delay as 2'b00. */ regmap_write(regmap, NAU8360_R6D_IVSNS_CFG1, 0x0f5c); /* sawtooth clock from ivsense clock, PWM frequency 432 Khz */ regmap_update_bits(regmap, NAU8360_RA4_ANA_REG_0, NAU8360_SEL_STCLK_MASK | NAU8360_NSEL_SAW_MASK, NAU8360_SEL_STCLK_IVCLK | 0x8); /* sawtooth PLL APR */ regmap_update_bits(regmap, NAU8360_RA5_ANA_REG_1, NAU8360_SAW_PLL_MASK, NAU8360_SAW_PLL_NOR); /* Set DAC gain (0dB/+3.2dB) by current cell adjustment */ regmap_update_bits(regmap, NAU8360_R6E_DAC_CFG0, NAU8360_DAC_CUR_MASK, nau8360->dac_cur_enable ? NAU8360_DAC_CUR_3_2DB : NAU8360_DAC_CUR_0DB); /* Config trim short current reference. Enable Non-overlap longer delay. * Set segment driver as half driving strength. */ regmap_update_bits(regmap, NAU8360_R68_ANALOG_CONTROL_1, NAU8360_DTX_EN | NAU8360_TRIMSCR_MASK | NAU8360_DRVCTL_SEGL_FULL | NAU8360_DRVCTL_SEGR_FULL, NAU8360_TRIMSCR_MLOW | NAU8360_DTX_EN); /* Enable SCP Clear Mode and Class D Modulator to Common Mode */ regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_SCP_CLEAR_MODE_MASK | NAU8360_CM_COMP_EN, NAU8360_SCP_CLEAR_MODE_AUTO | NAU8360_CM_COMP_EN); /* Set Analog Mute and Class D Modulator Gain as 14dB */ regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_ANA_MUTE | NAU8360_MOD_GAIN_MASK, NAU8360_ANA_MUTE | NAU8360_MOD_GAIN_14DB); /* Set Stereo or PBTL Mode */ regmap_update_bits(regmap, NAU8360_R67_ANALOG_CONTROL_0, NAU8360_V_PBTL_EN, nau8360->pbtl_enable ? NAU8360_V_PBTL_EN : 0); regmap_update_bits(regmap, NAU8360_R6C_IVSNS_CFG0, NAU8360_PBTL_ISENE_LR_MASK, NAU8360_PBTL_ISENE_LR_ILR); /* adjust HW3 default volume of voltage/current sense */ regmap_write(regmap, NAU8360_R8A_HW3_VL_CTL7, 0xbe90); regmap_write(regmap, NAU8360_R8B_HW3_VR_CTL8, 0xbe90); regmap_write(regmap, NAU8360_R8D_HW3_IL_CTL7, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); regmap_write(regmap, NAU8360_R8E_HW3_IR_CTL8, nau8360->pbtl_enable ? 0xc3aa : 0xc24c); /* Set HW3 Zero THD as 0xff */ regmap_update_bits(regmap, NAU8360_R8F_HW3_CTL9, NAU8360_HW3_ZERO_THD_MASK, 0xff); /* Enable TX SDOUT and Data at BCLK Rising. */ regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_TX_FILL_MASK, NAU8360_TX_FILL_ZERO); regmap_update_bits(regmap, NAU8360_R0D_I2S_PCM_CTRL3, NAU8360_VSNS_L_SLEN_MASK, NAU8360_VSNS_L_SLEN_16); regmap_update_bits(regmap, NAU8360_R0E_I2S_DATA_CTRL1, NAU8360_ISNS_L_SLEN_MASK, NAU8360_ISNS_L_SLEN_16); regmap_update_bits(regmap, NAU8360_R11_I2S_DATA_CTRL4, NAU8360_VSNS_R_SLEN_MASK | NAU8360_ISNS_R_SLEN_MASK, NAU8360_VSNS_R_SLEN_16 | NAU8360_ISNS_R_SLEN_16); /* set DAC channel temperature trim code slope as 0x7D */ regmap_update_bits(regmap, NAU8360_R7A_DAC_TRIM_CFG2, NAU8360_DAC_TEMP_SLOPE_MASK, 0x7D << NAU8360_DAC_TEMP_SLOPE_SFT); /* set ISNS temperature trim code slope as 0x1a */ regmap_update_bits(regmap, NAU8360_R7B_IVSNS_TRIM_CFG, NAU8360_ISNS_TEMP_SLOPE_MASK, 0x1a << NAU8360_ISNS_TEMP_SLOPE_SFT); /* set misc trim config as 0x67F0 */ regmap_update_bits(regmap, NAU8360_R7C_MISC_TRIM_CFG, NAU8360_DAC_GAIN_SB_MASK | NAU8360_DAC_TEMP_SB_MASK | NAU8360_ISNS_TEMP_SB_MASK | NAU8360_VSNS_TEMP_SB_MASK | NAU8360_ISNS_GAIN_SB_MASK | NAU8360_VSNS_GAIN_SB_MASK, (0x3 << NAU8360_DAC_TEMP_SB_SFT) | (0x7 << NAU8360_ISNS_TEMP_SB_SFT) | (0x3 << NAU8360_VSNS_TEMP_SB_SFT)); /* Enable Efuse Initianllization */ regmap_update_bits(regmap, NAU8360_R60_RST, NAU8360_EFUSE_CTRL_EN, NAU8360_EFUSE_CTRL_EN); /* Clear HW2 DRAM */ regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_DRAM_CLR, NAU8360_HW2_DRAM_CLR); /* Finish HW DRAM Clear */ regmap_update_bits(regmap, NAU8360_R90_HW2_CTL0, NAU8360_HW2_DRAM_CLR, 0); /* Clear HW3 DRAM */ regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_DRAM_CLR, NAU8360_HW3_DRAM_CLR); /* Finish HW DRAM Clear */ regmap_update_bits(regmap, NAU8360_R86_HW3_CTL0, NAU8360_HW3_DRAM_CLR, 0); } static void nau8360_print_device_properties(struct nau8360 *nau8360) { int i; dev_dbg(nau8360->dev, "pbtl-enable: %d", nau8360->pbtl_enable); dev_dbg(nau8360->dev, "dac-cur-enable: %d", nau8360->dac_cur_enable); for (i = 0; i < NAU8360_DSP_FW_NUM; i++) dev_dbg(nau8360->dev, "firmware-name[%d]: %s", i, nau8360->dsp_firmware[i]); for (i = 0; i < NAU8360_TDM_TXN; i++) dev_dbg(nau8360->dev, "dsp-tx-slot[%d] (%s): %d%s", i, nau8360_tx_func_names[i], nau8360->tdm_tx_func_slot[i], nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); for (i = 0; i < NAU8360_TDM_RXN; i++) dev_dbg(nau8360->dev, "dsp-rx-slot[%d] (%s): %d%s", i, nau8360_rx_func_names[i], nau8360->tdm_rx_func_slot[i], nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE ? " (none)" : ""); } static int nau8360_read_device_properties(struct nau8360 *nau8360) { const char *def_fws[NAU8360_DSP_FW_NUM] = { NAU8360_DSP_FIRMWARE".l", NAU8360_DSP_FIRMWARE".r" }; struct device *dev = nau8360->dev; const char *firmware_names[NAU8360_DSP_FW_NUM]; int i, ret; ret = device_property_read_u32_array(dev, "nuvoton,dsp-tx-slot-mapping", nau8360->tdm_tx_func_slot, NAU8360_TDM_TXN); if (ret) { for (i = 0; i < NAU8360_TDM_TXN; i++) nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; } else { for (i = 0; i < NAU8360_TDM_TXN; i++) { if (nau8360->tdm_tx_func_slot[i] == TDM_SLOT_NONE) continue; if (nau8360->tdm_tx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { dev_warn(dev, "Invalid TX slot %d, set to none\n", nau8360->tdm_tx_func_slot[i]); nau8360->tdm_tx_func_slot[i] = TDM_SLOT_NONE; } } } ret = device_property_read_u32_array(dev, "nuvoton,dsp-rx-slot-mapping", nau8360->tdm_rx_func_slot, NAU8360_TDM_RXN); if (ret) { nau8360->tdm_rx_func_slot[NAU8360_TDM_DACL] = 0; nau8360->tdm_rx_func_slot[NAU8360_TDM_DACR] = 1; nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCL] = TDM_SLOT_NONE; nau8360->tdm_rx_func_slot[NAU8360_TDM_ANCR] = TDM_SLOT_NONE; } else { for (i = 0; i < NAU8360_TDM_RXN; i++) { if (nau8360->tdm_rx_func_slot[i] == TDM_SLOT_NONE) continue; if (nau8360->tdm_rx_func_slot[i] >= NAU8360_TDM_MAX_CHAN) { dev_warn(dev, "Invalid RX slot %d, set to none\n", nau8360->tdm_rx_func_slot[i]); nau8360->tdm_rx_func_slot[i] = TDM_SLOT_NONE; } } } nau8360->pbtl_enable = device_property_read_bool(dev, "nuvoton,pbtl-enable"); nau8360->dac_cur_enable = device_property_read_bool(dev, "nuvoton,dac-cur-enable"); ret = device_property_read_string_array(dev, "firmware-name", firmware_names, NAU8360_DSP_FW_NUM); if (ret != NAU8360_DSP_FW_NUM) { dev_warn(dev, "The firmware-name was not found, using default."); for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { nau8360->dsp_firmware[i] = devm_kstrdup(dev, def_fws[i], GFP_KERNEL); if (!nau8360->dsp_firmware[i]) return -ENOMEM; } } else { for (i = 0; i < NAU8360_DSP_FW_NUM; i++) { nau8360->dsp_firmware[i] = devm_kasprintf(dev, GFP_KERNEL, NAU8360_DSP_FIRMDIR "%s", firmware_names[i]); if (!nau8360->dsp_firmware[i]) return -ENOMEM; } } return 0; } static struct reg_default *nau8360_alloc_defaults(struct device *dev, int *total_regs) { struct reg_default *dyn_defaults; int reg_num = ARRAY_SIZE(nau8360_reg_defaults); int total = reg_num + (2 * NAU8360_TOT_BAND_PER_CH * NAU8360_TOT_BAND_COE); int i, j, idx; dyn_defaults = devm_kzalloc(dev, total * sizeof(*dyn_defaults), GFP_KERNEL); if (!dyn_defaults) return NULL; memcpy(dyn_defaults, nau8360_reg_defaults, sizeof(*dyn_defaults) * reg_num); idx = reg_num; for (i = 0; i < NAU8360_TOT_BAND_PER_CH; i++) { unsigned int range = i * NAU8360_TOT_BAND_COE_RANGE; unsigned int l_base = NAU8360_R100_LEFT_BIQ0_COE + range; unsigned int r_base = NAU8360_R200_RIGHT_BIQ0_COE + range; for (j = 0; j < NAU8360_TOT_BAND_COE; j++) { dyn_defaults[idx++].reg = l_base + j; dyn_defaults[idx++].reg = r_base + j; } } *total_regs = total; return dyn_defaults; } static int nau8360_i2c_probe(struct i2c_client *i2c) { struct device *dev = &i2c->dev; struct nau8360 *nau8360; struct regmap_config regmap_cfg = nau8360_regmap_config; struct reg_default *dyn_defaults; int num_total_regs; int ret, value; nau8360 = devm_kzalloc(dev, sizeof(*nau8360), GFP_KERNEL); if (!nau8360) return -ENOMEM; i2c_set_clientdata(i2c, nau8360); mutex_init(&nau8360->lock); INIT_WORK(&nau8360->load_fw_work, nau8360_load_fw_work); dyn_defaults = nau8360_alloc_defaults(dev, &num_total_regs); if (!dyn_defaults) return -ENOMEM; regmap_cfg.reg_defaults = dyn_defaults; regmap_cfg.num_reg_defaults = num_total_regs; nau8360->regmap = devm_regmap_init(dev, NULL, i2c, ®map_cfg); if (IS_ERR(nau8360->regmap)) return PTR_ERR(nau8360->regmap); nau8360->dev = dev; nau8360_reset_chip(nau8360->regmap); ret = regmap_read(nau8360->regmap, NAU8360_R46_I2C_DEVICE_ID, &value); if (ret) { dev_err(dev, "Failed to read NAU83G60 device id %d", ret); return ret; } ret = nau8360_read_device_properties(nau8360); if (ret) return ret; nau8360_print_device_properties(nau8360); nau8360_init_regs(nau8360); return devm_snd_soc_register_component(dev, &soc_comp_dev_nau8360, &nau8360_dai, 1); } static void nau8360_i2c_remove(struct i2c_client *client) { struct nau8360 *nau8360 = i2c_get_clientdata(client); cancel_work_sync(&nau8360->load_fw_work); } static const struct i2c_device_id nau8360_i2c_ids[] = { { .name = "nau8360" }, {} }; MODULE_DEVICE_TABLE(i2c, nau8360_i2c_ids); static const struct of_device_id nau8360_of_ids[] = { { .compatible = "nuvoton,nau8360" }, {} }; MODULE_DEVICE_TABLE(of, nau8360_of_ids); static const struct acpi_device_id nau8360_acpi_match[] = { { .id = "NVTN2002" }, {} }; MODULE_DEVICE_TABLE(acpi, nau8360_acpi_match); static struct i2c_driver nau8360_i2c_driver = { .driver = { .name = "nau8360", .of_match_table = of_match_ptr(nau8360_of_ids), .acpi_match_table = ACPI_PTR(nau8360_acpi_match), }, .probe = nau8360_i2c_probe, .remove = nau8360_i2c_remove, .id_table = nau8360_i2c_ids, }; module_i2c_driver(nau8360_i2c_driver); MODULE_DESCRIPTION("ASoC NAU83G60 Stereo Class-D Amplifier with DSP and I/V-sense driver"); MODULE_AUTHOR("David Lin "); MODULE_AUTHOR("Seven Lee "); MODULE_AUTHOR("John Hsu "); MODULE_AUTHOR("Neo Chang "); MODULE_LICENSE("GPL");