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 >
2016-11-03 10:55:44 +03:00
* Copyright 2016 Chen - Yu Tsai < wens @ csie . 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_address.h>
2016-11-03 10:55:44 +03:00
# include <linux/of_device.h>
# include <linux/of_platform.h>
2015-09-12 16:26:24 +03:00
# include <linux/clk.h>
# include <linux/regmap.h>
2016-11-07 13:06:58 +03:00
# include <linux/reset.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>
2016-11-03 10:55:45 +03:00
/* Codec DAC digital controls and FIFO registers */
2015-09-12 16:26:24 +03:00
# 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)
2016-11-03 10:55:45 +03:00
/* Codec DAC side analog signal controls */
2015-09-12 16:26:24 +03:00
# 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)
2016-11-03 10:55:45 +03:00
/* Codec ADC digital controls and FIFO registers */
2015-09-12 16:26:24 +03:00
# 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)
2016-11-03 10:55:45 +03:00
/* Codec ADC side analog signal controls */
2015-09-12 16:26:24 +03:00
# 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)
2016-11-03 10:55:45 +03:00
/* FIFO counters */
2015-09-12 16:26:24 +03:00
# define SUN4I_CODEC_DAC_TXCNT (0x30)
# define SUN4I_CODEC_ADC_RXCNT (0x34)
2016-11-03 10:55:45 +03:00
/* Calibration register (sun7i only) */
2016-09-22 10:13:12 +03:00
# define SUN7I_CODEC_AC_DAC_CAL (0x38)
2016-11-03 10:55:45 +03:00
/* Microphone controls (sun7i only) */
2016-09-22 10:13:12 +03:00
# define SUN7I_CODEC_AC_MIC_PHONE_CAL (0x3c)
2015-09-12 16:26:24 +03:00
2016-11-03 10:55:48 +03:00
/*
* sun6i specific registers
*
* sun6i shares the same digital control and FIFO registers as sun4i ,
* but only the DAC digital controls are at the same offset . The others
* have been moved around to accommodate extra analog controls .
*/
/* Codec DAC digital controls and FIFO registers */
# define SUN6I_CODEC_ADC_FIFOC (0x10)
# define SUN6I_CODEC_ADC_FIFOC_EN_AD (28)
# define SUN6I_CODEC_ADC_FIFOS (0x14)
# define SUN6I_CODEC_ADC_RXDATA (0x18)
/* Output mixer and gain controls */
# define SUN6I_CODEC_OM_DACA_CTRL (0x20)
# define SUN6I_CODEC_OM_DACA_CTRL_DACAREN (31)
# define SUN6I_CODEC_OM_DACA_CTRL_DACALEN (30)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIXEN (29)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIXEN (28)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1 (23)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2 (22)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONE (21)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_PHONEP (20)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR (19)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR (18)
# define SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL (17)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1 (16)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2 (15)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONE (14)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_PHONEN (13)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL (12)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL (11)
# define SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR (10)
# define SUN6I_CODEC_OM_DACA_CTRL_RHPIS (9)
# define SUN6I_CODEC_OM_DACA_CTRL_LHPIS (8)
# define SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE (7)
# define SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE (6)
# define SUN6I_CODEC_OM_DACA_CTRL_HPVOL (0)
# define SUN6I_CODEC_OM_PA_CTRL (0x24)
# define SUN6I_CODEC_OM_PA_CTRL_HPPAEN (31)
# define SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL (29)
# define SUN6I_CODEC_OM_PA_CTRL_COMPTEN (28)
# define SUN6I_CODEC_OM_PA_CTRL_MIC1G (15)
# define SUN6I_CODEC_OM_PA_CTRL_MIC2G (12)
# define SUN6I_CODEC_OM_PA_CTRL_LINEING (9)
# define SUN6I_CODEC_OM_PA_CTRL_PHONEG (6)
# define SUN6I_CODEC_OM_PA_CTRL_PHONEPG (3)
# define SUN6I_CODEC_OM_PA_CTRL_PHONENG (0)
/* Microphone, line out and phone out controls */
# define SUN6I_CODEC_MIC_CTRL (0x28)
# define SUN6I_CODEC_MIC_CTRL_HBIASEN (31)
# define SUN6I_CODEC_MIC_CTRL_MBIASEN (30)
# define SUN6I_CODEC_MIC_CTRL_MIC1AMPEN (28)
# define SUN6I_CODEC_MIC_CTRL_MIC1BOOST (25)
# define SUN6I_CODEC_MIC_CTRL_MIC2AMPEN (24)
# define SUN6I_CODEC_MIC_CTRL_MIC2BOOST (21)
# define SUN6I_CODEC_MIC_CTRL_MIC2SLT (20)
# define SUN6I_CODEC_MIC_CTRL_LINEOUTLEN (19)
# define SUN6I_CODEC_MIC_CTRL_LINEOUTREN (18)
# define SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC (17)
# define SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC (16)
# define SUN6I_CODEC_MIC_CTRL_LINEOUTVC (11)
# define SUN6I_CODEC_MIC_CTRL_PHONEPREG (8)
/* ADC mixer controls */
# define SUN6I_CODEC_ADC_ACTL (0x2c)
# define SUN6I_CODEC_ADC_ACTL_ADCREN (31)
# define SUN6I_CODEC_ADC_ACTL_ADCLEN (30)
# define SUN6I_CODEC_ADC_ACTL_ADCRG (27)
# define SUN6I_CODEC_ADC_ACTL_ADCLG (24)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1 (13)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2 (12)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONE (11)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_PHONEP (10)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR (9)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR (8)
# define SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL (7)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1 (6)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2 (5)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONE (4)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_PHONEN (3)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL (2)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL (1)
# define SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR (0)
/* Analog performance tuning controls */
# define SUN6I_CODEC_ADDA_TUNE (0x30)
/* Calibration controls */
# define SUN6I_CODEC_CALIBRATION (0x34)
/* FIFO counters */
# define SUN6I_CODEC_DAC_TXCNT (0x40)
# define SUN6I_CODEC_ADC_RXCNT (0x44)
/* headset jack detection and button support registers */
# define SUN6I_CODEC_HMIC_CTL (0x50)
# define SUN6I_CODEC_HMIC_DATA (0x54)
/* TODO sun6i DAP (Digital Audio Processing) bits */
2016-11-25 15:34:36 +03:00
/* FIFO counters moved on A23 */
# define SUN8I_A23_CODEC_DAC_TXCNT (0x1c)
# define SUN8I_A23_CODEC_ADC_RXCNT (0x20)
2016-11-25 15:34:40 +03:00
/* TX FIFO moved on H3 */
# define SUN8I_H3_CODEC_DAC_TXDATA (0x20)
# define SUN8I_H3_CODEC_DAC_DBG (0x48)
# define SUN8I_H3_CODEC_ADC_DBG (0x4c)
/* TODO H3 DAP (Digital Audio Processing) bits */
2015-09-12 16:26:24 +03:00
struct sun4i_codec {
struct device * dev ;
struct regmap * regmap ;
struct clk * clk_apb ;
struct clk * clk_module ;
2016-11-07 13:06:58 +03:00
struct reset_control * rst ;
2015-12-11 21:43:57 +03:00
struct gpio_desc * gpio_pa ;
2015-09-12 16:26:24 +03:00
2016-11-03 10:55:44 +03:00
/* ADC_FIFOC register is at different offset on different SoCs */
struct regmap_field * reg_adc_fifoc ;
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 */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) ) ;
2015-11-30 18:37:47 +03:00
}
static void sun4i_codec_stop_capture ( struct sun4i_codec * scodec )
{
/* Disable ADC DRQ */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
BIT ( SUN4I_CODEC_ADC_FIFOC_ADC_DRQ_EN ) , 0 ) ;
2015-11-30 18:37:47 +03:00
}
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 */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
BIT ( SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_FIFO_FLUSH ) ) ;
2015-11-30 18:37:47 +03:00
/* Set RX FIFO trigger level */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
0xf < < SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL ,
0x7 < < SUN4I_CODEC_ADC_FIFOC_RX_TRIG_LEVEL ) ;
2015-11-30 18:37:47 +03:00
/*
* FIXME : Undocumented in the datasheet , but
* Allwinner ' s code mentions that it is related
* related to microphone gain
*/
2016-11-03 10:55:48 +03:00
if ( of_device_is_compatible ( scodec - > dev - > of_node ,
" allwinner,sun4i-a10-codec " ) | |
of_device_is_compatible ( scodec - > dev - > of_node ,
" allwinner,sun7i-a20-codec " ) ) {
regmap_update_bits ( scodec - > regmap , SUN4I_CODEC_ADC_ACTL ,
0x3 < < 25 ,
0x1 < < 25 ) ;
}
2015-11-30 18:37:47 +03:00
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 */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
BIT ( SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_RX_FIFO_MODE ) ) ;
2015-11-30 18:37:47 +03:00
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 */
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_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 )
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_adc_fifoc ,
BIT ( SUN4I_CODEC_ADC_FIFOC_MONO_EN ) ,
BIT ( SUN4I_CODEC_ADC_FIFOC_MONO_EN ) ) ;
2015-11-30 18:37:47 +03:00
else
2016-11-03 10:55:44 +03:00
regmap_field_update_bits ( scodec - > reg_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
} ;
2016-11-03 10:55:48 +03:00
/*** sun4i Codec ***/
2015-09-12 16:26:24 +03:00
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 ) ;
2016-09-24 23:05:01 +03:00
static const struct snd_kcontrol_new sun4i_codec_controls [ ] = {
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
} ;
2017-08-03 19:00:20 +03:00
static const struct snd_soc_codec_driver sun4i_codec_codec = {
2016-08-08 11:46:41 +03:00
. component_driver = {
2016-09-24 23:05:01 +03:00
. controls = sun4i_codec_controls ,
. num_controls = ARRAY_SIZE ( sun4i_codec_controls ) ,
2016-08-08 11:46:41 +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
} ;
2016-11-03 10:55:48 +03:00
/*** sun6i Codec ***/
/* mixer controls */
static const struct snd_kcontrol_new sun6i_codec_mixer_controls [ ] = {
SOC_DAPM_DOUBLE ( " DAC Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACL ,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACR , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " DAC Reversed Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_DACR ,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_DACL , 1 , 0 ) ,
2016-11-03 10:55:49 +03:00
SOC_DAPM_DOUBLE ( " Line In Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_LINEINL ,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_LINEINR , 1 , 0 ) ,
2016-11-03 10:55:51 +03:00
SOC_DAPM_DOUBLE ( " Mic1 Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC1 ,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC1 , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " Mic2 Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIX_MIC2 ,
SUN6I_CODEC_OM_DACA_CTRL_RMIX_MIC2 , 1 , 0 ) ,
2016-11-03 10:55:48 +03:00
} ;
2016-11-07 13:06:59 +03:00
/* ADC mixer controls */
static const struct snd_kcontrol_new sun6i_codec_adc_mixer_controls [ ] = {
SOC_DAPM_DOUBLE ( " Mixer Capture Switch " ,
SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXL ,
SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXR , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " Mixer Reversed Capture Switch " ,
SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_LADCMIX_OMIXR ,
SUN6I_CODEC_ADC_ACTL_RADCMIX_OMIXL , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " Line In Capture Switch " ,
SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_LADCMIX_LINEINL ,
SUN6I_CODEC_ADC_ACTL_RADCMIX_LINEINR , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " Mic1 Capture Switch " ,
SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC1 ,
SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC1 , 1 , 0 ) ,
SOC_DAPM_DOUBLE ( " Mic2 Capture Switch " ,
SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_LADCMIX_MIC2 ,
SUN6I_CODEC_ADC_ACTL_RADCMIX_MIC2 , 1 , 0 ) ,
} ;
2016-11-03 10:55:48 +03:00
/* headphone controls */
static const char * const sun6i_codec_hp_src_enum_text [ ] = {
" DAC " , " Mixer " ,
} ;
static SOC_ENUM_DOUBLE_DECL ( sun6i_codec_hp_src_enum ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LHPIS ,
SUN6I_CODEC_OM_DACA_CTRL_RHPIS ,
sun6i_codec_hp_src_enum_text ) ;
static const struct snd_kcontrol_new sun6i_codec_hp_src [ ] = {
SOC_DAPM_ENUM ( " Headphone Source Playback Route " ,
sun6i_codec_hp_src_enum ) ,
} ;
2016-11-03 10:55:51 +03:00
/* microphone controls */
static const char * const sun6i_codec_mic2_src_enum_text [ ] = {
" Mic2 " , " Mic3 " ,
} ;
static SOC_ENUM_SINGLE_DECL ( sun6i_codec_mic2_src_enum ,
SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MIC2SLT ,
sun6i_codec_mic2_src_enum_text ) ;
static const struct snd_kcontrol_new sun6i_codec_mic2_src [ ] = {
SOC_DAPM_ENUM ( " Mic2 Amplifier Source Route " ,
sun6i_codec_mic2_src_enum ) ,
} ;
2016-11-03 10:55:50 +03:00
/* line out controls */
static const char * const sun6i_codec_lineout_src_enum_text [ ] = {
" Stereo " , " Mono Differential " ,
} ;
static SOC_ENUM_DOUBLE_DECL ( sun6i_codec_lineout_src_enum ,
SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_LINEOUTLSRC ,
SUN6I_CODEC_MIC_CTRL_LINEOUTRSRC ,
sun6i_codec_lineout_src_enum_text ) ;
static const struct snd_kcontrol_new sun6i_codec_lineout_src [ ] = {
SOC_DAPM_ENUM ( " Line Out Source Playback Route " ,
sun6i_codec_lineout_src_enum ) ,
} ;
2016-11-03 10:55:48 +03:00
/* volume / mute controls */
static const DECLARE_TLV_DB_SCALE ( sun6i_codec_dvol_scale , - 7308 , 116 , 0 ) ;
static const DECLARE_TLV_DB_SCALE ( sun6i_codec_hp_vol_scale , - 6300 , 100 , 1 ) ;
2016-11-03 10:55:49 +03:00
static const DECLARE_TLV_DB_SCALE ( sun6i_codec_out_mixer_pregain_scale ,
- 450 , 150 , 0 ) ;
2016-11-03 10:55:50 +03:00
static const DECLARE_TLV_DB_RANGE ( sun6i_codec_lineout_vol_scale ,
0 , 1 , TLV_DB_SCALE_ITEM ( TLV_DB_GAIN_MUTE , 0 , 1 ) ,
2 , 31 , TLV_DB_SCALE_ITEM ( - 4350 , 150 , 0 ) ,
) ;
2016-11-03 10:55:51 +03:00
static const DECLARE_TLV_DB_RANGE ( sun6i_codec_mic_gain_scale ,
0 , 0 , TLV_DB_SCALE_ITEM ( 0 , 0 , 0 ) ,
1 , 7 , TLV_DB_SCALE_ITEM ( 2400 , 300 , 0 ) ,
) ;
2016-11-03 10:55:48 +03:00
static const struct snd_kcontrol_new sun6i_codec_codec_widgets [ ] = {
SOC_SINGLE_TLV ( " DAC Playback Volume " , SUN4I_CODEC_DAC_DPC ,
SUN4I_CODEC_DAC_DPC_DVOL , 0x3f , 1 ,
sun6i_codec_dvol_scale ) ,
SOC_SINGLE_TLV ( " Headphone Playback Volume " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_HPVOL , 0x3f , 0 ,
sun6i_codec_hp_vol_scale ) ,
2016-11-03 10:55:50 +03:00
SOC_SINGLE_TLV ( " Line Out Playback Volume " ,
SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_LINEOUTVC , 0x1f , 0 ,
sun6i_codec_lineout_vol_scale ) ,
2016-11-03 10:55:48 +03:00
SOC_DOUBLE ( " Headphone Playback Switch " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LHPPAMUTE ,
SUN6I_CODEC_OM_DACA_CTRL_RHPPAMUTE , 1 , 0 ) ,
2016-11-03 10:55:50 +03:00
SOC_DOUBLE ( " Line Out Playback Switch " ,
SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_LINEOUTLEN ,
SUN6I_CODEC_MIC_CTRL_LINEOUTREN , 1 , 0 ) ,
2016-11-03 10:55:49 +03:00
/* Mixer pre-gains */
SOC_SINGLE_TLV ( " Line In Playback Volume " ,
SUN6I_CODEC_OM_PA_CTRL , SUN6I_CODEC_OM_PA_CTRL_LINEING ,
0x7 , 0 , sun6i_codec_out_mixer_pregain_scale ) ,
2016-11-03 10:55:51 +03:00
SOC_SINGLE_TLV ( " Mic1 Playback Volume " ,
SUN6I_CODEC_OM_PA_CTRL , SUN6I_CODEC_OM_PA_CTRL_MIC1G ,
0x7 , 0 , sun6i_codec_out_mixer_pregain_scale ) ,
SOC_SINGLE_TLV ( " Mic2 Playback Volume " ,
SUN6I_CODEC_OM_PA_CTRL , SUN6I_CODEC_OM_PA_CTRL_MIC2G ,
0x7 , 0 , sun6i_codec_out_mixer_pregain_scale ) ,
/* Microphone Amp boost gains */
SOC_SINGLE_TLV ( " Mic1 Boost Volume " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MIC1BOOST , 0x7 , 0 ,
sun6i_codec_mic_gain_scale ) ,
SOC_SINGLE_TLV ( " Mic2 Boost Volume " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MIC2BOOST , 0x7 , 0 ,
sun6i_codec_mic_gain_scale ) ,
2016-11-07 13:06:59 +03:00
SOC_DOUBLE_TLV ( " ADC Capture Volume " ,
SUN6I_CODEC_ADC_ACTL , SUN6I_CODEC_ADC_ACTL_ADCLG ,
SUN6I_CODEC_ADC_ACTL_ADCRG , 0x7 , 0 ,
sun6i_codec_out_mixer_pregain_scale ) ,
2016-11-03 10:55:48 +03:00
} ;
static const struct snd_soc_dapm_widget sun6i_codec_codec_dapm_widgets [ ] = {
2016-11-03 10:55:51 +03:00
/* Microphone inputs */
SND_SOC_DAPM_INPUT ( " MIC1 " ) ,
SND_SOC_DAPM_INPUT ( " MIC2 " ) ,
SND_SOC_DAPM_INPUT ( " MIC3 " ) ,
/* Microphone Bias */
SND_SOC_DAPM_SUPPLY ( " HBIAS " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_HBIASEN , 0 , NULL , 0 ) ,
SND_SOC_DAPM_SUPPLY ( " MBIAS " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MBIASEN , 0 , NULL , 0 ) ,
/* Mic input path */
SND_SOC_DAPM_MUX ( " Mic2 Amplifier Source Route " ,
SND_SOC_NOPM , 0 , 0 , sun6i_codec_mic2_src ) ,
SND_SOC_DAPM_PGA ( " Mic1 Amplifier " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MIC1AMPEN , 0 , NULL , 0 ) ,
SND_SOC_DAPM_PGA ( " Mic2 Amplifier " , SUN6I_CODEC_MIC_CTRL ,
SUN6I_CODEC_MIC_CTRL_MIC2AMPEN , 0 , NULL , 0 ) ,
2016-11-03 10:55:49 +03:00
/* Line In */
SND_SOC_DAPM_INPUT ( " LINEIN " ) ,
2016-11-07 13:06:59 +03:00
/* Digital parts of the ADCs */
SND_SOC_DAPM_SUPPLY ( " ADC Enable " , SUN6I_CODEC_ADC_FIFOC ,
SUN6I_CODEC_ADC_FIFOC_EN_AD , 0 ,
NULL , 0 ) ,
/* Analog parts of the ADCs */
SND_SOC_DAPM_ADC ( " Left ADC " , " Codec Capture " , SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_ADCLEN , 0 ) ,
SND_SOC_DAPM_ADC ( " Right ADC " , " Codec Capture " , SUN6I_CODEC_ADC_ACTL ,
SUN6I_CODEC_ADC_ACTL_ADCREN , 0 ) ,
/* ADC Mixers */
SOC_MIXER_ARRAY ( " Left ADC Mixer " , SND_SOC_NOPM , 0 , 0 ,
sun6i_codec_adc_mixer_controls ) ,
SOC_MIXER_ARRAY ( " Right ADC Mixer " , SND_SOC_NOPM , 0 , 0 ,
sun6i_codec_adc_mixer_controls ) ,
2016-11-03 10:55:48 +03:00
/* Digital parts of the DACs */
SND_SOC_DAPM_SUPPLY ( " DAC Enable " , SUN4I_CODEC_DAC_DPC ,
SUN4I_CODEC_DAC_DPC_EN_DA , 0 ,
NULL , 0 ) ,
/* Analog parts of the DACs */
SND_SOC_DAPM_DAC ( " Left DAC " , " Codec Playback " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_DACALEN , 0 ) ,
SND_SOC_DAPM_DAC ( " Right DAC " , " Codec Playback " ,
SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_DACAREN , 0 ) ,
/* Mixers */
SOC_MIXER_ARRAY ( " Left Mixer " , SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_LMIXEN , 0 ,
sun6i_codec_mixer_controls ) ,
SOC_MIXER_ARRAY ( " Right Mixer " , SUN6I_CODEC_OM_DACA_CTRL ,
SUN6I_CODEC_OM_DACA_CTRL_RMIXEN , 0 ,
sun6i_codec_mixer_controls ) ,
/* Headphone output path */
SND_SOC_DAPM_MUX ( " Headphone Source Playback Route " ,
SND_SOC_NOPM , 0 , 0 , sun6i_codec_hp_src ) ,
SND_SOC_DAPM_OUT_DRV ( " Headphone Amp " , SUN6I_CODEC_OM_PA_CTRL ,
SUN6I_CODEC_OM_PA_CTRL_HPPAEN , 0 , NULL , 0 ) ,
SND_SOC_DAPM_SUPPLY ( " HPCOM Protection " , SUN6I_CODEC_OM_PA_CTRL ,
SUN6I_CODEC_OM_PA_CTRL_COMPTEN , 0 , NULL , 0 ) ,
SND_SOC_DAPM_REG ( snd_soc_dapm_supply , " HPCOM " , SUN6I_CODEC_OM_PA_CTRL ,
SUN6I_CODEC_OM_PA_CTRL_HPCOM_CTL , 0x3 , 0x3 , 0 ) ,
SND_SOC_DAPM_OUTPUT ( " HP " ) ,
2016-11-03 10:55:50 +03:00
/* Line Out path */
SND_SOC_DAPM_MUX ( " Line Out Source Playback Route " ,
SND_SOC_NOPM , 0 , 0 , sun6i_codec_lineout_src ) ,
SND_SOC_DAPM_OUTPUT ( " LINEOUT " ) ,
2016-11-03 10:55:48 +03:00
} ;
static const struct snd_soc_dapm_route sun6i_codec_codec_dapm_routes [ ] = {
/* DAC Routes */
{ " Left DAC " , NULL , " DAC Enable " } ,
{ " Right DAC " , NULL , " DAC Enable " } ,
2016-11-03 10:55:51 +03:00
/* Microphone Routes */
{ " Mic1 Amplifier " , NULL , " MIC1 " } ,
{ " Mic2 Amplifier Source Route " , " Mic2 " , " MIC2 " } ,
{ " Mic2 Amplifier Source Route " , " Mic3 " , " MIC3 " } ,
{ " Mic2 Amplifier " , NULL , " Mic2 Amplifier Source Route " } ,
2016-11-03 10:55:48 +03:00
/* Left Mixer Routes */
{ " Left Mixer " , " DAC Playback Switch " , " Left DAC " } ,
{ " Left Mixer " , " DAC Reversed Playback Switch " , " Right DAC " } ,
2016-11-03 10:55:49 +03:00
{ " Left Mixer " , " Line In Playback Switch " , " LINEIN " } ,
2016-11-03 10:55:51 +03:00
{ " Left Mixer " , " Mic1 Playback Switch " , " Mic1 Amplifier " } ,
{ " Left Mixer " , " Mic2 Playback Switch " , " Mic2 Amplifier " } ,
2016-11-03 10:55:48 +03:00
/* Right Mixer Routes */
{ " Right Mixer " , " DAC Playback Switch " , " Right DAC " } ,
{ " Right Mixer " , " DAC Reversed Playback Switch " , " Left DAC " } ,
2016-11-03 10:55:49 +03:00
{ " Right Mixer " , " Line In Playback Switch " , " LINEIN " } ,
2016-11-03 10:55:51 +03:00
{ " Right Mixer " , " Mic1 Playback Switch " , " Mic1 Amplifier " } ,
{ " Right Mixer " , " Mic2 Playback Switch " , " Mic2 Amplifier " } ,
2016-11-03 10:55:48 +03:00
2016-11-07 13:06:59 +03:00
/* Left ADC Mixer Routes */
{ " Left ADC Mixer " , " Mixer Capture Switch " , " Left Mixer " } ,
{ " Left ADC Mixer " , " Mixer Reversed Capture Switch " , " Right Mixer " } ,
{ " Left ADC Mixer " , " Line In Capture Switch " , " LINEIN " } ,
{ " Left ADC Mixer " , " Mic1 Capture Switch " , " Mic1 Amplifier " } ,
{ " Left ADC Mixer " , " Mic2 Capture Switch " , " Mic2 Amplifier " } ,
/* Right ADC Mixer Routes */
{ " Right ADC Mixer " , " Mixer Capture Switch " , " Right Mixer " } ,
{ " Right ADC Mixer " , " Mixer Reversed Capture Switch " , " Left Mixer " } ,
{ " Right ADC Mixer " , " Line In Capture Switch " , " LINEIN " } ,
{ " Right ADC Mixer " , " Mic1 Capture Switch " , " Mic1 Amplifier " } ,
{ " Right ADC Mixer " , " Mic2 Capture Switch " , " Mic2 Amplifier " } ,
2016-11-03 10:55:48 +03:00
/* Headphone Routes */
{ " Headphone Source Playback Route " , " DAC " , " Left DAC " } ,
{ " Headphone Source Playback Route " , " DAC " , " Right DAC " } ,
{ " Headphone Source Playback Route " , " Mixer " , " Left Mixer " } ,
{ " Headphone Source Playback Route " , " Mixer " , " Right Mixer " } ,
{ " Headphone Amp " , NULL , " Headphone Source Playback Route " } ,
{ " HP " , NULL , " Headphone Amp " } ,
{ " HPCOM " , NULL , " HPCOM Protection " } ,
2016-11-03 10:55:50 +03:00
/* Line Out Routes */
{ " Line Out Source Playback Route " , " Stereo " , " Left Mixer " } ,
{ " Line Out Source Playback Route " , " Stereo " , " Right Mixer " } ,
{ " Line Out Source Playback Route " , " Mono Differential " , " Left Mixer " } ,
2017-01-07 21:57:06 +03:00
{ " Line Out Source Playback Route " , " Mono Differential " , " Right Mixer " } ,
2016-11-03 10:55:50 +03:00
{ " LINEOUT " , NULL , " Line Out Source Playback Route " } ,
2016-11-07 13:06:59 +03:00
/* ADC Routes */
{ " Left ADC " , NULL , " ADC Enable " } ,
{ " Right ADC " , NULL , " ADC Enable " } ,
{ " Left ADC " , NULL , " Left ADC Mixer " } ,
{ " Right ADC " , NULL , " Right ADC Mixer " } ,
2016-11-03 10:55:48 +03:00
} ;
2017-08-03 19:00:20 +03:00
static const struct snd_soc_codec_driver sun6i_codec_codec = {
2016-11-03 10:55:48 +03:00
. component_driver = {
. controls = sun6i_codec_codec_widgets ,
. num_controls = ARRAY_SIZE ( sun6i_codec_codec_widgets ) ,
. dapm_widgets = sun6i_codec_codec_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( sun6i_codec_codec_dapm_widgets ) ,
. dapm_routes = sun6i_codec_codec_dapm_routes ,
. num_dapm_routes = ARRAY_SIZE ( sun6i_codec_codec_dapm_routes ) ,
} ,
} ;
2016-11-25 15:34:36 +03:00
/* sun8i A23 codec */
static const struct snd_kcontrol_new sun8i_a23_codec_codec_controls [ ] = {
SOC_SINGLE_TLV ( " DAC Playback Volume " , SUN4I_CODEC_DAC_DPC ,
SUN4I_CODEC_DAC_DPC_DVOL , 0x3f , 1 ,
sun6i_codec_dvol_scale ) ,
} ;
static const struct snd_soc_dapm_widget sun8i_a23_codec_codec_widgets [ ] = {
/* Digital parts of the ADCs */
SND_SOC_DAPM_SUPPLY ( " ADC Enable " , SUN6I_CODEC_ADC_FIFOC ,
SUN6I_CODEC_ADC_FIFOC_EN_AD , 0 , NULL , 0 ) ,
/* Digital parts of the DACs */
SND_SOC_DAPM_SUPPLY ( " DAC Enable " , SUN4I_CODEC_DAC_DPC ,
SUN4I_CODEC_DAC_DPC_EN_DA , 0 , NULL , 0 ) ,
} ;
2017-08-03 19:00:20 +03:00
static const struct snd_soc_codec_driver sun8i_a23_codec_codec = {
2016-11-25 15:34:36 +03:00
. component_driver = {
. controls = sun8i_a23_codec_codec_controls ,
. num_controls = ARRAY_SIZE ( sun8i_a23_codec_codec_controls ) ,
. dapm_widgets = sun8i_a23_codec_codec_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( sun8i_a23_codec_codec_widgets ) ,
} ,
} ;
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 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 ) ;
2017-07-17 00:11:06 +03:00
gpiod_set_value_cansleep ( scodec - > gpio_pa ,
! ! SND_SOC_DAPM_EVENT_ON ( event ) ) ;
2015-12-11 21:43:57 +03:00
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 )
2016-10-31 09:42:09 +03:00
return ERR_PTR ( - ENOMEM ) ;
2015-09-12 16:26:24 +03:00
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
2016-10-31 09:42:09 +03:00
return ERR_PTR ( - ENOMEM ) ;
2015-09-12 16:26:24 +03:00
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 ;
} ;
2016-11-03 10:55:53 +03:00
static const struct snd_soc_dapm_widget sun6i_codec_card_dapm_widgets [ ] = {
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
SND_SOC_DAPM_LINE ( " Line In " , NULL ) ,
SND_SOC_DAPM_LINE ( " Line Out " , NULL ) ,
SND_SOC_DAPM_MIC ( " Headset Mic " , NULL ) ,
SND_SOC_DAPM_MIC ( " Mic " , NULL ) ,
SND_SOC_DAPM_SPK ( " Speaker " , sun4i_codec_spk_event ) ,
} ;
2016-11-03 10:55:48 +03:00
static struct snd_soc_card * sun6i_codec_create_card ( struct device * dev )
{
struct snd_soc_card * card ;
2016-11-03 10:55:53 +03:00
int ret ;
2016-11-03 10:55:48 +03:00
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return ERR_PTR ( - ENOMEM ) ;
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
return ERR_PTR ( - ENOMEM ) ;
2016-11-03 10:55:53 +03:00
card - > dev = dev ;
card - > name = " A31 Audio Codec " ;
card - > dapm_widgets = sun6i_codec_card_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( sun6i_codec_card_dapm_widgets ) ;
card - > fully_routed = true ;
ret = snd_soc_of_parse_audio_routing ( card , " allwinner,audio-routing " ) ;
if ( ret )
dev_warn ( dev , " failed to parse audio-routing: %d \n " , ret ) ;
2016-11-03 10:55:48 +03:00
return card ;
} ;
2016-11-25 15:34:36 +03:00
/* Connect digital side enables to analog side widgets */
static const struct snd_soc_dapm_route sun8i_codec_card_routes [ ] = {
/* ADC Routes */
{ " Left ADC " , NULL , " ADC Enable " } ,
{ " Right ADC " , NULL , " ADC Enable " } ,
{ " Codec Capture " , NULL , " Left ADC " } ,
{ " Codec Capture " , NULL , " Right ADC " } ,
/* DAC Routes */
{ " Left DAC " , NULL , " DAC Enable " } ,
{ " Right DAC " , NULL , " DAC Enable " } ,
{ " Left DAC " , NULL , " Codec Playback " } ,
{ " Right DAC " , NULL , " Codec Playback " } ,
} ;
static struct snd_soc_aux_dev aux_dev = {
. name = " Codec Analog Controls " ,
} ;
static struct snd_soc_card * sun8i_a23_codec_create_card ( struct device * dev )
{
struct snd_soc_card * card ;
int ret ;
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return ERR_PTR ( - ENOMEM ) ;
aux_dev . codec_of_node = of_parse_phandle ( dev - > of_node ,
" allwinner,codec-analog-controls " ,
0 ) ;
if ( ! aux_dev . codec_of_node ) {
dev_err ( dev , " Can't find analog controls for codec. \n " ) ;
return ERR_PTR ( - EINVAL ) ;
} ;
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
return ERR_PTR ( - ENOMEM ) ;
card - > dev = dev ;
card - > name = " A23 Audio Codec " ;
card - > dapm_widgets = sun6i_codec_card_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( sun6i_codec_card_dapm_widgets ) ;
card - > dapm_routes = sun8i_codec_card_routes ;
card - > num_dapm_routes = ARRAY_SIZE ( sun8i_codec_card_routes ) ;
card - > aux_dev = & aux_dev ;
card - > num_aux_devs = 1 ;
card - > fully_routed = true ;
ret = snd_soc_of_parse_audio_routing ( card , " allwinner,audio-routing " ) ;
if ( ret )
dev_warn ( dev , " failed to parse audio-routing: %d \n " , ret ) ;
return card ;
} ;
2016-11-25 15:34:40 +03:00
static struct snd_soc_card * sun8i_h3_codec_create_card ( struct device * dev )
{
struct snd_soc_card * card ;
int ret ;
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return ERR_PTR ( - ENOMEM ) ;
aux_dev . codec_of_node = of_parse_phandle ( dev - > of_node ,
" allwinner,codec-analog-controls " ,
0 ) ;
if ( ! aux_dev . codec_of_node ) {
dev_err ( dev , " Can't find analog controls for codec. \n " ) ;
return ERR_PTR ( - EINVAL ) ;
} ;
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
return ERR_PTR ( - ENOMEM ) ;
card - > dev = dev ;
card - > name = " H3 Audio Codec " ;
card - > dapm_widgets = sun6i_codec_card_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( sun6i_codec_card_dapm_widgets ) ;
card - > dapm_routes = sun8i_codec_card_routes ;
card - > num_dapm_routes = ARRAY_SIZE ( sun8i_codec_card_routes ) ;
card - > aux_dev = & aux_dev ;
card - > num_aux_devs = 1 ;
card - > fully_routed = true ;
ret = snd_soc_of_parse_audio_routing ( card , " allwinner,audio-routing " ) ;
if ( ret )
dev_warn ( dev , " failed to parse audio-routing: %d \n " , ret ) ;
return card ;
} ;
2017-06-05 16:27:22 +03:00
static struct snd_soc_card * sun8i_v3s_codec_create_card ( struct device * dev )
{
struct snd_soc_card * card ;
int ret ;
card = devm_kzalloc ( dev , sizeof ( * card ) , GFP_KERNEL ) ;
if ( ! card )
return ERR_PTR ( - ENOMEM ) ;
aux_dev . codec_of_node = of_parse_phandle ( dev - > of_node ,
" allwinner,codec-analog-controls " ,
0 ) ;
if ( ! aux_dev . codec_of_node ) {
dev_err ( dev , " Can't find analog controls for codec. \n " ) ;
return ERR_PTR ( - EINVAL ) ;
} ;
card - > dai_link = sun4i_codec_create_link ( dev , & card - > num_links ) ;
if ( ! card - > dai_link )
return ERR_PTR ( - ENOMEM ) ;
card - > dev = dev ;
card - > name = " V3s Audio Codec " ;
card - > dapm_widgets = sun6i_codec_card_dapm_widgets ;
card - > num_dapm_widgets = ARRAY_SIZE ( sun6i_codec_card_dapm_widgets ) ;
card - > dapm_routes = sun8i_codec_card_routes ;
card - > num_dapm_routes = ARRAY_SIZE ( sun8i_codec_card_routes ) ;
card - > aux_dev = & aux_dev ;
card - > num_aux_devs = 1 ;
card - > fully_routed = true ;
ret = snd_soc_of_parse_audio_routing ( card , " allwinner,audio-routing " ) ;
if ( ret )
dev_warn ( dev , " failed to parse audio-routing: %d \n " , ret ) ;
return card ;
} ;
2016-11-03 10:55:43 +03:00
static const struct regmap_config sun4i_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN4I_CODEC_ADC_RXCNT ,
} ;
2016-11-03 10:55:48 +03:00
static const struct regmap_config sun6i_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN6I_CODEC_HMIC_DATA ,
} ;
2016-11-03 10:55:43 +03:00
static const struct regmap_config sun7i_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN7I_CODEC_AC_MIC_PHONE_CAL ,
} ;
2016-11-25 15:34:36 +03:00
static const struct regmap_config sun8i_a23_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN8I_A23_CODEC_ADC_RXCNT ,
} ;
2016-11-25 15:34:40 +03:00
static const struct regmap_config sun8i_h3_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN8I_H3_CODEC_ADC_DBG ,
} ;
2017-06-05 16:27:22 +03:00
static const struct regmap_config sun8i_v3s_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = SUN8I_H3_CODEC_ADC_DBG ,
} ;
2016-11-03 10:55:43 +03:00
struct sun4i_codec_quirks {
const struct regmap_config * regmap_config ;
2016-11-03 10:55:44 +03:00
const struct snd_soc_codec_driver * codec ;
struct snd_soc_card * ( * create_card ) ( struct device * dev ) ;
struct reg_field reg_adc_fifoc ; /* used for regmap_field */
unsigned int reg_dac_txdata ; /* TX FIFO offset for DMA config */
unsigned int reg_adc_rxdata ; /* RX FIFO offset for DMA config */
2016-11-07 13:06:58 +03:00
bool has_reset ;
2016-11-03 10:55:43 +03:00
} ;
static const struct sun4i_codec_quirks sun4i_codec_quirks = {
. regmap_config = & sun4i_codec_regmap_config ,
2016-11-03 10:55:44 +03:00
. codec = & sun4i_codec_codec ,
. create_card = sun4i_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN4I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA ,
2016-11-03 10:55:43 +03:00
} ;
2016-11-03 10:55:48 +03:00
static const struct sun4i_codec_quirks sun6i_a31_codec_quirks = {
. regmap_config = & sun6i_codec_regmap_config ,
. codec = & sun6i_codec_codec ,
. create_card = sun6i_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN6I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA ,
. has_reset = true ,
} ;
2016-11-03 10:55:43 +03:00
static const struct sun4i_codec_quirks sun7i_codec_quirks = {
. regmap_config = & sun7i_codec_regmap_config ,
2016-11-03 10:55:44 +03:00
. codec = & sun4i_codec_codec ,
. create_card = sun4i_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN4I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN4I_CODEC_ADC_RXDATA ,
2016-11-03 10:55:43 +03:00
} ;
2016-11-25 15:34:36 +03:00
static const struct sun4i_codec_quirks sun8i_a23_codec_quirks = {
. regmap_config = & sun8i_a23_codec_regmap_config ,
. codec = & sun8i_a23_codec_codec ,
. create_card = sun8i_a23_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN6I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA ,
. has_reset = true ,
} ;
2016-11-25 15:34:40 +03:00
static const struct sun4i_codec_quirks sun8i_h3_codec_quirks = {
. regmap_config = & sun8i_h3_codec_regmap_config ,
/*
* TODO Share the codec structure with A23 for now .
* This should be split out when adding digital audio
* processing support for the H3 .
*/
. codec = & sun8i_a23_codec_codec ,
. create_card = sun8i_h3_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN6I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA ,
. has_reset = true ,
} ;
2017-06-05 16:27:22 +03:00
static const struct sun4i_codec_quirks sun8i_v3s_codec_quirks = {
. regmap_config = & sun8i_v3s_codec_regmap_config ,
/*
* TODO The codec structure should be split out , like
* H3 , when adding digital audio processing support .
*/
. codec = & sun8i_a23_codec_codec ,
. create_card = sun8i_v3s_codec_create_card ,
. reg_adc_fifoc = REG_FIELD ( SUN6I_CODEC_ADC_FIFOC , 0 , 31 ) ,
. reg_dac_txdata = SUN8I_H3_CODEC_DAC_TXDATA ,
. reg_adc_rxdata = SUN6I_CODEC_ADC_RXDATA ,
. has_reset = true ,
} ;
2016-11-03 10:55:43 +03:00
static const struct of_device_id sun4i_codec_of_match [ ] = {
{
. compatible = " allwinner,sun4i-a10-codec " ,
. data = & sun4i_codec_quirks ,
} ,
2016-11-03 10:55:48 +03:00
{
. compatible = " allwinner,sun6i-a31-codec " ,
. data = & sun6i_a31_codec_quirks ,
} ,
2016-11-03 10:55:43 +03:00
{
. compatible = " allwinner,sun7i-a20-codec " ,
. data = & sun7i_codec_quirks ,
} ,
2016-11-25 15:34:36 +03:00
{
. compatible = " allwinner,sun8i-a23-codec " ,
. data = & sun8i_a23_codec_quirks ,
} ,
2016-11-25 15:34:40 +03:00
{
. compatible = " allwinner,sun8i-h3-codec " ,
. data = & sun8i_h3_codec_quirks ,
} ,
2017-06-05 16:27:22 +03:00
{
. compatible = " allwinner,sun8i-v3s-codec " ,
. data = & sun8i_v3s_codec_quirks ,
} ,
2016-11-03 10:55:43 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , sun4i_codec_of_match ) ;
2015-09-12 16:26:24 +03:00
static int sun4i_codec_probe ( struct platform_device * pdev )
{
struct snd_soc_card * card ;
struct sun4i_codec * scodec ;
2016-09-22 10:13:13 +03:00
const struct sun4i_codec_quirks * quirks ;
2015-09-12 16:26:24 +03:00
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 ) ;
}
2016-09-22 10:13:13 +03:00
quirks = of_device_get_match_data ( & pdev - > dev ) ;
if ( quirks = = NULL ) {
dev_err ( & pdev - > dev , " Failed to determine the quirks to use \n " ) ;
return - ENODEV ;
}
2015-09-12 16:26:24 +03:00
scodec - > regmap = devm_regmap_init_mmio ( & pdev - > dev , base ,
2016-09-22 10:13:13 +03:00
quirks - > regmap_config ) ;
2015-09-12 16:26:24 +03:00
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 ) ;
}
2016-11-07 13:06:58 +03:00
if ( quirks - > has_reset ) {
2017-07-19 18:26:43 +03:00
scodec - > rst = devm_reset_control_get_exclusive ( & pdev - > dev ,
NULL ) ;
2016-11-07 13:06:58 +03:00
if ( IS_ERR ( scodec - > rst ) ) {
dev_err ( & pdev - > dev , " Failed to get reset control \n " ) ;
return PTR_ERR ( scodec - > rst ) ;
}
2016-11-09 19:35:18 +03:00
}
2016-11-07 13:06:58 +03:00
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 ;
}
2016-11-03 10:55:44 +03:00
/* reg_field setup */
scodec - > reg_adc_fifoc = devm_regmap_field_alloc ( & pdev - > dev ,
scodec - > regmap ,
quirks - > reg_adc_fifoc ) ;
if ( IS_ERR ( scodec - > reg_adc_fifoc ) ) {
ret = PTR_ERR ( scodec - > reg_adc_fifoc ) ;
dev_err ( & pdev - > dev , " Failed to create regmap fields: %d \n " ,
ret ) ;
return ret ;
}
2016-11-01 09:31:55 +03:00
/* Enable the bus clock */
if ( clk_prepare_enable ( scodec - > clk_apb ) ) {
dev_err ( & pdev - > dev , " Failed to enable the APB clock \n " ) ;
return - EINVAL ;
}
2016-11-07 13:06:58 +03:00
/* Deassert the reset control */
if ( scodec - > rst ) {
ret = reset_control_deassert ( scodec - > rst ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Failed to deassert the reset control \n " ) ;
goto err_clk_disable ;
}
}
2015-09-12 16:26:24 +03:00
/* DMA configuration for TX FIFO */
2016-11-03 10:55:44 +03:00
scodec - > playback_dma_data . addr = res - > start + quirks - > reg_dac_txdata ;
2016-11-03 10:55:46 +03:00
scodec - > playback_dma_data . maxburst = 8 ;
2015-09-12 16:26:24 +03:00
scodec - > playback_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2015-11-30 18:37:47 +03:00
/* DMA configuration for RX FIFO */
2016-11-03 10:55:44 +03:00
scodec - > capture_dma_data . addr = res - > start + quirks - > reg_adc_rxdata ;
2016-11-03 10:55:46 +03:00
scodec - > capture_dma_data . maxburst = 8 ;
2015-11-30 18:37:47 +03:00
scodec - > capture_dma_data . addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES ;
2016-11-03 10:55:44 +03:00
ret = snd_soc_register_codec ( & pdev - > dev , quirks - > codec ,
2015-09-12 16:26:24 +03:00
& sun4i_codec_dai , 1 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register our codec \n " ) ;
2016-11-07 13:06:58 +03:00
goto err_assert_reset ;
2015-09-12 16:26:24 +03:00
}
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 ;
}
2016-11-03 10:55:44 +03:00
card = quirks - > create_card ( & pdev - > dev ) ;
2016-10-31 09:42:09 +03:00
if ( IS_ERR ( card ) ) {
ret = PTR_ERR ( card ) ;
2015-09-12 16:26:24 +03:00
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 ) ;
2016-11-07 13:06:58 +03:00
err_assert_reset :
if ( scodec - > rst )
reset_control_assert ( scodec - > rst ) ;
2015-09-12 16:26:24 +03:00
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 ) ;
2016-11-07 13:06:58 +03:00
if ( scodec - > rst )
reset_control_assert ( scodec - > rst ) ;
2015-09-12 16:26:24 +03:00
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> " ) ;
2016-11-03 10:55:44 +03:00
MODULE_AUTHOR ( " Chen-Yu Tsai <wens@csie.org> " ) ;
2015-09-12 16:26:24 +03:00
MODULE_LICENSE ( " GPL " ) ;