diff options
Diffstat (limited to 'sound/soc/sunxi/sun4i-codec.c')
| -rw-r--r-- | sound/soc/sunxi/sun4i-codec.c | 1157 |
1 files changed, 953 insertions, 204 deletions
diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index 150069987c0c..f4e22af594fa 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -1,21 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright 2014 Emilio López <emilio@elopez.com.ar> * Copyright 2014 Jon Smirl <jonsmirl@gmail.com> * Copyright 2015 Maxime Ripard <maxime.ripard@free-electrons.com> * Copyright 2015 Adam Sampson <ats@offog.org> * Copyright 2016 Chen-Yu Tsai <wens@csie.org> + * Copyright 2018 Mesih Kilinc <mesihkilinc@gmail.com> * * Based on the Allwinner SDK driver, released under the GPL. - * - * 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. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/init.h> @@ -24,16 +16,13 @@ #include <linux/platform_device.h> #include <linux/delay.h> #include <linux/slab.h> -#include <linux/of.h> -#include <linux/of_address.h> -#include <linux/of_device.h> -#include <linux/of_platform.h> #include <linux/clk.h> #include <linux/regmap.h> #include <linux/reset.h> #include <linux/gpio/consumer.h> #include <sound/core.h> +#include <sound/jack.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -64,9 +53,20 @@ #define SUN4I_CODEC_DAC_ACTL_DACAENR (31) #define SUN4I_CODEC_DAC_ACTL_DACAENL (30) #define SUN4I_CODEC_DAC_ACTL_MIXEN (29) +#define SUN4I_CODEC_DAC_ACTL_LNG (26) +#define SUN4I_CODEC_DAC_ACTL_FMG (23) +#define SUN4I_CODEC_DAC_ACTL_MICG (20) +#define SUN4I_CODEC_DAC_ACTL_LLNS (19) +#define SUN4I_CODEC_DAC_ACTL_RLNS (18) +#define SUN4I_CODEC_DAC_ACTL_LFMS (17) +#define SUN4I_CODEC_DAC_ACTL_RFMS (16) #define SUN4I_CODEC_DAC_ACTL_LDACLMIXS (15) #define SUN4I_CODEC_DAC_ACTL_RDACRMIXS (14) #define SUN4I_CODEC_DAC_ACTL_LDACRMIXS (13) +#define SUN4I_CODEC_DAC_ACTL_MIC1LS (12) +#define SUN4I_CODEC_DAC_ACTL_MIC1RS (11) +#define SUN4I_CODEC_DAC_ACTL_MIC2LS (10) +#define SUN4I_CODEC_DAC_ACTL_MIC2RS (9) #define SUN4I_CODEC_DAC_ACTL_DACPAS (8) #define SUN4I_CODEC_DAC_ACTL_MIXPAS (7) #define SUN4I_CODEC_DAC_ACTL_PA_MUTE (6) @@ -94,8 +94,11 @@ #define SUN4I_CODEC_ADC_ACTL_PREG1EN (29) #define SUN4I_CODEC_ADC_ACTL_PREG2EN (28) #define SUN4I_CODEC_ADC_ACTL_VMICEN (27) +#define SUN4I_CODEC_ADC_ACTL_PREG1 (25) +#define SUN4I_CODEC_ADC_ACTL_PREG2 (23) #define SUN4I_CODEC_ADC_ACTL_VADCG (20) #define SUN4I_CODEC_ADC_ACTL_ADCIS (17) +#define SUN4I_CODEC_ADC_ACTL_LNPREG (13) #define SUN4I_CODEC_ADC_ACTL_PA_EN (4) #define SUN4I_CODEC_ADC_ACTL_DDE (3) #define SUN4I_CODEC_ADC_DEBUG (0x2c) @@ -110,6 +113,9 @@ /* Microphone controls (sun7i only) */ #define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c) +#define SUN7I_CODEC_AC_MIC_PHONE_CAL_PREG1 (29) +#define SUN7I_CODEC_AC_MIC_PHONE_CAL_PREG2 (26) + /* * sun6i specific registers * @@ -222,8 +228,103 @@ #define SUN8I_H3_CODEC_DAC_DBG (0x48) #define SUN8I_H3_CODEC_ADC_DBG (0x4c) +/* H616 specific registers */ +#define SUN50I_H616_CODEC_DAC_FIFOC (0x10) + +#define SUN50I_DAC_FIFO_STA (0x14) +#define SUN50I_DAC_TXE_INT (3) +#define SUN50I_DAC_TXU_INT (2) +#define SUN50I_DAC_TXO_INT (1) + +#define SUN50I_DAC_CNT (0x24) +#define SUN50I_DAC_DG_REG (0x28) +#define SUN50I_DAC_DAP_CTL (0xf0) + +#define SUN50I_H616_DAC_AC_DAC_REG (0x310) +#define SUN50I_H616_DAC_LEN (15) +#define SUN50I_H616_DAC_REN (14) +#define SUN50I_H616_LINEOUTL_EN (13) +#define SUN50I_H616_LMUTE (12) +#define SUN50I_H616_LINEOUTR_EN (11) +#define SUN50I_H616_RMUTE (10) +#define SUN50I_H616_RSWITCH (9) +#define SUN50I_H616_RAMPEN (8) +#define SUN50I_H616_LINEOUTL_SEL (6) +#define SUN50I_H616_LINEOUTR_SEL (5) +#define SUN50I_H616_LINEOUT_VOL (0) + +#define SUN50I_H616_DAC_AC_MIXER_REG (0x314) +#define SUN50I_H616_LMIX_LDAC (21) +#define SUN50I_H616_LMIX_RDAC (20) +#define SUN50I_H616_RMIX_RDAC (17) +#define SUN50I_H616_RMIX_LDAC (16) +#define SUN50I_H616_LMIXEN (11) +#define SUN50I_H616_RMIXEN (10) + +#define SUN50I_H616_DAC_AC_RAMP_REG (0x31c) +#define SUN50I_H616_RAMP_STEP (4) +#define SUN50I_H616_RDEN (0) + /* TODO H3 DAP (Digital Audio Processing) bits */ +#define SUN4I_DMA_MAX_BURST (8) + +/* suniv specific registers */ + +#define SUNIV_DMA_MAX_BURST (4) + +/* Codec DAC digital controls and FIFO registers */ +#define SUNIV_CODEC_ADC_FIFOC (0x10) +#define SUNIV_CODEC_ADC_FIFOC_EN_AD (28) +#define SUNIV_CODEC_ADC_FIFOS (0x14) +#define SUNIV_CODEC_ADC_RXDATA (0x18) + +/* Output mixer and gain controls */ +#define SUNIV_CODEC_OM_DACA_CTRL (0x20) +#define SUNIV_CODEC_OM_DACA_CTRL_DACAREN (31) +#define SUNIV_CODEC_OM_DACA_CTRL_DACALEN (30) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXEN (29) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXEN (28) +#define SUNIV_CODEC_OM_DACA_CTRL_RHPPAMUTE (27) +#define SUNIV_CODEC_OM_DACA_CTRL_LHPPAMUTE (26) +#define SUNIV_CODEC_OM_DACA_CTRL_RHPIS (25) +#define SUNIV_CODEC_OM_DACA_CTRL_LHPIS (24) +#define SUNIV_CODEC_OM_DACA_CTRL_HPCOM_CTL (22) +#define SUNIV_CODEC_OM_DACA_CTRL_COMPTEN (21) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_MICIN (20) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_LINEIN (19) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_FMIN (18) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_RDAC (17) +#define SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_LDAC (16) +#define SUNIV_CODEC_OM_DACA_CTRL_HPPAEN (15) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_MICIN (12) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_LINEIN (11) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_FMIN (10) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_LDAC (9) +#define SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_RDAC (8) +#define SUNIV_CODEC_OM_DACA_CTRL_LTLNMUTE (7) +#define SUNIV_CODEC_OM_DACA_CTRL_RTLNMUTE (6) +#define SUNIV_CODEC_OM_DACA_CTRL_HPVOL (0) + +/* Analog Input Mixer controls */ +#define SUNIV_CODEC_ADC_ACTL (0x24) +#define SUNIV_CODEC_ADC_ADCEN (31) +#define SUNIV_CODEC_ADC_MICG (24) +#define SUNIV_CODEC_ADC_LINEINVOL (21) +#define SUNIV_CODEC_ADC_ADCG (16) +#define SUNIV_CODEC_ADC_ADCMIX_MIC (13) +#define SUNIV_CODEC_ADC_ADCMIX_FMINL (12) +#define SUNIV_CODEC_ADC_ADCMIX_FMINR (11) +#define SUNIV_CODEC_ADC_ADCMIX_LINEIN (10) +#define SUNIV_CODEC_ADC_ADCMIX_LOUT (9) +#define SUNIV_CODEC_ADC_ADCMIX_ROUT (8) +#define SUNIV_CODEC_ADC_PASPEEDSELECT (7) +#define SUNIV_CODEC_ADC_FMINVOL (4) +#define SUNIV_CODEC_ADC_MICAMPEN (3) +#define SUNIV_CODEC_ADC_MICBOOST (0) + +#define SUNIV_CODEC_ADC_DBG (0x4c) + struct sun4i_codec { struct device *dev; struct regmap *regmap; @@ -231,9 +332,12 @@ struct sun4i_codec { struct clk *clk_module; struct reset_control *rst; struct gpio_desc *gpio_pa; + struct gpio_desc *gpio_hp; /* ADC_FIFOC register is at different offset on different SoCs */ struct regmap_field *reg_adc_fifoc; + /* DAC_FIFOC register is at different offset on different SoCs */ + struct regmap_field *reg_dac_fifoc; struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; @@ -242,43 +346,39 @@ struct sun4i_codec { static void sun4i_codec_start_playback(struct sun4i_codec *scodec) { /* Flush TX FIFO */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), - BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); + regmap_field_set_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); /* Enable DAC DRQ */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), - BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN)); + regmap_field_set_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN)); } static void sun4i_codec_stop_playback(struct sun4i_codec *scodec) { /* Disable DAC DRQ */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN), - 0); + regmap_field_clear_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN)); } static void sun4i_codec_start_capture(struct sun4i_codec *scodec) { /* Enable ADC DRQ */ - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); + regmap_field_set_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); } static void sun4i_codec_stop_capture(struct sun4i_codec *scodec) { /* Disable ADC DRQ */ - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0); + regmap_field_clear_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); } static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); switch (cmd) { @@ -310,13 +410,12 @@ static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd, static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); /* Flush RX FIFO */ - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH), + regmap_field_set_bits(scodec->reg_adc_fifoc, BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH)); @@ -327,7 +426,7 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, /* * FIXME: Undocumented in the datasheet, but - * Allwinner's code mentions that it is related + * Allwinner's code mentions that it is * related to microphone gain */ if (of_device_is_compatible(scodec->dev->of_node, @@ -346,30 +445,24 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, 0x3 << 8, 0x1 << 8); - /* Fill most significant bits with valid data MSB */ - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE), - BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); - return 0; } static int sun4i_codec_prepare_playback(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); u32 val; /* Flush the TX FIFO */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH), - BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); + regmap_field_set_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH)); /* Set TX FIFO Empty Trigger Level */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL, - 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL); + regmap_field_update_bits(scodec->reg_dac_fifoc, + 0x3f << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL, + 0xf << SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL); if (substream->runtime->rate > 32000) /* Use 64 bits FIR filter */ @@ -378,14 +471,13 @@ static int sun4i_codec_prepare_playback(struct snd_pcm_substream *substream, /* Use 32 bits FIR filter */ val = BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION); - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION), - val); + regmap_field_update_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_FIR_VERSION), + val); /* Send zeros when we have an underrun */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT), - 0); + regmap_field_clear_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_SEND_LASAT)); return 0; }; @@ -482,13 +574,31 @@ static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec, /* Set the number of channels we want to use */ if (params_channels(params) == 1) - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), + regmap_field_set_bits(scodec->reg_adc_fifoc, BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); else - regmap_field_update_bits(scodec->reg_adc_fifoc, - BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), - 0); + regmap_field_clear_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); + + /* Set the number of sample bits to either 16 or 24 bits */ + if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) { + regmap_field_set_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS)); + + regmap_field_clear_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); + + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + } else { + regmap_field_clear_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS)); + + /* Fill most significant bits with valid data MSB */ + regmap_field_set_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); + + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + } return 0; } @@ -500,9 +610,9 @@ static int sun4i_codec_hw_params_playback(struct sun4i_codec *scodec, u32 val; /* Set DAC sample rate */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS, - hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS); + regmap_field_update_bits(scodec->reg_dac_fifoc, + 7 << SUN4I_CODEC_DAC_FIFOC_DAC_FS, + hwrate << SUN4I_CODEC_DAC_FIFOC_DAC_FS); /* Set the number of channels we want to use */ if (params_channels(params) == 1) @@ -510,31 +620,27 @@ static int sun4i_codec_hw_params_playback(struct sun4i_codec *scodec, else val = 0; - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN), - val); + regmap_field_update_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_MONO_EN), + val); /* Set the number of sample bits to either 16 or 24 bits */ if (hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min == 32) { - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), - BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS)); + regmap_field_set_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS)); /* Set TX FIFO mode to padding the LSBs with 0 */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), - 0); + regmap_field_clear_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE)); scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; } else { - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS), - 0); + regmap_field_clear_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS)); /* Set TX FIFO mode to repeat the MSB */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE), - BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE)); + regmap_field_set_bits(scodec->reg_dac_fifoc, + BIT(SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE)); scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; } @@ -546,7 +652,7 @@ static int sun4i_codec_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); unsigned long clk_freq; int ret, hwrate; @@ -574,16 +680,15 @@ static int sun4i_codec_hw_params(struct snd_pcm_substream *substream, static int sun4i_codec_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); /* * Stop issuing DRQ when we have room for less than 16 samples * in our TX FIFO */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_DAC_FIFOC, - 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT, - 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT); + regmap_field_set_bits(scodec->reg_dac_fifoc, + 3 << SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT); return clk_prepare_enable(scodec->clk_module); } @@ -591,7 +696,7 @@ static int sun4i_codec_startup(struct snd_pcm_substream *substream, static void sun4i_codec_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sun4i_codec *scodec = snd_soc_card_get_drvdata(rtd->card); clk_disable_unprepare(scodec->clk_module); @@ -605,6 +710,13 @@ static const struct snd_soc_dai_ops sun4i_codec_dai_ops = { .prepare = sun4i_codec_prepare, }; +#define SUN4I_CODEC_RATES ( \ + SNDRV_PCM_RATE_8000_48000 | \ + SNDRV_PCM_RATE_12000 | \ + SNDRV_PCM_RATE_24000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + static struct snd_soc_dai_driver sun4i_codec_dai = { .name = "Codec", .ops = &sun4i_codec_dai_ops, @@ -614,9 +726,7 @@ static struct snd_soc_dai_driver sun4i_codec_dai = { .channels_max = 2, .rate_min = 8000, .rate_max = 192000, - .rates = SNDRV_PCM_RATE_8000_48000 | - SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000, + .rates = SUN4I_CODEC_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, .sig_bits = 24, @@ -626,11 +736,8 @@ static struct snd_soc_dai_driver sun4i_codec_dai = { .channels_min = 1, .channels_max = 2, .rate_min = 8000, - .rate_max = 192000, - .rates = SNDRV_PCM_RATE_8000_48000 | - SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_192000 | - SNDRV_PCM_RATE_KNOT, + .rate_max = 48000, + .rates = SUN4I_CODEC_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE, .sig_bits = 24, @@ -643,23 +750,91 @@ static const struct snd_kcontrol_new sun4i_codec_pa_mute = SUN4I_CODEC_DAC_ACTL_PA_MUTE, 1, 0); static DECLARE_TLV_DB_SCALE(sun4i_codec_pa_volume_scale, -6300, 100, 1); +static DECLARE_TLV_DB_SCALE(sun4i_codec_linein_loopback_gain_scale, -150, 150, + 0); +static DECLARE_TLV_DB_SCALE(sun4i_codec_linein_preamp_gain_scale, -1200, 300, + 0); +static DECLARE_TLV_DB_SCALE(sun4i_codec_fmin_loopback_gain_scale, -450, 150, + 0); +static DECLARE_TLV_DB_SCALE(sun4i_codec_micin_loopback_gain_scale, -450, 150, + 0); +static DECLARE_TLV_DB_RANGE(sun4i_codec_micin_preamp_gain_scale, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_SCALE_ITEM(3500, 300, 0)); +static DECLARE_TLV_DB_RANGE(sun7i_codec_micin_preamp_gain_scale, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0)); static const struct snd_kcontrol_new sun4i_codec_controls[] = { SOC_SINGLE_TLV("Power Amplifier Volume", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_PA_VOL, 0x3F, 0, sun4i_codec_pa_volume_scale), + SOC_SINGLE_TLV("Line Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LNG, 1, 0, + sun4i_codec_linein_loopback_gain_scale), + SOC_SINGLE_TLV("Line Boost Volume", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_LNPREG, 7, 0, + sun4i_codec_linein_preamp_gain_scale), + SOC_SINGLE_TLV("FM Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_FMG, 3, 0, + sun4i_codec_fmin_loopback_gain_scale), + SOC_SINGLE_TLV("Mic Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MICG, 7, 0, + sun4i_codec_micin_loopback_gain_scale), + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_PREG1, 3, 0, + sun4i_codec_micin_preamp_gain_scale), + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_PREG2, 3, 0, + sun4i_codec_micin_preamp_gain_scale), }; -static const struct snd_kcontrol_new sun4i_codec_left_mixer_controls[] = { - SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, - SUN4I_CODEC_DAC_ACTL_LDACLMIXS, 1, 0), +static const struct snd_kcontrol_new sun7i_codec_controls[] = { + SOC_SINGLE_TLV("Power Amplifier Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_PA_VOL, 0x3F, 0, + sun4i_codec_pa_volume_scale), + SOC_SINGLE_TLV("Line Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LNG, 1, 0, + sun4i_codec_linein_loopback_gain_scale), + SOC_SINGLE_TLV("Line Boost Volume", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_LNPREG, 7, 0, + sun4i_codec_linein_preamp_gain_scale), + SOC_SINGLE_TLV("FM Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_FMG, 3, 0, + sun4i_codec_fmin_loopback_gain_scale), + SOC_SINGLE_TLV("Mic Playback Volume", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MICG, 7, 0, + sun4i_codec_micin_loopback_gain_scale), + SOC_SINGLE_TLV("Mic1 Boost Volume", SUN7I_CODEC_AC_MIC_PHONE_CAL, + SUN7I_CODEC_AC_MIC_PHONE_CAL_PREG1, 7, 0, + sun7i_codec_micin_preamp_gain_scale), + SOC_SINGLE_TLV("Mic2 Boost Volume", SUN7I_CODEC_AC_MIC_PHONE_CAL, + SUN7I_CODEC_AC_MIC_PHONE_CAL_PREG2, 7, 0, + sun7i_codec_micin_preamp_gain_scale), }; -static const struct snd_kcontrol_new sun4i_codec_right_mixer_controls[] = { - SOC_DAPM_SINGLE("Right DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, - SUN4I_CODEC_DAC_ACTL_RDACRMIXS, 1, 0), - SOC_DAPM_SINGLE("Left DAC Playback Switch", SUN4I_CODEC_DAC_ACTL, +static const struct snd_kcontrol_new sun4i_codec_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Mixer Left DAC Playback Switch", + SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_LDACLMIXS, + 1, 0), + SOC_DAPM_SINGLE("Right Mixer Right DAC Playback Switch", + SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_RDACRMIXS, + 1, 0), + SOC_DAPM_SINGLE("Right Mixer Left DAC Playback Switch", + SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_LDACRMIXS, 1, 0), + SOC_DAPM_DOUBLE("Line Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LLNS, + SUN4I_CODEC_DAC_ACTL_RLNS, 1, 0), + SOC_DAPM_DOUBLE("FM Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_LFMS, + SUN4I_CODEC_DAC_ACTL_RFMS, 1, 0), + SOC_DAPM_DOUBLE("Mic1 Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MIC1LS, + SUN4I_CODEC_DAC_ACTL_MIC1RS, 1, 0), + SOC_DAPM_DOUBLE("Mic2 Playback Switch", SUN4I_CODEC_DAC_ACTL, + SUN4I_CODEC_DAC_ACTL_MIC2LS, + SUN4I_CODEC_DAC_ACTL_MIC2RS, 1, 0), }; static const struct snd_kcontrol_new sun4i_codec_pa_mixer_controls[] = { @@ -694,11 +869,11 @@ static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets[] = { /* Mixers */ SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, - sun4i_codec_left_mixer_controls, - ARRAY_SIZE(sun4i_codec_left_mixer_controls)), + sun4i_codec_mixer_controls, + ARRAY_SIZE(sun4i_codec_mixer_controls)), SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, - sun4i_codec_right_mixer_controls, - ARRAY_SIZE(sun4i_codec_right_mixer_controls)), + sun4i_codec_mixer_controls, + ARRAY_SIZE(sun4i_codec_mixer_controls)), /* Global Mixer Enable */ SND_SOC_DAPM_SUPPLY("Mixer Enable", SUN4I_CODEC_DAC_ACTL, @@ -711,6 +886,8 @@ static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets[] = { /* Mic Pre-Amplifiers */ SND_SOC_DAPM_PGA("MIC1 Pre-Amplifier", SUN4I_CODEC_ADC_ACTL, SUN4I_CODEC_ADC_ACTL_PREG1EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("MIC2 Pre-Amplifier", SUN4I_CODEC_ADC_ACTL, + SUN4I_CODEC_ADC_ACTL_PREG2EN, 0, NULL, 0), /* Power Amplifier */ SND_SOC_DAPM_MIXER("Power Amplifier", SUN4I_CODEC_ADC_ACTL, @@ -720,7 +897,12 @@ static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets[] = { SND_SOC_DAPM_SWITCH("Power Amplifier Mute", SND_SOC_NOPM, 0, 0, &sun4i_codec_pa_mute), + SND_SOC_DAPM_INPUT("Line Right"), + SND_SOC_DAPM_INPUT("Line Left"), + SND_SOC_DAPM_INPUT("FM Right"), + SND_SOC_DAPM_INPUT("FM Left"), SND_SOC_DAPM_INPUT("Mic1"), + SND_SOC_DAPM_INPUT("Mic2"), SND_SOC_DAPM_OUTPUT("HP Right"), SND_SOC_DAPM_OUTPUT("HP Left"), @@ -737,12 +919,20 @@ static const struct snd_soc_dapm_route sun4i_codec_codec_dapm_routes[] = { /* Right Mixer Routes */ { "Right Mixer", NULL, "Mixer Enable" }, - { "Right Mixer", "Left DAC Playback Switch", "Left DAC" }, - { "Right Mixer", "Right DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Mixer Left DAC Playback Switch", "Left DAC" }, + { "Right Mixer", "Right Mixer Right DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "Line Playback Switch", "Line Right" }, + { "Right Mixer", "FM Playback Switch", "FM Right" }, + { "Right Mixer", "Mic1 Playback Switch", "MIC1 Pre-Amplifier" }, + { "Right Mixer", "Mic2 Playback Switch", "MIC2 Pre-Amplifier" }, /* Left Mixer Routes */ { "Left Mixer", NULL, "Mixer Enable" }, - { "Left Mixer", "Left DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Mixer Left DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "Line Playback Switch", "Line Left" }, + { "Left Mixer", "FM Playback Switch", "FM Left" }, + { "Left Mixer", "Mic1 Playback Switch", "MIC1 Pre-Amplifier" }, + { "Left Mixer", "Mic2 Playback Switch", "MIC2 Pre-Amplifier" }, /* Power Amplifier Routes */ { "Power Amplifier", "Mixer Playback Switch", "Left Mixer" }, @@ -760,17 +950,36 @@ static const struct snd_soc_dapm_route sun4i_codec_codec_dapm_routes[] = { { "Right ADC", NULL, "MIC1 Pre-Amplifier" }, { "MIC1 Pre-Amplifier", NULL, "Mic1"}, { "Mic1", NULL, "VMIC" }, + + /* Mic2 Routes */ + { "Left ADC", NULL, "MIC2 Pre-Amplifier" }, + { "Right ADC", NULL, "MIC2 Pre-Amplifier" }, + { "MIC2 Pre-Amplifier", NULL, "Mic2"}, + { "Mic2", NULL, "VMIC" }, }; -static struct snd_soc_codec_driver sun4i_codec_codec = { - .component_driver = { - .controls = sun4i_codec_controls, - .num_controls = ARRAY_SIZE(sun4i_codec_controls), - .dapm_widgets = sun4i_codec_codec_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets), - .dapm_routes = sun4i_codec_codec_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes), - }, +static const struct snd_soc_component_driver sun4i_codec_codec = { + .controls = sun4i_codec_controls, + .num_controls = ARRAY_SIZE(sun4i_codec_controls), + .dapm_widgets = sun4i_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets), + .dapm_routes = sun4i_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct snd_soc_component_driver sun7i_codec_codec = { + .controls = sun7i_codec_controls, + .num_controls = ARRAY_SIZE(sun7i_codec_controls), + .dapm_widgets = sun4i_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun4i_codec_codec_dapm_widgets), + .dapm_routes = sun4i_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun4i_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, }; /*** sun6i Codec ***/ @@ -1068,15 +1277,16 @@ static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes[] = { { "Right ADC", NULL, "Right ADC Mixer" }, }; -static struct snd_soc_codec_driver sun6i_codec_codec = { - .component_driver = { - .controls = sun6i_codec_codec_widgets, - .num_controls = ARRAY_SIZE(sun6i_codec_codec_widgets), - .dapm_widgets = sun6i_codec_codec_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(sun6i_codec_codec_dapm_widgets), - .dapm_routes = sun6i_codec_codec_dapm_routes, - .num_dapm_routes = ARRAY_SIZE(sun6i_codec_codec_dapm_routes), - }, +static const struct snd_soc_component_driver sun6i_codec_codec = { + .controls = sun6i_codec_codec_widgets, + .num_controls = ARRAY_SIZE(sun6i_codec_codec_widgets), + .dapm_widgets = sun6i_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun6i_codec_codec_dapm_widgets), + .dapm_routes = sun6i_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sun6i_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, }; /* sun8i A23 codec */ @@ -1096,20 +1306,246 @@ static const struct snd_soc_dapm_widget sun8i_a23_codec_codec_widgets[] = { }; -static struct snd_soc_codec_driver sun8i_a23_codec_codec = { - .component_driver = { - .controls = sun8i_a23_codec_codec_controls, - .num_controls = ARRAY_SIZE(sun8i_a23_codec_codec_controls), - .dapm_widgets = sun8i_a23_codec_codec_widgets, - .num_dapm_widgets = ARRAY_SIZE(sun8i_a23_codec_codec_widgets), - }, +static const struct snd_soc_component_driver sun8i_a23_codec_codec = { + .controls = sun8i_a23_codec_codec_controls, + .num_controls = ARRAY_SIZE(sun8i_a23_codec_codec_controls), + .dapm_widgets = sun8i_a23_codec_codec_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun8i_a23_codec_codec_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +/*suniv F1C100s codec */ + +/* headphone controls */ +static const char * const suniv_codec_hp_src_enum_text[] = { + "DAC", "Mixer", +}; + +static SOC_ENUM_DOUBLE_DECL(suniv_codec_hp_src_enum, + SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LHPIS, + SUNIV_CODEC_OM_DACA_CTRL_RHPIS, + suniv_codec_hp_src_enum_text); + +static const struct snd_kcontrol_new suniv_codec_hp_src[] = { + SOC_DAPM_ENUM("Headphone Source Playback Route", + suniv_codec_hp_src_enum), +}; + +/* mixer controls */ +static const struct snd_kcontrol_new suniv_codec_adc_mixer_controls[] = { + SOC_DAPM_SINGLE("Right Out Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_ROUT, 1, 0), + SOC_DAPM_SINGLE("Left Out Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_LOUT, 1, 0), + SOC_DAPM_SINGLE("Line In Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_LINEIN, 1, 0), + SOC_DAPM_SINGLE("Right FM In Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_FMINR, 1, 0), + SOC_DAPM_SINGLE("Left FM In Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_FMINL, 1, 0), + SOC_DAPM_SINGLE("Mic Capture Switch", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCMIX_MIC, 1, 0), +}; + +static const struct snd_kcontrol_new suniv_codec_dac_lmixer_controls[] = { + SOC_DAPM_SINGLE("Right DAC Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_RDAC, 1, 0), + SOC_DAPM_SINGLE("Left DAC Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_LDAC, 1, 0), + SOC_DAPM_SINGLE("FM In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_FMIN, 1, 0), + SOC_DAPM_SINGLE("Line In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_LINEIN, 1, 0), + SOC_DAPM_SINGLE("Mic In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXMUTE_MICIN, 1, 0), +}; + +static const struct snd_kcontrol_new suniv_codec_dac_rmixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_LDAC, 1, 0), + SOC_DAPM_SINGLE("Right DAC Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_RDAC, 1, 0), + SOC_DAPM_SINGLE("FM In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_FMIN, 1, 0), + SOC_DAPM_SINGLE("Line In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_LINEIN, 1, 0), + SOC_DAPM_SINGLE("Mic In Playback Switch", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXMUTE_MICIN, 1, 0), +}; + +static const DECLARE_TLV_DB_SCALE(suniv_codec_dvol_scale, -7308, 116, 0); +static const DECLARE_TLV_DB_SCALE(suniv_codec_hp_vol_scale, -6300, 100, 1); +static const DECLARE_TLV_DB_SCALE(suniv_codec_out_mixer_pregain_scale, + -450, 150, 0); + +static const DECLARE_TLV_DB_RANGE(suniv_codec_mic_gain_scale, + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), +); + +static const struct snd_kcontrol_new suniv_codec_codec_widgets[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1, + suniv_codec_dvol_scale), + SOC_SINGLE_TLV("Headphone Playback Volume", + SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_HPVOL, 0x3f, 0, + suniv_codec_hp_vol_scale), + SOC_DOUBLE("Headphone Playback Switch", + SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LHPPAMUTE, + SUNIV_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0), + SOC_SINGLE_TLV("Line In Playback Volume", + SUNIV_CODEC_ADC_ACTL, SUNIV_CODEC_ADC_LINEINVOL, + 0x7, 0, suniv_codec_out_mixer_pregain_scale), + SOC_SINGLE_TLV("FM In Playback Volume", + SUNIV_CODEC_ADC_ACTL, SUNIV_CODEC_ADC_FMINVOL, + 0x7, 0, suniv_codec_out_mixer_pregain_scale), + SOC_SINGLE_TLV("Mic In Playback Volume", + SUNIV_CODEC_ADC_ACTL, SUNIV_CODEC_ADC_MICG, + 0x7, 0, suniv_codec_out_mixer_pregain_scale), + + /* Microphone Amp boost gains */ + SOC_SINGLE_TLV("Mic Boost Volume", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_MICBOOST, 0x7, 0, + suniv_codec_mic_gain_scale), + SOC_SINGLE_TLV("ADC Capture Volume", + SUNIV_CODEC_ADC_ACTL, SUNIV_CODEC_ADC_ADCG, + 0x7, 0, suniv_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget suniv_codec_codec_dapm_widgets[] = { + /* Microphone inputs */ + SND_SOC_DAPM_INPUT("MIC"), + + /* Microphone Bias */ + /* deleted: HBIAS, MBIAS */ + + /* Mic input path */ + SND_SOC_DAPM_PGA("Mic Amplifier", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_MICAMPEN, 0, NULL, 0), + + /* Line In */ + SND_SOC_DAPM_INPUT("LINEIN"), + + /* FM In */ + SND_SOC_DAPM_INPUT("FMINR"), + SND_SOC_DAPM_INPUT("FMINL"), + + /* Digital parts of the ADCs */ + SND_SOC_DAPM_SUPPLY("ADC Enable", SUNIV_CODEC_ADC_FIFOC, + SUNIV_CODEC_ADC_FIFOC_EN_AD, 0, + NULL, 0), + + /* Analog parts of the ADCs */ + SND_SOC_DAPM_ADC("ADC", "Codec Capture", SUNIV_CODEC_ADC_ACTL, + SUNIV_CODEC_ADC_ADCEN, 0), + + /* ADC Mixers */ + SOC_MIXER_ARRAY("ADC Mixer", SUNIV_CODEC_ADC_ACTL, + SND_SOC_NOPM, 0, + suniv_codec_adc_mixer_controls), + + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_EN_DA, 0, + NULL, 0), + + /* Analog parts of the DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", + SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_DACALEN, 0), + SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", + SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_DACAREN, 0), + + /* Mixers */ + SOC_MIXER_ARRAY("Left Mixer", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_LMIXEN, 0, + suniv_codec_dac_lmixer_controls), + SOC_MIXER_ARRAY("Right Mixer", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_RMIXEN, 0, + suniv_codec_dac_rmixer_controls), + + /* Headphone output path */ + SND_SOC_DAPM_MUX("Headphone Source Playback Route", + SND_SOC_NOPM, 0, 0, suniv_codec_hp_src), + SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_HPPAEN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_COMPTEN, 0, NULL, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUNIV_CODEC_OM_DACA_CTRL, + SUNIV_CODEC_OM_DACA_CTRL_HPCOM_CTL, 0x3, 0x3, 0), + SND_SOC_DAPM_OUTPUT("HP"), +}; + +static const struct snd_soc_dapm_route suniv_codec_codec_dapm_routes[] = { + /* DAC Routes */ + { "Left DAC", NULL, "DAC Enable" }, + { "Right DAC", NULL, "DAC Enable" }, + + /* Microphone Routes */ + { "Mic Amplifier", NULL, "MIC"}, + + /* Left Mixer Routes */ + { "Left Mixer", "Right DAC Playback Switch", "Right DAC" }, + { "Left Mixer", "Left DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "FM In Playback Switch", "FMINL" }, + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + { "Left Mixer", "Mic In Playback Switch", "Mic Amplifier" }, + + /* Right Mixer Routes */ + { "Right Mixer", "Left DAC Playback Switch", "Left DAC" }, + { "Right Mixer", "Right DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "FM In Playback Switch", "FMINR" }, + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + { "Right Mixer", "Mic In Playback Switch", "Mic Amplifier" }, + + /* ADC Mixer Routes */ + { "ADC Mixer", "Right Out Capture Switch", "Right Mixer" }, + { "ADC Mixer", "Left Out Capture Switch", "Left Mixer" }, + { "ADC Mixer", "Line In Capture Switch", "LINEIN" }, + { "ADC Mixer", "Right FM In Capture Switch", "FMINR" }, + { "ADC Mixer", "Left FM In Capture Switch", "FMINL" }, + { "ADC Mixer", "Mic Capture Switch", "Mic Amplifier" }, + + /* Headphone Routes */ + { "Headphone Source Playback Route", "DAC", "Left DAC" }, + { "Headphone Source Playback Route", "DAC", "Right DAC" }, + { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, + { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, + { "Headphone Amp", NULL, "Headphone Source Playback Route" }, + { "HP", NULL, "Headphone Amp" }, + { "HPCOM", NULL, "HPCOM Protection" }, + + /* ADC Routes */ + { "ADC", NULL, "ADC Mixer" }, + { "ADC", NULL, "ADC Enable" }, +}; + +static const struct snd_soc_component_driver suniv_codec_codec = { + .controls = suniv_codec_codec_widgets, + .num_controls = ARRAY_SIZE(suniv_codec_codec_widgets), + .dapm_widgets = suniv_codec_codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(suniv_codec_codec_dapm_widgets), + .dapm_routes = suniv_codec_codec_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(suniv_codec_codec_dapm_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, }; static const struct snd_soc_component_driver sun4i_codec_component = { - .name = "sun4i-codec", + .name = "sun4i-codec", + .legacy_dai_naming = 1, +#ifdef CONFIG_DEBUG_FS + .debugfs_prefix = "cpu", +#endif }; -#define SUN4I_CODEC_RATES SNDRV_PCM_RATE_8000_192000 #define SUN4I_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ SNDRV_PCM_FMTBIT_S32_LE) @@ -1124,9 +1560,12 @@ static int sun4i_codec_dai_probe(struct snd_soc_dai *dai) return 0; } +static const struct snd_soc_dai_ops dummy_dai_ops = { + .probe = sun4i_codec_dai_probe, +}; + static struct snd_soc_dai_driver dummy_cpu_dai = { .name = "sun4i-codec-cpu-dai", - .probe = sun4i_codec_dai_probe, .playback = { .stream_name = "Playback", .channels_min = 1, @@ -1143,23 +1582,78 @@ static struct snd_soc_dai_driver dummy_cpu_dai = { .formats = SUN4I_CODEC_FORMATS, .sig_bits = 24, }, + .ops = &dummy_dai_ops, +}; + +static struct snd_soc_jack sun4i_headphone_jack; + +static struct snd_soc_jack_pin sun4i_headphone_jack_pins[] = { + { .pin = "Headphone", .mask = SND_JACK_HEADPHONE }, }; +static struct snd_soc_jack_gpio sun4i_headphone_jack_gpio = { + .name = "hp-det", + .report = SND_JACK_HEADPHONE, + .debounce_time = 150, +}; + +static int sun4i_codec_machine_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); + int ret; + + if (scodec->gpio_hp) { + ret = snd_soc_card_jack_new_pins(card, "Headphone Jack", + SND_JACK_HEADPHONE, + &sun4i_headphone_jack, + sun4i_headphone_jack_pins, + ARRAY_SIZE(sun4i_headphone_jack_pins)); + if (ret) { + dev_err(rtd->dev, + "Headphone jack creation failed: %d\n", ret); + return ret; + } + + sun4i_headphone_jack_gpio.desc = scodec->gpio_hp; + ret = snd_soc_jack_add_gpios(&sun4i_headphone_jack, 1, + &sun4i_headphone_jack_gpio); + + if (ret) { + dev_err(rtd->dev, "Headphone GPIO not added: %d\n", ret); + return ret; + } + } + + return 0; +} + static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, int *num_links) { struct snd_soc_dai_link *link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL); - if (!link) + struct snd_soc_dai_link_component *dlc = devm_kzalloc(dev, + 3 * sizeof(*dlc), GFP_KERNEL); + if (!link || !dlc) return NULL; + link->cpus = &dlc[0]; + link->codecs = &dlc[1]; + link->platforms = &dlc[2]; + + link->num_cpus = 1; + link->num_codecs = 1; + link->num_platforms = 1; + link->name = "cdc"; link->stream_name = "CDC PCM"; - link->codec_dai_name = "Codec"; - link->cpu_dai_name = dev_name(dev); - link->codec_name = dev_name(dev); - link->platform_name = dev_name(dev); + link->codecs->dai_name = "Codec"; + link->cpus->dai_name = dev_name(dev); + link->codecs->name = dev_name(dev); + link->platforms->name = dev_name(dev); link->dai_fmt = SND_SOC_DAIFMT_I2S; + link->init = sun4i_codec_machine_init; *num_links = 1; @@ -1169,11 +1663,20 @@ static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, static int sun4i_codec_spk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - struct sun4i_codec *scodec = snd_soc_card_get_drvdata(w->dapm->card); + struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); + struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); + + gpiod_set_value_cansleep(scodec->gpio_pa, + !!SND_SOC_DAPM_EVENT_ON(event)); - if (scodec->gpio_pa) - gpiod_set_value_cansleep(scodec->gpio_pa, - !!SND_SOC_DAPM_EVENT_ON(event)); + if (SND_SOC_DAPM_EVENT_ON(event)) { + /* + * Need a delay to wait for DAC to push the data. 700ms seems + * to be the best compromise not to feel this delay while + * playing a sound. + */ + msleep(700); + } return 0; } @@ -1200,6 +1703,7 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev) return ERR_PTR(-ENOMEM); card->dev = dev; + card->owner = THIS_MODULE; card->name = "sun4i-codec"; card->dapm_widgets = sun4i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun4i_codec_card_dapm_widgets); @@ -1232,6 +1736,7 @@ static struct snd_soc_card *sun6i_codec_create_card(struct device *dev) return ERR_PTR(-ENOMEM); card->dev = dev; + card->owner = THIS_MODULE; card->name = "A31 Audio Codec"; card->dapm_widgets = sun6i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); @@ -1260,7 +1765,7 @@ static const struct snd_soc_dapm_route sun8i_codec_card_routes[] = { }; static struct snd_soc_aux_dev aux_dev = { - .name = "Codec Analog Controls", + .dlc = COMP_EMPTY(), }; static struct snd_soc_card *sun8i_a23_codec_create_card(struct device *dev) @@ -1272,19 +1777,20 @@ static struct snd_soc_card *sun8i_a23_codec_create_card(struct device *dev) if (!card) return ERR_PTR(-ENOMEM); - aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + aux_dev.dlc.of_node = of_parse_phandle(dev->of_node, "allwinner,codec-analog-controls", 0); - if (!aux_dev.codec_of_node) { + if (!aux_dev.dlc.of_node) { dev_err(dev, "Can't find analog controls for codec.\n"); return ERR_PTR(-EINVAL); - }; + } card->dai_link = sun4i_codec_create_link(dev, &card->num_links); if (!card->dai_link) return ERR_PTR(-ENOMEM); card->dev = dev; + card->owner = THIS_MODULE; card->name = "A23 Audio Codec"; card->dapm_widgets = sun6i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); @@ -1310,19 +1816,20 @@ static struct snd_soc_card *sun8i_h3_codec_create_card(struct device *dev) if (!card) return ERR_PTR(-ENOMEM); - aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + aux_dev.dlc.of_node = of_parse_phandle(dev->of_node, "allwinner,codec-analog-controls", 0); - if (!aux_dev.codec_of_node) { + if (!aux_dev.dlc.of_node) { dev_err(dev, "Can't find analog controls for codec.\n"); return ERR_PTR(-EINVAL); - }; + } card->dai_link = sun4i_codec_create_link(dev, &card->num_links); if (!card->dai_link) return ERR_PTR(-ENOMEM); card->dev = dev; + card->owner = THIS_MODULE; card->name = "H3 Audio Codec"; card->dapm_widgets = sun6i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); @@ -1348,19 +1855,20 @@ static struct snd_soc_card *sun8i_v3s_codec_create_card(struct device *dev) if (!card) return ERR_PTR(-ENOMEM); - aux_dev.codec_of_node = of_parse_phandle(dev->of_node, + aux_dev.dlc.of_node = of_parse_phandle(dev->of_node, "allwinner,codec-analog-controls", 0); - if (!aux_dev.codec_of_node) { + if (!aux_dev.dlc.of_node) { dev_err(dev, "Can't find analog controls for codec.\n"); return ERR_PTR(-EINVAL); - }; + } card->dai_link = sun4i_codec_create_link(dev, &card->num_links); if (!card->dai_link) return ERR_PTR(-ENOMEM); card->dev = dev; + card->owner = THIS_MODULE; card->name = "V3s Audio Codec"; card->dapm_widgets = sun6i_codec_card_dapm_widgets; card->num_dapm_widgets = ARRAY_SIZE(sun6i_codec_card_dapm_widgets); @@ -1377,6 +1885,202 @@ static struct snd_soc_card *sun8i_v3s_codec_create_card(struct device *dev) return card; }; +static const struct snd_kcontrol_new sun50i_h616_codec_codec_controls[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1, + sun6i_codec_dvol_scale), + SOC_SINGLE_TLV("Line Out Playback Volume", + SUN50I_H616_DAC_AC_DAC_REG, + SUN50I_H616_LINEOUT_VOL, 0x1f, 0, + sun6i_codec_lineout_vol_scale), + SOC_DOUBLE("Line Out Playback Switch", + SUN50I_H616_DAC_AC_DAC_REG, + SUN50I_H616_LINEOUTL_EN, + SUN50I_H616_LINEOUTR_EN, 1, 0), +}; + +static const struct snd_kcontrol_new sun50i_h616_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE("DAC Playback Switch", + SUN50I_H616_DAC_AC_MIXER_REG, + SUN50I_H616_LMIX_LDAC, + SUN50I_H616_RMIX_RDAC, 1, 0), + SOC_DAPM_DOUBLE("DAC Reversed Playback Switch", + SUN50I_H616_DAC_AC_MIXER_REG, + SUN50I_H616_LMIX_RDAC, + SUN50I_H616_RMIX_LDAC, 1, 0), +}; + +static SOC_ENUM_DOUBLE_DECL(sun50i_h616_codec_lineout_src_enum, + SUN50I_H616_DAC_AC_DAC_REG, + SUN50I_H616_LINEOUTL_SEL, + SUN50I_H616_LINEOUTR_SEL, + sun6i_codec_lineout_src_enum_text); + +static const struct snd_kcontrol_new sun50i_h616_codec_lineout_src[] = { + SOC_DAPM_ENUM("Line Out Source Playback Route", + sun50i_h616_codec_lineout_src_enum), +}; + +static const struct snd_soc_dapm_widget sun50i_h616_codec_codec_widgets[] = { + /* Digital parts of the DACs */ + SND_SOC_DAPM_SUPPLY("DAC Enable", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_EN_DA, 0, + NULL, 0), + + /* Analog parts of the DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Codec Playback", + SUN50I_H616_DAC_AC_DAC_REG, + SUN50I_H616_DAC_LEN, 0), + SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", + SUN50I_H616_DAC_AC_DAC_REG, + SUN50I_H616_DAC_REN, 0), + + /* Mixers */ + SOC_MIXER_ARRAY("Left Mixer", SUN50I_H616_DAC_AC_MIXER_REG, + SUN50I_H616_LMIXEN, 0, + sun50i_h616_codec_mixer_controls), + SOC_MIXER_ARRAY("Right Mixer", SUN50I_H616_DAC_AC_MIXER_REG, + SUN50I_H616_RMIXEN, 0, + sun50i_h616_codec_mixer_controls), + + /* Line Out path */ + SND_SOC_DAPM_MUX("Line Out Source Playback Route", + SND_SOC_NOPM, 0, 0, sun50i_h616_codec_lineout_src), + SND_SOC_DAPM_OUT_DRV("Line Out Ramp Controller", + SUN50I_H616_DAC_AC_RAMP_REG, + SUN50I_H616_RDEN, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("LINEOUT"), +}; + +static const struct snd_soc_component_driver sun50i_h616_codec_codec = { + .controls = sun50i_h616_codec_codec_controls, + .num_controls = ARRAY_SIZE(sun50i_h616_codec_codec_controls), + .dapm_widgets = sun50i_h616_codec_codec_widgets, + .num_dapm_widgets = ARRAY_SIZE(sun50i_h616_codec_codec_widgets), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, +}; + +static const struct snd_kcontrol_new sun50i_h616_card_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget sun50i_h616_codec_card_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("Line Out", NULL), + SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), +}; + +/* Connect digital side enables to analog side widgets */ +static const struct snd_soc_dapm_route sun50i_h616_codec_card_routes[] = { + /* DAC Routes */ + { "Left DAC", NULL, "DAC Enable" }, + { "Right DAC", NULL, "DAC Enable" }, + + /* Left Mixer Routes */ + { "Left Mixer", "DAC Playback Switch", "Left DAC" }, + { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, + + /* Right Mixer Routes */ + { "Right Mixer", "DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, + + /* Line Out Routes */ + { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, + { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, + { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, + { "Line Out Ramp Controller", NULL, "Line Out Source Playback Route" }, + { "LINEOUT", NULL, "Line Out Ramp Controller" }, +}; + +static struct snd_soc_card *sun50i_h616_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dai_link->playback_only = true; + card->dai_link->capture_only = false; + + card->dev = dev; + card->owner = THIS_MODULE; + card->name = "H616 Audio Codec"; + card->long_name = "h616-audio-codec"; + card->driver_name = "sun4i-codec"; + card->controls = sun50i_h616_card_controls; + card->num_controls = ARRAY_SIZE(sun50i_h616_card_controls); + card->dapm_widgets = sun50i_h616_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sun50i_h616_codec_card_dapm_widgets); + card->dapm_routes = sun50i_h616_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(sun50i_h616_codec_card_routes); + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + +static const struct snd_soc_dapm_widget suniv_codec_card_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_LINE("Right FM In", NULL), + SND_SOC_DAPM_LINE("Left FM In", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), +}; + +/* Connect digital side enables to analog side widgets */ +static const struct snd_soc_dapm_route suniv_codec_card_routes[] = { + /* ADC Routes */ + { "ADC", NULL, "ADC Enable" }, + { "Codec Capture", NULL, "ADC" }, + + /* DAC Routes */ + { "Left DAC", NULL, "DAC Enable" }, + { "Right DAC", NULL, "DAC Enable" }, + { "Left DAC", NULL, "Codec Playback" }, + { "Right DAC", NULL, "Codec Playback" }, +}; + +static struct snd_soc_card *suniv_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return ERR_PTR(-ENOMEM); + + card->dai_link = sun4i_codec_create_link(dev, &card->num_links); + if (!card->dai_link) + return ERR_PTR(-ENOMEM); + + card->dev = dev; + card->name = "F1C100s Audio Codec"; + card->dapm_widgets = suniv_codec_card_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(suniv_codec_card_dapm_widgets); + card->dapm_routes = suniv_codec_card_routes; + card->num_dapm_routes = ARRAY_SIZE(suniv_codec_card_routes); + card->fully_routed = true; + + ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing"); + if (ret) + dev_warn(dev, "failed to parse audio-routing: %d\n", ret); + + return card; +}; + static const struct regmap_config sun4i_codec_regmap_config = { .reg_bits = 32, .reg_stride = 4, @@ -1419,14 +2123,32 @@ static const struct regmap_config sun8i_v3s_codec_regmap_config = { .max_register = SUN8I_H3_CODEC_ADC_DBG, }; +static const struct regmap_config sun50i_h616_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN50I_H616_DAC_AC_RAMP_REG, + .cache_type = REGCACHE_NONE, +}; + +static const struct regmap_config suniv_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUNIV_CODEC_ADC_DBG, +}; + struct sun4i_codec_quirks { const struct regmap_config *regmap_config; - const struct snd_soc_codec_driver *codec; + const struct snd_soc_component_driver *codec; struct snd_soc_card * (*create_card)(struct device *dev); struct reg_field reg_adc_fifoc; /* used for regmap_field */ + struct reg_field reg_dac_fifoc; /* used for regmap_field */ unsigned int reg_dac_txdata; /* TX FIFO offset for DMA config */ unsigned int reg_adc_rxdata; /* RX FIFO offset for DMA config */ bool has_reset; + bool playback_only; + u32 dma_max_burst; }; static const struct sun4i_codec_quirks sun4i_codec_quirks = { @@ -1434,8 +2156,10 @@ static const struct sun4i_codec_quirks sun4i_codec_quirks = { .codec = &sun4i_codec_codec, .create_card = sun4i_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, + .dma_max_burst = SUN4I_DMA_MAX_BURST, }; static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = { @@ -1443,18 +2167,22 @@ static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = { .codec = &sun6i_codec_codec, .create_card = sun6i_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, .has_reset = true, + .dma_max_burst = SUN4I_DMA_MAX_BURST, }; static const struct sun4i_codec_quirks sun7i_codec_quirks = { .regmap_config = &sun7i_codec_regmap_config, - .codec = &sun4i_codec_codec, + .codec = &sun7i_codec_codec, .create_card = sun4i_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, + .dma_max_burst = SUN4I_DMA_MAX_BURST, }; static const struct sun4i_codec_quirks sun8i_a23_codec_quirks = { @@ -1462,9 +2190,11 @@ static const struct sun4i_codec_quirks sun8i_a23_codec_quirks = { .codec = &sun8i_a23_codec_codec, .create_card = sun8i_a23_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, .has_reset = true, + .dma_max_burst = SUN4I_DMA_MAX_BURST, }; static const struct sun4i_codec_quirks sun8i_h3_codec_quirks = { @@ -1477,9 +2207,11 @@ static const struct sun4i_codec_quirks sun8i_h3_codec_quirks = { .codec = &sun8i_a23_codec_codec, .create_card = sun8i_h3_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, .has_reset = true, + .dma_max_burst = SUN4I_DMA_MAX_BURST, }; static const struct sun4i_codec_quirks sun8i_v3s_codec_quirks = { @@ -1491,9 +2223,33 @@ static const struct sun4i_codec_quirks sun8i_v3s_codec_quirks = { .codec = &sun8i_a23_codec_codec, .create_card = sun8i_v3s_codec_create_card, .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, .has_reset = true, + .dma_max_burst = SUN4I_DMA_MAX_BURST, +}; + +static const struct sun4i_codec_quirks sun50i_h616_codec_quirks = { + .regmap_config = &sun50i_h616_codec_regmap_config, + .codec = &sun50i_h616_codec_codec, + .create_card = sun50i_h616_codec_create_card, + .reg_dac_fifoc = REG_FIELD(SUN50I_H616_CODEC_DAC_FIFOC, 0, 31), + .reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA, + .has_reset = true, + .dma_max_burst = SUN4I_DMA_MAX_BURST, +}; + +static const struct sun4i_codec_quirks suniv_f1c100s_codec_quirks = { + .regmap_config = &suniv_codec_regmap_config, + .codec = &suniv_codec_codec, + .create_card = suniv_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUNIV_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_fifoc = REG_FIELD(SUN4I_CODEC_DAC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUNIV_CODEC_ADC_RXDATA, + .has_reset = true, + .dma_max_burst = SUNIV_DMA_MAX_BURST, }; static const struct of_device_id sun4i_codec_of_match[] = { @@ -1521,6 +2277,14 @@ static const struct of_device_id sun4i_codec_of_match[] = { .compatible = "allwinner,sun8i-v3s-codec", .data = &sun8i_v3s_codec_quirks, }, + { + .compatible = "allwinner,sun50i-h616-codec", + .data = &sun50i_h616_codec_quirks, + }, + { + .compatible = "allwinner,suniv-f1c100s-codec", + .data = &suniv_f1c100s_codec_quirks, + }, {} }; MODULE_DEVICE_TABLE(of, sun4i_codec_of_match); @@ -1540,12 +2304,9 @@ static int sun4i_codec_probe(struct platform_device *pdev) scodec->dev = &pdev->dev; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(base)) { - dev_err(&pdev->dev, "Failed to map the registers\n"); + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) return PTR_ERR(base); - } quirks = of_device_get_match_data(&pdev->dev); if (quirks == NULL) { @@ -1561,7 +2322,7 @@ static int sun4i_codec_probe(struct platform_device *pdev) } /* Get the clocks from the DT */ - scodec->clk_apb = devm_clk_get(&pdev->dev, "apb"); + scodec->clk_apb = devm_clk_get_enabled(&pdev->dev, "apb"); if (IS_ERR(scodec->clk_apb)) { dev_err(&pdev->dev, "Failed to get the APB clock\n"); return PTR_ERR(scodec->clk_apb); @@ -1574,7 +2335,7 @@ static int sun4i_codec_probe(struct platform_device *pdev) } if (quirks->has_reset) { - scodec->rst = devm_reset_control_get(&pdev->dev, NULL); + scodec->rst = devm_reset_control_get_exclusive_deasserted(&pdev->dev, NULL); if (IS_ERR(scodec->rst)) { dev_err(&pdev->dev, "Failed to get reset control\n"); return PTR_ERR(scodec->rst); @@ -1585,8 +2346,14 @@ static int sun4i_codec_probe(struct platform_device *pdev) GPIOD_OUT_LOW); if (IS_ERR(scodec->gpio_pa)) { ret = PTR_ERR(scodec->gpio_pa); - if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, "Failed to get pa gpio: %d\n", ret); + dev_err_probe(&pdev->dev, ret, "Failed to get pa gpio\n"); + return ret; + } + + scodec->gpio_hp = devm_gpiod_get_optional(&pdev->dev, "hp-det", GPIOD_IN); + if (IS_ERR(scodec->gpio_hp)) { + ret = PTR_ERR(scodec->gpio_hp); + dev_err_probe(&pdev->dev, ret, "Failed to get hp-det gpio\n"); return ret; } @@ -1601,37 +2368,34 @@ static int sun4i_codec_probe(struct platform_device *pdev) return ret; } - /* Enable the bus clock */ - if (clk_prepare_enable(scodec->clk_apb)) { - dev_err(&pdev->dev, "Failed to enable the APB clock\n"); - return -EINVAL; - } - - /* Deassert the reset control */ - if (scodec->rst) { - ret = reset_control_deassert(scodec->rst); - if (ret) { - dev_err(&pdev->dev, - "Failed to deassert the reset control\n"); - goto err_clk_disable; - } + scodec->reg_dac_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_dac_fifoc); + if (IS_ERR(scodec->reg_dac_fifoc)) { + ret = PTR_ERR(scodec->reg_dac_fifoc); + dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", + ret); + return ret; } /* DMA configuration for TX FIFO */ scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; - scodec->playback_dma_data.maxburst = 8; + scodec->playback_dma_data.maxburst = quirks->dma_max_burst; scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; - /* DMA configuration for RX FIFO */ - scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata; - scodec->capture_dma_data.maxburst = 8; - scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + if (!quirks->playback_only) { + /* DMA configuration for RX FIFO */ + scodec->capture_dma_data.addr = res->start + + quirks->reg_adc_rxdata; + scodec->capture_dma_data.maxburst = quirks->dma_max_burst; + scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + } - ret = snd_soc_register_codec(&pdev->dev, quirks->codec, + ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec, &sun4i_codec_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our codec\n"); - goto err_assert_reset; + return ret; } ret = devm_snd_soc_register_component(&pdev->dev, @@ -1639,55 +2403,38 @@ static int sun4i_codec_probe(struct platform_device *pdev) &dummy_cpu_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our DAI\n"); - goto err_unregister_codec; + return ret; } ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); if (ret) { dev_err(&pdev->dev, "Failed to register against DMAEngine\n"); - goto err_unregister_codec; + return ret; } card = quirks->create_card(&pdev->dev); if (IS_ERR(card)) { ret = PTR_ERR(card); dev_err(&pdev->dev, "Failed to create our card\n"); - goto err_unregister_codec; + return ret; } - platform_set_drvdata(pdev, card); snd_soc_card_set_drvdata(card, scodec); ret = snd_soc_register_card(card); if (ret) { - dev_err(&pdev->dev, "Failed to register our card\n"); - goto err_unregister_codec; + dev_err_probe(&pdev->dev, ret, "Failed to register our card\n"); + return ret; } return 0; - -err_unregister_codec: - snd_soc_unregister_codec(&pdev->dev); -err_assert_reset: - if (scodec->rst) - reset_control_assert(scodec->rst); -err_clk_disable: - clk_disable_unprepare(scodec->clk_apb); - return ret; } -static int sun4i_codec_remove(struct platform_device *pdev) +static void sun4i_codec_remove(struct platform_device *pdev) { struct snd_soc_card *card = platform_get_drvdata(pdev); - struct sun4i_codec *scodec = snd_soc_card_get_drvdata(card); snd_soc_unregister_card(card); - snd_soc_unregister_codec(&pdev->dev); - if (scodec->rst) - reset_control_assert(scodec->rst); - clk_disable_unprepare(scodec->clk_apb); - - return 0; } static struct platform_driver sun4i_codec_driver = { @@ -1705,4 +2452,6 @@ MODULE_AUTHOR("Emilio López <emilio@elopez.com.ar>"); MODULE_AUTHOR("Jon Smirl <jonsmirl@gmail.com>"); MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); +MODULE_AUTHOR("Ryan Walklin <ryan@testtoast.com"); +MODULE_AUTHOR("Mesih Kilinc <mesikilinc@gmail.com>"); MODULE_LICENSE("GPL"); |
