Created
March 12, 2017 02:37
-
-
Save h-yamamo/1aa5967557836e489e37c2c22c300f43 to your computer and use it in GitHub Desktop.
alternative sound module in linux-4.9 for Allwinner A10,A13,R8,A20 Audio Codec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2014 Emilio López <[email protected]> | |
* Copyright 2014 Jon Smirl <[email protected]> | |
* Copyright 2015 Maxime Ripard <[email protected]> | |
* Copyright 2015 Adam Sampson <[email protected]> | |
* | |
* 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 <linux/gpio/consumer.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; | |
struct gpio_desc *gpio_pa; | |
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_dai_probe(struct snd_soc_dai *dai) | |
{ | |
struct snd_soc_card *card = snd_soc_dai_get_drvdata(dai); | |
struct sun4i_priv *priv = snd_soc_card_get_drvdata(card); | |
struct regmap *rgmp = priv->regmap; | |
snd_soc_dai_init_dma_data(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; | |
} | |
/* callback from dapm */ | |
static int sun4i_codec_spk_event(struct snd_soc_dapm_widget *w, | |
struct snd_kcontrol *k, int event) | |
{ | |
struct sun4i_priv *priv = snd_soc_card_get_drvdata(w->dapm->card); | |
if (priv->gpio_pa) | |
gpiod_set_value_cansleep(priv->gpio_pa, | |
!!SND_SOC_DAPM_EVENT_ON(event)); | |
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), | |
SND_SOC_DAPM_SPK("Speaker", sun4i_codec_spk_event), | |
}; | |
static const struct snd_soc_dapm_route sun4iac_board_routes[] = { | |
{ "Headphone", NULL, "HPOUT" }, | |
{ "MICIN", NULL, "Microphone" }, | |
{ "Speaker", NULL, "HPOUT" }, | |
}; | |
/*** Codec driver & Codec DAI ***/ | |
static struct snd_soc_codec_driver sun4iac_codec_drv = { | |
.component_driver = { | |
.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.component_driver.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", | |
.probe = sun4iac_dai_probe, | |
.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, | |
.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); | |
} | |
priv->gpio_pa = devm_gpiod_get_optional(dev, "allwinner,pa", | |
GPIOD_OUT_LOW); | |
if (IS_ERR(priv->gpio_pa)) { | |
ret = PTR_ERR(priv->gpio_pa); | |
if (ret != -EPROBE_DEFER) | |
dev_err(dev, "Failed to get pa gpio: %d\n", ret); | |
return ret; | |
} | |
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 <[email protected]>"); | |
MODULE_AUTHOR("Jon Smirl <[email protected]>"); | |
MODULE_AUTHOR("Maxime Ripard <[email protected]>"); | |
MODULE_LICENSE("GPL"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See comments for linux-4.4's.
Note: not tested
gpio-pa
function.