diff options
-rw-r--r-- | Documentation/devicetree/bindings/sound/sun4i-codec.txt | 22 | ||||
-rw-r--r-- | sound/soc/sunxi/sun4i-codec.c | 488 | ||||
-rw-r--r-- | sound/soc/sunxi/sun4i-i2s.c | 52 |
3 files changed, 473 insertions, 89 deletions
diff --git a/Documentation/devicetree/bindings/sound/sun4i-codec.txt b/Documentation/devicetree/bindings/sound/sun4i-codec.txt index 0dce690f78f5..bf480e9683a3 100644 --- a/Documentation/devicetree/bindings/sound/sun4i-codec.txt +++ b/Documentation/devicetree/bindings/sound/sun4i-codec.txt @@ -1,8 +1,10 @@ * Allwinner A10 Codec Required properties: -- compatible: must be either "allwinner,sun4i-a10-codec" or - "allwinner,sun7i-a20-codec" +- compatible: must be one of the following compatibles: + - "allwinner,sun4i-a10-codec" + - "allwinner,sun6i-a31-codec" + - "allwinner,sun7i-a20-codec" - reg: must contain the registers location and length - interrupts: must contain the codec interrupt - dmas: DMA channels for tx and rx dma. See the DMA client binding, @@ -17,6 +19,10 @@ Required properties: Optional properties: - allwinner,pa-gpios: gpio to enable external amplifier +Required properties for the following compatibles: + - "allwinner,sun6i-a31-codec" +- resets: phandle to the reset control for this device + Example: codec: codec@01c22c00 { #sound-dai-cells = <0>; @@ -28,3 +34,15 @@ codec: codec@01c22c00 { dmas = <&dma 0 19>, <&dma 0 19>; dma-names = "rx", "tx"; }; + +codec: codec@01c22c00 { + #sound-dai-cells = <0>; + compatible = "allwinner,sun6i-a31-codec"; + reg = <0x01c22c00 0x98>; + interrupts = <GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&ccu CLK_APB1_CODEC>, <&ccu CLK_CODEC>; + clock-names = "apb", "codec"; + resets = <&ccu RST_APB1_CODEC>; + dmas = <&dma 15>, <&dma 15>; + dma-names = "rx", "tx"; +}; diff --git a/sound/soc/sunxi/sun4i-codec.c b/sound/soc/sunxi/sun4i-codec.c index e047ec06d538..72a84f76aa57 100644 --- a/sound/soc/sunxi/sun4i-codec.c +++ b/sound/soc/sunxi/sun4i-codec.c @@ -3,6 +3,7 @@ * Copyright 2014 Jon Smirl <[email protected]> * Copyright 2015 Maxime Ripard <[email protected]> * Copyright 2015 Adam Sampson <[email protected]> + * Copyright 2016 Chen-Yu Tsai <[email protected]> * * Based on the Allwinner SDK driver, released under the GPL. * @@ -24,8 +25,9 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/of.h> -#include <linux/of_platform.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/gpio/consumer.h> @@ -38,7 +40,7 @@ #include <sound/initval.h> #include <sound/dmaengine_pcm.h> -/* Codec DAC register offsets and bit fields */ +/* Codec DAC digital controls and FIFO registers */ #define SUN4I_CODEC_DAC_DPC (0x00) #define SUN4I_CODEC_DAC_DPC_EN_DA (31) #define SUN4I_CODEC_DAC_DPC_DVOL (12) @@ -55,6 +57,8 @@ #define SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH (0) #define SUN4I_CODEC_DAC_FIFOS (0x08) #define SUN4I_CODEC_DAC_TXDATA (0x0c) + +/* Codec DAC side analog signal controls */ #define SUN4I_CODEC_DAC_ACTL (0x10) #define SUN4I_CODEC_DAC_ACTL_DACAENR (31) #define SUN4I_CODEC_DAC_ACTL_DACAENL (30) @@ -69,7 +73,7 @@ #define SUN4I_CODEC_DAC_TUNE (0x14) #define SUN4I_CODEC_DAC_DEBUG (0x18) -/* Codec ADC register offsets and bit fields */ +/* Codec ADC digital controls and FIFO registers */ #define SUN4I_CODEC_ADC_FIFOC (0x1c) #define SUN4I_CODEC_ADC_FIFOC_ADC_FS (29) #define SUN4I_CODEC_ADC_FIFOC_EN_AD (28) @@ -81,6 +85,8 @@ #define SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH (0) #define SUN4I_CODEC_ADC_FIFOS (0x20) #define SUN4I_CODEC_ADC_RXDATA (0x24) + +/* Codec ADC side analog signal controls */ #define SUN4I_CODEC_ADC_ACTL (0x28) #define SUN4I_CODEC_ADC_ACTL_ADC_R_EN (31) #define SUN4I_CODEC_ADC_ACTL_ADC_L_EN (30) @@ -93,12 +99,119 @@ #define SUN4I_CODEC_ADC_ACTL_DDE (3) #define SUN4I_CODEC_ADC_DEBUG (0x2c) -/* Other various ADC registers */ +/* FIFO counters */ #define SUN4I_CODEC_DAC_TXCNT (0x30) #define SUN4I_CODEC_ADC_RXCNT (0x34) + +/* Calibration register (sun7i only) */ #define SUN7I_CODEC_AC_DAC_CAL (0x38) + +/* Microphone controls (sun7i only) */ #define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c) +/* + * sun6i specific registers + * + * sun6i shares the same digital control and FIFO registers as sun4i, + * but only the DAC digital controls are at the same offset. The others + * have been moved around to accommodate extra analog controls. + */ + +/* Codec DAC digital controls and FIFO registers */ +#define SUN6I_CODEC_ADC_FIFOC (0x10) +#define SUN6I_CODEC_ADC_FIFOC_EN_AD (28) +#define SUN6I_CODEC_ADC_FIFOS (0x14) +#define SUN6I_CODEC_ADC_RXDATA (0x18) + +/* Output mixer and gain controls */ +#define SUN6I_CODEC_OM_DACA_CTRL (0x20) +#define SUN6I_CODEC_OM_DACA_CTRL_DACAREN (31) +#define SUN6I_CODEC_OM_DACA_CTRL_DACALEN (30) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIXEN (29) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIXEN (28) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1 (23) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2 (22) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONE (21) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONEP (20) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR (19) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR (18) +#define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL (17) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1 (16) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2 (15) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONE (14) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONEN (13) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL (12) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL (11) +#define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR (10) +#define SUN6I_CODEC_OM_DACA_CTRL_RHPIS (9) +#define SUN6I_CODEC_OM_DACA_CTRL_LHPIS (8) +#define SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE (7) +#define SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE (6) +#define SUN6I_CODEC_OM_DACA_CTRL_HPVOL (0) +#define SUN6I_CODEC_OM_PA_CTRL (0x24) +#define SUN6I_CODEC_OM_PA_CTRL_HPPAEN (31) +#define SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL (29) +#define SUN6I_CODEC_OM_PA_CTRL_COMPTEN (28) +#define SUN6I_CODEC_OM_PA_CTRL_MIC1G (15) +#define SUN6I_CODEC_OM_PA_CTRL_MIC2G (12) +#define SUN6I_CODEC_OM_PA_CTRL_LINEING (9) +#define SUN6I_CODEC_OM_PA_CTRL_PHONEG (6) +#define SUN6I_CODEC_OM_PA_CTRL_PHONEPG (3) +#define SUN6I_CODEC_OM_PA_CTRL_PHONENG (0) + +/* Microphone, line out and phone out controls */ +#define SUN6I_CODEC_MIC_CTRL (0x28) +#define SUN6I_CODEC_MIC_CTRL_HBIASEN (31) +#define SUN6I_CODEC_MIC_CTRL_MBIASEN (30) +#define SUN6I_CODEC_MIC_CTRL_MIC1AMPEN (28) +#define SUN6I_CODEC_MIC_CTRL_MIC1BOOST (25) +#define SUN6I_CODEC_MIC_CTRL_MIC2AMPEN (24) +#define SUN6I_CODEC_MIC_CTRL_MIC2BOOST (21) +#define SUN6I_CODEC_MIC_CTRL_MIC2SLT (20) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTLEN (19) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTREN (18) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC (17) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC (16) +#define SUN6I_CODEC_MIC_CTRL_LINEOUTVC (11) +#define SUN6I_CODEC_MIC_CTRL_PHONEPREG (8) + +/* ADC mixer controls */ +#define SUN6I_CODEC_ADC_ACTL (0x2c) +#define SUN6I_CODEC_ADC_ACTL_ADCREN (31) +#define SUN6I_CODEC_ADC_ACTL_ADCLEN (30) +#define SUN6I_CODEC_ADC_ACTL_ADCRG (27) +#define SUN6I_CODEC_ADC_ACTL_ADCLG (24) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1 (13) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2 (12) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONE (11) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONEP (10) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR (9) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR (8) +#define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL (7) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1 (6) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2 (5) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONE (4) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONEN (3) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL (2) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL (1) +#define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR (0) + +/* Analog performance tuning controls */ +#define SUN6I_CODEC_ADDA_TUNE (0x30) + +/* Calibration controls */ +#define SUN6I_CODEC_CALIBRATION (0x34) + +/* FIFO counters */ +#define SUN6I_CODEC_DAC_TXCNT (0x40) +#define SUN6I_CODEC_ADC_RXCNT (0x44) + +/* headset jack detection and button support registers */ +#define SUN6I_CODEC_HMIC_CTL (0x50) +#define SUN6I_CODEC_HMIC_DATA (0x54) + +/* TODO sun6i DAP (Digital Audio Processing) bits */ + struct sun4i_codec { struct device *dev; struct regmap *regmap; @@ -106,6 +219,9 @@ struct sun4i_codec { struct clk *clk_module; struct gpio_desc *gpio_pa; + /* ADC_FIFOC register is at different offset on different SoCs */ + struct regmap_field *reg_adc_fifoc; + struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; }; @@ -134,16 +250,16 @@ static void sun4i_codec_stop_playback(struct sun4i_codec *scodec) static void sun4i_codec_start_capture(struct sun4i_codec *scodec) { /* Enable ADC DRQ */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN)); } static void sun4i_codec_stop_capture(struct sun4i_codec *scodec) { /* Disable ADC DRQ */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0); + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN), 0); } static int sun4i_codec_trigger(struct snd_pcm_substream *substream, int cmd, @@ -186,24 +302,29 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, /* Flush RX FIFO */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH), - BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH)); + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH), + BIT(SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH)); /* Set RX FIFO trigger level */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL, - 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL); + regmap_field_update_bits(scodec->reg_adc_fifoc, + 0xf << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL, + 0x7 << SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL); /* * FIXME: Undocumented in the datasheet, but * Allwinner's code mentions that it is related * related to microphone gain */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL, - 0x3 << 25, - 0x1 << 25); + if (of_device_is_compatible(scodec->dev->of_node, + "allwinner,sun4i-a10-codec") || + of_device_is_compatible(scodec->dev->of_node, + "allwinner,sun7i-a20-codec")) { + regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_ACTL, + 0x3 << 25, + 0x1 << 25); + } if (of_device_is_compatible(scodec->dev->of_node, "allwinner,sun7i-a20-codec")) @@ -213,9 +334,9 @@ static int sun4i_codec_prepare_capture(struct snd_pcm_substream *substream, 0x1 << 8); /* Fill most significant bits with valid data MSB */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE), - BIT(SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE)); + 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; } @@ -342,18 +463,19 @@ static int sun4i_codec_hw_params_capture(struct sun4i_codec *scodec, unsigned int hwrate) { /* Set ADC sample rate */ - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS, - hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS); + regmap_field_update_bits(scodec->reg_adc_fifoc, + 7 << SUN4I_CODEC_ADC_FIFOC_ADC_FS, + hwrate << SUN4I_CODEC_ADC_FIFOC_ADC_FS); /* Set the number of channels we want to use */ if (params_channels(params) == 1) - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), - BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN)); else - regmap_update_bits(scodec->regmap, SUN4I_CODEC_ADC_FIFOC, - BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), 0); + regmap_field_update_bits(scodec->reg_adc_fifoc, + BIT(SUN4I_CODEC_ADC_FIFOC_MONO_EN), + 0); return 0; } @@ -502,7 +624,7 @@ static struct snd_soc_dai_driver sun4i_codec_dai = { }, }; -/*** Codec ***/ +/*** sun4i Codec ***/ static const struct snd_kcontrol_new sun4i_codec_pa_mute = SOC_DAPM_SINGLE("Switch", SUN4I_CODEC_DAC_ACTL, SUN4I_CODEC_DAC_ACTL_PA_MUTE, 1, 0); @@ -638,6 +760,137 @@ static struct snd_soc_codec_driver sun4i_codec_codec = { }, }; +/*** sun6i Codec ***/ + +/* mixer controls */ +static const struct snd_kcontrol_new sun6i_codec_mixer_controls[] = { + SOC_DAPM_DOUBLE("DAC Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR, 1, 0), + SOC_DAPM_DOUBLE("DAC Reversed Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL, 1, 0), + SOC_DAPM_DOUBLE("Line In Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL, + SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR, 1, 0), +}; + +/* headphone controls */ +static const char * const sun6i_codec_hp_src_enum_text[] = { + "DAC", "Mixer", +}; + +static SOC_ENUM_DOUBLE_DECL(sun6i_codec_hp_src_enum, + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LHPIS, + SUN6I_CODEC_OM_DACA_CTRL_RHPIS, + sun6i_codec_hp_src_enum_text); + +static const struct snd_kcontrol_new sun6i_codec_hp_src[] = { + SOC_DAPM_ENUM("Headphone Source Playback Route", + sun6i_codec_hp_src_enum), +}; + +/* volume / mute controls */ +static const DECLARE_TLV_DB_SCALE(sun6i_codec_dvol_scale, -7308, 116, 0); +static const DECLARE_TLV_DB_SCALE(sun6i_codec_hp_vol_scale, -6300, 100, 1); +static const DECLARE_TLV_DB_SCALE(sun6i_codec_out_mixer_pregain_scale, + -450, 150, 0); + +static const struct snd_kcontrol_new sun6i_codec_codec_widgets[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SUN4I_CODEC_DAC_DPC, + SUN4I_CODEC_DAC_DPC_DVOL, 0x3f, 1, + sun6i_codec_dvol_scale), + SOC_SINGLE_TLV("Headphone Playback Volume", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_HPVOL, 0x3f, 0, + sun6i_codec_hp_vol_scale), + SOC_DOUBLE("Headphone Playback Switch", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE, + SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE, 1, 0), + /* Mixer pre-gains */ + SOC_SINGLE_TLV("Line In Playback Volume", + SUN6I_CODEC_OM_PA_CTRL, SUN6I_CODEC_OM_PA_CTRL_LINEING, + 0x7, 0, sun6i_codec_out_mixer_pregain_scale), +}; + +static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets[] = { + /* Line In */ + SND_SOC_DAPM_INPUT("LINEIN"), + + /* 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", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_DACALEN, 0), + SND_SOC_DAPM_DAC("Right DAC", "Codec Playback", + SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_DACAREN, 0), + + /* Mixers */ + SOC_MIXER_ARRAY("Left Mixer", SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_LMIXEN, 0, + sun6i_codec_mixer_controls), + SOC_MIXER_ARRAY("Right Mixer", SUN6I_CODEC_OM_DACA_CTRL, + SUN6I_CODEC_OM_DACA_CTRL_RMIXEN, 0, + sun6i_codec_mixer_controls), + + /* Headphone output path */ + SND_SOC_DAPM_MUX("Headphone Source Playback Route", + SND_SOC_NOPM, 0, 0, sun6i_codec_hp_src), + SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_HPPAEN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_COMPTEN, 0, NULL, 0), + SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN6I_CODEC_OM_PA_CTRL, + SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL, 0x3, 0x3, 0), + SND_SOC_DAPM_OUTPUT("HP"), +}; + +static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_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" }, + { "Left Mixer", "Line In Playback Switch", "LINEIN" }, + + /* Right Mixer Routes */ + { "Right Mixer", "DAC Playback Switch", "Right DAC" }, + { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, + { "Right Mixer", "Line In Playback Switch", "LINEIN" }, + + /* 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" }, +}; + +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 sun4i_codec_component = { .name = "sun4i-codec", }; @@ -678,45 +931,6 @@ static struct snd_soc_dai_driver dummy_cpu_dai = { }, }; -static const struct regmap_config sun4i_codec_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = SUN4I_CODEC_ADC_RXCNT, -}; - -static const struct regmap_config sun7i_codec_regmap_config = { - .reg_bits = 32, - .reg_stride = 4, - .val_bits = 32, - .max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL, -}; - -struct sun4i_codec_quirks { - const struct regmap_config *regmap_config; -}; - -static const struct sun4i_codec_quirks sun4i_codec_quirks = { - .regmap_config = &sun4i_codec_regmap_config, -}; - -static const struct sun4i_codec_quirks sun7i_codec_quirks = { - .regmap_config = &sun7i_codec_regmap_config, -}; - -static const struct of_device_id sun4i_codec_of_match[] = { - { - .compatible = "allwinner,sun4i-a10-codec", - .data = &sun4i_codec_quirks, - }, - { - .compatible = "allwinner,sun7i-a20-codec", - .data = &sun7i_codec_quirks, - }, - {} -}; -MODULE_DEVICE_TABLE(of, sun4i_codec_of_match); - static struct snd_soc_dai_link *sun4i_codec_create_link(struct device *dev, int *num_links) { @@ -765,11 +979,11 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev) card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) - return NULL; + return ERR_PTR(-ENOMEM); card->dai_link = sun4i_codec_create_link(dev, &card->num_links); if (!card->dai_link) - return NULL; + return ERR_PTR(-ENOMEM); card->dev = dev; card->name = "sun4i-codec"; @@ -781,6 +995,99 @@ static struct snd_soc_card *sun4i_codec_create_card(struct device *dev) return card; }; +static struct snd_soc_card *sun6i_codec_create_card(struct device *dev) +{ + struct snd_soc_card *card; + + 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 = "A31 Audio Codec"; + + return card; +}; + +static const struct regmap_config sun4i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN4I_CODEC_ADC_RXCNT, +}; + +static const struct regmap_config sun6i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN6I_CODEC_HMIC_DATA, +}; + +static const struct regmap_config sun7i_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL, +}; + +struct sun4i_codec_quirks { + const struct regmap_config *regmap_config; + const struct snd_soc_codec_driver *codec; + struct snd_soc_card * (*create_card)(struct device *dev); + struct reg_field reg_adc_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 */ +}; + +static const struct sun4i_codec_quirks sun4i_codec_quirks = { + .regmap_config = &sun4i_codec_regmap_config, + .codec = &sun4i_codec_codec, + .create_card = sun4i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, +}; + +static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = { + .regmap_config = &sun6i_codec_regmap_config, + .codec = &sun6i_codec_codec, + .create_card = sun6i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN6I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA, + .has_reset = true, +}; + +static const struct sun4i_codec_quirks sun7i_codec_quirks = { + .regmap_config = &sun7i_codec_regmap_config, + .codec = &sun4i_codec_codec, + .create_card = sun4i_codec_create_card, + .reg_adc_fifoc = REG_FIELD(SUN4I_CODEC_ADC_FIFOC, 0, 31), + .reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA, + .reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA, +}; + +static const struct of_device_id sun4i_codec_of_match[] = { + { + .compatible = "allwinner,sun4i-a10-codec", + .data = &sun4i_codec_quirks, + }, + { + .compatible = "allwinner,sun6i-a31-codec", + .data = &sun6i_a31_codec_quirks, + }, + { + .compatible = "allwinner,sun7i-a20-codec", + .data = &sun7i_codec_quirks, + }, + {} +}; +MODULE_DEVICE_TABLE(of, sun4i_codec_of_match); + static int sun4i_codec_probe(struct platform_device *pdev) { struct snd_soc_card *card; @@ -829,12 +1136,6 @@ static int sun4i_codec_probe(struct platform_device *pdev) return PTR_ERR(scodec->clk_module); } - /* Enable the bus clock */ - if (clk_prepare_enable(scodec->clk_apb)) { - dev_err(&pdev->dev, "Failed to enable the APB clock\n"); - return -EINVAL; - } - scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa", GPIOD_OUT_LOW); if (IS_ERR(scodec->gpio_pa)) { @@ -844,17 +1145,34 @@ static int sun4i_codec_probe(struct platform_device *pdev) return ret; } + /* reg_field setup */ + scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev, + scodec->regmap, + quirks->reg_adc_fifoc); + if (IS_ERR(scodec->reg_adc_fifoc)) { + ret = PTR_ERR(scodec->reg_adc_fifoc); + dev_err(&pdev->dev, "Failed to create regmap fields: %d\n", + ret); + 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; + } + /* DMA configuration for TX FIFO */ - scodec->playback_dma_data.addr = res->start + SUN4I_CODEC_DAC_TXDATA; - scodec->playback_dma_data.maxburst = 4; + scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata; + scodec->playback_dma_data.maxburst = 8; scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; /* DMA configuration for RX FIFO */ - scodec->capture_dma_data.addr = res->start + SUN4I_CODEC_ADC_RXDATA; - scodec->capture_dma_data.maxburst = 4; + 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; - ret = snd_soc_register_codec(&pdev->dev, &sun4i_codec_codec, + ret = snd_soc_register_codec(&pdev->dev, quirks->codec, &sun4i_codec_dai, 1); if (ret) { dev_err(&pdev->dev, "Failed to register our codec\n"); @@ -875,8 +1193,9 @@ static int sun4i_codec_probe(struct platform_device *pdev) goto err_unregister_codec; } - card = sun4i_codec_create_card(&pdev->dev); - if (!card) { + 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; } @@ -925,4 +1244,5 @@ MODULE_DESCRIPTION("Allwinner A10 codec driver"); MODULE_AUTHOR("Emilio López <[email protected]>"); MODULE_AUTHOR("Jon Smirl <[email protected]>"); MODULE_AUTHOR("Maxime Ripard <[email protected]>"); +MODULE_AUTHOR("Chen-Yu Tsai <[email protected]>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/sunxi/sun4i-i2s.c b/sound/soc/sunxi/sun4i-i2s.c index 687a8f83dbe5..a7653114e895 100644 --- a/sound/soc/sunxi/sun4i-i2s.c +++ b/sound/soc/sunxi/sun4i-i2s.c @@ -93,6 +93,7 @@ struct sun4i_i2s { struct clk *mod_clk; struct regmap *regmap; + struct snd_dmaengine_dai_dma_data capture_dma_data; struct snd_dmaengine_dai_dma_data playback_dma_data; }; @@ -341,6 +342,27 @@ static int sun4i_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } +static void sun4i_i2s_start_capture(struct sun4i_i2s *i2s) +{ + /* Flush RX FIFO */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_FIFO_CTRL_REG, + SUN4I_I2S_FIFO_CTRL_FLUSH_RX, + SUN4I_I2S_FIFO_CTRL_FLUSH_RX); + + /* Clear RX counter */ + regmap_write(i2s->regmap, SUN4I_I2S_RX_CNT_REG, 0); + + /* Enable RX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_RX_EN, + SUN4I_I2S_CTRL_RX_EN); + + /* Enable RX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN); +} + static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s) { /* Flush TX FIFO */ @@ -362,6 +384,18 @@ static void sun4i_i2s_start_playback(struct sun4i_i2s *i2s) SUN4I_I2S_DMA_INT_CTRL_TX_DRQ_EN); } +static void sun4i_i2s_stop_capture(struct sun4i_i2s *i2s) +{ + /* Disable RX Block */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_CTRL_REG, + SUN4I_I2S_CTRL_RX_EN, + 0); + + /* Disable RX DRQ */ + regmap_update_bits(i2s->regmap, SUN4I_I2S_DMA_INT_CTRL_REG, + SUN4I_I2S_DMA_INT_CTRL_RX_DRQ_EN, + 0); +} static void sun4i_i2s_stop_playback(struct sun4i_i2s *i2s) { @@ -388,7 +422,7 @@ static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) sun4i_i2s_start_playback(i2s); else - return -EINVAL; + sun4i_i2s_start_capture(i2s); break; case SNDRV_PCM_TRIGGER_STOP: @@ -397,7 +431,7 @@ static int sun4i_i2s_trigger(struct snd_pcm_substream *substream, int cmd, if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) sun4i_i2s_stop_playback(i2s); else - return -EINVAL; + sun4i_i2s_stop_capture(i2s); break; default: @@ -459,7 +493,9 @@ static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai) { struct sun4i_i2s *i2s = snd_soc_dai_get_drvdata(dai); - snd_soc_dai_init_dma_data(dai, &i2s->playback_dma_data, NULL); + snd_soc_dai_init_dma_data(dai, + &i2s->playback_dma_data, + &i2s->capture_dma_data); snd_soc_dai_set_drvdata(dai, i2s); @@ -468,6 +504,13 @@ static int sun4i_i2s_dai_probe(struct snd_soc_dai *dai) static struct snd_soc_dai_driver sun4i_i2s_dai = { .probe = sun4i_i2s_dai_probe, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, .playback = { .stream_name = "Playback", .channels_min = 2, @@ -630,6 +673,9 @@ static int sun4i_i2s_probe(struct platform_device *pdev) i2s->playback_dma_data.addr = res->start + SUN4I_I2S_FIFO_TX_REG; i2s->playback_dma_data.maxburst = 4; + i2s->capture_dma_data.addr = res->start + SUN4I_I2S_FIFO_RX_REG; + i2s->capture_dma_data.maxburst = 4; + pm_runtime_enable(&pdev->dev); if (!pm_runtime_enabled(&pdev->dev)) { ret = sun4i_i2s_runtime_resume(&pdev->dev); |