2015-09-12 16:26:24 +03:00
/*
* 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 >
2015-10-28 00:00:45 +03:00
* Copyright 2015 Adam Sampson < ats @ offog . org >
2015-09-12 16:26:24 +03:00
*
* 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>
2015-12-11 21:43:57 +03:00
# include <linux/gpio/consumer.h>
2015-09-12 16:26:24 +03:00
# 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>
/* Codec DAC register offsets and bit fields */
# define SUN4I_CODEC_DAC_DPC (0x00)
# define SUN4I_CODEC_DAC_DPC_EN_DA (31)
# define SUN4I_CODEC_DAC_DPC_DVOL (12)
# define SUN4I_CODEC_DAC_FIFOC (0x04)
# define SUN4I_CODEC_DAC_FIFOC_DAC_FS (29)
# define SUN4I_CODEC_DAC_FIFOC_FIR_VERSION (28)
# define SUN4I_CODEC_DAC_FIFOC_SEND_LASAT (26)
# define SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE (24)
# define SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT (21)
# define SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL (8)
# define SUN4I_CODEC_DAC_FIFOC_MONO_EN (6)
# define SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS (5)
# define SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN (4)
# define SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH (0)
# define SUN4I_CODEC_DAC_FIFOS (0x08)
# define SUN4I_CODEC_DAC_TXDATA (0x0c)
# define SUN4I_CODEC_DAC_ACTL (0x10)
# define SUN4I_CODEC_DAC_ACTL_DACAENR (31)
# define SUN4I_CODEC_DAC_ACTL_DACAENL (30)
# define SUN4I_CODEC_DAC_ACTL_MIXEN (29)
# define SUN4I_CODEC_DAC_ACTL_LDACLMIXS (15)
# define SUN4I_CODEC_DAC_ACTL_RDACRMIXS (14)
# define SUN4I_CODEC_DAC_ACTL_LDACRMIXS (13)
# define SUN4I_CODEC_DAC_ACTL_DACPAS (8)
# define SUN4I_CODEC_DAC_ACTL_MIXPAS (7)
# define SUN4I_CODEC_DAC_ACTL_PA_MUTE (6)
# define SUN4I_CODEC_DAC_ACTL_PA_VOL (0)
# define SUN4I_CODEC_DAC_TUNE (0x14)
# define SUN4I_CODEC_DAC_DEBUG (0x18)
/* Codec ADC register offsets and bit fields */
# define SUN4I_CODEC_ADC_FIFOC (0x1c)
2015-11-30 18:37:47 +03:00
# define SUN4I_CODEC_ADC_FIFOC_ADC_FS (29)
2015-09-12 16:26:24 +03:00
# define SUN4I_CODEC_ADC_FIFOC_EN_AD (28)
# define SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE (24)
# define SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL (8)
# define SUN4I_CODEC_ADC_FIFOC_MONO_EN (7)
# define SUN4I_CODEC_ADC_FIFOC_RX_SAMPLE_BITS (6)
# define SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN (4)
# define SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH (0)
# define SUN4I_CODEC_ADC_FIFOS (0x20)
# define SUN4I_CODEC_ADC_RXDATA (0x24)
# define SUN4I_CODEC_ADC_ACTL (0x28)
# define SUN4I_CODEC_ADC_ACTL_ADC_R_EN (31)
# define SUN4I_CODEC_ADC_ACTL_ADC_L_EN (30)
# define SUN4I_CODEC_ADC_ACTL_PREG1EN (29)
# define SUN4I_CODEC_ADC_ACTL_PREG2EN (28)
# define SUN4I_CODEC_ADC_ACTL_VMICEN (27)
# define SUN4I_CODEC_ADC_ACTL_VADCG (20)
# define SUN4I_CODEC_ADC_ACTL_ADCIS (17)
# define SUN4I_CODEC_ADC_ACTL_PA_EN (4)
# define SUN4I_CODEC_ADC_ACTL_DDE (3)
# define SUN4I_CODEC_ADC_DEBUG (0x2c)
/* Other various ADC registers */
# define SUN4I_CODEC_DAC_TXCNT (0x30)
# define SUN4I_CODEC_ADC_RXCNT (0x34)
# define SUN4I_CODEC_AC_SYS_VERI (0x38)
# define SUN4I_CODEC_AC_MIC_PHONE_CAL (0x3c)
struct sun4i_codec {
struct device * dev ;
struct regmap * regmap ;
struct clk * clk_apb ;
struct clk * clk_module ;
2015-12-11 21:43:57 +03:00
struct gpio_desc * gpio_pa ;
2015-09-12 16:26:24 +03:00
2015-11-30 18:37:47 +03:00
struct snd_dmaengine_dai_dma_data capture_dma_data ;
2015-09-12 16:26:24 +03:00
struct snd_dmaengine_dai_dma_data playback_dma_data ;
} ;
static void sun4i_codec_start_playback ( struct sun4i_codec * scodec )
{
/* Flush TX FIFO */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH ) ,
BIT ( SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH ) ) ;
/* Enable DAC DRQ */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN ) ,
BIT ( SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN ) ) ;
}
static void sun4i_codec_stop_playback ( struct sun4i_codec * scodec )
{
/* Disable DAC DRQ */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_DAC_DRQ_EN ) ,
0 ) ;
}
2015-11-30 18:37:47 +03:00
static void sun4i_codec_start_capture ( struct sun4i_codec * scodec )
{
/* Enable ADC DRQ */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) ) ;
}
static void sun4i_codec_stop_capture ( struct sun4i_codec * scodec )
{
/* Disable ADC DRQ */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) , 0 ) ;
}
2015-09-12 16:26:24 +03:00
static int sun4i_codec_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = 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 :
2015-11-30 18:37:47 +03:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
sun4i_codec_start_playback ( scodec ) ;
else
sun4i_codec_start_capture ( scodec ) ;
2015-09-12 16:26:24 +03:00
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
2015-11-30 18:37:47 +03:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
sun4i_codec_stop_playback ( scodec ) ;
else
sun4i_codec_stop_capture ( scodec ) ;
2015-09-12 16:26:24 +03:00
break ;
default :
return - EINVAL ;
}
return 0 ;
}
2015-11-30 18:37:47 +03:00
static int sun4i_codec_prepare_capture ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
2015-09-12 16:26:24 +03:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( rtd - > card ) ;
2015-11-30 18:37:47 +03:00
/* Flush RX FIFO */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH ) ) ;
/* Set RX FIFO trigger level */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
0xf < < SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL ,
0x7 < < SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL ) ;
/*
* FIXME : Undocumented in the datasheet , but
* Allwinner ' s code mentions that it is related
* related to microphone gain
*/
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_ACTL ,
0x3 < < 25 ,
0x1 < < 25 ) ;
if ( of_device_is_compatible ( scodec - > dev - > of_node ,
" allwinner,sun7i-a20-codec " ) )
/* FIXME: Undocumented bits */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_TUNE ,
0x3 < < 8 ,
0x1 < < 8 ) ;
/* Fill most significant bits with valid data MSB */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE ) ) ;
return 0 ;
}
static int sun4i_codec_prepare_playback ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( rtd - > card ) ;
u32 val ;
2015-09-12 16:26:24 +03:00
/* Flush the TX FIFO */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH ) ,
BIT ( SUN4I_CODEC_DAC_FIFOC_FIFO_FLUSH ) ) ;
/* Set TX FIFO Empty Trigger Level */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
0x3f < < SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL ,
0xf < < SUN4I_CODEC_DAC_FIFOC_TX_TRIG_LEVEL ) ;
if ( substream - > runtime - > rate > 32000 )
/* Use 64 bits FIR filter */
val = 0 ;
else
/* Use 32 bits FIR filter */
val = BIT ( SUN4I_CODEC_DAC_FIFOC_FIR_VERSION ) ;
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_FIR_VERSION ) ,
val ) ;
/* Send zeros when we have an underrun */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_SEND_LASAT ) ,
0 ) ;
return 0 ;
2015-11-30 18:37:47 +03:00
} ;
static int sun4i_codec_prepare ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
return sun4i_codec_prepare_playback ( substream , dai ) ;
return sun4i_codec_prepare_capture ( substream , dai ) ;
2015-09-12 16:26:24 +03:00
}
static unsigned long sun4i_codec_get_mod_freq ( struct snd_pcm_hw_params * params )
{
unsigned int rate = params_rate ( params ) ;
switch ( rate ) {
case 176400 :
case 88200 :
case 44100 :
case 33075 :
case 22050 :
case 14700 :
case 11025 :
case 7350 :
return 22579200 ;
case 192000 :
case 96000 :
case 48000 :
case 32000 :
case 24000 :
case 16000 :
case 12000 :
case 8000 :
return 24576000 ;
default :
return 0 ;
}
}
static int sun4i_codec_get_hw_rate ( struct snd_pcm_hw_params * params )
{
unsigned int rate = params_rate ( params ) ;
switch ( rate ) {
case 192000 :
case 176400 :
return 6 ;
case 96000 :
case 88200 :
return 7 ;
case 48000 :
case 44100 :
return 0 ;
case 32000 :
case 33075 :
return 1 ;
case 24000 :
case 22050 :
return 2 ;
case 16000 :
case 14700 :
return 3 ;
case 12000 :
case 11025 :
return 4 ;
case 8000 :
case 7350 :
return 5 ;
default :
return - EINVAL ;
}
}
2015-11-30 18:37:47 +03:00
static int sun4i_codec_hw_params_capture ( struct sun4i_codec * scodec ,
struct snd_pcm_hw_params * params ,
unsigned int hwrate )
2015-09-12 16:26:24 +03:00
{
2015-11-30 18:37:47 +03:00
/* Set ADC sample rate */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
7 < < SUN4I_CODEC_ADC_FIFOC_ADC_FS ,
hwrate < < SUN4I_CODEC_ADC_FIFOC_ADC_FS ) ;
2015-09-12 16:26:24 +03:00
2015-11-30 18:37:47 +03:00
/* Set the number of channels we want to use */
if ( params_channels ( params ) = = 1 )
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_MONO_EN ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_MONO_EN ) ) ;
else
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_FIFOC ,
BIT ( SUN4I_CODEC_ADC_FIFOC_MONO_EN ) , 0 ) ;
2015-09-12 16:26:24 +03:00
2015-11-30 18:37:47 +03:00
return 0 ;
}
2015-09-12 16:26:24 +03:00
2015-11-30 18:37:47 +03:00
static int sun4i_codec_hw_params_playback ( struct sun4i_codec * scodec ,
struct snd_pcm_hw_params * params ,
unsigned int hwrate )
{
u32 val ;
2015-09-12 16:26:24 +03:00
/* Set DAC sample rate */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
7 < < SUN4I_CODEC_DAC_FIFOC_DAC_FS ,
hwrate < < SUN4I_CODEC_DAC_FIFOC_DAC_FS ) ;
/* Set the number of channels we want to use */
if ( params_channels ( params ) = = 1 )
val = BIT ( SUN4I_CODEC_DAC_FIFOC_MONO_EN ) ;
else
val = 0 ;
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_MONO_EN ) ,
val ) ;
/* Set the number of sample bits to either 16 or 24 bits */
if ( hw_param_interval ( params , SNDRV_PCM_HW_PARAM_SAMPLE_BITS ) - > min = = 32 ) {
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS ) ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS ) ) ;
/* Set TX FIFO mode to padding the LSBs with 0 */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE ) ,
0 ) ;
scodec - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES ;
} else {
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_SAMPLE_BITS ) ,
0 ) ;
/* Set TX FIFO mode to repeat the MSB */
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE ) ,
BIT ( SUN4I_CODEC_DAC_FIFOC_TX_FIFO_MODE ) ) ;
scodec - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES ;
}
return 0 ;
}
2015-11-30 18:37:47 +03:00
static int sun4i_codec_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( rtd - > card ) ;
unsigned long clk_freq ;
2015-12-01 14:06:47 +03:00
int ret , hwrate ;
2015-11-30 18:37:47 +03:00
clk_freq = sun4i_codec_get_mod_freq ( params ) ;
if ( ! clk_freq )
return - EINVAL ;
2015-12-01 14:06:47 +03:00
ret = clk_set_rate ( scodec - > clk_module , clk_freq ) ;
if ( ret )
return ret ;
2015-11-30 18:37:47 +03:00
hwrate = sun4i_codec_get_hw_rate ( params ) ;
if ( hwrate < 0 )
return hwrate ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
return sun4i_codec_hw_params_playback ( scodec , params ,
hwrate ) ;
return sun4i_codec_hw_params_capture ( scodec , params ,
hwrate ) ;
}
2015-09-12 16:26:24 +03:00
static int sun4i_codec_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( rtd - > card ) ;
/*
* Stop issuing DRQ when we have room for less than 16 samples
* in our TX FIFO
*/
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_DAC_FIFOC ,
3 < < SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT ,
3 < < SUN4I_CODEC_DAC_FIFOC_DRQ_CLR_CNT ) ;
return clk_prepare_enable ( scodec - > clk_module ) ;
}
static void sun4i_codec_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( rtd - > card ) ;
clk_disable_unprepare ( scodec - > clk_module ) ;
}
static const struct snd_soc_dai_ops sun4i_codec_dai_ops = {
. startup = sun4i_codec_startup ,
. shutdown = sun4i_codec_shutdown ,
. trigger = sun4i_codec_trigger ,
. hw_params = sun4i_codec_hw_params ,
. prepare = sun4i_codec_prepare ,
} ;
static struct snd_soc_dai_driver sun4i_codec_dai = {
. name = " Codec " ,
. ops = & sun4i_codec_dai_ops ,
. playback = {
. stream_name = " Codec Playback " ,
. channels_min = 1 ,
. channels_max = 2 ,
. rate_min = 8000 ,
. rate_max = 192000 ,
. rates = SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_96000 |
2015-09-29 22:43:18 +03:00
SNDRV_PCM_RATE_192000 ,
2015-09-12 16:26:24 +03:00
. formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE ,
. sig_bits = 24 ,
} ,
2015-11-30 18:37:47 +03:00
. capture = {
. stream_name = " Codec Capture " ,
. channels_min = 1 ,
. channels_max = 2 ,
. rate_min = 8000 ,
. rate_max = 192000 ,
. rates = SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_96000 |
SNDRV_PCM_RATE_192000 |
SNDRV_PCM_RATE_KNOT ,
. formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE ,
. sig_bits = 24 ,
} ,
2015-09-12 16:26:24 +03:00
} ;
/*** Codec ***/
static const struct snd_kcontrol_new sun4i_codec_pa_mute =
SOC_DAPM_SINGLE ( " Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_PA_MUTE , 1 , 0 ) ;
static DECLARE_TLV_DB_SCALE ( sun4i_codec_pa_volume_scale , - 6300 , 100 , 1 ) ;
static const struct snd_kcontrol_new sun4i_codec_widgets [ ] = {
2015-10-28 00:00:45 +03:00
SOC_SINGLE_TLV ( " Power Amplifier Volume " , SUN4I_CODEC_DAC_ACTL ,
2015-09-12 16:26:24 +03:00
SUN4I_CODEC_DAC_ACTL_PA_VOL , 0x3F , 0 ,
sun4i_codec_pa_volume_scale ) ,
} ;
static const struct snd_kcontrol_new sun4i_codec_left_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " Left DAC Playback Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_LDACLMIXS , 1 , 0 ) ,
} ;
static const struct snd_kcontrol_new sun4i_codec_right_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " Right DAC Playback Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_RDACRMIXS , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Left DAC Playback Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_LDACRMIXS , 1 , 0 ) ,
} ;
static const struct snd_kcontrol_new sun4i_codec_pa_mixer_controls [ ] = {
SOC_DAPM_SINGLE ( " DAC Playback Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_DACPAS , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Mixer Playback Switch " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_MIXPAS , 1 , 0 ) ,
} ;
2015-12-11 21:43:56 +03:00
static const struct snd_soc_dapm_widget sun4i_codec_codec_dapm_widgets [ ] = {
2015-11-30 18:37:47 +03:00
/* Digital parts of the ADCs */
SND_SOC_DAPM_SUPPLY ( " ADC " , SUN4I_CODEC_ADC_FIFOC ,
SUN4I_CODEC_ADC_FIFOC_EN_AD , 0 ,
NULL , 0 ) ,
2015-09-12 16:26:24 +03:00
/* Digital parts of the DACs */
SND_SOC_DAPM_SUPPLY ( " DAC " , SUN4I_CODEC_DAC_DPC ,
SUN4I_CODEC_DAC_DPC_EN_DA , 0 ,
NULL , 0 ) ,
2015-11-30 18:37:47 +03:00
/* Analog parts of the ADCs */
SND_SOC_DAPM_ADC ( " Left ADC " , " Codec Capture " , SUN4I_CODEC_ADC_ACTL ,
SUN4I_CODEC_ADC_ACTL_ADC_L_EN , 0 ) ,
SND_SOC_DAPM_ADC ( " Right ADC " , " Codec Capture " , SUN4I_CODEC_ADC_ACTL ,
SUN4I_CODEC_ADC_ACTL_ADC_R_EN , 0 ) ,
2015-09-12 16:26:24 +03:00
/* Analog parts of the DACs */
SND_SOC_DAPM_DAC ( " Left DAC " , " Codec Playback " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_DACAENL , 0 ) ,
SND_SOC_DAPM_DAC ( " Right DAC " , " Codec Playback " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_DACAENR , 0 ) ,
/* Mixers */
SND_SOC_DAPM_MIXER ( " Left Mixer " , SND_SOC_NOPM , 0 , 0 ,
sun4i_codec_left_mixer_controls ,
ARRAY_SIZE ( sun4i_codec_left_mixer_controls ) ) ,
SND_SOC_DAPM_MIXER ( " Right Mixer " , SND_SOC_NOPM , 0 , 0 ,
sun4i_codec_right_mixer_controls ,
ARRAY_SIZE ( sun4i_codec_right_mixer_controls ) ) ,
/* Global Mixer Enable */
SND_SOC_DAPM_SUPPLY ( " Mixer Enable " , SUN4I_CODEC_DAC_ACTL ,
SUN4I_CODEC_DAC_ACTL_MIXEN , 0 , NULL , 0 ) ,
2015-11-30 18:37:47 +03:00
/* VMIC */
SND_SOC_DAPM_SUPPLY ( " VMIC " , SUN4I_CODEC_ADC_ACTL ,
SUN4I_CODEC_ADC_ACTL_VMICEN , 0 , NULL , 0 ) ,
/* Mic Pre-Amplifiers */
SND_SOC_DAPM_PGA ( " MIC1 Pre-Amplifier " , SUN4I_CODEC_ADC_ACTL ,
SUN4I_CODEC_ADC_ACTL_PREG1EN , 0 , NULL , 0 ) ,
2015-10-28 00:00:45 +03:00
/* Power Amplifier */
SND_SOC_DAPM_MIXER ( " Power Amplifier " , SUN4I_CODEC_ADC_ACTL ,
2015-09-12 16:26:24 +03:00
SUN4I_CODEC_ADC_ACTL_PA_EN , 0 ,
sun4i_codec_pa_mixer_controls ,
ARRAY_SIZE ( sun4i_codec_pa_mixer_controls ) ) ,
2015-10-28 00:00:45 +03:00
SND_SOC_DAPM_SWITCH ( " Power Amplifier Mute " , SND_SOC_NOPM , 0 , 0 ,
2015-09-12 16:26:24 +03:00
& sun4i_codec_pa_mute ) ,
2015-11-30 18:37:47 +03:00
SND_SOC_DAPM_INPUT ( " Mic1 " ) ,
2015-09-12 16:26:24 +03:00
SND_SOC_DAPM_OUTPUT ( " HP Right " ) ,
SND_SOC_DAPM_OUTPUT ( " HP Left " ) ,
} ;
2015-12-11 21:43:56 +03:00
static const struct snd_soc_dapm_route sun4i_codec_codec_dapm_routes [ ] = {
2015-11-30 18:37:47 +03:00
/* Left ADC / DAC Routes */
{ " Left ADC " , NULL , " ADC " } ,
2015-09-12 16:26:24 +03:00
{ " Left DAC " , NULL , " DAC " } ,
2015-11-30 18:37:47 +03:00
/* Right ADC / DAC Routes */
{ " Right ADC " , NULL , " ADC " } ,
2015-09-12 16:26:24 +03:00
{ " Right DAC " , NULL , " DAC " } ,
/* Right Mixer Routes */
{ " Right Mixer " , NULL , " Mixer Enable " } ,
{ " Right Mixer " , " Left DAC Playback Switch " , " Left DAC " } ,
{ " Right Mixer " , " Right DAC Playback Switch " , " Right DAC " } ,
/* Left Mixer Routes */
{ " Left Mixer " , NULL , " Mixer Enable " } ,
{ " Left Mixer " , " Left DAC Playback Switch " , " Left DAC " } ,
2015-10-28 00:00:45 +03:00
/* Power Amplifier Routes */
{ " Power Amplifier " , " Mixer Playback Switch " , " Left Mixer " } ,
{ " Power Amplifier " , " Mixer Playback Switch " , " Right Mixer " } ,
{ " Power Amplifier " , " DAC Playback Switch " , " Left DAC " } ,
{ " Power Amplifier " , " DAC Playback Switch " , " Right DAC " } ,
2015-09-12 16:26:24 +03:00
2015-10-28 00:00:45 +03:00
/* Headphone Output Routes */
{ " Power Amplifier Mute " , " Switch " , " Power Amplifier " } ,
{ " HP Right " , NULL , " Power Amplifier Mute " } ,
{ " HP Left " , NULL , " Power Amplifier Mute " } ,
2015-11-30 18:37:47 +03:00
/* Mic1 Routes */
{ " Left ADC " , NULL , " MIC1 Pre-Amplifier " } ,
{ " Right ADC " , NULL , " MIC1 Pre-Amplifier " } ,
{ " MIC1 Pre-Amplifier " , NULL , " Mic1 " } ,
{ " Mic1 " , NULL , " VMIC " } ,
2015-09-12 16:26:24 +03:00
} ;
static struct snd_soc_codec_driver sun4i_codec_codec = {
. controls = sun4i_codec_widgets ,
. num_controls = ARRAY_SIZE ( sun4i_codec_widgets ) ,
2015-12-11 21:43:56 +03:00
. dapm_widgets = sun4i_codec_codec_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( sun4i_codec_codec_dapm_widgets ) ,
. dapm_routes = sun4i_codec_codec_dapm_routes ,
. num_dapm_routes = ARRAY_SIZE ( sun4i_codec_codec_dapm_routes ) ,
2015-09-12 16:26:24 +03:00
} ;
static const struct snd_soc_component_driver sun4i_codec_component = {
. name = " sun4i-codec " ,
} ;
# define SUN4I_CODEC_RATES SNDRV_PCM_RATE_8000_192000
# define SUN4I_CODEC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S32_LE )
static int sun4i_codec_dai_probe ( struct snd_soc_dai * dai )
{
struct snd_soc_card * card = snd_soc_dai_get_drvdata ( dai ) ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( card ) ;
snd_soc_dai_init_dma_data ( dai , & scodec - > playback_dma_data ,
2015-11-30 18:37:47 +03:00
& scodec - > capture_dma_data ) ;
2015-09-12 16:26:24 +03:00
return 0 ;
}
static struct snd_soc_dai_driver dummy_cpu_dai = {
. name = " sun4i-codec-cpu-dai " ,
. probe = sun4i_codec_dai_probe ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = SUN4I_CODEC_RATES ,
. formats = SUN4I_CODEC_FORMATS ,
. sig_bits = 24 ,
} ,
2015-11-30 18:37:47 +03:00
. capture = {
. stream_name = " Capture " ,
. channels_min = 1 ,
. channels_max = 2 ,
. rates = SUN4I_CODEC_RATES ,
. formats = SUN4I_CODEC_FORMATS ,
. sig_bits = 24 ,
} ,
2015-09-12 16:26:24 +03:00
} ;
static const struct regmap_config sun4i_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN4I_CODEC_AC_MIC_PHONE_CAL ,
} ;
static const struct of_device_id sun4i_codec_of_match [ ] = {
{ . compatible = " allwinner,sun4i-a10-codec " } ,
{ . compatible = " allwinner,sun7i-a20-codec " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sun4i_codec_of_match ) ;
static struct snd_soc_dai_link * sun4i_codec_create_link ( struct device * dev ,
int * num_links )
{
struct snd_soc_dai_link * link = devm_kzalloc ( dev , sizeof ( * link ) ,
GFP_KERNEL ) ;
if ( ! link )
return NULL ;
link - > name = " cdc " ;
link - > stream_name = " CDC PCM " ;
link - > codec_dai_name = " Codec " ;
link - > cpu_dai_name = dev_name ( dev ) ;
link - > codec_name = dev_name ( dev ) ;
link - > platform_name = dev_name ( dev ) ;
link - > dai_fmt = SND_SOC_DAIFMT_I2S ;
* num_links = 1 ;
return link ;
} ;
2015-12-11 21:43:57 +03:00
static int sun4i_codec_spk_event ( struct snd_soc_dapm_widget * w ,
struct snd_kcontrol * k , int event )
{
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( w - > dapm - > card ) ;
if ( scodec - > gpio_pa )
gpiod_set_value_cansleep ( scodec - > gpio_pa ,
! ! SND_SOC_DAPM_EVENT_ON ( event ) ) ;
return 0 ;
}
static const struct snd_soc_dapm_widget sun4i_codec_card_dapm_widgets [ ] = {
SND_SOC_DAPM_SPK ( " Speaker " , sun4i_codec_spk_event ) ,
} ;
static const struct snd_soc_dapm_route sun4i_codec_card_dapm_routes [ ] = {
2015-12-23 01:00:17 +03:00
{ " Speaker " , NULL , " HP Right " } ,
{ " Speaker " , NULL , " HP Left " } ,
2015-12-11 21:43:57 +03:00
} ;
2015-09-12 16:26:24 +03:00
static struct snd_soc_card * sun4i_codec_create_card ( struct device * dev )
{
struct snd_soc_card * card ;
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return NULL ;
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
return NULL ;
card - > dev = dev ;
card - > name = " sun4i-codec " ;
2015-12-11 21:43:57 +03:00
card - > dapm_widgets = sun4i_codec_card_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( sun4i_codec_card_dapm_widgets ) ;
card - > dapm_routes = sun4i_codec_card_dapm_routes ;
card - > num_dapm_routes = ARRAY_SIZE ( sun4i_codec_card_dapm_routes ) ;
2015-09-12 16:26:24 +03:00
return card ;
} ;
static int sun4i_codec_probe ( struct platform_device * pdev )
{
struct snd_soc_card * card ;
struct sun4i_codec * scodec ;
struct resource * res ;
void __iomem * base ;
int ret ;
scodec = devm_kzalloc ( & pdev - > dev , sizeof ( * scodec ) , GFP_KERNEL ) ;
if ( ! scodec )
return - ENOMEM ;
scodec - > dev = & pdev - > dev ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( base ) ) {
dev_err ( & pdev - > dev , " Failed to map the registers \n " ) ;
return PTR_ERR ( base ) ;
}
scodec - > regmap = devm_regmap_init_mmio ( & pdev - > dev , base ,
& sun4i_codec_regmap_config ) ;
if ( IS_ERR ( scodec - > regmap ) ) {
dev_err ( & pdev - > dev , " Failed to create our regmap \n " ) ;
return PTR_ERR ( scodec - > regmap ) ;
}
/* Get the clocks from the DT */
scodec - > clk_apb = devm_clk_get ( & pdev - > dev , " apb " ) ;
if ( IS_ERR ( scodec - > clk_apb ) ) {
dev_err ( & pdev - > dev , " Failed to get the APB clock \n " ) ;
return PTR_ERR ( scodec - > clk_apb ) ;
}
scodec - > clk_module = devm_clk_get ( & pdev - > dev , " codec " ) ;
if ( IS_ERR ( scodec - > clk_module ) ) {
dev_err ( & pdev - > dev , " Failed to get the module clock \n " ) ;
return PTR_ERR ( scodec - > clk_module ) ;
}
/* Enable the bus clock */
if ( clk_prepare_enable ( scodec - > clk_apb ) ) {
dev_err ( & pdev - > dev , " Failed to enable the APB clock \n " ) ;
return - EINVAL ;
}
2015-12-11 21:43:57 +03:00
scodec - > gpio_pa = devm_gpiod_get_optional ( & pdev - > dev , " allwinner,pa " ,
GPIOD_OUT_LOW ) ;
if ( IS_ERR ( scodec - > gpio_pa ) ) {
ret = PTR_ERR ( scodec - > gpio_pa ) ;
if ( ret ! = - EPROBE_DEFER )
dev_err ( & pdev - > dev , " Failed to get pa gpio: %d \n " , ret ) ;
return ret ;
}
2015-09-12 16:26:24 +03:00
/* DMA configuration for TX FIFO */
scodec - > playback_dma_data . addr = res - > start + SUN4I_CODEC_DAC_TXDATA ;
scodec - > playback_dma_data . maxburst = 4 ;
scodec - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2015-11-30 18:37:47 +03:00
/* DMA configuration for RX FIFO */
scodec - > capture_dma_data . addr = res - > start + SUN4I_CODEC_ADC_RXDATA ;
scodec - > capture_dma_data . maxburst = 4 ;
scodec - > capture_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2015-09-12 16:26:24 +03:00
ret = snd_soc_register_codec ( & pdev - > dev , & sun4i_codec_codec ,
& sun4i_codec_dai , 1 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register our codec \n " ) ;
goto err_clk_disable ;
}
ret = devm_snd_soc_register_component ( & pdev - > dev ,
& sun4i_codec_component ,
& dummy_cpu_dai , 1 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register our DAI \n " ) ;
goto err_unregister_codec ;
}
ret = devm_snd_dmaengine_pcm_register ( & pdev - > dev , NULL , 0 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register against DMAEngine \n " ) ;
goto err_unregister_codec ;
}
card = sun4i_codec_create_card ( & pdev - > dev ) ;
if ( ! card ) {
dev_err ( & pdev - > dev , " Failed to create our card \n " ) ;
goto err_unregister_codec ;
}
platform_set_drvdata ( pdev , card ) ;
snd_soc_card_set_drvdata ( card , scodec ) ;
ret = snd_soc_register_card ( card ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register our card \n " ) ;
goto err_unregister_codec ;
}
return 0 ;
err_unregister_codec :
snd_soc_unregister_codec ( & pdev - > dev ) ;
err_clk_disable :
clk_disable_unprepare ( scodec - > clk_apb ) ;
return ret ;
}
static int sun4i_codec_remove ( struct platform_device * pdev )
{
struct snd_soc_card * card = platform_get_drvdata ( pdev ) ;
struct sun4i_codec * scodec = snd_soc_card_get_drvdata ( card ) ;
snd_soc_unregister_card ( card ) ;
snd_soc_unregister_codec ( & pdev - > dev ) ;
clk_disable_unprepare ( scodec - > clk_apb ) ;
return 0 ;
}
static struct platform_driver sun4i_codec_driver = {
. driver = {
. name = " sun4i-codec " ,
. of_match_table = sun4i_codec_of_match ,
} ,
. probe = sun4i_codec_probe ,
. remove = sun4i_codec_remove ,
} ;
module_platform_driver ( sun4i_codec_driver ) ;
MODULE_DESCRIPTION ( " Allwinner A10 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 " ) ;