Skip to content

Instantly share code, notes, and snippets.

@h-yamamo
Last active April 7, 2019 15:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save h-yamamo/72ef520058b7971ff02d to your computer and use it in GitHub Desktop.
Save h-yamamo/72ef520058b7971ff02d to your computer and use it in GitHub Desktop.
alternative sound module in linux-4.4, add support Capture, for Allwinner A10,A13,R8,A20 Audio Codec
/*
* 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>
*
* 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>
#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_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/tlv.h>
#include <sound/initval.h>
#include <sound/dmaengine_pcm.h>
/* Audio Codec DAC register offsets and bit fields */
#define SUN4I_DAC_DPC (0x00) /* Digital Part Control */
#define SUN4I_DAC_DPC_EN_DA (31)
#define SUN4I_DAC_DPC_MODQU (25)
#define SUN4I_DAC_DPC_DWA (24)
#define SUN4I_DAC_DPC_HPF_EN (18)
#define SUN4I_DAC_DPC_DVOL (12)
#define SUN4I_DAC_FIFOC (0x04) /* FIFO Control */
#define SUN4I_DAC_FIFOC_DAC_FS (29)
#define SUN4I_DAC_FIFOC_FIR_VERSION (28)
#define SUN4I_DAC_FIFOC_SEND_LASAT (26)
#define SUN4I_DAC_FIFOC_FIFO_MODE (24)
#define SUN4I_DAC_FIFOC_DAC_DRQ_CLR_CNT (21)
#define SUN4I_DAC_FIFOC_TX_TRIG_LEVEL (8)
#define SUN4I_DAC_FIFOC_ADDA_LOOP_EN (7)
#define SUN4I_DAC_FIFOC_DAC_MONO_EN (6)
#define SUN4I_DAC_FIFOC_TX_SAMPLE_BITS (5)
#define SUN4I_DAC_FIFOC_DAC_DRQ_EN (4)
#define SUN4I_DAC_FIFOC_DAC_IRQ_EN (3)
#define SUN4I_DAC_FIFOC_FIFO_UNDERRUN_IRQ_EN (2)
#define SUN4I_DAC_FIFOC_FIFO_OVERRUN_IRQ_EN (1)
#define SUN4I_DAC_FIFOC_FIFO_FLUSH (0)
#define SUN4I_DAC_FIFOS (0x08) /* FIFO Status */
#define SUN4I_DAC_FIFOS_TX_EMPTY (23)
#define SUN4I_DAC_FIFOS_TXE_CNT (8)
#define SUN4I_DAC_FIFOS_TXE_INT (3)
#define SUN4I_DAC_FIFOS_TXU_INT (2)
#define SUN4I_DAC_FIFOS_TXO_INT (1)
#define SUN4I_DAC_TXDATA (0x0c) /* TX Data */
#define SUN4I_DAC_TXDATA_TX_DATA (0)
#define SUN4I_DAC_ACTL (0x10) /* Analog Control */
#define SUN4I_DAC_ACTL_DACAREN (31)
#define SUN4I_DAC_ACTL_DACALEN (30)
#define SUN4I_DAC_ACTL_MIXEN (29)
#define SUN4I_DAC_ACTL_LNG (26)
#define SUN4I_DAC_ACTL_FMG (23)
#define SUN4I_DAC_ACTL_MICG (20)
#define SUN4I_DAC_ACTL_LLNS (19)
#define SUN4I_DAC_ACTL_RLNS (18)
#define SUN4I_DAC_ACTL_LFMS (17)
#define SUN4I_DAC_ACTL_RFMS (16)
#define SUN4I_DAC_ACTL_LDACLMIXS (15)
#define SUN4I_DAC_ACTL_RDACRMIXS (14)
#define SUN4I_DAC_ACTL_LDACRMIXS (13)
#define SUN4I_DAC_ACTL_MIC1LS (12)
#define SUN4I_DAC_ACTL_MIC1RS (11)
#define SUN4I_DAC_ACTL_MIC2LS (10)
#define SUN4I_DAC_ACTL_MIC2RS (9)
#define SUN4I_DAC_ACTL_DACPAS (8)
#define SUN4I_DAC_ACTL_MIXPAS (7)
#define SUN4I_DAC_ACTL_PAMUTE (6)
#define SUN4I_DAC_ACTL_PAVOL (0)
#define SUN4I_DAC_TUNE (0x14) /* Undocumented */
#define SUN4I_DAC_DEBUG (0x18) /* Undocumented */
/* Audio Codec ADC register offsets and bit fields */
#define SUN4I_ADC_FIFOC (0x1c) /* FIFO Control */
#define SUN4I_ADC_FIFOC_ADFS (29)
#define SUN4I_ADC_FIFOC_EN_AD (28)
#define SUN4I_ADC_FIFOC_RX_FIFO_MODE (24)
#define SUN4I_ADC_FIFOC_RX_FIFO_TRG_LEVEL (8)
#define SUN4I_ADC_FIFOC_ADC_MONO_EN (7)
#define SUN4I_ADC_FIFOC_RX_SAMPLE_BITS (6)
#define SUN4I_ADC_FIFOC_ADC_DRQ_EN (4)
#define SUN4I_ADC_FIFOC_ADC_IRQ_EN (3)
#define SUN4I_ADC_FIFOC_ADC_OVERRUN_IRQ_EN (1)
#define SUN4I_ADC_FIFOC_ADC_FIFO_FLUSH (0)
#define SUN4I_ADC_FIFOS (0x20) /* FIFO Status */
#define SUN4I_ADC_FIFOS_RXA (23)
#define SUN4I_ADC_FIFOS_RXA_CNT (8)
#define SUN4I_ADC_FIFOS_RXA_INT (3)
#define SUN4I_ADC_FIFOS_RXO_INT (1)
#define SUN4I_ADC_RXDATA (0x24) /* RX Data */
#define SUN4I_ADC_RXDATA_RX_DATA (0)
#define SUN4I_ADC_ACTL (0x28) /* Analog Control */
#define SUN4I_ADC_ACTL_ADCREN (31)
#define SUN4I_ADC_ACTL_ADCLEN (30)
#define SUN4I_ADC_ACTL_PREG1EN (29)
#define SUN4I_ADC_ACTL_PREG2EN (28)
#define SUN4I_ADC_ACTL_VMICEN (27)
#define SUN4I_ADC_ACTL_PREG1 (25) /* A10-13 R8 */
#define SUN4I_ADC_ACTL_PREG2 (23) /* A10-13 R8 */
#define SUN4I_ADC_ACTL_ADCG (20)
#define SUN4I_ADC_ACTL_ADCIS (17)
#define SUN4I_ADC_ACTL_LNRDF (16)
#define SUN4I_ADC_ACTL_LNPREG (13)
#define SUN4I_ADC_ACTL_MIC1NEN (12) /* A10-13 R8 */
#define SUN4I_ADC_ACTL_LHPOUTN (11) /* A20 */
#define SUN4I_ADC_ACTL_RHPOUTN (10) /* A20 */
#define SUN4I_ADC_ACTL_DITHER (8)
#define SUN4I_ADC_ACTL_DITHER_CLK_SELECT (6) /* A20 */
#define SUN4I_ADC_ACTL_PA_EN (4)
#define SUN4I_ADC_ACTL_DDE (3)
#define SUN4I_ADC_ACTL_COMPTEN (2)
#define SUN4I_ADC_ACTL_PTDBS (0)
#define SUN4I_ADC_DEBUG (0x2c) /* Undocumented */
/* counter registers */
#define SUN4I_DAC_CNT (0x30) /* TX FIFO Counter */
#define SUN4I_DAC_CNT_TX_CNT (0)
#define SUN4I_ADC_CNT (0x34) /* RX FIFO Counter */
#define SUN4I_ADC_CNT_RX_CNT (0)
/* A20 registers */
#define SUN4I_SYS_VERI (0x38) /* System Calibration Verify */
#define SUN4I_SYS_VERI_BIASCALIVERIFY (23)
#define SUN4I_SYS_VERI_BIASVERIFY (17)
#define SUN4I_SYS_VERI_BIASCALI (11)
#define SUN4I_SYS_VERI_DA16CALIVERIFY (10)
#define SUN4I_SYS_VERI_DA16VERIFY (5)
#define SUN4I_SYS_VERI_DA16CALI (0)
#define SUN4I_MIC_PHONE_CAL (0x3c) /* MIC Gain & Phone out Ctrl */
#define SUN4I_MIC_PHONE_CAL_PREG1 (29)
#define SUN4I_MIC_PHONE_CAL_PREG2 (26)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTG (5)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTEN (4)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTS3 (3)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTS2 (2)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTS1 (1)
#define SUN4I_MIC_PHONE_CAL_PHONEOUTS0 (0)
/* supported rates and formats */
#define SUN4I_DAC_PCM_RATES (SNDRV_PCM_RATE_8000_48000 | \
SNDRV_PCM_RATE_96000 | \
SNDRV_PCM_RATE_192000 | \
SNDRV_PCM_RATE_KNOT)
#define SUN4I_ADC_PCM_RATES (SNDRV_PCM_RATE_8000_48000 | \
SNDRV_PCM_RATE_KNOT)
#define SUN4I_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
/* Supported SoC families - used for quirks */
enum sun4i_soc_family {
SUN4IA, /* A10 SoC - revision A */
SUN4I, /* A10 SoC - later revisions */
SUN5IS, /* A10s SoCs */
SUN5I, /* A13, R8 SoCs */
SUN7I, /* A20 SoC */
};
struct sun4i_priv {
struct device *dev;
struct regmap *regmap;
struct clk *clk_apb;
struct clk *clk_module;
enum sun4i_soc_family revision;
struct snd_dmaengine_dai_dma_data playback_dma_data;
struct snd_dmaengine_dai_dma_data capture_dma_data;
};
/* convenient macros */
#define SUN4I_REGMAP_UPDBIT(map, reg, shift, x) \
regmap_update_bits(map, reg, 1u << shift, (x) << shift)
#define SUN4I_REGMAP_UPDVAL(map, reg, shift, bits, val) \
regmap_update_bits(map, reg, \
(0xffffffff >> (32 - (bits))) << shift, \
(val) << shift)
static void sun4iac_play_start(struct sun4i_priv *priv)
{
/* enable DAC DRQ */
SUN4I_REGMAP_UPDBIT(priv->regmap, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_DAC_DRQ_EN, 1);
/* flush TX FIFO */
SUN4I_REGMAP_UPDBIT(priv->regmap, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_FIFO_FLUSH, 0);
}
static void sun4iac_play_stop(struct sun4i_priv *priv)
{
/* disable DAC DRQ */
SUN4I_REGMAP_UPDBIT(priv->regmap, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_DAC_DRQ_EN, 0);
}
static void sun4iac_capture_start(struct sun4i_priv *priv)
{
struct regmap *rgmp = priv->regmap;
/* enable ADC digital */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_EN_AD, 1);
/* enable ADC DRQ */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_DRQ_EN, 1);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_FIFO_FLUSH, 0);
}
static void sun4iac_capture_stop(struct sun4i_priv *priv)
{
struct regmap *rgmp = priv->regmap;
/* disable ADC DRQ */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_DRQ_EN, 0);
/* flush RX FIFO */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_FIFO_FLUSH, 1);
/* disable ADC digital */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_EN_AD, 0);
/* disable ADC analog # ADCREN + ADCLEN (R+L) */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_ADCLEN, 2, 0);
if (priv->revision == SUN7I)
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_TUNE, 8, 2, 0);
}
static int sun4iac_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sun4i_priv *priv = snd_soc_card_get_drvdata(rtd->card);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sun4iac_play_start(priv);
else
sun4iac_capture_start(priv);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
sun4iac_play_stop(priv);
else
sun4iac_capture_stop(priv);
break;
default:
return -EINVAL;
}
return 0;
}
static int sun4iac_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sun4i_priv *priv = snd_soc_card_get_drvdata(rtd->card);
struct regmap *rgmp = priv->regmap;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_FIFO_FLUSH, 1);
if (substream->runtime->rate > 32000)
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_FIR_VERSION, 0);
else
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_FIR_VERSION, 1);
/* send last sample when DAC FIFO under run */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_SEND_LASAT, 0);
}
else {
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_FIFO_FLUSH, 1);
/* enable ADC analog # ADCREN + ADCLEN (R+L) */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_ADCLEN, 2, 0x3);
if (priv->revision == SUN7I)
/* boost up record effect */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_TUNE, 8, 2, 0x3);
}
return 0;
}
static int sun4iac_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sun4i_priv *priv = snd_soc_card_get_drvdata(rtd->card);
struct regmap *rgmp = priv->regmap;
int is_mono = (params_channels(params) == 1);
int is_24bit = (hw_param_interval(params,
SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->
min == 32);
unsigned int rate = params_rate(params);
unsigned int hwrate = 0;
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK &&
rate > 48000)
return -ENOTSUPP;
switch (rate) {
case 176400:
case 88200:
case 44100:
case 29400:
case 22050:
case 14700:
case 11025:
case 7350:
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_module, 24576000);
break;
default:
return -ENOTSUPP;
}
switch (rate) {
case 192000:
case 176400:
hwrate = 6;
break;
case 96000:
case 88200:
hwrate = 7;
break;
case 32000:
case 29400:
hwrate = 1;
break;
case 24000:
case 22050:
hwrate = 2;
break;
case 16000:
case 14700:
hwrate = 3;
break;
case 12000:
case 11025:
hwrate = 4;
break;
case 8000:
case 7350:
hwrate = 5;
break;
default: /* 48000 or 44100 */
break;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_DAC_FS, 3, hwrate);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_DAC_MONO_EN, is_mono);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_TX_SAMPLE_BITS, is_24bit);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_FIFOC,
SUN4I_DAC_FIFOC_FIFO_MODE, !is_24bit);
if (is_24bit)
priv->playback_dma_data.addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
else
priv->playback_dma_data.addr_width =
DMA_SLAVE_BUSWIDTH_2_BYTES;
}
else { /* capture */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADFS, 3, hwrate);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_ADC_MONO_EN, is_mono);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_RX_SAMPLE_BITS, is_24bit);
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_FIFOC,
SUN4I_ADC_FIFOC_RX_FIFO_MODE, !is_24bit);
if (is_24bit)
priv->capture_dma_data.addr_width =
DMA_SLAVE_BUSWIDTH_4_BYTES;
else
priv->capture_dma_data.addr_width =
DMA_SLAVE_BUSWIDTH_2_BYTES;
}
return 0;
}
/* initialize */
static int sun4iac_late_probe(struct snd_soc_card *card)
{
struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai;
struct sun4i_priv *priv = snd_soc_card_get_drvdata(card);
struct regmap *rgmp = priv->regmap;
snd_soc_dai_init_dma_data(cpu_dai, &priv->playback_dma_data,
&priv->capture_dma_data);
/* DAC enable */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_DPC, SUN4I_DAC_DPC_EN_DA, 1);
if (priv->revision > SUN4IA)
/* set Digital Volume 0dB */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_DPC,
SUN4I_DAC_DPC_DVOL, 6, 2);
/* internal DAC Analog enable # DACAREN + DACALEN (R+L) */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_DACALEN, 2, 0x3);
/* set DAC to Mixer mux # LDACLMIXS + RDACRMIXS (Stereo) */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_LDACRMIXS, 3, 0x6);
/* set Mic1 to Mixer L+R # MIC1LS + MIC1RS */
SUN4I_REGMAP_UPDVAL(rgmp, SUN4I_DAC_ACTL, SUN4I_DAC_ACTL_MIC1RS,
2, 0x3);
/* DAC to PowerAmp switch on */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_DAC_ACTL, SUN4I_DAC_ACTL_DACPAS, 1);
/* PowerAmp enable */
SUN4I_REGMAP_UPDBIT(rgmp, SUN4I_ADC_ACTL, SUN4I_ADC_ACTL_PA_EN, 1);
return 0;
}
/*** Mixer Controls ***/
static DECLARE_TLV_DB_SCALE(pa_volume_scale, -6300, 100, 1);
static DECLARE_TLV_DB_SCALE(digi_volume_scale, -7076, 116, 0);
static DECLARE_TLV_DB_SCALE(adc_gain_scale, -450, 150, 0);
static DECLARE_TLV_DB_SCALE(mico_gain_scale, -450, 150, 0);
static DECLARE_TLV_DB_SCALE(line_gain_scale, -1200, 300, 0);
static const char *mic_preamp_text[] = {"0dB", "35dB", "38dB", "41dB"};
static SOC_ENUM_SINGLE_DECL(mic1_preamp_mixer, SUN4I_MIC_PHONE_CAL,
SUN4I_MIC_PHONE_CAL_PREG1, mic_preamp_text);
static SOC_ENUM_SINGLE_DECL(mic2_preamp_mixer, SUN4I_MIC_PHONE_CAL,
SUN4I_MIC_PHONE_CAL_PREG2, mic_preamp_text);
static const char *mic_a20preamp_text[] = {"0dB", "24dB", "27dB", "30dB",
"33dB", "36dB", "39dB", "42dB"};
static SOC_ENUM_SINGLE_DECL(mic1_a20_preamp_mixer, SUN4I_MIC_PHONE_CAL,
SUN4I_MIC_PHONE_CAL_PREG1, mic_a20preamp_text);
static SOC_ENUM_SINGLE_DECL(mic2_a20_preamp_mixer, SUN4I_MIC_PHONE_CAL,
SUN4I_MIC_PHONE_CAL_PREG2, mic_a20preamp_text);
static const char *adcis_text[] = {"Line In", "FM In", "Mic1 Mono", "Mic2 Mono",
"Mic1,Mic2", "Mic1+2 Mono", "MIX Out", "Line,Mic1"};
static SOC_ENUM_SINGLE_DECL(adcis_mixer, SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_ADCIS, adcis_text);
static const char *adcis_a13_text[] = {"Mic1 Mono", "MIX Out"};
static const unsigned int adcis_a13_values[] = {0x2, 0x6};
static SOC_VALUE_ENUM_SINGLE_DECL(adcis_a13_mixer, SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_ADCIS, 0x7,
adcis_a13_text, adcis_a13_values);
static const char *mictomix_text[] = {"Off", "MIX.Rch", "MIX.Lch",
"MIX.L+R"};
/* # MIC1LS + MIC1RS (L+R) */
static SOC_ENUM_SINGLE_DECL(mic1tomix_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_MIC1RS, mictomix_text);
/* # MIC2LS + MIC2RS (L+R) */
static SOC_ENUM_SINGLE_DECL(mic2tomix_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_MIC2RS, mictomix_text);
static const char *off_on_text[] = {"Off", "On"};
static const char *off_on_lr_text[] = {"Off", "On", "L,-", "-,R"};
static const unsigned int linetomix_values[] = {0, 0x3, 0x2, 0x1};
/* # LLNS + RLNS (L+R) */
static SOC_VALUE_ENUM_SINGLE_DECL(linetomix_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_RLNS, 0x3,
off_on_lr_text, linetomix_values);
/* # LFMS + RFMS (L+R) */
static SOC_VALUE_ENUM_SINGLE_DECL(fmtomix_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_RFMS, 0x3,
off_on_lr_text, linetomix_values);
static const char *dacmixs_text[] = {"Off", "Stereo", "Mono(L)", "-,L", "-,R",
"-,LR", "L,-", "L,LR"};
static const unsigned int dacmixs_values[] = {0, 0x6, 0x5, 0x1, 0x2,
0x3, 0x4, 0x7};
/* # LDACLMIXS + RDACRMIXS + LDACRMIXS */
static SOC_VALUE_ENUM_SINGLE_DECL(dacmixs_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_LDACRMIXS, 0x7,
dacmixs_text, dacmixs_values);
static SOC_ENUM_SINGLE_DECL(mixenable_mixer, SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_MIXEN, off_on_text);
static SOC_ENUM_SINGLE_DECL(vmicen_mixer, SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_VMICEN, off_on_text);
static SOC_ENUM_SINGLE_DECL(hpfen_mixer, SUN4I_DAC_DPC,
SUN4I_DAC_DPC_HPF_EN, off_on_text);
static struct snd_kcontrol_new sun4iac_controls[20] = {
SOC_SINGLE("Mic1 Switch", SUN4I_ADC_ACTL, SUN4I_ADC_ACTL_PREG1EN, 1, 0),
SOC_SINGLE_TLV("Mic MIX Gain Volume", SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_MICG, 0x7, 0, mico_gain_scale),
SOC_ENUM("Mic1-MIX", mic1tomix_mixer),
SOC_SINGLE_TLV("Capture Volume", SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_ADCG, 0x7, 0, adc_gain_scale),
SOC_SINGLE("MIX-PA Switch", SUN4I_DAC_ACTL, SUN4I_DAC_ACTL_MIXPAS,
1, 0),
SOC_SINGLE("DAC-PA Playback Switch", SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_DACPAS, 1, 0),
SOC_ENUM("DAC-MIX Playback Enum", dacmixs_mixer),
SOC_ENUM("Mic BiasVolt", vmicen_mixer),
SOC_SINGLE("Master Playback Switch", SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_PAMUTE, 1, 0),
/* 9 fixed items plus below variable items. */
};
static const struct snd_kcontrol_new sun4iac_add_items[] = {
SOC_SINGLE_TLV("Master Playback Volume", SUN4I_DAC_ACTL,
SUN4I_DAC_ACTL_PAVOL, 0x3f, 0, pa_volume_scale),
SOC_SINGLE("Master Playback Volume", SUN4I_DAC_DPC, SUN4I_DAC_DPC_DVOL,
0x3f, 0),
SOC_SINGLE_TLV("PCM Digital Playback Volume", SUN4I_DAC_DPC,
SUN4I_DAC_DPC_DVOL, 0x3f, 1, digi_volume_scale),
SOC_ENUM("Mic1 Gain", mic1_preamp_mixer),
SOC_ENUM("Mic2 Gain", mic2_preamp_mixer),
SOC_ENUM("Mic1 Gain", mic1_a20_preamp_mixer),
SOC_ENUM("Mic2 Gain", mic2_a20_preamp_mixer),
SOC_SINGLE("Mic2 Switch", SUN4I_ADC_ACTL, SUN4I_ADC_ACTL_PREG2EN, 1, 0),
SOC_ENUM("Mic2-MIX", mic2tomix_mixer),
SOC_SINGLE_TLV("Line Gain Volume", SUN4I_ADC_ACTL,
SUN4I_ADC_ACTL_LNPREG, 0x7, 0, line_gain_scale),
SOC_ENUM("Line-MIX", linetomix_mixer),
SOC_ENUM("FM-MIX", fmtomix_mixer),
SOC_ENUM("Capture Select Capture Enum", adcis_mixer),
SOC_ENUM("Capture Select Capture Enum", adcis_a13_mixer),
SOC_ENUM("HighPassFilter Capture Enum", hpfen_mixer),
};
/*
not implemented items:
FM In Gain, MIC Out, PHONE Out, ...
*/
static const bool sun4iac_item_use[SUN7I + 1][ARRAY_SIZE(sun4iac_add_items)] =
{ /* mstvl dv micg a20 mic2 line fm adcis hp */
/* A10rA */ {0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0}, /* guess */
/* A10 */ {1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1},
/* A10s */ {1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0},
/* A13 R8 */ {1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
/* A20 */ {1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1}
};
/* item A10 A10s A13 A20
MIC 1 (L) o o o o
MIC 2 (R) o o - o
LINE IN L/R o o - o
FM IN L/R o o - o
MIC1OUT o o - -
PHONEOUT - - - o
DVOL o - o o
HPF_EN o - o o
*/
static const struct snd_kcontrol_new sun4iac_dapm_item =
SOC_DAPM_ENUM("MIX Enable Switch", mixenable_mixer);
static const struct snd_soc_dapm_widget sun4iac_widgets[] = {
/* Output */
SND_SOC_DAPM_DAC("DAC", "AC_Playback", SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MIXER_NAMED_CTL("MIXER", SND_SOC_NOPM, 0, 0,
&sun4iac_dapm_item, 1),
SND_SOC_DAPM_OUTPUT("HPOUT"),
/* Input */
SND_SOC_DAPM_INPUT("MICIN"),
SND_SOC_DAPM_ADC("ADC", "AC_Capture", SND_SOC_NOPM, 0, 0),
};
static const struct snd_soc_dapm_route sun4iac_routes[] = {
{ "MIXER", "MIX Enable Switch", "DAC" },
{ "HPOUT", NULL, "MIXER" },
{ "MIXER", "MIX Enable Switch", "MICIN" },
{ "ADC", NULL, "MIXER" },
};
/*** Board routing ***/
static const struct snd_soc_dapm_widget sun4iac_board_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Microphone", NULL),
};
static const struct snd_soc_dapm_route sun4iac_board_routes[] = {
{ "Headphone", NULL, "HPOUT" },
{ "MICIN", NULL, "Microphone" },
};
/*** Codec driver & Codec DAI ***/
static struct snd_soc_codec_driver sun4iac_codec_drv = {
.controls = sun4iac_controls,
/* .num_controls = (below func.) */
.dapm_widgets = sun4iac_widgets,
.num_dapm_widgets = ARRAY_SIZE(sun4iac_widgets),
.dapm_routes = sun4iac_routes,
.num_dapm_routes = ARRAY_SIZE(sun4iac_routes),
};
static struct snd_soc_dai_driver sun4iac_codec_dai = {
.name = "AudioCodec",
.playback = {
.stream_name = "AC_Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min = 7350,
.rate_max = 192000,
.rates = SUN4I_DAC_PCM_RATES,
.formats = SUN4I_PCM_FORMATS,
.sig_bits = 24,
},
.capture = {
.stream_name = "AC_Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min = 7350,
.rate_max = 48000,
.rates = SUN4I_ADC_PCM_RATES,
.formats = SUN4I_PCM_FORMATS,
.sig_bits = 24,
},
};
static void sun4iac_init_kcontrol_items(enum sun4i_soc_family revision)
{
int i, n = 0;
for (i = 0; i < ARRAY_SIZE(sun4iac_add_items); i++) {
if (sun4iac_item_use[revision][i]) {
sun4iac_controls[9 + n] = sun4iac_add_items[i];
n++;
}
}
sun4iac_codec_drv.num_controls = 9 + n;
}
/*** Component & CPU DAI */
static struct snd_soc_component_driver sun4iac_component = {
.name = "sun4i-codec-comp",
};
static struct snd_soc_dai_driver sun4iac_cpu_dai = {
.name = "sun4i-cpu-dai",
.playback = {
.channels_min = 1,
.channels_max = 2,
.rates = SUN4I_DAC_PCM_RATES,
.formats = SUN4I_PCM_FORMATS,
.sig_bits = 24,
},
.capture = {
.channels_min = 1,
.channels_max = 2,
.rates = SUN4I_ADC_PCM_RATES,
.formats = SUN4I_PCM_FORMATS,
.sig_bits = 24,
},
};
/*** Card and DAI Link ***/
static const struct snd_soc_ops sun4iac_ops = {
/* calling order */
/* .startup = * 1. */
/* .shutdown = */
.hw_params = sun4iac_hw_params, /* 2. */
/* .hw_free = */
.prepare = sun4iac_prepare, /* 3. */
.trigger = sun4iac_trigger, /* 4. */
};
static struct snd_soc_dai_link sun4iac_dai_link = {
.name = "sun4i-codec",
.stream_name = "sun4i PCM",
.codec_dai_name = "AudioCodec",
.dai_fmt = SND_SOC_DAIFMT_I2S,
.ops = &sun4iac_ops,
};
static struct snd_soc_card sun4iac_card = {
.name = "sun4i-codec",
.owner = THIS_MODULE,
.late_probe = sun4iac_late_probe,
.dai_link = &sun4iac_dai_link,
.num_links = 1,
.dapm_widgets = sun4iac_board_widgets,
.num_dapm_widgets = ARRAY_SIZE(sun4iac_board_widgets),
.dapm_routes = sun4iac_board_routes,
.num_dapm_routes = ARRAY_SIZE(sun4iac_board_routes),
};
static const struct regmap_config sun4iac_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = SUN4I_MIC_PHONE_CAL,
};
static const struct of_device_id sun4i_codec_of_match[] = {
{ .compatible = "allwinner,sun4i-a10a-codec",.data = (void *)SUN4IA},
{ .compatible = "allwinner,sun4i-a10-codec", .data = (void *)SUN4I },
{ .compatible = "allwinner,sun5i-a10s-codec",.data = (void *)SUN5IS},
{ .compatible = "allwinner,sun5i-a13-codec", .data = (void *)SUN5I },
{ .compatible = "allwinner,sun7i-a20-codec", .data = (void *)SUN7I },
{ }
};
MODULE_DEVICE_TABLE(of, sun4i_codec_of_match);
static int sun4i_codec_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &sun4iac_card;
const struct of_device_id *of_id;
struct device *dev = &pdev->dev;
struct sun4i_priv *priv;
struct resource *res;
void __iomem *base;
int ret;
if (!of_device_is_available(np))
return -ENODEV;
of_id = of_match_device(sun4i_codec_of_match, dev);
if (!of_id)
return -EINVAL;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
card->dev = dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, priv);
priv->revision = (enum sun4i_soc_family)of_id->data;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
priv->regmap = devm_regmap_init_mmio(dev, base,
&sun4iac_regmap_config);
if (IS_ERR(priv->regmap))
return PTR_ERR(priv->regmap);
/* Get the clocks from the DT */
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_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);
}
if (clk_prepare_enable(priv->clk_module)) {
dev_err(dev, "failed to enable codec clock\n");
return -EINVAL;
}
/* Enable the clock on a basic rate */
ret = clk_set_rate(priv->clk_module, 24576000);
if (ret) {
dev_err(dev, "failed to set codec base clock rate\n");
goto err_clk_disable;
}
/* Enable the bus clock */
if (clk_prepare_enable(priv->clk_apb)) {
dev_err(dev, "failed to enable apb clock\n");
ret = -EINVAL;
goto err_clk_disable;
}
/* DMA configuration for TX FIFO */
priv->playback_dma_data.addr = res->start + SUN4I_DAC_TXDATA;
priv->playback_dma_data.maxburst = 4;
priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
/* DMA configuration for RX FIFO */
priv->capture_dma_data.addr = res->start + SUN4I_ADC_RXDATA;
priv->capture_dma_data.maxburst = 4;
priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
sun4iac_init_kcontrol_items(priv->revision);
ret = snd_soc_register_codec(dev, &sun4iac_codec_drv,
&sun4iac_codec_dai, 1);
if (ret) {
dev_err(dev, "snd_soc_register_codec() failed: %d\n", ret);
goto err_clk_disable2;
}
ret = devm_snd_soc_register_component(dev, &sun4iac_component,
&sun4iac_cpu_dai, 1);
if (ret) {
dev_err(dev, "snd_soc_register_component() failed: %d\n", ret);
goto err;
}
ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0);
if (ret) {
dev_err(dev, "snd_dmaengine_pcm_register() failed: %d\n", ret);
goto err;
}
sun4iac_dai_link.cpu_dai_name = dev_name(dev);
sun4iac_dai_link.codec_name = dev_name(dev);
sun4iac_dai_link.platform_name = dev_name(dev);
ret = devm_snd_soc_register_card(dev, card);
if (ret) {
dev_err(dev, "snd_soc_register_card() failed: %d\n", ret);
goto err;
}
return 0;
err:
snd_soc_unregister_codec(dev);
err_clk_disable2:
clk_disable_unprepare(priv->clk_apb);
err_clk_disable:
clk_disable_unprepare(priv->clk_module);
return ret;
}
static int sun4i_codec_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct sun4i_priv *priv = snd_soc_card_get_drvdata(card);
snd_soc_unregister_codec(&pdev->dev);
clk_disable_unprepare(priv->clk_apb);
clk_disable_unprepare(priv->clk_module);
return 0;
}
static struct platform_driver sun4i_codec_pdriver = {
.driver = {
.name = "sun4i-codec",
.owner = THIS_MODULE,
.of_match_table = sun4i_codec_of_match,
},
.probe = sun4i_codec_probe,
.remove = sun4i_codec_remove,
};
module_platform_driver(sun4i_codec_pdriver);
MODULE_DESCRIPTION("Allwinner A10-A20 codec driver");
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_LICENSE("GPL");
@h-yamamo
Copy link
Author

