[alsa-devel] [PATCH] First attempt at a driver for the internal codec on the Allwinner A20.
First attempt at a driver for the internal codec on the Allwinner A20. This codec is a combination codec and DAI, it is all on-chip.
So why don't I have a DMA buffer? Any other hints to get this working would be appreciated.
[ 31.910394] pcm_sanity_check (null) (null) [ 31.910415] ------------[ cut here ]------------ [ 31.910440] WARNING: CPU: 1 PID: 738 at sound/core/pcm_lib.c:2090 pcm_sanity_check+0x70/0x98() [ 31.910448] Modules linked in: brcmfmac brcmutil [ 31.910470] CPU: 1 PID: 738 Comm: aplay Not tainted 3.15.0+ #161 [ 31.910508] [<c001513c>] (unwind_backtrace) from [<c0011d08>] (show_stack+0x18/0x1c) [ 31.910528] [<c0011d08>] (show_stack) from [<c0566934>] (dump_stack+0x78/0x94) [ 31.910549] [<c0566934>] (dump_stack) from [<c00237c4>] (warn_slowpath_common+0x6c/0x90) [ 31.910567] [<c00237c4>] (warn_slowpath_common) from [<c0023890>] (warn_slowpath_null+0x20/0x28) [ 31.910585] [<c0023890>] (warn_slowpath_null) from [<c03f7e64>] (pcm_sanity_check+0x70/0x98) [ 31.910602] [<c03f7e64>] (pcm_sanity_check) from [<c03faa08>] (snd_pcm_lib_write+0x1c/0x74) [ 31.910619] [<c03faa08>] (snd_pcm_lib_write) from [<c03f6400>] (snd_pcm_playback_ioctl1+0x3c8/0x4a8) [ 31.910637] [<c03f6400>] (snd_pcm_playback_ioctl1) from [<c013762c>] (do_vfs_ioctl+0x4d4/0x594) [ 31.910653] [<c013762c>] (do_vfs_ioctl) from [<c0137740>] (SyS_ioctl+0x54/0x7c) [ 31.910668] [<c0137740>] (SyS_ioctl) from [<c000e420>] (ret_fast_syscall+0x0/0x30) [ 31.910677] ---[ end trace 86fcd83010b5b5b5 ]--- --- sound/soc/sunxi/sunxi_codec_main.c | 687 ++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 sound/soc/sunxi/sunxi_codec_main.c
diff --git a/sound/soc/sunxi/sunxi_codec_main.c b/sound/soc/sunxi/sunxi_codec_main.c new file mode 100644 index 0000000..4070bfe --- /dev/null +++ b/sound/soc/sunxi/sunxi_codec_main.c @@ -0,0 +1,687 @@ +/* + * + * Licensed under the GPL-2. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include <linux/regmap.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/dmaengine_pcm.h> + +//Codec Register +#define SUNXI_DAC_DPC (0x00) +#define SUNXI_DAC_FIFOC (0x04) +#define SUNXI_DAC_FIFOS (0x08) +#define SUNXI_DAC_TXDATA (0x0c) +#define SUNXI_DAC_ACTL (0x10) +#define SUNXI_DAC_TUNE (0x14) +#define SUNXI_DAC_DEBUG (0x18) +#define SUNXI_ADC_FIFOC (0x1c) +#define SUNXI_ADC_FIFOS (0x20) +#define SUNXI_ADC_RXDATA (0x24) +#define SUNXI_ADC_ACTL (0x28) +#define SUNXI_ADC_DEBUG (0x2c) +#define SUNXI_DAC_TXCNT (0x30) +#define SUNXI_ADC_RXCNT (0x34) +#define SUNXI_BIAS_CRT (0x38) +#define SUNXI_MIC_CRT (0x3c) +#define SUNXI_CODEC_REGS_NUM (13) + +#define DAIFMT_16BITS (16) +#define DAIFMT_20BITS (20) + +#define DAIFMT_BS_MASK (~(1<<16)) //FIFO big small mode mask +#define DAIFMT_BITS_MASK (~(1<<5)) //FIFO Bits select mask,not used yet. +#define SAMPLE_RATE_MASK (~(7<<29)) //Sample Rate slect mask + +#define DAC_EN (31) +#define DIGITAL_VOL (12) +//For CODEC OLD VERSION +#define DAC_VERSION (23) + +#define DAC_CHANNEL (6) +#define LAST_SE (26) +#define TX_FIFO_MODE (24) +#define DRA_LEVEL (21) +#define TX_TRI_LEVEL (8) +#define DAC_MODE (6) //not used yet +#define TASR (5) //not used yet +#define DAC_DRQ (4) +#define DAC_FIFO_FLUSH (0) + +#define VOLUME (0) +#define PA_MUTE (6) +#define MIXPAS (7) +#define DACPAS (8) +#define MIXEN (29) +#define DACAEN_L (30) +#define DACAEN_R (31) + +#define ADC_DIG_EN (28) +#define RX_FIFO_MODE (24) +#define RX_TRI_LEVEL (8) +#define ADC_MODE (7) +#define RASR (6) +#define ADC_DRQ (4) +#define ADC_FIFO_FLUSH (0) + +#define ADC_LF_EN (31) +#define ADC_RI_EN (30) +#define ADC_EN (30) +#define MIC1_EN (29) +#define MIC2_EN (28) +#define VMIC_EN (27) +#define MIC_GAIN (25) +#define ADC_SELECT (17) +#define PA_ENABLE (4) +#define HP_DIRECT (3) + + +enum sunxi_device_id {SUN4A, SUN4I, SUN5I, SUN7I}; + +struct sunxi_priv { + struct regmap *regmap; + int irq; + struct clk *clk_apb, *clk_pll2, *clk_module; + + enum sunxi_device_id id; + + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; +}; + +static int codec_play_start(struct sunxi_priv *priv) +{ +#ifdef JDS + if (gpio_pa_shutdown) + gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl"); +#endif + //flush TX FIFO + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH); + //enable dac drq + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x1 << DAC_DRQ); + return 0; +} + +static int codec_play_stop(struct sunxi_priv *priv) +{ + //pa mute +#ifdef JDS + if (gpio_pa_shutdown) + gpio_write_one_pin_value(gpio_pa_shutdown, 0, "audio_pa_ctrl"); +#endif + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << PA_MUTE); + mdelay(5); + //disable dac drq + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x0 << DAC_DRQ); + //pa mute + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << PA_MUTE); + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_L, 0x0 << DACAEN_L); + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_R, 0x0 << DACAEN_R); + return 0; +} + +static int codec_capture_start(struct sunxi_priv *priv) +{ + //enable adc drq +#ifdef JDS + if (gpio_pa_shutdown) + gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl"); +#endif + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x1 << ADC_DRQ); + return 0; +} + +static int codec_capture_stop(struct sunxi_priv *priv) +{ + //disable adc drq + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x0 << ADC_DRQ); + //enable mic1 pa + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << MIC1_EN, 0x0 << MIC1_EN); + //enable VMIC + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << VMIC_EN, 0x0 << VMIC_EN); + if (priv->id == SUN7I) { + regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 8, 0x0 << 8); + } + //enable adc digital + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DIG_EN, 0x0 << ADC_DIG_EN); + //set RX FIFO mode + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << RX_FIFO_MODE, 0x0 << RX_FIFO_MODE); + //flush RX FIFO + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_FIFO_FLUSH, 0x0 << ADC_FIFO_FLUSH); + //enable adc1 analog + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 0x0 << ADC_EN); + return 0; +} + +static int sunxi_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = codec->card; + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + + printk("JDS - sunxi_codec_trigger 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_CAPTURE) + codec_capture_start(priv); + else + codec_play_start(priv); + break; break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + codec_capture_stop(priv); + else + codec_play_stop(priv); + break; + default: + return -EINVAL; + } + return 0; +} + +static int sunxi_codec_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = codec->card; + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + + printk("JDS - sunxi_codec_prepare\n"); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){ + regmap_update_bits(priv->regmap, SUNXI_DAC_DPC, 0x1 << DAC_EN, 0x1 << DAC_EN); + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH); + //set TX FIFO send drq level + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0xf << TX_TRI_LEVEL, 0xf << TX_TRI_LEVEL); + if (substream->runtime->rate > 32000) { + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 28, 0x0 << 28); + } else { + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 28, 0x1 << 28); + } + //set TX FIFO MODE + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << TX_FIFO_MODE, 0x1 << TX_FIFO_MODE); + //send last sample when dac fifo under run + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << LAST_SE, 0x0 << LAST_SE); + //enable dac analog + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_L, 0x1 << DACAEN_L); + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_R, 0x1 << DACAEN_R); + //enable dac to pa + regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACPAS, 0x1 << DACPAS); + } else { + //enable mic1 pa + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << MIC1_EN, 0x1 << MIC1_EN); + //mic1 gain 32dB + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << 25, 0x1 << 25); + //enable VMIC + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << VMIC_EN, 0x1 << VMIC_EN); + + if (priv->id == SUN7I) { + /* boost up record effect */ + regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 8, 0x1 << 8); + } + + //enable adc digital + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DIG_EN, 0x1 << ADC_DIG_EN); + //set RX FIFO mode + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << RX_FIFO_MODE, 0x1 << RX_FIFO_MODE); + //flush RX FIFO + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_FIFO_FLUSH, 0x1 << ADC_FIFO_FLUSH); + //set RX FIFO rec drq level + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0xf << RX_TRI_LEVEL, 0x7 << RX_TRI_LEVEL); + //enable adc1 analog + regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 0x3 << ADC_EN); + } + return 0; +} + +static int sunxi_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = codec->card; + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + unsigned int rate = params_rate(params); + + printk("CLK - sunxi_codec_hw_params substream %p runtime %p\n", substream, rtd); + switch (params_rate(params)) { + case 44100: + case 22050: + case 11025: + default: + clk_set_rate(priv->clk_pll2, 22579200); + clk_set_rate(priv->clk_module, 22579200); + break; + case 192000: + case 96000: + case 48000: + case 32000: + case 24000: + case 16000: + case 12000: + case 8000: + clk_set_rate(priv->clk_pll2, 24576000); + clk_set_rate(priv->clk_module, 24576000); + break; + } + + switch (params_rate(params)) { + default: + case 44100: + rate = 0; + break; + case 22050: + rate = 2; + break; + case 11025: + rate = 4; + break; + case 192000: + rate = 6; + break; + case 96000: + rate = 7; + break; + case 48000: + rate = 0; + break; + case 32000: + rate = 1; + break; + case 24000: + rate = 2; + break; + case 16000: + rate = 3; + break; + case 12000: + rate = 4; + break; + case 8000: + rate = 5; + break; + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 7 << 29, rate << 29); + if (substream->runtime->channels == 1) + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 6, 1 << 6); + else + regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 6, 0 << 6); + } else { + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 7 << 29, rate << 29); + if (substream->runtime->channels == 1) + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 7, 1 << 7); + else + regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 7, 0 << 7); + } + return 0; +} + +static const struct snd_kcontrol_new sun7i_dac_ctls[] = { + /*SUNXI_DAC_ACTL = 0x10,PAVOL*/ + SOC_SINGLE("Master Playback Volume", SUNXI_DAC_ACTL, 0, 0x3f, 0), + SOC_SINGLE("Playback Switch", SUNXI_DAC_ACTL, 6, 1, 0), //全局输出开关 + SOC_SINGLE("FmL Switch", SUNXI_DAC_ACTL, 17, 1, 0), //Fm左开关 + SOC_SINGLE("FmR Switch", SUNXI_DAC_ACTL, 16, 1, 0), //Fm右开关 + SOC_SINGLE("LineL Switch", SUNXI_DAC_ACTL, 19, 1, 0), //Line左开关 + SOC_SINGLE("LineR Switch", SUNXI_DAC_ACTL, 18, 1, 0), //Line右开关 + SOC_SINGLE("Ldac Left Mixer", SUNXI_DAC_ACTL, 15, 1, 0), + SOC_SINGLE("Rdac Right Mixer", SUNXI_DAC_ACTL, 14, 1, 0), + SOC_SINGLE("Ldac Right Mixer", SUNXI_DAC_ACTL, 13, 1, 0), + SOC_SINGLE("Mic Input Mux", SUNXI_DAC_ACTL, 9, 15, 0), //from bit 9 to bit 12.Mic(麦克风)输入静音 + SOC_SINGLE("MIC output volume", SUNXI_DAC_ACTL, 20, 7, 0), + /* FM Input to output mixer Gain Control + * From -4.5db to 6db,1.5db/step,default is 0db + * -4.5db:0x0,-3.0db:0x1,-1.5db:0x2,0db:0x3 + * 1.5db:0x4,3.0db:0x5,4.5db:0x6,6db:0x7 + */ + SOC_SINGLE("Fm output Volume", SUNXI_DAC_ACTL, 23, 7, 0), + /* Line-in gain stage to output mixer Gain Control + * 0:-1.5db,1:0db + */ + SOC_SINGLE("Line output Volume", SUNXI_DAC_ACTL, 26, 1, 0), + + SOC_SINGLE("Master Capture Mute", SUNXI_ADC_ACTL, 4, 1, 0), + SOC_SINGLE("Right Capture Mute", SUNXI_ADC_ACTL, 31, 1, 0), + SOC_SINGLE("Left Capture Mute", SUNXI_ADC_ACTL, 30, 1, 0), + SOC_SINGLE("Linein Pre-AMP", SUNXI_ADC_ACTL, 13, 7, 0), + SOC_SINGLE("LINEIN APM Volume", SUNXI_MIC_CRT, 13, 0x7, 0), + /* ADC Input Gain Control, capture volume + * 000:-4.5db,001:-3db,010:-1.5db,011:0db,100:1.5db,101:3db,110:4.5db,111:6db + */ + SOC_SINGLE("Capture Volume", SUNXI_ADC_ACTL, 20, 7, 0), + /* + * MIC2 pre-amplifier Gain Control + * 00:0db,01:35db,10:38db,11:41db + */ + SOC_SINGLE("MicL Volume", SUNXI_ADC_ACTL, 25, 3, 0), //mic左音量 + SOC_SINGLE("MicR Volume", SUNXI_ADC_ACTL, 23, 3, 0), //mic右音量 + SOC_SINGLE("Mic2 Boost", SUNXI_ADC_ACTL, 29, 1, 0), + SOC_SINGLE("Mic1 Boost", SUNXI_ADC_ACTL, 28, 1, 0), + SOC_SINGLE("Mic Power", SUNXI_ADC_ACTL, 27, 1, 0), + SOC_SINGLE("ADC Input Mux", SUNXI_ADC_ACTL, 17, 7, 0), //ADC输入静音 + SOC_SINGLE("Mic2 gain Volume", SUNXI_MIC_CRT, 26, 7, 0), + /* + * MIC1 pre-amplifier Gain Control + * 00:0db,01:35db,10:38db,11:41db + */ + SOC_SINGLE("Mic1 gain Volume", SUNXI_MIC_CRT, 29, 3, 0), +}; + + +static int sunxi_codec_dai_probe(struct snd_soc_dai *dai) +{ + struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + + printk("CLK - sunxi_codec_dai_probe %p\n", priv); + snd_soc_dai_init_dma_data(dai, &priv->playback_dma_data, &priv->capture_dma_data); + + return 0; +} + +static int sunxi_codec_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = codec->card; + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + + int ret; + printk("JDS - CLK sunxi_codec_startup %p %p\n", codec, priv->clk_module); + ret = clk_prepare_enable(priv->clk_module); + if (ret) + return ret; + + printk("JDS - sunxi_codec_startup - ok\n"); + return 0; +} + +static void sunxi_codec_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_card *card = codec->card; + struct sunxi_priv *priv = snd_soc_card_get_drvdata(card); + + printk("JDS - sunxi_codec_shutdown\n"); + clk_disable_unprepare(priv->clk_module); +} + +static const struct snd_soc_dai_ops sunxi_codec_dai_ops = { + .startup = sunxi_codec_startup, + .shutdown = sunxi_codec_shutdown, + .trigger = sunxi_codec_trigger, + .hw_params = sunxi_codec_hw_params, + .prepare = sunxi_codec_prepare, +}; + +static struct snd_soc_dai_driver sunxi_codec_dai = { + .probe = sunxi_codec_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_22050| SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 |SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000 |\ + SNDRV_PCM_RATE_KNOT), + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &sunxi_codec_dai_ops, +}; + +static const struct snd_soc_component_driver sunxi_codec_component = { + .name = "sunxi-codec", +}; + +static const struct regmap_config sunxi_codec_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = SUNXI_MIC_CRT, +}; + +static const struct snd_soc_dapm_widget codec_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("Mic Bias"), + SND_SOC_DAPM_OUTPUT("HP_OUT"), + SND_SOC_DAPM_INPUT("MIC_IN"), + SND_SOC_DAPM_INPUT("LINE_IN"), +}; + +static struct snd_soc_dai_link cdc_dai = { + .name = "cdc", + .stream_name = "CDC PCM", + .codec_dai_name = "sunxi-codec-dai", + .cpu_dai_name = "1c22c00.codec", + .codec_name = "1c22c00.codec", + //.init = tegra_wm8903_init, + //.ops = &tegra_wm8903_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S, +}; + +static struct snd_soc_card snd_soc_sunxi_codec = { + .name = "sunxi-codec", + .owner = THIS_MODULE, + .dai_link = &cdc_dai, + .num_links = 1, + + .fully_routed = true, +}; + +static struct snd_soc_codec_driver dummy_codec = { + .controls = sun7i_dac_ctls, + .num_controls = ARRAY_SIZE(sun7i_dac_ctls), + .dapm_widgets = codec_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(codec_dapm_widgets), +}; + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_U8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_U24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE | \ + SNDRV_PCM_FMTBIT_U32_LE | \ + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) +static struct snd_soc_dai_driver dummy_dai = { + .name = "sunxi-codec-dai", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static const struct of_device_id sunxi_codec_of_match[] = { + { .compatible = "allwinner,sun4i-a10a-codec", .data = (void *)SUN4A}, + { .compatible = "allwinner,sun4i-a10-codec", .data = (void *)SUN4I}, + { .compatible = "allwinner,sun5i-a13-codec", .data = (void *)SUN5I}, + { .compatible = "allwinner,sun7i-a20-codec", .data = (void *)SUN7I}, + {} +}; +MODULE_DEVICE_TABLE(of, sunxi_codec_of_match); + +static int sunxi_codec_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_sunxi_codec; + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct sunxi_priv *priv; + struct resource *res; + void __iomem *base; + int ret; + + if (!of_device_is_available(np)) + return -ENODEV; + + of_id = of_match_device(sunxi_codec_of_match, dev); + if (!of_id) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, priv); + + priv->id = (int)of_id->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &sunxi_codec_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->irq = irq_of_parse_and_map(np, 0); + if (!priv->irq) { + dev_err(dev, "no irq for node %s\n", np->full_name); + return -ENXIO; + } + + /* Clock */ + priv->clk_apb = devm_clk_get(dev, "apb"); + if (IS_ERR(priv->clk_apb)) { + dev_err(dev, "failed to get apb clock.\n"); + return PTR_ERR(priv->clk_apb); + } + priv->clk_pll2 = devm_clk_get(dev, "pll2"); + if (IS_ERR(priv->clk_pll2)) { + dev_err(dev, "failed to get pll2 clock.\n"); + return PTR_ERR(priv->clk_pll2); + } + priv->clk_module = devm_clk_get(dev, "codec"); + if (IS_ERR(priv->clk_module)) { + dev_err(dev, "failed to get codec clock.\n"); + return PTR_ERR(priv->clk_module); + } + printk("CLK = card %p priv %p priv->clk_module %p\n", card, priv, priv->clk_module); + + ret = clk_set_rate(priv->clk_pll2, 24576000); + if (ret) { + dev_err(dev, "set codec base clock rate failed!\n"); + return ret; + } + if (clk_prepare_enable(priv->clk_pll2)) { + dev_err(dev, "try to enable clk_pll2 failed\n"); + return -EINVAL; + } + if (clk_prepare_enable(priv->clk_apb)) { + dev_err(dev, "try to enable clk_apb failed\n"); + return -EINVAL; + } + + priv->playback_dma_data.addr = res->start + SUNXI_DAC_TXDATA; + priv->playback_dma_data.maxburst = 4; + priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + priv->capture_dma_data.addr = res->start + SUNXI_ADC_RXDATA; + priv->capture_dma_data.maxburst = 4; + priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + + ret = snd_soc_register_codec(&pdev->dev, &dummy_codec, &dummy_dai, 1); + + ret = devm_snd_soc_register_component(&pdev->dev, &sunxi_codec_component, &sunxi_codec_dai, 1); + if (ret) + goto err_clk_disable; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + goto err_clk_disable; + + ret = snd_soc_of_parse_audio_routing(card, "routing"); + if (ret) + goto err; + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: +err: +err_clk_disable: + clk_disable_unprepare(priv->clk_module); + clk_disable_unprepare(priv->clk_apb); + clk_disable_unprepare(priv->clk_pll2); + return ret; +} + +static int sunxi_codec_dev_remove(struct platform_device *pdev) +{ + struct sunxi_priv *priv = platform_get_drvdata(pdev); + + clk_disable_unprepare(priv->clk_module); + clk_disable_unprepare(priv->clk_apb); + clk_disable_unprepare(priv->clk_pll2); + + return 0; +} + +static struct platform_driver sunxi_codec_driver = { + .driver = { + .name = "sunxi-codec", + .owner = THIS_MODULE, + .of_match_table = sunxi_codec_of_match, + }, + .probe = sunxi_codec_probe, + .remove = sunxi_codec_dev_remove, +}; +module_platform_driver(sunxi_codec_driver); + +MODULE_ALIAS("platform:sunxi-codec"); +MODULE_DESCRIPTION("sunxi CODEC ALSA codec driver"); +MODULE_AUTHOR("software"); +MODULE_LICENSE("GPL v2"); +
Missing DMA buffer was caused by missing platform_name.
static struct snd_soc_dai_link cdc_dai = { .name = "cdc", .stream_name = "CDC PCM", .codec_dai_name = "sunxi-codec-dai", .cpu_dai_name = "1c22c00.codec", .codec_name = "1c22c00.codec", .platform_name = "1c22c00.codec",
Something is still messed up...
root@linaro-developer:~# aplay x.wav Playing WAVE 'x.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono aplay: pcm_write:1939: write error: Input/output error root@linaro-developer:~#
On Tue, Jul 1, 2014 at 11:36 AM, Jon Smirl jonsmirl@gmail.com wrote:
First attempt at a driver for the internal codec on the Allwinner A20. This codec is a combination codec and DAI, it is all on-chip.
So why don't I have a DMA buffer? Any other hints to get this working would be appreciated.
[ 31.910394] pcm_sanity_check (null) (null) [ 31.910415] ------------[ cut here ]------------ [ 31.910440] WARNING: CPU: 1 PID: 738 at sound/core/pcm_lib.c:2090 pcm_sanity_check+0x70/0x98() [ 31.910448] Modules linked in: brcmfmac brcmutil [ 31.910470] CPU: 1 PID: 738 Comm: aplay Not tainted 3.15.0+ #161 [ 31.910508] [<c001513c>] (unwind_backtrace) from [<c0011d08>] (show_stack+0x18/0x1c) [ 31.910528] [<c0011d08>] (show_stack) from [<c0566934>] (dump_stack+0x78/0x94) [ 31.910549] [<c0566934>] (dump_stack) from [<c00237c4>] (warn_slowpath_common+0x6c/0x90) [ 31.910567] [<c00237c4>] (warn_slowpath_common) from [<c0023890>] (warn_slowpath_null+0x20/0x28) [ 31.910585] [<c0023890>] (warn_slowpath_null) from [<c03f7e64>] (pcm_sanity_check+0x70/0x98) [ 31.910602] [<c03f7e64>] (pcm_sanity_check) from [<c03faa08>] (snd_pcm_lib_write+0x1c/0x74) [ 31.910619] [<c03faa08>] (snd_pcm_lib_write) from [<c03f6400>] (snd_pcm_playback_ioctl1+0x3c8/0x4a8) [ 31.910637] [<c03f6400>] (snd_pcm_playback_ioctl1) from [<c013762c>] (do_vfs_ioctl+0x4d4/0x594) [ 31.910653] [<c013762c>] (do_vfs_ioctl) from [<c0137740>] (SyS_ioctl+0x54/0x7c) [ 31.910668] [<c0137740>] (SyS_ioctl) from [<c000e420>] (ret_fast_syscall+0x0/0x30) [ 31.910677] ---[ end trace 86fcd83010b5b5b5 ]---
sound/soc/sunxi/sunxi_codec_main.c | 687 ++++++++++++++++++++++++++++++++++++ 1 file changed, 687 insertions(+) create mode 100644 sound/soc/sunxi/sunxi_codec_main.c
diff --git a/sound/soc/sunxi/sunxi_codec_main.c b/sound/soc/sunxi/sunxi_codec_main.c new file mode 100644 index 0000000..4070bfe --- /dev/null +++ b/sound/soc/sunxi/sunxi_codec_main.c @@ -0,0 +1,687 @@ +/*
- Licensed under the GPL-2.
- */
+#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include <linux/regmap.h>
+#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/dmaengine_pcm.h>
+//Codec Register +#define SUNXI_DAC_DPC (0x00) +#define SUNXI_DAC_FIFOC (0x04) +#define SUNXI_DAC_FIFOS (0x08) +#define SUNXI_DAC_TXDATA (0x0c) +#define SUNXI_DAC_ACTL (0x10) +#define SUNXI_DAC_TUNE (0x14) +#define SUNXI_DAC_DEBUG (0x18) +#define SUNXI_ADC_FIFOC (0x1c) +#define SUNXI_ADC_FIFOS (0x20) +#define SUNXI_ADC_RXDATA (0x24) +#define SUNXI_ADC_ACTL (0x28) +#define SUNXI_ADC_DEBUG (0x2c) +#define SUNXI_DAC_TXCNT (0x30) +#define SUNXI_ADC_RXCNT (0x34) +#define SUNXI_BIAS_CRT (0x38) +#define SUNXI_MIC_CRT (0x3c) +#define SUNXI_CODEC_REGS_NUM (13)
+#define DAIFMT_16BITS (16) +#define DAIFMT_20BITS (20)
+#define DAIFMT_BS_MASK (~(1<<16)) //FIFO big small mode mask +#define DAIFMT_BITS_MASK (~(1<<5)) //FIFO Bits select mask,not used yet. +#define SAMPLE_RATE_MASK (~(7<<29)) //Sample Rate slect mask
+#define DAC_EN (31) +#define DIGITAL_VOL (12) +//For CODEC OLD VERSION +#define DAC_VERSION (23)
+#define DAC_CHANNEL (6) +#define LAST_SE (26) +#define TX_FIFO_MODE (24) +#define DRA_LEVEL (21) +#define TX_TRI_LEVEL (8) +#define DAC_MODE (6) //not used yet +#define TASR (5) //not used yet +#define DAC_DRQ (4) +#define DAC_FIFO_FLUSH (0)
+#define VOLUME (0) +#define PA_MUTE (6) +#define MIXPAS (7) +#define DACPAS (8) +#define MIXEN (29) +#define DACAEN_L (30) +#define DACAEN_R (31)
+#define ADC_DIG_EN (28) +#define RX_FIFO_MODE (24) +#define RX_TRI_LEVEL (8) +#define ADC_MODE (7) +#define RASR (6) +#define ADC_DRQ (4) +#define ADC_FIFO_FLUSH (0)
+#define ADC_LF_EN (31) +#define ADC_RI_EN (30) +#define ADC_EN (30) +#define MIC1_EN (29) +#define MIC2_EN (28) +#define VMIC_EN (27) +#define MIC_GAIN (25) +#define ADC_SELECT (17) +#define PA_ENABLE (4) +#define HP_DIRECT (3)
+enum sunxi_device_id {SUN4A, SUN4I, SUN5I, SUN7I};
+struct sunxi_priv {
struct regmap *regmap;
int irq;
struct clk *clk_apb, *clk_pll2, *clk_module;
enum sunxi_device_id id;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_dmaengine_dai_dma_data capture_dma_data;
+};
+static int codec_play_start(struct sunxi_priv *priv) +{ +#ifdef JDS
if (gpio_pa_shutdown)
gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl");
+#endif
//flush TX FIFO
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH);
//enable dac drq
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x1 << DAC_DRQ);
return 0;
+}
+static int codec_play_stop(struct sunxi_priv *priv) +{
//pa mute
+#ifdef JDS
if (gpio_pa_shutdown)
gpio_write_one_pin_value(gpio_pa_shutdown, 0, "audio_pa_ctrl");
+#endif
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << PA_MUTE);
mdelay(5);
//disable dac drq
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_DRQ, 0x0 << DAC_DRQ);
//pa mute
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << PA_MUTE, 0x0 << PA_MUTE);
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_L, 0x0 << DACAEN_L);
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_R, 0x0 << DACAEN_R);
return 0;
+}
+static int codec_capture_start(struct sunxi_priv *priv) +{
//enable adc drq
+#ifdef JDS
if (gpio_pa_shutdown)
gpio_write_one_pin_value(gpio_pa_shutdown, 1, "audio_pa_ctrl");
+#endif
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x1 << ADC_DRQ);
return 0;
+}
+static int codec_capture_stop(struct sunxi_priv *priv) +{
//disable adc drq
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DRQ, 0x0 << ADC_DRQ);
//enable mic1 pa
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << MIC1_EN, 0x0 << MIC1_EN);
//enable VMIC
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << VMIC_EN, 0x0 << VMIC_EN);
if (priv->id == SUN7I) {
regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 8, 0x0 << 8);
}
//enable adc digital
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DIG_EN, 0x0 << ADC_DIG_EN);
//set RX FIFO mode
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << RX_FIFO_MODE, 0x0 << RX_FIFO_MODE);
//flush RX FIFO
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_FIFO_FLUSH, 0x0 << ADC_FIFO_FLUSH);
//enable adc1 analog
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 0x0 << ADC_EN);
return 0;
+}
+static int sunxi_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_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_card *card = codec->card;
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
printk("JDS - sunxi_codec_trigger 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_CAPTURE)
codec_capture_start(priv);
else
codec_play_start(priv);
break; break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
codec_capture_stop(priv);
else
codec_play_stop(priv);
break;
default:
return -EINVAL;
}
return 0;
+}
+static int sunxi_codec_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_card *card = codec->card;
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
printk("JDS - sunxi_codec_prepare\n");
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){
regmap_update_bits(priv->regmap, SUNXI_DAC_DPC, 0x1 << DAC_EN, 0x1 << DAC_EN);
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << DAC_FIFO_FLUSH, 0x1 << DAC_FIFO_FLUSH);
//set TX FIFO send drq level
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0xf << TX_TRI_LEVEL, 0xf << TX_TRI_LEVEL);
if (substream->runtime->rate > 32000) {
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 28, 0x0 << 28);
} else {
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << 28, 0x1 << 28);
}
//set TX FIFO MODE
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << TX_FIFO_MODE, 0x1 << TX_FIFO_MODE);
//send last sample when dac fifo under run
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 0x1 << LAST_SE, 0x0 << LAST_SE);
//enable dac analog
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_L, 0x1 << DACAEN_L);
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACAEN_R, 0x1 << DACAEN_R);
//enable dac to pa
regmap_update_bits(priv->regmap, SUNXI_DAC_ACTL, 0x1 << DACPAS, 0x1 << DACPAS);
} else {
//enable mic1 pa
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << MIC1_EN, 0x1 << MIC1_EN);
//mic1 gain 32dB
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << 25, 0x1 << 25);
//enable VMIC
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x1 << VMIC_EN, 0x1 << VMIC_EN);
if (priv->id == SUN7I) {
/* boost up record effect */
regmap_update_bits(priv->regmap, SUNXI_DAC_TUNE, 0x3 << 8, 0x1 << 8);
}
//enable adc digital
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_DIG_EN, 0x1 << ADC_DIG_EN);
//set RX FIFO mode
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << RX_FIFO_MODE, 0x1 << RX_FIFO_MODE);
//flush RX FIFO
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0x1 << ADC_FIFO_FLUSH, 0x1 << ADC_FIFO_FLUSH);
//set RX FIFO rec drq level
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 0xf << RX_TRI_LEVEL, 0x7 << RX_TRI_LEVEL);
//enable adc1 analog
regmap_update_bits(priv->regmap, SUNXI_ADC_ACTL, 0x3 << ADC_EN, 0x3 << ADC_EN);
}
return 0;
+}
+static int sunxi_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_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_card *card = codec->card;
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
unsigned int rate = params_rate(params);
printk("CLK - sunxi_codec_hw_params substream %p runtime %p\n", substream, rtd);
switch (params_rate(params)) {
case 44100:
case 22050:
case 11025:
default:
clk_set_rate(priv->clk_pll2, 22579200);
clk_set_rate(priv->clk_module, 22579200);
break;
case 192000:
case 96000:
case 48000:
case 32000:
case 24000:
case 16000:
case 12000:
case 8000:
clk_set_rate(priv->clk_pll2, 24576000);
clk_set_rate(priv->clk_module, 24576000);
break;
}
switch (params_rate(params)) {
default:
case 44100:
rate = 0;
break;
case 22050:
rate = 2;
break;
case 11025:
rate = 4;
break;
case 192000:
rate = 6;
break;
case 96000:
rate = 7;
break;
case 48000:
rate = 0;
break;
case 32000:
rate = 1;
break;
case 24000:
rate = 2;
break;
case 16000:
rate = 3;
break;
case 12000:
rate = 4;
break;
case 8000:
rate = 5;
break;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 7 << 29, rate << 29);
if (substream->runtime->channels == 1)
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 6, 1 << 6);
else
regmap_update_bits(priv->regmap, SUNXI_DAC_FIFOC, 1 << 6, 0 << 6);
} else {
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 7 << 29, rate << 29);
if (substream->runtime->channels == 1)
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 7, 1 << 7);
else
regmap_update_bits(priv->regmap, SUNXI_ADC_FIFOC, 1 << 7, 0 << 7);
}
return 0;
+}
+static const struct snd_kcontrol_new sun7i_dac_ctls[] = {
/*SUNXI_DAC_ACTL = 0x10,PAVOL*/
SOC_SINGLE("Master Playback Volume", SUNXI_DAC_ACTL, 0, 0x3f, 0),
SOC_SINGLE("Playback Switch", SUNXI_DAC_ACTL, 6, 1, 0), //全局输出开关
SOC_SINGLE("FmL Switch", SUNXI_DAC_ACTL, 17, 1, 0), //Fm左开关
SOC_SINGLE("FmR Switch", SUNXI_DAC_ACTL, 16, 1, 0), //Fm右开关
SOC_SINGLE("LineL Switch", SUNXI_DAC_ACTL, 19, 1, 0), //Line左开关
SOC_SINGLE("LineR Switch", SUNXI_DAC_ACTL, 18, 1, 0), //Line右开关
SOC_SINGLE("Ldac Left Mixer", SUNXI_DAC_ACTL, 15, 1, 0),
SOC_SINGLE("Rdac Right Mixer", SUNXI_DAC_ACTL, 14, 1, 0),
SOC_SINGLE("Ldac Right Mixer", SUNXI_DAC_ACTL, 13, 1, 0),
SOC_SINGLE("Mic Input Mux", SUNXI_DAC_ACTL, 9, 15, 0), //from bit 9 to bit 12.Mic(麦克风)输入静音
SOC_SINGLE("MIC output volume", SUNXI_DAC_ACTL, 20, 7, 0),
/* FM Input to output mixer Gain Control
* From -4.5db to 6db,1.5db/step,default is 0db
* -4.5db:0x0,-3.0db:0x1,-1.5db:0x2,0db:0x3
* 1.5db:0x4,3.0db:0x5,4.5db:0x6,6db:0x7
*/
SOC_SINGLE("Fm output Volume", SUNXI_DAC_ACTL, 23, 7, 0),
/* Line-in gain stage to output mixer Gain Control
* 0:-1.5db,1:0db
*/
SOC_SINGLE("Line output Volume", SUNXI_DAC_ACTL, 26, 1, 0),
SOC_SINGLE("Master Capture Mute", SUNXI_ADC_ACTL, 4, 1, 0),
SOC_SINGLE("Right Capture Mute", SUNXI_ADC_ACTL, 31, 1, 0),
SOC_SINGLE("Left Capture Mute", SUNXI_ADC_ACTL, 30, 1, 0),
SOC_SINGLE("Linein Pre-AMP", SUNXI_ADC_ACTL, 13, 7, 0),
SOC_SINGLE("LINEIN APM Volume", SUNXI_MIC_CRT, 13, 0x7, 0),
/* ADC Input Gain Control, capture volume
* 000:-4.5db,001:-3db,010:-1.5db,011:0db,100:1.5db,101:3db,110:4.5db,111:6db
*/
SOC_SINGLE("Capture Volume", SUNXI_ADC_ACTL, 20, 7, 0),
/*
* MIC2 pre-amplifier Gain Control
* 00:0db,01:35db,10:38db,11:41db
*/
SOC_SINGLE("MicL Volume", SUNXI_ADC_ACTL, 25, 3, 0), //mic左音量
SOC_SINGLE("MicR Volume", SUNXI_ADC_ACTL, 23, 3, 0), //mic右音量
SOC_SINGLE("Mic2 Boost", SUNXI_ADC_ACTL, 29, 1, 0),
SOC_SINGLE("Mic1 Boost", SUNXI_ADC_ACTL, 28, 1, 0),
SOC_SINGLE("Mic Power", SUNXI_ADC_ACTL, 27, 1, 0),
SOC_SINGLE("ADC Input Mux", SUNXI_ADC_ACTL, 17, 7, 0), //ADC输入静音
SOC_SINGLE("Mic2 gain Volume", SUNXI_MIC_CRT, 26, 7, 0),
/*
* MIC1 pre-amplifier Gain Control
* 00:0db,01:35db,10:38db,11:41db
*/
SOC_SINGLE("Mic1 gain Volume", SUNXI_MIC_CRT, 29, 3, 0),
+};
+static int sunxi_codec_dai_probe(struct snd_soc_dai *dai) +{
struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai);
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
printk("CLK - sunxi_codec_dai_probe %p\n", priv);
snd_soc_dai_init_dma_data(dai, &priv->playback_dma_data, &priv->capture_dma_data);
return 0;
+}
+static int sunxi_codec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_card *card = codec->card;
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
int ret;
printk("JDS - CLK sunxi_codec_startup %p %p\n", codec, priv->clk_module);
ret = clk_prepare_enable(priv->clk_module);
if (ret)
return ret;
printk("JDS - sunxi_codec_startup - ok\n");
return 0;
+}
+static void sunxi_codec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
+{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_card *card = codec->card;
struct sunxi_priv *priv = snd_soc_card_get_drvdata(card);
printk("JDS - sunxi_codec_shutdown\n");
clk_disable_unprepare(priv->clk_module);
+}
+static const struct snd_soc_dai_ops sunxi_codec_dai_ops = {
.startup = sunxi_codec_startup,
.shutdown = sunxi_codec_shutdown,
.trigger = sunxi_codec_trigger,
.hw_params = sunxi_codec_hw_params,
.prepare = sunxi_codec_prepare,
+};
+static struct snd_soc_dai_driver sunxi_codec_dai = {
.probe = sunxi_codec_dai_probe,
.playback = {
.channels_min = 1,
.channels_max = 2,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_22050| SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000 |SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000 |\
SNDRV_PCM_RATE_KNOT),
.rate_min = 8000,
.rate_max = 192000,
},
.ops = &sunxi_codec_dai_ops,
+};
+static const struct snd_soc_component_driver sunxi_codec_component = {
.name = "sunxi-codec",
+};
+static const struct regmap_config sunxi_codec_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUNXI_MIC_CRT,
+};
+static const struct snd_soc_dapm_widget codec_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("Mic Bias"),
SND_SOC_DAPM_OUTPUT("HP_OUT"),
SND_SOC_DAPM_INPUT("MIC_IN"),
SND_SOC_DAPM_INPUT("LINE_IN"),
+};
+static struct snd_soc_dai_link cdc_dai = {
.name = "cdc",
.stream_name = "CDC PCM",
.codec_dai_name = "sunxi-codec-dai",
.cpu_dai_name = "1c22c00.codec",
.codec_name = "1c22c00.codec",
//.init = tegra_wm8903_init,
//.ops = &tegra_wm8903_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S,
+};
+static struct snd_soc_card snd_soc_sunxi_codec = {
.name = "sunxi-codec",
.owner = THIS_MODULE,
.dai_link = &cdc_dai,
.num_links = 1,
.fully_routed = true,
+};
+static struct snd_soc_codec_driver dummy_codec = {
.controls = sun7i_dac_ctls,
.num_controls = ARRAY_SIZE(sun7i_dac_ctls),
.dapm_widgets = codec_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(codec_dapm_widgets),
+};
+#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_U8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_U16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_U24_LE | \
SNDRV_PCM_FMTBIT_S32_LE | \
SNDRV_PCM_FMTBIT_U32_LE | \
SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
+static struct snd_soc_dai_driver dummy_dai = {
.name = "sunxi-codec-dai",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = STUB_RATES,
.formats = STUB_FORMATS,
},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = STUB_RATES,
.formats = STUB_FORMATS,
},
+};
+static const struct of_device_id sunxi_codec_of_match[] = {
{ .compatible = "allwinner,sun4i-a10a-codec", .data = (void *)SUN4A},
{ .compatible = "allwinner,sun4i-a10-codec", .data = (void *)SUN4I},
{ .compatible = "allwinner,sun5i-a13-codec", .data = (void *)SUN5I},
{ .compatible = "allwinner,sun7i-a20-codec", .data = (void *)SUN7I},
{}
+}; +MODULE_DEVICE_TABLE(of, sunxi_codec_of_match);
+static int sunxi_codec_probe(struct platform_device *pdev) +{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &snd_soc_sunxi_codec;
const struct of_device_id *of_id;
struct device *dev = &pdev->dev;
struct sunxi_priv *priv;
struct resource *res;
void __iomem *base;
int ret;
if (!of_device_is_available(np))
return -ENODEV;
of_id = of_match_device(sunxi_codec_of_match, dev);
if (!of_id)
return -EINVAL;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, priv);
priv->id = (int)of_id->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
&sunxi_codec_regmap_config);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
priv->irq = irq_of_parse_and_map(np, 0);
if (!priv->irq) {
dev_err(dev, "no irq for node %s\n", np->full_name);
return -ENXIO;
}
/* Clock */
priv->clk_apb = devm_clk_get(dev, "apb");
if (IS_ERR(priv->clk_apb)) {
dev_err(dev, "failed to get apb clock.\n");
return PTR_ERR(priv->clk_apb);
}
priv->clk_pll2 = devm_clk_get(dev, "pll2");
if (IS_ERR(priv->clk_pll2)) {
dev_err(dev, "failed to get pll2 clock.\n");
return PTR_ERR(priv->clk_pll2);
}
priv->clk_module = devm_clk_get(dev, "codec");
if (IS_ERR(priv->clk_module)) {
dev_err(dev, "failed to get codec clock.\n");
return PTR_ERR(priv->clk_module);
}
printk("CLK = card %p priv %p priv->clk_module %p\n", card, priv, priv->clk_module);
ret = clk_set_rate(priv->clk_pll2, 24576000);
if (ret) {
dev_err(dev, "set codec base clock rate failed!\n");
return ret;
}
if (clk_prepare_enable(priv->clk_pll2)) {
dev_err(dev, "try to enable clk_pll2 failed\n");
return -EINVAL;
}
if (clk_prepare_enable(priv->clk_apb)) {
dev_err(dev, "try to enable clk_apb failed\n");
return -EINVAL;
}
priv->playback_dma_data.addr = res->start + SUNXI_DAC_TXDATA;
priv->playback_dma_data.maxburst = 4;
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
priv->capture_dma_data.addr = res->start + SUNXI_ADC_RXDATA;
priv->capture_dma_data.maxburst = 4;
priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
ret = snd_soc_register_codec(&pdev->dev, &dummy_codec, &dummy_dai, 1);
ret = devm_snd_soc_register_component(&pdev->dev, &sunxi_codec_component, &sunxi_codec_dai, 1);
if (ret)
goto err_clk_disable;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
goto err_clk_disable;
ret = snd_soc_of_parse_audio_routing(card, "routing");
if (ret)
goto err;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
goto err_fini_utils;
}
return 0;
+err_fini_utils: +err: +err_clk_disable:
clk_disable_unprepare(priv->clk_module);
clk_disable_unprepare(priv->clk_apb);
clk_disable_unprepare(priv->clk_pll2);
return ret;
+}
+static int sunxi_codec_dev_remove(struct platform_device *pdev) +{
struct sunxi_priv *priv = platform_get_drvdata(pdev);
clk_disable_unprepare(priv->clk_module);
clk_disable_unprepare(priv->clk_apb);
clk_disable_unprepare(priv->clk_pll2);
return 0;
+}
+static struct platform_driver sunxi_codec_driver = {
.driver = {
.name = "sunxi-codec",
.owner = THIS_MODULE,
.of_match_table = sunxi_codec_of_match,
},
.probe = sunxi_codec_probe,
.remove = sunxi_codec_dev_remove,
+}; +module_platform_driver(sunxi_codec_driver);
+MODULE_ALIAS("platform:sunxi-codec"); +MODULE_DESCRIPTION("sunxi CODEC ALSA codec driver"); +MODULE_AUTHOR("software"); +MODULE_LICENSE("GPL v2");
On 07/02/2014 03:05 PM, jonsmirl@gmail.com wrote:
Missing DMA buffer was caused by missing platform_name.
static struct snd_soc_dai_link cdc_dai = { .name = "cdc", .stream_name = "CDC PCM", .codec_dai_name = "sunxi-codec-dai", .cpu_dai_name = "1c22c00.codec", .codec_name = "1c22c00.codec", .platform_name = "1c22c00.codec",
Something is still messed up...
root@linaro-developer:~# aplay x.wav Playing WAVE 'x.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono aplay: pcm_write:1939: write error: Input/output error root@linaro-developer:~#
This error is usually caused by a stuck DMA channel. IOW, the DMA pointer is not moving forward, and after some seconds, ALSA decides that something can't be right and shuts down the stream again.
Maybe start with adding printks to your DMA controller's IRQ routine or something.
Daniel
participants (3)
-
Daniel Mack
-
Jon Smirl
-
jonsmirl@gmail.com