/* * sound\soc\sunxi\snd_sun8iw21_codec.c * (C) Copyright 2021-2025 * AllWinner Technology Co., Ltd. * Dby * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snd_sunxi_log.h" #include "snd_sunxi_pcm.h" #include "snd_sunxi_rxsync.h" #include "snd_sunxi_common.h" #include "snd_sun8iw21_codec.h" #define HLOG "CODEC" #define DRV_NAME "sunxi-snd-codec" struct resource g_res; static struct regmap_config g_regmap_config = { .reg_bits = 32, .reg_stride = 4, .val_bits = 32, .max_register = SUNXI_CODEC_REG_MAX, .cache_type = REGCACHE_NONE, }; struct sample_rate { unsigned int samplerate; unsigned int rate_bit; }; static const struct sample_rate sample_rate_conv[] = { {8000, 5}, {11025, 4}, {12000, 4}, {16000, 3}, {22050, 2}, {24000, 2}, {32000, 1}, {44100, 0}, {48000, 0}, {96000, 7}, {192000, 6}, }; static struct reg_label g_reg_labels[] = { REG_LABEL(SUNXI_DAC_DPC), REG_LABEL(SUNXI_DAC_VOL_CTRL), REG_LABEL(SUNXI_DAC_FIFOC), REG_LABEL(SUNXI_DAC_FIFOS), REG_LABEL(SUNXI_DAC_CNT), REG_LABEL(SUNXI_DAC_DG), REG_LABEL(SUNXI_ADC_FIFOC), REG_LABEL(SUNXI_ADC_VOL_CTRL), REG_LABEL(SUNXI_ADC_FIFOS), REG_LABEL(SUNXI_ADC_CNT), REG_LABEL(SUNXI_ADC_DG), REG_LABEL(SUNXI_ADC_DIG_CTRL), REG_LABEL(SUNXI_VRA1SPEEDUP_DOWN_CTRL), REG_LABEL(SUNXI_DAC_DAP_CTL), REG_LABEL(SUNXI_ADC_DAP_CTL), REG_LABEL(SUNXI_ADC1_REG), REG_LABEL(SUNXI_ADC2_REG), REG_LABEL(SUNXI_DAC_REG), REG_LABEL(SUNXI_MICBIAS_REG), REG_LABEL(SUNXI_RAMP_REG), REG_LABEL(SUNXI_BIAS_REG), REG_LABEL(SUNXI_POWER_REG), REG_LABEL(SUNXI_ADC_CUR_REG), REG_LABEL_END, }; static int snd_sunxi_clk_init(struct platform_device *pdev, struct sunxi_clk *clk); static void snd_sunxi_clk_exit(struct platform_device *pdev, struct sunxi_clk *clk); static int snd_sunxi_clk_enable(struct platform_device *pdev, struct sunxi_clk *clk); static void snd_sunxi_clk_disable(struct platform_device *pdev, struct sunxi_clk *clk); static int snd_sunxi_regulator_init(struct platform_device *pdev, struct sunxi_regulator *rglt); static void snd_sunxi_regulator_exit(struct platform_device *pdev, struct sunxi_regulator *rglt); static int snd_sunxi_regulator_enable(struct platform_device *pdev, struct sunxi_regulator *rglt); static void snd_sunxi_regulator_disable(struct platform_device *pdev, struct sunxi_regulator *rglt); static void sunxi_rx_sync_enable(void *data, bool enable); static int sunxi_internal_codec_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_dts *dts = &codec->dts; SND_LOG_DEBUG(HLOG, "\n"); if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { if (dts->rx_sync_en && dts->rx_sync_ctl) sunxi_rx_sync_startup((void *)codec, dts->rx_sync_domain, dts->rx_sync_id, sunxi_rx_sync_enable); } return 0; } static void sunxi_internal_codec_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_dts *dts = &codec->dts; SND_LOG_DEBUG(HLOG, "\n"); if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { if (dts->rx_sync_en && dts->rx_sync_ctl) sunxi_rx_sync_shutdown(dts->rx_sync_domain, dts->rx_sync_id); } } static void adchpf_config(struct regmap *regmap, unsigned int rate) { switch (rate) { case 16000: regmap_write(regmap, SUNXI_ADC_DRC_HHPFC, (0x00F623A5 >> 16) & 0xFFFF); regmap_write(regmap, SUNXI_ADC_DRC_LHPFC, 0x00F623A5 & 0xFFFF); break; case 44100: regmap_write(regmap, SUNXI_ADC_DRC_HHPFC, (0x00FC60DB >> 16) & 0xFFFF); regmap_write(regmap, SUNXI_ADC_DRC_LHPFC, 0x00FC60DB & 0xFFFF); break; default: regmap_write(regmap, SUNXI_ADC_DRC_HHPFC, (0x00FCABB3 >> 16) & 0xFFFF); regmap_write(regmap, SUNXI_ADC_DRC_LHPFC, 0x00FCABB3 & 0xFFFF); break; } } static int sunxi_internal_codec_dai_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { int i; struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "\n"); /* set bits */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 3 << FIFO_MODE, 3 << FIFO_MODE); regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << TX_SAMPLE_BITS, 0 << TX_SAMPLE_BITS); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_FIFO_MODE, 1 << RX_FIFO_MODE); regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_SAMPLE_BITS, 0 << RX_SAMPLE_BITS); } break; case SNDRV_PCM_FORMAT_S24_LE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 3 << FIFO_MODE, 0 << FIFO_MODE); regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << TX_SAMPLE_BITS, 1 << TX_SAMPLE_BITS); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_FIFO_MODE, 0 << RX_FIFO_MODE); regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_SAMPLE_BITS, 1 << RX_SAMPLE_BITS); } break; default: break; } /* set rate */ for (i = 0; i < ARRAY_SIZE(sample_rate_conv); i++) { if (sample_rate_conv[i].samplerate == params_rate(params)) { if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 0x7 << DAC_FS, sample_rate_conv[i].rate_bit << DAC_FS); } else { if (sample_rate_conv[i].samplerate > 48000) return -EINVAL; regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x7 << ADC_FS, sample_rate_conv[i].rate_bit << ADC_FS); } } } /* reset the adchpf func setting for different sampling */ adchpf_config(regmap, params_rate(params)); /* set channels */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { switch (params_channels(params)) { case 1: /* DACL & DACR send same data */ regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_MONO_EN, 0x1 << DAC_MONO_EN); break; case 2: regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_MONO_EN, 0x0 << DAC_MONO_EN); break; default: SND_LOG_WARN(HLOG, "not support channels:%u", params_channels(params)); return -EINVAL; } } return 0; } static int sunxi_internal_codec_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_clk *clk = &codec->clk; SND_LOG_DEBUG(HLOG, "stream -> %s, freq_in ->%u, freq_out ->%u\n", pll_id ? "adc" : "dac", freq_in, freq_out); if (pll_id == SNDRV_PCM_STREAM_PLAYBACK) goto set_pll_playback; else goto set_pll_capture; set_pll_playback: if (clk_set_rate(clk->pllaudio, freq_in)) { SND_LOG_ERR(HLOG, "pllaudio set rate failed\n"); return -EINVAL; } if (clk_set_rate(clk->dacclk, freq_out)) { SND_LOG_ERR(HLOG, "dacclk set rate failed\n"); return -EINVAL; } return 0; set_pll_capture: if (clk_set_rate(clk->pllaudio, freq_in)) { SND_LOG_ERR(HLOG, "pllaudio set rate failed\n"); return -EINVAL; } if (clk_set_rate(clk->adcclk, freq_out)) { SND_LOG_ERR(HLOG, "adcclk set rate failed\n"); return -EINVAL; } return 0; } static int sunxi_internal_codec_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "\n"); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << FIFO_FLUSH, 1 << FIFO_FLUSH); regmap_write(regmap, SUNXI_DAC_FIFOS, 1 << DAC_TXE_INT |\ 1 << DAC_TXU_INT |\ 1 << DAC_TXO_INT); regmap_write(regmap, SUNXI_DAC_CNT, 0); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << ADC_FIFO_FLUSH, 1 << ADC_FIFO_FLUSH); regmap_write(regmap, SUNXI_ADC_FIFOS, 1 << ADC_RXA_INT |\ 1 << ADC_RXO_INT); regmap_write(regmap, SUNXI_ADC_CNT, 0); } return 0; } static int sunxi_internal_codec_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct snd_soc_codec *snd_codec = dai->codec; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_dts *dts = &codec->dts; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "cmd -> %d\n", cmd); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << DAC_DRQ_EN, 1 << DAC_DRQ_EN); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << ADC_DRQ_EN, 1 << ADC_DRQ_EN); if (dts->rx_sync_en && dts->rx_sync_ctl) sunxi_rx_sync_control(dts->rx_sync_domain, dts->rx_sync_id, true); } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << DAC_DRQ_EN, 0 << DAC_DRQ_EN); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << ADC_DRQ_EN, 0 << ADC_DRQ_EN); if (dts->rx_sync_en && dts->rx_sync_ctl) sunxi_rx_sync_control(dts->rx_sync_domain, dts->rx_sync_id, false); } break; default: return -EINVAL; } return 0; } static const struct snd_soc_dai_ops sunxi_internal_codec_dai_ops = { .startup = sunxi_internal_codec_dai_startup, .set_pll = sunxi_internal_codec_dai_set_pll, .hw_params = sunxi_internal_codec_dai_hw_params, .prepare = sunxi_internal_codec_dai_prepare, .trigger = sunxi_internal_codec_dai_trigger, .shutdown = sunxi_internal_codec_dai_shutdown, }; static struct snd_soc_dai_driver sunxi_internal_codec_dai = { .name = DRV_NAME, .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, }, .ops = &sunxi_internal_codec_dai_ops, }; /******************************************************************************* * *** sound card & component function source *** * @0 sound card probe * @1 component function kcontrol register ******************************************************************************/ static void sunxi_rx_sync_enable(void *data, bool enable) { struct sunxi_codec *codec = data; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "%s\n", enable ? "on" : "off"); if (enable) { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_SYNC_EN_START, 1 << RX_SYNC_EN_START); } else { regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << RX_SYNC_EN_START, 0 << RX_SYNC_EN_START); } return; } /* components function kcontrol setting */ static int sunxi_get_tx_hub_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *snd_codec = snd_soc_component_to_codec(component); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, SUNXI_DAC_DPC, ®_val); ucontrol->value.integer.value[0] = ((reg_val & (0x1 << DAC_HUB_EN)) ? 1 : 0); return 0; } static int sunxi_set_tx_hub_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *snd_codec = snd_soc_component_to_codec(component); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; switch (ucontrol->value.integer.value[0]) { case 0: regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x0 << LINEOUTLEN); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLEN, 0x0 << DACLEN); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLMUTE, 0x0 << DACLMUTE); regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << EN_DAC, 0x0 << EN_DAC); regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << DAC_HUB_EN, 0x0 << DAC_HUB_EN); break; case 1: regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << DAC_HUB_EN, 0x1 << DAC_HUB_EN); regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1 << EN_DAC, 0x1 << EN_DAC); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLMUTE, 0x1 << DACLMUTE); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLEN, 0x1 << DACLEN); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x1 << LINEOUTLEN); break; default: return -EINVAL; } return 0; } static int sunxi_get_rx_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct sunxi_dts *dts = &codec->dts; ucontrol->value.integer.value[0] = dts->rx_sync_ctl; return 0; } static int sunxi_set_rx_sync_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct sunxi_dts *dts = &codec->dts; struct regmap *regmap = codec->mem.regmap; switch (ucontrol->value.integer.value[0]) { case 0: dts->rx_sync_ctl = 0; regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << RX_SYNC_EN, 0x0 << RX_SYNC_EN); break; case 1: regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << RX_SYNC_EN, 0x1 << RX_SYNC_EN); dts->rx_sync_ctl = 1; break; default: return -EINVAL; } return 0; } static const char *sunxi_switch_text[] = {"Off", "On"}; static SOC_ENUM_SINGLE_EXT_DECL(sunxi_tx_hub_mode_enum, sunxi_switch_text); static SOC_ENUM_SINGLE_EXT_DECL(sunxi_rx_sync_mode_enum, sunxi_switch_text); static const struct snd_kcontrol_new sunxi_tx_hub_controls[] = { SOC_ENUM_EXT("tx hub mode", sunxi_tx_hub_mode_enum, sunxi_get_tx_hub_mode, sunxi_set_tx_hub_mode), }; static const struct snd_kcontrol_new sunxi_rx_sync_controls[] = { SOC_ENUM_EXT("rx sync mode", sunxi_rx_sync_mode_enum, sunxi_get_rx_sync_mode, sunxi_set_rx_sync_mode), }; /* kcontrol setting */ static SOC_ENUM_SINGLE_EXT_DECL(sunxi_codec_dacdrc_mode_enum, sunxi_switch_text); static SOC_ENUM_SINGLE_EXT_DECL(sunxi_codec_adcdrc_mode_enum, sunxi_switch_text); static SOC_ENUM_SINGLE_EXT_DECL(sunxi_codec_dachpf_mode_enum, sunxi_switch_text); static SOC_ENUM_SINGLE_EXT_DECL(sunxi_codec_adchpf_mode_enum, sunxi_switch_text); static SOC_ENUM_SINGLE_DECL(sunxi_codec_adc_swap_enum, SUNXI_ADC_DG, AD_SWP1, sunxi_switch_text); static const DECLARE_TLV_DB_SCALE(digital_tlv, -7424, 116, 0); static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -11925, 75, 0); static const DECLARE_TLV_DB_SCALE(adc_vol_tlv, -11925, 75, 0); static const DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); static const DECLARE_TLV_DB_SCALE(linein_gain_tlv, 0, 100, 0); static const unsigned int lineout_tlv[] = { TLV_DB_RANGE_HEAD(2), 0, 1, TLV_DB_SCALE_ITEM(SNDRV_CTL_TLVD_DB_GAIN_MUTE, 0, 1), 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), }; /* DAC&ADC-DRC&HPF FUNC */ static void dacdrc_enable(struct sunxi_codec *codec, bool on) { struct sunxi_dap *dac_dap = &codec->dac_dap; struct regmap *regmap = codec->mem.regmap; mutex_lock(&dac_dap->mutex); if (on) { if (dac_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_EN, 0x1 << DDAP_EN); } dac_dap->dap_enable |= BIT(0); regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_DRC_EN, 0x1 << DDAP_DRC_EN); } else { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_DRC_EN, 0x0 << DDAP_DRC_EN); dac_dap->dap_enable &= ~BIT(0); if (dac_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_EN, 0x0 << DDAP_EN); } } mutex_unlock(&dac_dap->mutex); } static int sunxi_codec_get_dacdrc_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, SUNXI_DAC_DAP_CTL, ®_val); ucontrol->value.integer.value[0] = ((reg_val & (0x1 << DDAP_DRC_EN)) ? 1 : 0); return 0; } static int sunxi_codec_set_dacdrc_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); switch (ucontrol->value.integer.value[0]) { case 0: dacdrc_enable(codec, 0); break; case 1: dacdrc_enable(codec, 1); break; default: return -EINVAL; } return 0; } static void adcdrc_enable(struct sunxi_codec *codec, bool on) { struct sunxi_dap *adc_dap = &codec->adc_dap; struct regmap *regmap = codec->mem.regmap; mutex_lock(&adc_dap->mutex); if (on) { if (adc_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP0_EN, 0x1 << ADC_DAP0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP1_EN, 0x1 << ADC_DAP1_EN); } adc_dap->dap_enable |= BIT(0); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DRC0_EN, 0x1 << ADC_DRC0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DRC1_EN, 0x1 << ADC_DRC1_EN); } else { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DRC0_EN, 0x0 << ADC_DRC0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DRC1_EN, 0x0 << ADC_DRC1_EN); adc_dap->dap_enable &= ~BIT(0); if (adc_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP0_EN, 0x0 << ADC_DAP0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP1_EN, 0x0 << ADC_DAP1_EN); } } mutex_unlock(&adc_dap->mutex); } static int sunxi_codec_get_adcdrc_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, SUNXI_ADC_DAP_CTL, ®_val); ucontrol->value.integer.value[0] = ((reg_val & (0x1 << ADC_DRC0_EN)) ? 1 : 0); return 0; } static int sunxi_codec_set_adcdrc_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); switch (ucontrol->value.integer.value[0]) { case 0: adcdrc_enable(codec, 0); break; case 1: adcdrc_enable(codec, 1); break; default: return -EINVAL; } return 0; } static void dachpf_enable(struct sunxi_codec *codec, bool on) { struct sunxi_dap *dac_dap = &codec->dac_dap; struct regmap *regmap = codec->mem.regmap; mutex_lock(&dac_dap->mutex); if (on) { if (dac_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_EN, 0x1 << DDAP_EN); } dac_dap->dap_enable |= BIT(1); regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_HPF_EN, 0x1 << DDAP_HPF_EN); } else { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_HPF_EN, 0x0 << DDAP_HPF_EN); dac_dap->dap_enable &= ~BIT(1); if (dac_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_DAC_DAP_CTL, 0x1 << DDAP_EN, 0x0 << DDAP_EN); } } mutex_unlock(&dac_dap->mutex); } static int sunxi_codec_get_dachpf_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, SUNXI_DAC_DAP_CTL, ®_val); ucontrol->value.integer.value[0] = ((reg_val & (0x1 << DDAP_HPF_EN)) ? 1 : 0); return 0; } static int sunxi_codec_set_dachpf_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); switch (ucontrol->value.integer.value[0]) { case 0: dachpf_enable(codec, 0); break; case 1: dachpf_enable(codec, 1); break; default: return -EINVAL; } return 0; } static void adchpf_enable(struct sunxi_codec *codec, bool on) { struct sunxi_dap *adc_dap = &codec->adc_dap; struct regmap *regmap = codec->mem.regmap; mutex_lock(&adc_dap->mutex); if (on) { if (adc_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP0_EN, 0x1 << ADC_DAP0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP1_EN, 0x1 << ADC_DAP1_EN); } adc_dap->dap_enable |= BIT(1); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_HPF0_EN, 0x1 << ADC_HPF0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_HPF1_EN, 0x1 << ADC_HPF1_EN); } else { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_HPF0_EN, 0x0 << ADC_HPF0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_HPF1_EN, 0x0 << ADC_HPF1_EN); adc_dap->dap_enable &= ~BIT(1); if (adc_dap->dap_enable == 0) { regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP0_EN, 0x0 << ADC_DAP0_EN); regmap_update_bits(regmap, SUNXI_ADC_DAP_CTL, 0x1 << ADC_DAP1_EN, 0x0 << ADC_DAP1_EN); } } mutex_unlock(&adc_dap->mutex); } static int sunxi_codec_get_adchpf_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, SUNXI_ADC_DAP_CTL, ®_val); ucontrol->value.integer.value[0] = ((reg_val & (0x1 << ADC_HPF0_EN)) ? 1 : 0); return 0; } static int sunxi_codec_set_adchpf_mode(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); switch (ucontrol->value.integer.value[0]) { case 0: adchpf_enable(codec, 0); break; case 1: adchpf_enable(codec, 1); break; default: return -EINVAL; } return 0; } static int sunxi_codec_get_gain_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct sunxi_codec_runtime *runtime = &codec->runtime; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int val_tmp; unsigned int invert = mc->invert; unsigned int shift = mc->shift; int max = mc->max; int min = mc->min; switch (shift) { case KCONTROL_SHIFT_MIC1_GAIN: val_tmp = runtime->mic1gain; break; case KCONTROL_SHIFT_MIC2_GAIN: val_tmp = runtime->mic2gain; break; case KCONTROL_SHIFT_LINEINL_GAIN: val_tmp = runtime->lineinlgain; break; case KCONTROL_SHIFT_LINEINR_GAIN: val_tmp = runtime->lineinrgain; break; default: SND_LOG_ERR(HLOG, "unsupport control shift %u\n", shift); break; } if (val_tmp > max || val_tmp < min) return -1; ucontrol->value.integer.value[0] = val_tmp - min; if (invert) ucontrol->value.integer.value[0] = max - ucontrol->value.integer.value[0]; return 0; } static int sunxi_codec_put_gain_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); struct sunxi_codec *codec = snd_soc_component_get_drvdata(component); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int val_tmp; unsigned int invert = mc->invert; unsigned int shift = mc->shift; int max = mc->max; int min = mc->min; val_tmp = ucontrol->value.integer.value[0] + min; if (invert) val_tmp = max - val_tmp; switch (shift) { case KCONTROL_SHIFT_MIC1_GAIN: runtime->mic1gain = val_tmp; if (runtime->mic1_run) regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->mic1gain << ADC1_PGA_GAIN_CTRL); break; case KCONTROL_SHIFT_MIC2_GAIN: runtime->mic2gain = val_tmp; if (runtime->mic2_run) regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, runtime->mic2gain << ADC2_PGA_GAIN_CTRL); break; case KCONTROL_SHIFT_LINEINL_GAIN: runtime->lineinlgain = val_tmp; if (runtime->linein_run) regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->lineinlgain << ADC1_PGA_GAIN_CTRL); break; case KCONTROL_SHIFT_LINEINR_GAIN: runtime->lineinrgain = val_tmp; if (runtime->linein_run) regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->lineinrgain << ADC1_PGA_GAIN_CTRL); break; default: SND_LOG_ERR(HLOG, "unsupport control shift %u\n", shift); break; } return 0; } static const struct snd_kcontrol_new sunxi_codec_controls[] = { /* DAP func */ SOC_ENUM_EXT("DACDRC", sunxi_codec_dacdrc_mode_enum, sunxi_codec_get_dacdrc_mode, sunxi_codec_set_dacdrc_mode), SOC_ENUM_EXT("ADCDRC", sunxi_codec_adcdrc_mode_enum, sunxi_codec_get_adcdrc_mode, sunxi_codec_set_adcdrc_mode), SOC_ENUM_EXT("DACHPF", sunxi_codec_dachpf_mode_enum, sunxi_codec_get_dachpf_mode, sunxi_codec_set_dachpf_mode), SOC_ENUM_EXT("ADCHPF", sunxi_codec_adchpf_mode_enum, sunxi_codec_get_adchpf_mode, sunxi_codec_set_adchpf_mode), SOC_ENUM("ADC1 ADC2 swap", sunxi_codec_adc_swap_enum), /* Digital Volume */ SOC_SINGLE_TLV("digital volume", SUNXI_DAC_DPC, DVOL, 0x3F, 1, digital_tlv), /* DAC Volume */ SOC_SINGLE_TLV("DAC volume", SUNXI_DAC_VOL_CTRL, DAC_VOL_L, 0xFF, 0, dac_vol_tlv), /* ADC1 Volume */ SOC_SINGLE_TLV("ADC1 volume", SUNXI_ADC_VOL_CTRL, ADC1_VOL, 0xFF, 0, adc_vol_tlv), /* ADC2 Volume */ SOC_SINGLE_TLV("ADC2 volume", SUNXI_ADC_VOL_CTRL, ADC2_VOL, 0xFF, 0, adc_vol_tlv), /* MIC1 Gain */ SOC_SINGLE_EXT_TLV("MIC1 gain volume", SND_SOC_NOPM, KCONTROL_SHIFT_MIC1_GAIN, 0x1F, 0, sunxi_codec_get_gain_volsw, sunxi_codec_put_gain_volsw, mic_gain_tlv), /* MIC2 Gain */ SOC_SINGLE_EXT_TLV("MIC2 gain volume", SND_SOC_NOPM, KCONTROL_SHIFT_MIC2_GAIN, 0x1F, 0, sunxi_codec_get_gain_volsw, sunxi_codec_put_gain_volsw, mic_gain_tlv), /* LINEIN_L Gain */ SOC_SINGLE_EXT_TLV("LINEINL gain volume", SND_SOC_NOPM, KCONTROL_SHIFT_LINEINL_GAIN, 0x1F, 0, sunxi_codec_get_gain_volsw, sunxi_codec_put_gain_volsw, linein_gain_tlv), /* LINEIN_R Gain */ SOC_SINGLE_EXT_TLV("LINEINR gain volume", SND_SOC_NOPM, KCONTROL_SHIFT_LINEINR_GAIN, 0x1F, 0, sunxi_codec_get_gain_volsw, sunxi_codec_put_gain_volsw, linein_gain_tlv), /* LINEOUT Volume */ SOC_SINGLE_TLV("LINEOUT volume", SUNXI_DAC_REG, LINEOUT_VOL, 0x1F, 0, lineout_tlv), }; /* dapm setting */ static int sunxi_codec_get_dapm_vol(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; switch (w->shift) { case WIDGET_SHIFT_MIC1_INPUT_SELECT: ucontrol->value.enumerated.item[0] = w->off_val ^ runtime->mic1_single; break; case WIDGET_SHIFT_MIC2_INPUT_SELECT: ucontrol->value.enumerated.item[0] = w->off_val ^ runtime->mic2_single; break; default: SND_LOG_ERR(HLOG, "unsupport control shift %u\n", w->shift); break; } return 0; } static int sunxi_codec_put_dapm_vol(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol); struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; switch (w->shift) { case WIDGET_SHIFT_MIC1_INPUT_SELECT: runtime->mic1_single = ucontrol->value.enumerated.item[0] ^ w->off_val; break; case WIDGET_SHIFT_MIC2_INPUT_SELECT: runtime->mic2_single = ucontrol->value.enumerated.item[0] ^ w->off_val; break; default: SND_LOG_ERR(HLOG, "unsupport control shift %u\n", w->shift); break; } return 0; } static const char *differ_select_text[] = {"single", "differ"}; static SOC_ENUM_SINGLE_DECL(lineoutl_output_enum, SUNXI_DAC_REG, LINEOUTLDIFFEN, differ_select_text); static SOC_ENUM_SINGLE_EXT_DECL(mic1_input_enum, differ_select_text); static SOC_ENUM_SINGLE_EXT_DECL(mic2_input_enum, differ_select_text); static const struct snd_kcontrol_new lineoutl_output_mux = SOC_DAPM_ENUM("LINEOUT Output Select", lineoutl_output_enum); static const struct snd_kcontrol_new mic1_input_mux = SOC_DAPM_ENUM_EXT("MIC1 Input Select", mic1_input_enum, sunxi_codec_get_dapm_vol, sunxi_codec_put_dapm_vol); static const struct snd_kcontrol_new mic2_input_mux = SOC_DAPM_ENUM_EXT("MIC2 Input Select", mic2_input_enum, sunxi_codec_get_dapm_vol, sunxi_codec_put_dapm_vol); static int sunxi_codec_dacl_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLEN, 0x1 << DACLEN); regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << DACLMUTE, 0x1 << DACLMUTE); regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x1<dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_POST_PMU: regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 0x1 << ADC1_CHANNEL_EN, 0x1 << ADC1_CHANNEL_EN); regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << ADC1_EN, 0x1 << ADC1_EN); regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << EN_AD, 0x1 << EN_AD); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << EN_AD, 0x0 << EN_AD); regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << ADC1_EN, 0x0 << ADC1_EN); regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 0x1 << ADC1_CHANNEL_EN, 0x0 << ADC1_CHANNEL_EN); break; default: break; } return 0; } static int sunxi_codec_adc2_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_POST_PMU: regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 0x1 << ADC2_CHANNEL_EN, 0x1 << ADC2_CHANNEL_EN); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << ADC2_EN, 0x1 << ADC2_EN); regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << EN_AD, 0x1 << EN_AD); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << EN_AD, 0x0 << EN_AD); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << ADC2_EN, 0x0 << ADC2_EN); regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 0x1 << ADC2_CHANNEL_EN, 0x0 << ADC2_CHANNEL_EN); break; default: break; } return 0; } static int sunxi_codec_mic_input_select_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (w->shift) { case WIDGET_SHIFT_MIC1_INPUT_SELECT: regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_SIN_EN, runtime->mic1_single << MIC1_SIN_EN); break; case WIDGET_SHIFT_MIC2_INPUT_SELECT: regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_SIN_EN, runtime->mic2_single << MIC2_SIN_EN); break; default: SND_LOG_ERR(HLOG, "unsupport widget shift %u\n", w->shift); break; } return 0; } static int sunxi_codec_adc_gain_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (w->shift) { case WIDGET_SHIFT_MIC1_GAIN: switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->mic1gain << ADC1_PGA_GAIN_CTRL); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, 0x0 << ADC1_PGA_GAIN_CTRL); break; default: SND_LOG_ERR(HLOG, "unsupport control flag %d\n", event); break; } break; case WIDGET_SHIFT_MIC2_GAIN: switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, runtime->mic2gain << ADC2_PGA_GAIN_CTRL); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, 0x0 << ADC2_PGA_GAIN_CTRL); break; default: SND_LOG_ERR(HLOG, "unsupport control flag %d\n", event); break; } break; case WIDGET_SHIFT_LINEINL_GAIN: switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->lineinlgain << ADC1_PGA_GAIN_CTRL); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, 0x0 << ADC1_PGA_GAIN_CTRL); break; default: SND_LOG_ERR(HLOG, "unsupport control flag %d\n", event); break; } break; case WIDGET_SHIFT_LINEINR_GAIN: switch (event) { case SND_SOC_DAPM_PRE_PMU: regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, runtime->lineinrgain << ADC2_PGA_GAIN_CTRL); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, 0x0 << ADC2_PGA_GAIN_CTRL); break; default: SND_LOG_ERR(HLOG, "unsupport control flag %d\n", event); break; } break; default: SND_LOG_ERR(HLOG, "unsupport control shift %u\n", w->shift); break; } return 0; } static int sunxi_codec_mic1_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_PRE_PMU: mutex_lock(&runtime->input_mutex); runtime->mic1_run = true; if (runtime->linein_run) SND_LOG_WARN(HLOG, "Only one of mic and linein allow to be On\n"); mutex_unlock(&runtime->input_mutex); regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_PGA_EN, 0x1 << MIC1_PGA_EN); break; case SND_SOC_DAPM_POST_PMD: mutex_lock(&runtime->input_mutex); runtime->mic1_run = false; mutex_unlock(&runtime->input_mutex); regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_PGA_EN, 0x0 << MIC1_PGA_EN); break; default: break; } return 0; } static int sunxi_codec_mic2_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_PRE_PMU: mutex_lock(&runtime->input_mutex); runtime->mic2_run = true; if (runtime->linein_run) SND_LOG_WARN(HLOG, "Only one of mic and linein allow to be On\n"); mutex_unlock(&runtime->input_mutex); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_PGA_EN, 0x1 << MIC2_PGA_EN); break; case SND_SOC_DAPM_POST_PMD: mutex_lock(&runtime->input_mutex); runtime->mic2_run = false; mutex_unlock(&runtime->input_mutex); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_PGA_EN, 0x0 << MIC2_PGA_EN); break; default: break; } return 0; } static int sunxi_codec_linein_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); /* note: use micin single mode instead of linein */ switch (event) { case SND_SOC_DAPM_POST_PMU: mutex_lock(&runtime->input_mutex); runtime->linein_run = true; if (runtime->mic1_run || runtime->mic2_run) SND_LOG_WARN(HLOG, "Only one of mic and linein allow to be On\n"); mutex_unlock(&runtime->input_mutex); /* set single mode */ regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_SIN_EN, 0x1 << MIC1_SIN_EN); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_SIN_EN, 0x1 << MIC2_SIN_EN); /* enable */ regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_PGA_EN, 0x1 << MIC1_PGA_EN); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_PGA_EN, 0x1 << MIC2_PGA_EN); break; case SND_SOC_DAPM_PRE_PMD: mutex_lock(&runtime->input_mutex); runtime->linein_run = false; mutex_unlock(&runtime->input_mutex); /* disable */ regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_PGA_EN, 0x0 << MIC1_PGA_EN); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_PGA_EN, 0x0 << MIC2_PGA_EN); break; default: break; } return 0; } static int sunxi_codec_lineout_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_POST_PMU: regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x1 << LINEOUTLEN); break; case SND_SOC_DAPM_PRE_PMD: regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLEN, 0x0 << LINEOUTLEN); break; default: break; } return 0; } static int sunxi_codec_speaker_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { struct snd_soc_codec *snd_codec = snd_soc_dapm_to_codec(w->dapm); struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); SND_LOG_DEBUG(HLOG, "event -> 0x%x\n", event); switch (event) { case SND_SOC_DAPM_POST_PMU: snd_sunxi_pa_pin_enable(codec->pa_config, codec->pa_pin_max); break; case SND_SOC_DAPM_PRE_PMD: snd_sunxi_pa_pin_disable(codec->pa_config, codec->pa_pin_max); break; default: break; } return 0; } static const struct snd_soc_dapm_widget sunxi_codec_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN_E("DACL", "Playback", 0, SND_SOC_NOPM, 0, 0, sunxi_codec_dacl_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_AIF_OUT_E("ADC1", "Capture", 0, SND_SOC_NOPM, 0, 0, sunxi_codec_adc1_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_AIF_OUT_E("ADC2", "Capture", 0, SND_SOC_NOPM, 0, 0, sunxi_codec_adc2_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("LINEOUT Output Select", SND_SOC_NOPM, 0, 0, &lineoutl_output_mux), SND_SOC_DAPM_MUX_E("MIC1 Input Select", SND_SOC_NOPM, WIDGET_SHIFT_MIC1_INPUT_SELECT, 1, &mic1_input_mux, sunxi_codec_mic_input_select_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX_E("MIC2 Input Select", SND_SOC_NOPM, WIDGET_SHIFT_MIC2_INPUT_SELECT, 1, &mic2_input_mux, sunxi_codec_mic_input_select_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("MIC1 PGA", SND_SOC_NOPM, WIDGET_SHIFT_MIC1_GAIN, 0, NULL, 0, sunxi_codec_adc_gain_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("MIC2 PGA", SND_SOC_NOPM, WIDGET_SHIFT_MIC2_GAIN, 0, NULL, 0, sunxi_codec_adc_gain_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("LINEINL PGA", SND_SOC_NOPM, WIDGET_SHIFT_LINEINL_GAIN, 0, NULL, 0, sunxi_codec_adc_gain_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_E("LINEINR PGA", SND_SOC_NOPM, WIDGET_SHIFT_LINEINR_GAIN, 0, NULL, 0, sunxi_codec_adc_gain_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MICBIAS("MIC1 Bias", SUNXI_MICBIAS_REG, MMICBIASEN, 0), SND_SOC_DAPM_MICBIAS("MIC2 Bias", SUNXI_MICBIAS_REG, MMICBIASEN, 0), SND_SOC_DAPM_INPUT("MIC1_PIN"), SND_SOC_DAPM_INPUT("MIC2_PIN"), SND_SOC_DAPM_INPUT("LINEINL_PIN"), SND_SOC_DAPM_INPUT("LINEINR_PIN"), SND_SOC_DAPM_OUTPUT("LINEOUTL_PIN"), SND_SOC_DAPM_MIC("MIC1", sunxi_codec_mic1_event), SND_SOC_DAPM_MIC("MIC2", sunxi_codec_mic2_event), SND_SOC_DAPM_LINE("LINEIN", sunxi_codec_linein_event), SND_SOC_DAPM_LINE("LINEOUT", sunxi_codec_lineout_event), SND_SOC_DAPM_SPK("SPK", sunxi_codec_speaker_event), }; static const struct snd_soc_dapm_route sunxi_codec_dapm_routes[] = { /* input route -> MIC1 MIC2 */ {"MIC1_PIN", NULL, "MIC1"}, {"MIC2_PIN", NULL, "MIC2"}, {"MIC1 Bias", NULL, "MIC1_PIN"}, {"MIC2 Bias", NULL, "MIC2_PIN"}, {"MIC1 Input Select", "single", "MIC1 Bias"}, {"MIC1 Input Select", "differ", "MIC1 Bias"}, {"MIC2 Input Select", "single", "MIC2 Bias"}, {"MIC2 Input Select", "differ", "MIC2 Bias"}, {"MIC1 PGA", NULL, "MIC1 Input Select"}, {"MIC2 PGA", NULL, "MIC2 Input Select"}, {"ADC1", NULL, "MIC1 PGA"}, {"ADC2", NULL, "MIC2 PGA"}, /* input route -> LINEIN */ {"LINEINL_PIN", NULL, "LINEIN"}, {"LINEINR_PIN", NULL, "LINEIN"}, {"LINEINL PGA", NULL, "LINEINL_PIN"}, {"LINEINR PGA", NULL, "LINEINR_PIN"}, {"ADC1", NULL, "LINEINL PGA"}, {"ADC2", NULL, "LINEINR PGA"}, /* output route -> LINEOUT */ {"LINEOUT Output Select", "single", "DACL"}, {"LINEOUT Output Select", "differ", "DACL"}, {"LINEOUTL_PIN", NULL, "LINEOUT Output Select"}, {"LINEOUT", NULL, "LINEOUTL_PIN"}, {"SPK", NULL, "LINEOUTL_PIN"}, }; static void sunxi_codec_runtime_params_init(struct snd_soc_codec *snd_codec) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct sunxi_dts *dts = &codec->dts; mutex_init(&runtime->input_mutex); runtime->mic1gain = dts->mic1gain; runtime->mic2gain = dts->mic2gain; runtime->mic1_single = dts->mic1_single; runtime->mic2_single = dts->mic2_single; runtime->lineinlgain = 1; /* default 6dB (reg val 1) when in mic single mode */ runtime->lineinrgain = 1; runtime->mic1_run = false; runtime->mic2_run = false; runtime->linein_run = false; } static void sunxi_codec_runtime_params_exit(struct snd_soc_codec *snd_codec) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; mutex_destroy(&runtime->input_mutex); } static void sunxi_codec_init(struct snd_soc_codec *snd_codec) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_codec_runtime *runtime = &codec->runtime; struct sunxi_regulator *rglt = &codec->rglt; struct sunxi_dts *dts = &codec->dts; struct regmap *regmap = codec->mem.regmap; unsigned int adc_dtime_map; unsigned int avcc_vol_map; SND_LOG_DEBUG(HLOG, "\n"); if (rglt->external_avcc) { regmap_update_bits(regmap, SUNXI_POWER_REG, 0x1 << ALDO_EN, 0x0 << ALDO_EN); } else { switch (rglt->avcc_vol) { case 1650000: avcc_vol_map = 0; break; case 1700000: avcc_vol_map = 1; break; case 1750000: avcc_vol_map = 2; break; case 1800000: avcc_vol_map = 3; break; case 1850000: avcc_vol_map = 4; break; case 1900000: avcc_vol_map = 5; break; case 1950000: avcc_vol_map = 6; break; case 2000000: avcc_vol_map = 7; break; default: avcc_vol_map = 3; SND_LOG_DEBUG(HLOG, "unsupport avcc vol, default 1.8v\n"); break; } regmap_update_bits(regmap, SUNXI_POWER_REG, 0x7 << ALDO_OUTPUT_VOLTAGE, avcc_vol_map << ALDO_OUTPUT_VOLTAGE); regmap_update_bits(regmap, SUNXI_POWER_REG, 0x1 << ALDO_EN, 0x1 << ALDO_EN); } regmap_update_bits(regmap, SUNXI_RAMP_REG, 0x1 << RMC_EN, 0x1 << RMC_EN); /* Enable ADCFDT to overcome niose at the beginning */ regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << ADCDFEN, 0x1 << ADCDFEN); switch (dts->adc_dtime) { case 5: adc_dtime_map = 0; break; case 10: adc_dtime_map = 1; break; case 20: adc_dtime_map = 2; break; case 30: adc_dtime_map = 3; break; case 0: default: regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << ADCDFEN, 0x0 << ADCDFEN); break; } regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x3 << ADCFDT, adc_dtime_map << ADCFDT); regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 0x1 << RX_SYNC_EN, 0x0 << RX_SYNC_EN); /* Digital VOL defeult setting */ regmap_update_bits(regmap, SUNXI_DAC_DPC, 0x3F << DVOL, 0 << DVOL); /* LINEOUT VOL defeult setting */ regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1F << LINEOUT_VOL, dts->lineout_vol << LINEOUT_VOL); /* ADCL MIC1 gain defeult setting */ regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1F << ADC1_PGA_GAIN_CTRL, runtime->mic1gain << ADC1_PGA_GAIN_CTRL); /* ADCR MIC2 gain defeult setting */ regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1F << ADC2_PGA_GAIN_CTRL, runtime->mic2gain << ADC2_PGA_GAIN_CTRL); /* ADC IOP params default setting */ regmap_update_bits(regmap, SUNXI_ADC1_REG, 0xFF << ADC1_IOPMIC, 0x55 << ADC1_IOPMIC); regmap_update_bits(regmap, SUNXI_ADC2_REG, 0xFF << ADC2_IOPMIC, 0x55 << ADC2_IOPMIC); /* lineout & mic1 & mic2 diff or single setting */ if (dts->lineout_single) regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLDIFFEN, 0x0 << LINEOUTLDIFFEN); else regmap_update_bits(regmap, SUNXI_DAC_REG, 0x1 << LINEOUTLDIFFEN, 0x1 << LINEOUTLDIFFEN); if (runtime->mic1_single) regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_SIN_EN, 0x1 << MIC1_SIN_EN); else regmap_update_bits(regmap, SUNXI_ADC1_REG, 0x1 << MIC1_SIN_EN, 0x0 << MIC1_SIN_EN); if (runtime->mic2_single) regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_SIN_EN, 0x1 << MIC2_SIN_EN); else regmap_update_bits(regmap, SUNXI_ADC2_REG, 0x1 << MIC2_SIN_EN, 0x0 << MIC2_SIN_EN); regmap_update_bits(regmap, SUNXI_DAC_VOL_CTRL, 1 << DAC_VOL_SEL, 1 << DAC_VOL_SEL); regmap_update_bits(regmap, SUNXI_ADC_DIG_CTRL, 1 << ADC1_2_VOL_EN, 1 << ADC1_2_VOL_EN); } static int sunxi_internal_codec_probe(struct snd_soc_codec *snd_codec) { int ret; struct snd_soc_dapm_context *dapm = &snd_codec->component.dapm; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_dts *dts = &codec->dts; SND_LOG_DEBUG(HLOG, "\n"); sunxi_codec_runtime_params_init(snd_codec); mutex_init(&codec->dac_dap.mutex); mutex_init(&codec->adc_dap.mutex); /* component kcontrols -> tx_hub */ if (dts->tx_hub_en) { ret = snd_soc_add_codec_controls(snd_codec, sunxi_tx_hub_controls, ARRAY_SIZE(sunxi_tx_hub_controls)); if (ret) SND_LOG_ERR(HLOG, "add tx_hub kcontrols failed\n"); } /* component kcontrols -> rx_sync */ if (dts->rx_sync_en) { ret = snd_soc_add_codec_controls(snd_codec, sunxi_rx_sync_controls, ARRAY_SIZE(sunxi_rx_sync_controls)); if (ret) SND_LOG_ERR(HLOG, "add rx_sync kcontrols failed\n"); } ret = snd_soc_add_codec_controls(snd_codec, sunxi_codec_controls, ARRAY_SIZE(sunxi_codec_controls)); if (ret) SND_LOG_ERR(HLOG, "add kcontrols failed\n"); snd_soc_dapm_new_controls(dapm, sunxi_codec_dapm_widgets, ARRAY_SIZE(sunxi_codec_dapm_widgets)); snd_soc_dapm_add_routes(dapm, sunxi_codec_dapm_routes, ARRAY_SIZE(sunxi_codec_dapm_routes)); sunxi_codec_init(snd_codec); return 0; } static int sunxi_internal_codec_remove(struct snd_soc_codec *snd_codec) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); SND_LOG_DEBUG(HLOG, "\n"); sunxi_codec_runtime_params_exit(snd_codec); mutex_destroy(&codec->dac_dap.mutex); mutex_destroy(&codec->adc_dap.mutex); return 0; } static int sunxi_internal_codec_suspend(struct snd_soc_codec *snd_codec) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_clk *clk = &codec->clk; struct sunxi_regulator *rglt = &codec->rglt; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "\n"); /* save reg value */ snd_sunxi_save_reg(regmap, g_reg_labels); /* disable clk & regulator */ snd_sunxi_pa_pin_disable(codec->pa_config, codec->pa_pin_max); snd_sunxi_regulator_disable(codec->pdev, rglt); snd_sunxi_clk_disable(codec->pdev, clk); return 0; } static int sunxi_internal_codec_resume(struct snd_soc_codec *snd_codec) { int ret; struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct sunxi_clk *clk = &codec->clk; struct sunxi_regulator *rglt = &codec->rglt; struct regmap *regmap = codec->mem.regmap; SND_LOG_DEBUG(HLOG, "\n"); snd_sunxi_pa_pin_disable(codec->pa_config, codec->pa_pin_max); ret = snd_sunxi_clk_enable(codec->pdev, clk); if (ret) { SND_LOG_ERR(HLOG, "clk enable failed\n"); return ret; } ret = snd_sunxi_regulator_enable(codec->pdev, rglt); if (ret) { SND_LOG_ERR(HLOG, "regulator enable failed\n"); return ret; } /* for codec init */ sunxi_codec_init(snd_codec); /* resume reg value */ snd_sunxi_echo_reg(regmap, g_reg_labels); /* for clear TX fifo */ regmap_update_bits(regmap, SUNXI_DAC_FIFOC, 1 << FIFO_FLUSH, 1 << FIFO_FLUSH); regmap_write(regmap, SUNXI_DAC_FIFOS, 1 << DAC_TXE_INT | 1 << DAC_TXU_INT | 1 << DAC_TXO_INT); regmap_write(regmap, SUNXI_DAC_CNT, 0); /* for clear RX fifo */ regmap_update_bits(regmap, SUNXI_ADC_FIFOC, 1 << ADC_FIFO_FLUSH, 1 << ADC_FIFO_FLUSH); regmap_write(regmap, SUNXI_ADC_FIFOS, 1 << ADC_RXA_INT | 1 << ADC_RXO_INT); regmap_write(regmap, SUNXI_ADC_CNT, 0); return 0; } static unsigned int sunxi_internal_codec_read(struct snd_soc_codec *snd_codec, unsigned int reg) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; unsigned int reg_val; regmap_read(regmap, reg, ®_val); return reg_val; } static int sunxi_internal_codec_write(struct snd_soc_codec *snd_codec, unsigned int reg, unsigned int val) { struct sunxi_codec *codec = snd_soc_codec_get_drvdata(snd_codec); struct regmap *regmap = codec->mem.regmap; regmap_write(regmap, reg, val); return 0; }; static struct snd_soc_codec_driver sunxi_internal_codec_dev = { .probe = sunxi_internal_codec_probe, .remove = sunxi_internal_codec_remove, .suspend = sunxi_internal_codec_suspend, .resume = sunxi_internal_codec_resume, .read = sunxi_internal_codec_read, .write = sunxi_internal_codec_write, .ignore_pmdown_time = 1, /* Doesn't benefit from pmdown delay */ }; /******************************************************************************* * *** kernel source *** * @1 regmap * @2 clk * @3 regulator * @4 pa pin * @5 dts params ******************************************************************************/ static int snd_sunxi_clk_init(struct platform_device *pdev, struct sunxi_clk *clk) { int ret = 0; struct device_node *np = pdev->dev.of_node; SND_LOG_DEBUG(HLOG, "\n"); clk->pllaudio = of_clk_get(np, 0); if (IS_ERR_OR_NULL(clk->pllaudio)) { SND_LOG_ERR(HLOG, "pllaudio get failed\n"); ret = PTR_ERR(clk->pllaudio); goto err_pllaudio; } clk->dacclk = of_clk_get(np, 1); if (IS_ERR_OR_NULL(clk->dacclk)) { SND_LOG_ERR(HLOG, "dacclk get failed\n"); ret = PTR_ERR(clk->dacclk); goto err_dacclk; } clk->adcclk = of_clk_get(np, 2); if (IS_ERR_OR_NULL(clk->adcclk)) { SND_LOG_ERR(HLOG, "adcclk get failed\n"); ret = PTR_ERR(clk->adcclk); goto err_adcclk; } if (clk_set_parent(clk->dacclk, clk->pllaudio)) { SND_LOG_ERR(HLOG, "set parent of dacclk to pllaudio failed\n"); ret = -EINVAL; goto err_set_parent; } if (clk_set_parent(clk->adcclk, clk->pllaudio)) { SND_LOG_ERR(HLOG, "set parent of adcclk to pllaudio failed\n"); ret = -EINVAL; goto err_set_parent; } ret = snd_sunxi_clk_enable(pdev, clk); if (ret) { SND_LOG_ERR(HLOG, "clk enable failed\n"); ret = -EINVAL; goto err_clk_enable; } return 0; err_clk_enable: err_set_parent: clk_put(clk->adcclk); err_adcclk: clk_put(clk->dacclk); err_dacclk: clk_put(clk->pllaudio); err_pllaudio: return ret; } static void snd_sunxi_clk_exit(struct platform_device *pdev, struct sunxi_clk *clk) { SND_LOG_DEBUG(HLOG, "\n"); snd_sunxi_clk_disable(pdev, clk); clk_put(clk->adcclk); clk_put(clk->dacclk); clk_put(clk->pllaudio); } static int snd_sunxi_clk_enable(struct platform_device *pdev, struct sunxi_clk *clk) { int ret = 0; SND_LOG_DEBUG(HLOG, "\n"); /* (22579200 or 24576000) * n */ if (clk_set_rate(clk->pllaudio, 22579200)) { SND_LOG_ERR(HLOG, "pllaudio set rate failed\n"); goto err_set_rate; } if (clk_prepare_enable(clk->pllaudio)) { SND_LOG_ERR(HLOG, "pllaudio enable failed\n"); goto err_enable_pllaudio; } if (clk_prepare_enable(clk->dacclk)) { SND_LOG_ERR(HLOG, "dacclk enable failed\n"); goto err_enable_dacclk; } if (clk_prepare_enable(clk->adcclk)) { SND_LOG_ERR(HLOG, "adcclk enable failed\n"); goto err_enable_adcclk; } return 0; err_enable_adcclk: clk_disable_unprepare(clk->dacclk); err_enable_dacclk: clk_disable_unprepare(clk->pllaudio); err_enable_pllaudio: err_set_rate: return ret; } static void snd_sunxi_clk_disable(struct platform_device *pdev, struct sunxi_clk *clk) { SND_LOG_DEBUG(HLOG, "\n"); clk_disable_unprepare(clk->adcclk); clk_disable_unprepare(clk->dacclk); clk_disable_unprepare(clk->pllaudio); } static int snd_sunxi_regulator_init(struct platform_device *pdev, struct sunxi_regulator *rglt) { int ret = 0; unsigned int temp_val; struct device_node *np = pdev->dev.of_node; SND_LOG_DEBUG(HLOG, "\n"); rglt->external_avcc = of_property_read_bool(np, "external-avcc"); ret = of_property_read_u32(np, "avcc-vol", &temp_val); if (ret < 0) { /* default avcc voltage: 1.8v */ rglt->avcc_vol = 1800000; } else { rglt->avcc_vol = temp_val; } if (rglt->external_avcc) { SND_LOG_DEBUG(HLOG, "use external avcc\n"); rglt->avcc = regulator_get(&pdev->dev, "avcc"); if (IS_ERR_OR_NULL(rglt->avcc)) { SND_LOG_WARN(HLOG, "get avcc failed, unused external pmu\n"); return 0; } } else { SND_LOG_DEBUG(HLOG, "use internal avcc\n"); return 0; } ret = regulator_set_voltage(rglt->avcc, rglt->avcc_vol, rglt->avcc_vol); if (ret < 0) { SND_LOG_ERR(HLOG, "set avcc voltage failed\n"); ret = -EFAULT; goto err_regulator_set_vol_avcc; } ret = regulator_enable(rglt->avcc); if (ret < 0) { SND_LOG_ERR(HLOG, "enable avcc failed\n"); ret = -EFAULT; goto err_regulator_enable_avcc; } return 0; err_regulator_enable_avcc: err_regulator_set_vol_avcc: if (rglt->avcc) regulator_put(rglt->avcc); return ret; } static void snd_sunxi_regulator_exit(struct platform_device *pdev, struct sunxi_regulator *rglt) { SND_LOG_DEBUG(HLOG, "\n"); if (rglt->avcc) if (!IS_ERR_OR_NULL(rglt->avcc)) { regulator_disable(rglt->avcc); regulator_put(rglt->avcc); } } static int snd_sunxi_regulator_enable(struct platform_device *pdev, struct sunxi_regulator *rglt) { int ret; SND_LOG_DEBUG(HLOG, "\n"); if (rglt->avcc) if (!IS_ERR_OR_NULL(rglt->avcc)) { ret = regulator_enable(rglt->avcc); if (ret) { SND_LOG_ERR(HLOG, "enable avcc failed\n"); return -1; } } return 0; } static void snd_sunxi_regulator_disable(struct platform_device *pdev, struct sunxi_regulator *rglt) { SND_LOG_DEBUG(HLOG, "\n"); if (rglt->avcc) if (!IS_ERR_OR_NULL(rglt->avcc)) { regulator_disable(rglt->avcc); } } static void snd_sunxi_dts_params_init(struct platform_device *pdev, struct sunxi_dts *dts) { int ret = 0; unsigned int temp_val; struct device_node *np = pdev->dev.of_node; SND_LOG_DEBUG(HLOG, "\n"); /* lineout volume */ ret = of_property_read_u32(np, "lineout-vol", &temp_val); if (ret < 0) { dts->lineout_vol = 0; } else { dts->lineout_vol = temp_val; } /* mic gain for capturing */ ret = of_property_read_u32(np, "mic1gain", &temp_val); if (ret < 0) { dts->mic1gain = 31; } else { dts->mic1gain = temp_val; } ret = of_property_read_u32(np, "mic2gain", &temp_val); if (ret < 0) { dts->mic2gain = 31; } else { dts->mic2gain = temp_val; } ret = of_property_read_u32(np, "adcdelaytime", &temp_val); if (ret < 0) { dts->adc_dtime = 0; } else { switch (temp_val) { case 0: case 5: case 10: case 20: case 30: dts->adc_dtime = temp_val; break; default: SND_LOG_WARN(HLOG, "adc delay time supoort only 0,5,10,20,30ms\n"); dts->adc_dtime = 0; break; } } /* lineout & mic1 & mic2 diff or single */ dts->lineout_single = of_property_read_bool(np, "lineout-single"); dts->mic1_single = of_property_read_bool(np, "mic1-single"); dts->mic2_single = of_property_read_bool(np, "mic2-single"); /* tx_hub */ dts->tx_hub_en = of_property_read_bool(np, "tx-hub-en"); /* components func -> rx_sync */ dts->rx_sync_en = of_property_read_bool(np, "rx-sync-en"); if (dts->rx_sync_en) { dts->rx_sync_ctl = false; dts->rx_sync_domain = RX_SYNC_SYS_DOMAIN; dts->rx_sync_id = sunxi_rx_sync_probe(dts->rx_sync_domain); if (dts->rx_sync_id < 0) { SND_LOG_ERR(HLOG, "sunxi_rx_sync_probe failed\n"); } else { SND_LOG_DEBUG(HLOG, "sunxi_rx_sync_probe successful. domain=%d, id=%d\n", dts->rx_sync_domain, dts->rx_sync_id); } } SND_LOG_DEBUG(HLOG, "lineout vol -> %u\n", dts->lineout_vol); SND_LOG_DEBUG(HLOG, "mic1 gain -> %u\n", dts->mic1gain); SND_LOG_DEBUG(HLOG, "mic2 gain -> %u\n", dts->mic2gain); } static int sunxi_internal_codec_dev_probe(struct platform_device *pdev) { int ret; struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct sunxi_codec *codec; struct sunxi_mem *mem; SND_LOG_DEBUG(HLOG, "\n"); /* sunxi codec info */ codec = devm_kzalloc(dev, sizeof(struct sunxi_codec), GFP_KERNEL); if (IS_ERR_OR_NULL(codec)) { SND_LOG_ERR(HLOG, "alloc sunxi_codec failed\n"); ret = -ENOMEM; goto err_devm_kzalloc; } dev_set_drvdata(dev, codec); codec->pdev = pdev; /* for kernel source */ mem = &codec->mem; mem->dev_name = DRV_NAME; mem->res = &g_res; mem->regmap_config = &g_regmap_config; ret = snd_sunxi_mem_init(pdev, mem); if (ret) { SND_LOG_ERR(HLOG, "mem init failed\n"); ret = -ENOMEM; goto err_mem_init; } ret = snd_sunxi_clk_init(pdev, &codec->clk); if (ret) { SND_LOG_ERR(HLOG, "clk init failed\n"); ret = -ENOMEM; goto err_clk_init; } ret = snd_sunxi_regulator_init(pdev, &codec->rglt); if (ret) { SND_LOG_ERR(HLOG, "regulator init failed\n"); ret = -ENOMEM; goto err_regulator_init; } codec->pa_config = snd_sunxi_pa_pin_init(pdev, &codec->pa_pin_max); snd_sunxi_dts_params_init(pdev, &codec->dts); /* alsa codec register */ ret = snd_soc_register_codec(dev, &sunxi_internal_codec_dev, &sunxi_internal_codec_dai, 1); if (ret) { SND_LOG_ERR(HLOG, "internal-codec component register failed\n"); ret = -ENOMEM; goto err_register_component; } SND_LOG_DEBUG(HLOG, "register internal-codec codec success\n"); return 0; err_register_component: snd_sunxi_regulator_exit(pdev, &codec->rglt); err_regulator_init: snd_sunxi_clk_exit(pdev, &codec->clk); err_clk_init: snd_sunxi_mem_exit(pdev, mem); err_mem_init: devm_kfree(dev, codec); err_devm_kzalloc: of_node_put(np); return ret; } static int sunxi_internal_codec_dev_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = pdev->dev.of_node; struct sunxi_codec *codec = dev_get_drvdata(&pdev->dev); struct sunxi_dts *dts = &codec->dts; SND_LOG_DEBUG(HLOG, "\n"); /* remove components */ if (dts->rx_sync_en) sunxi_rx_sync_remove(dts->rx_sync_domain); /* alsa codec unregister */ snd_soc_unregister_codec(&pdev->dev); /* for kernel source */ snd_sunxi_clk_exit(pdev, &codec->clk); snd_sunxi_mem_exit(pdev, &codec->mem); snd_sunxi_regulator_exit(pdev, &codec->rglt); snd_sunxi_pa_pin_exit(pdev, codec->pa_config, codec->pa_pin_max); /* sunxi codec custom info free */ devm_kfree(dev, codec); of_node_put(np); SND_LOG_DEBUG(HLOG, "unregister internal-codec codec success\n"); return 0; } static const struct of_device_id sunxi_internal_codec_of_match[] = { { .compatible = "allwinner," DRV_NAME, }, {}, }; MODULE_DEVICE_TABLE(of, sunxi_internal_codec_of_match); static struct platform_driver sunxi_internal_codec_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = sunxi_internal_codec_of_match, }, .probe = sunxi_internal_codec_dev_probe, .remove = sunxi_internal_codec_dev_remove, }; module_platform_driver(sunxi_internal_codec_driver); MODULE_AUTHOR("Dby@allwinnertech.com"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("sunxi soundcard codec of internal-codec");