h-yamamo commented Nov 19, 2015

(2017-03-12) it's valid with linux 4.4.52.

On linux-4.4, sound module for Allwinner SoCs (A10-A20) is supported, but there are some issues.

  • No capture support (supported from linux-4.5)
  • No good harmonious with alsa and pulseaudio
    • Difficult to understand mixer controls have what function from its name
    • With pulseaudio, cannot mapping to Master Volume
  • At first bootup time, no sound out till some mixer operation

For these reason, I remade this sound module.

File path:
linux-4.4.X/sound/soc/sunxi/sun4i-codec.c

Kernel config:
CONFIG_SND_SUN4I_CODEC=m or y
CONFIG_DMA_SUN4I=y

For BananaPi or other boards, add lines to linux-4.4.X/arch/arm/boot/dts/sunXi-(your board).dts

&codec {
    status = "okay";
};

After installed new sound module, you may need to do:

rm /var/lib/alsa/*
rm $HOME/.config/pulse/*

You may need to change a line in /usr/share/alsa/alsa.conf

#pcm.front cards.pcm.front
pcm.front cards.pcm.default

Screen captures of alsamixer on Ubuntu vivid (Allwinner A20)
alsamixer-pb1
alsamixer-pb2
alsamixer-pb3
alsamixer-cap

Diagram of flow

        Mic Line FM
           | | |
ADC <------+-+-+----
           | | |   ^
           v v v   |
DAC --+--> Mixer --+--> PA(PowerAmp) --> HeadPhone
      \----(bypass)----/

You can enjoy excellent sound with bypass Mixer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment