2018-08-21 19:52:46 +03:00
// SPDX-License-Identifier: GPL-2.0
2018-08-29 00:42:30 +03:00
//
// PCM3060 codec driver
//
2019-07-10 08:51:35 +03:00
// Copyright (C) 2018 Kirill Marinushkin <kmarinushkin@birdec.com>
2018-08-21 19:52:46 +03:00
# include <linux/module.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include <sound/tlv.h>
# include "pcm3060.h"
/* dai */
static int pcm3060_set_sysclk ( struct snd_soc_dai * dai , int clk_id ,
unsigned int freq , int dir )
{
struct snd_soc_component * comp = dai - > component ;
struct pcm3060_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
2019-02-11 09:08:39 +03:00
unsigned int reg ;
unsigned int val ;
2018-08-21 19:52:46 +03:00
if ( dir ! = SND_SOC_CLOCK_IN ) {
dev_err ( comp - > dev , " unsupported sysclock dir: %d \n " , dir ) ;
return - EINVAL ;
}
2019-02-11 09:08:39 +03:00
switch ( clk_id ) {
case PCM3060_CLK_DEF :
val = 0 ;
break ;
case PCM3060_CLK1 :
val = ( dai - > id = = PCM3060_DAI_ID_DAC ? PCM3060_REG_CSEL : 0 ) ;
break ;
case PCM3060_CLK2 :
val = ( dai - > id = = PCM3060_DAI_ID_DAC ? 0 : PCM3060_REG_CSEL ) ;
break ;
default :
dev_err ( comp - > dev , " unsupported sysclock id: %d \n " , clk_id ) ;
return - EINVAL ;
}
if ( dai - > id = = PCM3060_DAI_ID_DAC )
reg = PCM3060_REG67 ;
else
reg = PCM3060_REG72 ;
regmap_update_bits ( priv - > regmap , reg , PCM3060_REG_CSEL , val ) ;
2018-08-21 19:52:46 +03:00
priv - > dai [ dai - > id ] . sclk_freq = freq ;
return 0 ;
}
static int pcm3060_set_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
struct snd_soc_component * comp = dai - > component ;
struct pcm3060_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
unsigned int reg ;
unsigned int val ;
if ( ( fmt & SND_SOC_DAIFMT_INV_MASK ) ! = SND_SOC_DAIFMT_NB_NF ) {
dev_err ( comp - > dev , " unsupported DAI polarity: 0x%x \n " , fmt ) ;
return - EINVAL ;
}
2022-02-23 04:47:31 +03:00
switch ( fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK ) {
case SND_SOC_DAIFMT_CBP_CFP :
priv - > dai [ dai - > id ] . is_provider = true ;
2018-08-21 19:52:46 +03:00
break ;
2022-02-23 04:47:31 +03:00
case SND_SOC_DAIFMT_CBC_CFC :
priv - > dai [ dai - > id ] . is_provider = false ;
2018-08-21 19:52:46 +03:00
break ;
default :
2022-02-23 04:47:31 +03:00
dev_err ( comp - > dev , " unsupported DAI mode: 0x%x \n " , fmt ) ;
2018-08-21 19:52:46 +03:00
return - EINVAL ;
}
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
val = PCM3060_REG_FMT_I2S ;
break ;
case SND_SOC_DAIFMT_RIGHT_J :
val = PCM3060_REG_FMT_RJ ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
val = PCM3060_REG_FMT_LJ ;
break ;
default :
dev_err ( comp - > dev , " unsupported DAI format: 0x%x \n " , fmt ) ;
return - EINVAL ;
}
2018-08-29 00:42:31 +03:00
if ( dai - > id = = PCM3060_DAI_ID_DAC )
reg = PCM3060_REG67 ;
else
reg = PCM3060_REG72 ;
2018-08-21 19:52:46 +03:00
regmap_update_bits ( priv - > regmap , reg , PCM3060_REG_MASK_FMT , val ) ;
return 0 ;
}
static int pcm3060_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct snd_soc_component * comp = dai - > component ;
struct pcm3060_priv * priv = snd_soc_component_get_drvdata ( comp ) ;
unsigned int rate ;
unsigned int ratio ;
unsigned int reg ;
unsigned int val ;
2022-02-23 04:47:31 +03:00
if ( ! priv - > dai [ dai - > id ] . is_provider ) {
2018-08-21 19:52:46 +03:00
val = PCM3060_REG_MS_S ;
goto val_ready ;
}
rate = params_rate ( params ) ;
if ( ! rate ) {
dev_err ( comp - > dev , " rate is not configured \n " ) ;
return - EINVAL ;
}
ratio = priv - > dai [ dai - > id ] . sclk_freq / rate ;
switch ( ratio ) {
case 768 :
val = PCM3060_REG_MS_M768 ;
break ;
case 512 :
val = PCM3060_REG_MS_M512 ;
break ;
case 384 :
val = PCM3060_REG_MS_M384 ;
break ;
case 256 :
val = PCM3060_REG_MS_M256 ;
break ;
case 192 :
val = PCM3060_REG_MS_M192 ;
break ;
case 128 :
val = PCM3060_REG_MS_M128 ;
break ;
default :
dev_err ( comp - > dev , " unsupported ratio: %d \n " , ratio ) ;
return - EINVAL ;
}
val_ready :
2018-08-29 00:42:31 +03:00
if ( dai - > id = = PCM3060_DAI_ID_DAC )
reg = PCM3060_REG67 ;
else
reg = PCM3060_REG72 ;
2018-08-21 19:52:46 +03:00
regmap_update_bits ( priv - > regmap , reg , PCM3060_REG_MASK_MS , val ) ;
return 0 ;
}
static const struct snd_soc_dai_ops pcm3060_dai_ops = {
. set_sysclk = pcm3060_set_sysclk ,
. set_fmt = pcm3060_set_fmt ,
. hw_params = pcm3060_hw_params ,
} ;
# define PCM3060_DAI_RATES_ADC (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_32000 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 )
# define PCM3060_DAI_RATES_DAC (PCM3060_DAI_RATES_ADC | \
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 )
static struct snd_soc_dai_driver pcm3060_dai [ ] = {
{
. name = " pcm3060-dac " ,
. id = PCM3060_DAI_ID_DAC ,
. playback = {
. stream_name = " Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = PCM3060_DAI_RATES_DAC ,
. formats = SNDRV_PCM_FMTBIT_S24_LE ,
} ,
. ops = & pcm3060_dai_ops ,
} ,
{
. name = " pcm3060-adc " ,
. id = PCM3060_DAI_ID_ADC ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = PCM3060_DAI_RATES_ADC ,
. formats = SNDRV_PCM_FMTBIT_S24_LE ,
} ,
. ops = & pcm3060_dai_ops ,
} ,
} ;
/* dapm */
static DECLARE_TLV_DB_SCALE ( pcm3060_dapm_tlv , - 10050 , 50 , 1 ) ;
static const struct snd_kcontrol_new pcm3060_dapm_controls [ ] = {
SOC_DOUBLE_R_RANGE_TLV ( " Master Playback Volume " ,
PCM3060_REG65 , PCM3060_REG66 , 0 ,
PCM3060_REG_AT2_MIN , PCM3060_REG_AT2_MAX ,
0 , pcm3060_dapm_tlv ) ,
SOC_DOUBLE ( " Master Playback Switch " , PCM3060_REG68 ,
PCM3060_REG_SHIFT_MUT21 , PCM3060_REG_SHIFT_MUT22 , 1 , 1 ) ,
SOC_DOUBLE_R_RANGE_TLV ( " Master Capture Volume " ,
PCM3060_REG70 , PCM3060_REG71 , 0 ,
PCM3060_REG_AT1_MIN , PCM3060_REG_AT1_MAX ,
0 , pcm3060_dapm_tlv ) ,
SOC_DOUBLE ( " Master Capture Switch " , PCM3060_REG73 ,
PCM3060_REG_SHIFT_MUT11 , PCM3060_REG_SHIFT_MUT12 , 1 , 1 ) ,
} ;
static const struct snd_soc_dapm_widget pcm3060_dapm_widgets [ ] = {
2018-12-07 22:02:34 +03:00
SND_SOC_DAPM_DAC ( " DAC " , " Playback " , PCM3060_REG64 ,
PCM3060_REG_SHIFT_DAPSV , 1 ) ,
2018-11-12 10:08:34 +03:00
SND_SOC_DAPM_OUTPUT ( " OUTL " ) ,
SND_SOC_DAPM_OUTPUT ( " OUTR " ) ,
2018-08-21 19:52:46 +03:00
SND_SOC_DAPM_INPUT ( " INL " ) ,
SND_SOC_DAPM_INPUT ( " INR " ) ,
2018-12-07 22:02:34 +03:00
SND_SOC_DAPM_ADC ( " ADC " , " Capture " , PCM3060_REG64 ,
PCM3060_REG_SHIFT_ADPSV , 1 ) ,
2018-08-21 19:52:46 +03:00
} ;
static const struct snd_soc_dapm_route pcm3060_dapm_map [ ] = {
2018-12-07 22:02:34 +03:00
{ " OUTL " , NULL , " DAC " } ,
{ " OUTR " , NULL , " DAC " } ,
2018-08-21 19:52:46 +03:00
2018-12-07 22:02:34 +03:00
{ " ADC " , NULL , " INL " } ,
{ " ADC " , NULL , " INR " } ,
2018-08-21 19:52:46 +03:00
} ;
/* soc component */
static const struct snd_soc_component_driver pcm3060_soc_comp_driver = {
. controls = pcm3060_dapm_controls ,
. num_controls = ARRAY_SIZE ( pcm3060_dapm_controls ) ,
. dapm_widgets = pcm3060_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( pcm3060_dapm_widgets ) ,
. dapm_routes = pcm3060_dapm_map ,
. num_dapm_routes = ARRAY_SIZE ( pcm3060_dapm_map ) ,
2022-05-04 20:08:48 +03:00
. endianness = 1 ,
2018-08-21 19:52:46 +03:00
} ;
/* regmap */
static bool pcm3060_reg_writeable ( struct device * dev , unsigned int reg )
{
return ( reg > = PCM3060_REG64 ) ;
}
static bool pcm3060_reg_readable ( struct device * dev , unsigned int reg )
{
return ( reg > = PCM3060_REG64 ) ;
}
static bool pcm3060_reg_volatile ( struct device * dev , unsigned int reg )
{
/* PCM3060_REG64 is volatile */
return ( reg = = PCM3060_REG64 ) ;
}
static const struct reg_default pcm3060_reg_defaults [ ] = {
{ PCM3060_REG64 , 0xF0 } ,
{ PCM3060_REG65 , 0xFF } ,
{ PCM3060_REG66 , 0xFF } ,
{ PCM3060_REG67 , 0x00 } ,
{ PCM3060_REG68 , 0x00 } ,
{ PCM3060_REG69 , 0x00 } ,
{ PCM3060_REG70 , 0xD7 } ,
{ PCM3060_REG71 , 0xD7 } ,
{ PCM3060_REG72 , 0x00 } ,
{ PCM3060_REG73 , 0x00 } ,
} ;
const struct regmap_config pcm3060_regmap = {
. reg_bits = 8 ,
. val_bits = 8 ,
. writeable_reg = pcm3060_reg_writeable ,
. readable_reg = pcm3060_reg_readable ,
. volatile_reg = pcm3060_reg_volatile ,
. max_register = PCM3060_REG73 ,
. reg_defaults = pcm3060_reg_defaults ,
. num_reg_defaults = ARRAY_SIZE ( pcm3060_reg_defaults ) ,
. cache_type = REGCACHE_RBTREE ,
} ;
EXPORT_SYMBOL ( pcm3060_regmap ) ;
/* device */
2018-11-12 10:08:33 +03:00
static void pcm3060_parse_dt ( const struct device_node * np ,
struct pcm3060_priv * priv )
{
priv - > out_se = of_property_read_bool ( np , " ti,out-single-ended " ) ;
}
2018-08-21 19:52:46 +03:00
int pcm3060_probe ( struct device * dev )
{
int rc ;
2018-11-12 10:08:33 +03:00
struct pcm3060_priv * priv = dev_get_drvdata ( dev ) ;
2019-02-11 09:08:38 +03:00
/* soft reset */
rc = regmap_update_bits ( priv - > regmap , PCM3060_REG64 ,
PCM3060_REG_MRST , 0 ) ;
if ( rc ) {
dev_err ( dev , " failed to reset component, rc=%d \n " , rc ) ;
return rc ;
}
2018-11-12 10:08:33 +03:00
if ( dev - > of_node )
pcm3060_parse_dt ( dev - > of_node , priv ) ;
if ( priv - > out_se )
regmap_update_bits ( priv - > regmap , PCM3060_REG64 ,
PCM3060_REG_SE , PCM3060_REG_SE ) ;
2018-08-21 19:52:46 +03:00
rc = devm_snd_soc_register_component ( dev , & pcm3060_soc_comp_driver ,
pcm3060_dai ,
ARRAY_SIZE ( pcm3060_dai ) ) ;
if ( rc ) {
dev_err ( dev , " failed to register component, rc=%d \n " , rc ) ;
return rc ;
}
return 0 ;
}
EXPORT_SYMBOL ( pcm3060_probe ) ;
MODULE_DESCRIPTION ( " PCM3060 codec driver " ) ;
2019-07-10 08:51:35 +03:00
MODULE_AUTHOR ( " Kirill Marinushkin <kmarinushkin@birdec.com> " ) ;
2018-08-21 19:52:46 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;