172ed5a8cSapatard@mandriva.com /* 272ed5a8cSapatard@mandriva.com * cs42l51.c 372ed5a8cSapatard@mandriva.com * 472ed5a8cSapatard@mandriva.com * ASoC Driver for Cirrus Logic CS42L51 codecs 572ed5a8cSapatard@mandriva.com * 672ed5a8cSapatard@mandriva.com * Copyright (c) 2010 Arnaud Patard <apatard@mandriva.com> 772ed5a8cSapatard@mandriva.com * 872ed5a8cSapatard@mandriva.com * Based on cs4270.c - Copyright (c) Freescale Semiconductor 972ed5a8cSapatard@mandriva.com * 1072ed5a8cSapatard@mandriva.com * This program is free software; you can redistribute it and/or modify 1172ed5a8cSapatard@mandriva.com * it under the terms of the GNU General Public License version 2 as 1272ed5a8cSapatard@mandriva.com * published by the Free Software Foundation. 1372ed5a8cSapatard@mandriva.com * 1472ed5a8cSapatard@mandriva.com * This program is distributed in the hope that it will be useful, 1572ed5a8cSapatard@mandriva.com * but WITHOUT ANY WARRANTY; without even the implied warranty of 1672ed5a8cSapatard@mandriva.com * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1772ed5a8cSapatard@mandriva.com * GNU General Public License for more details. 1872ed5a8cSapatard@mandriva.com * 1972ed5a8cSapatard@mandriva.com * For now: 2072ed5a8cSapatard@mandriva.com * - Only I2C is support. Not SPI 2172ed5a8cSapatard@mandriva.com * - master mode *NOT* supported 2272ed5a8cSapatard@mandriva.com */ 2372ed5a8cSapatard@mandriva.com 2472ed5a8cSapatard@mandriva.com #include <linux/module.h> 2572ed5a8cSapatard@mandriva.com #include <linux/slab.h> 2672ed5a8cSapatard@mandriva.com #include <sound/core.h> 2772ed5a8cSapatard@mandriva.com #include <sound/soc.h> 2872ed5a8cSapatard@mandriva.com #include <sound/tlv.h> 2972ed5a8cSapatard@mandriva.com #include <sound/initval.h> 3072ed5a8cSapatard@mandriva.com #include <sound/pcm_params.h> 3172ed5a8cSapatard@mandriva.com #include <sound/pcm.h> 32da071489SMark Brown #include <linux/regmap.h> 3372ed5a8cSapatard@mandriva.com 3472ed5a8cSapatard@mandriva.com #include "cs42l51.h" 3572ed5a8cSapatard@mandriva.com 3672ed5a8cSapatard@mandriva.com enum master_slave_mode { 3772ed5a8cSapatard@mandriva.com MODE_SLAVE, 3872ed5a8cSapatard@mandriva.com MODE_SLAVE_AUTO, 3972ed5a8cSapatard@mandriva.com MODE_MASTER, 4072ed5a8cSapatard@mandriva.com }; 4172ed5a8cSapatard@mandriva.com 4272ed5a8cSapatard@mandriva.com struct cs42l51_private { 4372ed5a8cSapatard@mandriva.com unsigned int mclk; 4472ed5a8cSapatard@mandriva.com unsigned int audio_mode; /* The mode (I2S or left-justified) */ 4572ed5a8cSapatard@mandriva.com enum master_slave_mode func; 4672ed5a8cSapatard@mandriva.com }; 4772ed5a8cSapatard@mandriva.com 4872ed5a8cSapatard@mandriva.com #define CS42L51_FORMATS ( \ 4972ed5a8cSapatard@mandriva.com SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ 5072ed5a8cSapatard@mandriva.com SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ 5172ed5a8cSapatard@mandriva.com SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ 5272ed5a8cSapatard@mandriva.com SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE) 5372ed5a8cSapatard@mandriva.com 5472ed5a8cSapatard@mandriva.com static int cs42l51_get_chan_mix(struct snd_kcontrol *kcontrol, 5572ed5a8cSapatard@mandriva.com struct snd_ctl_elem_value *ucontrol) 5672ed5a8cSapatard@mandriva.com { 5772ed5a8cSapatard@mandriva.com struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 5872ed5a8cSapatard@mandriva.com unsigned long value = snd_soc_read(codec, CS42L51_PCM_MIXER)&3; 5972ed5a8cSapatard@mandriva.com 6072ed5a8cSapatard@mandriva.com switch (value) { 6172ed5a8cSapatard@mandriva.com default: 6272ed5a8cSapatard@mandriva.com case 0: 6372ed5a8cSapatard@mandriva.com ucontrol->value.integer.value[0] = 0; 6472ed5a8cSapatard@mandriva.com break; 6572ed5a8cSapatard@mandriva.com /* same value : (L+R)/2 and (R+L)/2 */ 6672ed5a8cSapatard@mandriva.com case 1: 6772ed5a8cSapatard@mandriva.com case 2: 6872ed5a8cSapatard@mandriva.com ucontrol->value.integer.value[0] = 1; 6972ed5a8cSapatard@mandriva.com break; 7072ed5a8cSapatard@mandriva.com case 3: 7172ed5a8cSapatard@mandriva.com ucontrol->value.integer.value[0] = 2; 7272ed5a8cSapatard@mandriva.com break; 7372ed5a8cSapatard@mandriva.com } 7472ed5a8cSapatard@mandriva.com 7572ed5a8cSapatard@mandriva.com return 0; 7672ed5a8cSapatard@mandriva.com } 7772ed5a8cSapatard@mandriva.com 7872ed5a8cSapatard@mandriva.com #define CHAN_MIX_NORMAL 0x00 7972ed5a8cSapatard@mandriva.com #define CHAN_MIX_BOTH 0x55 8072ed5a8cSapatard@mandriva.com #define CHAN_MIX_SWAP 0xFF 8172ed5a8cSapatard@mandriva.com 8272ed5a8cSapatard@mandriva.com static int cs42l51_set_chan_mix(struct snd_kcontrol *kcontrol, 8372ed5a8cSapatard@mandriva.com struct snd_ctl_elem_value *ucontrol) 8472ed5a8cSapatard@mandriva.com { 8572ed5a8cSapatard@mandriva.com struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); 8672ed5a8cSapatard@mandriva.com unsigned char val; 8772ed5a8cSapatard@mandriva.com 8872ed5a8cSapatard@mandriva.com switch (ucontrol->value.integer.value[0]) { 8972ed5a8cSapatard@mandriva.com default: 9072ed5a8cSapatard@mandriva.com case 0: 9172ed5a8cSapatard@mandriva.com val = CHAN_MIX_NORMAL; 9272ed5a8cSapatard@mandriva.com break; 9372ed5a8cSapatard@mandriva.com case 1: 9472ed5a8cSapatard@mandriva.com val = CHAN_MIX_BOTH; 9572ed5a8cSapatard@mandriva.com break; 9672ed5a8cSapatard@mandriva.com case 2: 9772ed5a8cSapatard@mandriva.com val = CHAN_MIX_SWAP; 9872ed5a8cSapatard@mandriva.com break; 9972ed5a8cSapatard@mandriva.com } 10072ed5a8cSapatard@mandriva.com 10172ed5a8cSapatard@mandriva.com snd_soc_write(codec, CS42L51_PCM_MIXER, val); 10272ed5a8cSapatard@mandriva.com 10372ed5a8cSapatard@mandriva.com return 1; 10472ed5a8cSapatard@mandriva.com } 10572ed5a8cSapatard@mandriva.com 10672ed5a8cSapatard@mandriva.com static const DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -5150, 50, 0); 10772ed5a8cSapatard@mandriva.com static const DECLARE_TLV_DB_SCALE(tone_tlv, -1050, 150, 0); 1087272e051SBrian Austin 1097272e051SBrian Austin static const DECLARE_TLV_DB_SCALE(aout_tlv, -10200, 50, 0); 11072ed5a8cSapatard@mandriva.com 11172ed5a8cSapatard@mandriva.com static const DECLARE_TLV_DB_SCALE(boost_tlv, 1600, 1600, 0); 11272ed5a8cSapatard@mandriva.com static const char *chan_mix[] = { 11372ed5a8cSapatard@mandriva.com "L R", 11472ed5a8cSapatard@mandriva.com "L+R", 11572ed5a8cSapatard@mandriva.com "R L", 11672ed5a8cSapatard@mandriva.com }; 11772ed5a8cSapatard@mandriva.com 1186109ab2bSTakashi Iwai static SOC_ENUM_SINGLE_EXT_DECL(cs42l51_chan_mix, chan_mix); 11972ed5a8cSapatard@mandriva.com 12072ed5a8cSapatard@mandriva.com static const struct snd_kcontrol_new cs42l51_snd_controls[] = { 12172ed5a8cSapatard@mandriva.com SOC_DOUBLE_R_SX_TLV("PCM Playback Volume", 12272ed5a8cSapatard@mandriva.com CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 1237272e051SBrian Austin 0, 0x19, 0x7F, adc_pcm_tlv), 12472ed5a8cSapatard@mandriva.com SOC_DOUBLE_R("PCM Playback Switch", 12572ed5a8cSapatard@mandriva.com CS42L51_PCMA_VOL, CS42L51_PCMB_VOL, 7, 1, 1), 12672ed5a8cSapatard@mandriva.com SOC_DOUBLE_R_SX_TLV("Analog Playback Volume", 12772ed5a8cSapatard@mandriva.com CS42L51_AOUTA_VOL, CS42L51_AOUTB_VOL, 1281d99f243SBrian Austin 0, 0x34, 0xE4, aout_tlv), 12972ed5a8cSapatard@mandriva.com SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", 13072ed5a8cSapatard@mandriva.com CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 1317272e051SBrian Austin 0, 0x19, 0x7F, adc_pcm_tlv), 13272ed5a8cSapatard@mandriva.com SOC_DOUBLE_R("ADC Mixer Switch", 13372ed5a8cSapatard@mandriva.com CS42L51_ADCA_VOL, CS42L51_ADCB_VOL, 7, 1, 1), 13472ed5a8cSapatard@mandriva.com SOC_SINGLE("Playback Deemphasis Switch", CS42L51_DAC_CTL, 3, 1, 0), 13572ed5a8cSapatard@mandriva.com SOC_SINGLE("Auto-Mute Switch", CS42L51_DAC_CTL, 2, 1, 0), 13672ed5a8cSapatard@mandriva.com SOC_SINGLE("Soft Ramp Switch", CS42L51_DAC_CTL, 1, 1, 0), 13772ed5a8cSapatard@mandriva.com SOC_SINGLE("Zero Cross Switch", CS42L51_DAC_CTL, 0, 0, 0), 13872ed5a8cSapatard@mandriva.com SOC_DOUBLE_TLV("Mic Boost Volume", 13972ed5a8cSapatard@mandriva.com CS42L51_MIC_CTL, 0, 1, 1, 0, boost_tlv), 14072ed5a8cSapatard@mandriva.com SOC_SINGLE_TLV("Bass Volume", CS42L51_TONE_CTL, 0, 0xf, 1, tone_tlv), 14172ed5a8cSapatard@mandriva.com SOC_SINGLE_TLV("Treble Volume", CS42L51_TONE_CTL, 4, 0xf, 1, tone_tlv), 14272ed5a8cSapatard@mandriva.com SOC_ENUM_EXT("PCM channel mixer", 14372ed5a8cSapatard@mandriva.com cs42l51_chan_mix, 14472ed5a8cSapatard@mandriva.com cs42l51_get_chan_mix, cs42l51_set_chan_mix), 14572ed5a8cSapatard@mandriva.com }; 14672ed5a8cSapatard@mandriva.com 14772ed5a8cSapatard@mandriva.com /* 14872ed5a8cSapatard@mandriva.com * to power down, one must: 14972ed5a8cSapatard@mandriva.com * 1.) Enable the PDN bit 15072ed5a8cSapatard@mandriva.com * 2.) enable power-down for the select channels 15172ed5a8cSapatard@mandriva.com * 3.) disable the PDN bit. 15272ed5a8cSapatard@mandriva.com */ 15372ed5a8cSapatard@mandriva.com static int cs42l51_pdn_event(struct snd_soc_dapm_widget *w, 15472ed5a8cSapatard@mandriva.com struct snd_kcontrol *kcontrol, int event) 15572ed5a8cSapatard@mandriva.com { 15672ed5a8cSapatard@mandriva.com switch (event) { 15772ed5a8cSapatard@mandriva.com case SND_SOC_DAPM_PRE_PMD: 158e94de1e8SAxel Lin snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, 159e94de1e8SAxel Lin CS42L51_POWER_CTL1_PDN, 160e94de1e8SAxel Lin CS42L51_POWER_CTL1_PDN); 16172ed5a8cSapatard@mandriva.com break; 16272ed5a8cSapatard@mandriva.com default: 16372ed5a8cSapatard@mandriva.com case SND_SOC_DAPM_POST_PMD: 164e94de1e8SAxel Lin snd_soc_update_bits(w->codec, CS42L51_POWER_CTL1, 165e94de1e8SAxel Lin CS42L51_POWER_CTL1_PDN, 0); 16672ed5a8cSapatard@mandriva.com break; 16772ed5a8cSapatard@mandriva.com } 16872ed5a8cSapatard@mandriva.com 16972ed5a8cSapatard@mandriva.com return 0; 17072ed5a8cSapatard@mandriva.com } 17172ed5a8cSapatard@mandriva.com 17272ed5a8cSapatard@mandriva.com static const char *cs42l51_dac_names[] = {"Direct PCM", 17372ed5a8cSapatard@mandriva.com "DSP PCM", "ADC"}; 1746109ab2bSTakashi Iwai static SOC_ENUM_SINGLE_DECL(cs42l51_dac_mux_enum, 1756109ab2bSTakashi Iwai CS42L51_DAC_CTL, 6, cs42l51_dac_names); 17672ed5a8cSapatard@mandriva.com static const struct snd_kcontrol_new cs42l51_dac_mux_controls = 17772ed5a8cSapatard@mandriva.com SOC_DAPM_ENUM("Route", cs42l51_dac_mux_enum); 17872ed5a8cSapatard@mandriva.com 17972ed5a8cSapatard@mandriva.com static const char *cs42l51_adcl_names[] = {"AIN1 Left", "AIN2 Left", 18072ed5a8cSapatard@mandriva.com "MIC Left", "MIC+preamp Left"}; 1816109ab2bSTakashi Iwai static SOC_ENUM_SINGLE_DECL(cs42l51_adcl_mux_enum, 1826109ab2bSTakashi Iwai CS42L51_ADC_INPUT, 4, cs42l51_adcl_names); 18372ed5a8cSapatard@mandriva.com static const struct snd_kcontrol_new cs42l51_adcl_mux_controls = 18472ed5a8cSapatard@mandriva.com SOC_DAPM_ENUM("Route", cs42l51_adcl_mux_enum); 18572ed5a8cSapatard@mandriva.com 18672ed5a8cSapatard@mandriva.com static const char *cs42l51_adcr_names[] = {"AIN1 Right", "AIN2 Right", 18772ed5a8cSapatard@mandriva.com "MIC Right", "MIC+preamp Right"}; 1886109ab2bSTakashi Iwai static SOC_ENUM_SINGLE_DECL(cs42l51_adcr_mux_enum, 1896109ab2bSTakashi Iwai CS42L51_ADC_INPUT, 6, cs42l51_adcr_names); 19072ed5a8cSapatard@mandriva.com static const struct snd_kcontrol_new cs42l51_adcr_mux_controls = 19172ed5a8cSapatard@mandriva.com SOC_DAPM_ENUM("Route", cs42l51_adcr_mux_enum); 19272ed5a8cSapatard@mandriva.com 19372ed5a8cSapatard@mandriva.com static const struct snd_soc_dapm_widget cs42l51_dapm_widgets[] = { 19472ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MICBIAS("Mic Bias", CS42L51_MIC_POWER_CTL, 1, 1), 19572ed5a8cSapatard@mandriva.com SND_SOC_DAPM_PGA_E("Left PGA", CS42L51_POWER_CTL1, 3, 1, NULL, 0, 19672ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 19772ed5a8cSapatard@mandriva.com SND_SOC_DAPM_PGA_E("Right PGA", CS42L51_POWER_CTL1, 4, 1, NULL, 0, 19872ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 19972ed5a8cSapatard@mandriva.com SND_SOC_DAPM_ADC_E("Left ADC", "Left HiFi Capture", 20072ed5a8cSapatard@mandriva.com CS42L51_POWER_CTL1, 1, 1, 20172ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 20272ed5a8cSapatard@mandriva.com SND_SOC_DAPM_ADC_E("Right ADC", "Right HiFi Capture", 20372ed5a8cSapatard@mandriva.com CS42L51_POWER_CTL1, 2, 1, 20472ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 20572ed5a8cSapatard@mandriva.com SND_SOC_DAPM_DAC_E("Left DAC", "Left HiFi Playback", 20672ed5a8cSapatard@mandriva.com CS42L51_POWER_CTL1, 5, 1, 20772ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 20872ed5a8cSapatard@mandriva.com SND_SOC_DAPM_DAC_E("Right DAC", "Right HiFi Playback", 20972ed5a8cSapatard@mandriva.com CS42L51_POWER_CTL1, 6, 1, 21072ed5a8cSapatard@mandriva.com cs42l51_pdn_event, SND_SOC_DAPM_PRE_POST_PMD), 21172ed5a8cSapatard@mandriva.com 21272ed5a8cSapatard@mandriva.com /* analog/mic */ 21372ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("AIN1L"), 21472ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("AIN1R"), 21572ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("AIN2L"), 21672ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("AIN2R"), 21772ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("MICL"), 21872ed5a8cSapatard@mandriva.com SND_SOC_DAPM_INPUT("MICR"), 21972ed5a8cSapatard@mandriva.com 22072ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MIXER("Mic Preamp Left", 22172ed5a8cSapatard@mandriva.com CS42L51_MIC_POWER_CTL, 2, 1, NULL, 0), 22272ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MIXER("Mic Preamp Right", 22372ed5a8cSapatard@mandriva.com CS42L51_MIC_POWER_CTL, 3, 1, NULL, 0), 22472ed5a8cSapatard@mandriva.com 22572ed5a8cSapatard@mandriva.com /* HP */ 22672ed5a8cSapatard@mandriva.com SND_SOC_DAPM_OUTPUT("HPL"), 22772ed5a8cSapatard@mandriva.com SND_SOC_DAPM_OUTPUT("HPR"), 22872ed5a8cSapatard@mandriva.com 22972ed5a8cSapatard@mandriva.com /* mux */ 23072ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MUX("DAC Mux", SND_SOC_NOPM, 0, 0, 23172ed5a8cSapatard@mandriva.com &cs42l51_dac_mux_controls), 23272ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MUX("PGA-ADC Mux Left", SND_SOC_NOPM, 0, 0, 23372ed5a8cSapatard@mandriva.com &cs42l51_adcl_mux_controls), 23472ed5a8cSapatard@mandriva.com SND_SOC_DAPM_MUX("PGA-ADC Mux Right", SND_SOC_NOPM, 0, 0, 23572ed5a8cSapatard@mandriva.com &cs42l51_adcr_mux_controls), 23672ed5a8cSapatard@mandriva.com }; 23772ed5a8cSapatard@mandriva.com 23872ed5a8cSapatard@mandriva.com static const struct snd_soc_dapm_route cs42l51_routes[] = { 23972ed5a8cSapatard@mandriva.com {"HPL", NULL, "Left DAC"}, 24072ed5a8cSapatard@mandriva.com {"HPR", NULL, "Right DAC"}, 24172ed5a8cSapatard@mandriva.com 24272ed5a8cSapatard@mandriva.com {"Left ADC", NULL, "Left PGA"}, 24372ed5a8cSapatard@mandriva.com {"Right ADC", NULL, "Right PGA"}, 24472ed5a8cSapatard@mandriva.com 24572ed5a8cSapatard@mandriva.com {"Mic Preamp Left", NULL, "MICL"}, 24672ed5a8cSapatard@mandriva.com {"Mic Preamp Right", NULL, "MICR"}, 24772ed5a8cSapatard@mandriva.com 24872ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Left", "AIN1 Left", "AIN1L" }, 24972ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Left", "AIN2 Left", "AIN2L" }, 25072ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Left", "MIC Left", "MICL" }, 25172ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Left", "MIC+preamp Left", "Mic Preamp Left" }, 25272ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Right", "AIN1 Right", "AIN1R" }, 25372ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Right", "AIN2 Right", "AIN2R" }, 25472ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Right", "MIC Right", "MICR" }, 25572ed5a8cSapatard@mandriva.com {"PGA-ADC Mux Right", "MIC+preamp Right", "Mic Preamp Right" }, 25672ed5a8cSapatard@mandriva.com 25772ed5a8cSapatard@mandriva.com {"Left PGA", NULL, "PGA-ADC Mux Left"}, 25872ed5a8cSapatard@mandriva.com {"Right PGA", NULL, "PGA-ADC Mux Right"}, 25972ed5a8cSapatard@mandriva.com }; 26072ed5a8cSapatard@mandriva.com 26172ed5a8cSapatard@mandriva.com static int cs42l51_set_dai_fmt(struct snd_soc_dai *codec_dai, 26272ed5a8cSapatard@mandriva.com unsigned int format) 26372ed5a8cSapatard@mandriva.com { 26472ed5a8cSapatard@mandriva.com struct snd_soc_codec *codec = codec_dai->codec; 26572ed5a8cSapatard@mandriva.com struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); 26672ed5a8cSapatard@mandriva.com 26772ed5a8cSapatard@mandriva.com switch (format & SND_SOC_DAIFMT_FORMAT_MASK) { 26872ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_I2S: 26972ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_LEFT_J: 27072ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_RIGHT_J: 27172ed5a8cSapatard@mandriva.com cs42l51->audio_mode = format & SND_SOC_DAIFMT_FORMAT_MASK; 27272ed5a8cSapatard@mandriva.com break; 27372ed5a8cSapatard@mandriva.com default: 27472ed5a8cSapatard@mandriva.com dev_err(codec->dev, "invalid DAI format\n"); 275ac60155fSAxel Lin return -EINVAL; 27672ed5a8cSapatard@mandriva.com } 27772ed5a8cSapatard@mandriva.com 27872ed5a8cSapatard@mandriva.com switch (format & SND_SOC_DAIFMT_MASTER_MASK) { 27972ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_CBM_CFM: 28072ed5a8cSapatard@mandriva.com cs42l51->func = MODE_MASTER; 28172ed5a8cSapatard@mandriva.com break; 28272ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_CBS_CFS: 28372ed5a8cSapatard@mandriva.com cs42l51->func = MODE_SLAVE_AUTO; 28472ed5a8cSapatard@mandriva.com break; 28572ed5a8cSapatard@mandriva.com default: 286ac60155fSAxel Lin dev_err(codec->dev, "Unknown master/slave configuration\n"); 287ac60155fSAxel Lin return -EINVAL; 28872ed5a8cSapatard@mandriva.com } 28972ed5a8cSapatard@mandriva.com 290ac60155fSAxel Lin return 0; 29172ed5a8cSapatard@mandriva.com } 29272ed5a8cSapatard@mandriva.com 29372ed5a8cSapatard@mandriva.com struct cs42l51_ratios { 29472ed5a8cSapatard@mandriva.com unsigned int ratio; 29572ed5a8cSapatard@mandriva.com unsigned char speed_mode; 29672ed5a8cSapatard@mandriva.com unsigned char mclk; 29772ed5a8cSapatard@mandriva.com }; 29872ed5a8cSapatard@mandriva.com 29972ed5a8cSapatard@mandriva.com static struct cs42l51_ratios slave_ratios[] = { 30072ed5a8cSapatard@mandriva.com { 512, CS42L51_QSM_MODE, 0 }, { 768, CS42L51_QSM_MODE, 0 }, 30172ed5a8cSapatard@mandriva.com { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, 30272ed5a8cSapatard@mandriva.com { 2048, CS42L51_QSM_MODE, 0 }, { 3072, CS42L51_QSM_MODE, 0 }, 30372ed5a8cSapatard@mandriva.com { 256, CS42L51_HSM_MODE, 0 }, { 384, CS42L51_HSM_MODE, 0 }, 30472ed5a8cSapatard@mandriva.com { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, 30572ed5a8cSapatard@mandriva.com { 1024, CS42L51_HSM_MODE, 0 }, { 1536, CS42L51_HSM_MODE, 0 }, 30672ed5a8cSapatard@mandriva.com { 128, CS42L51_SSM_MODE, 0 }, { 192, CS42L51_SSM_MODE, 0 }, 30772ed5a8cSapatard@mandriva.com { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, 30872ed5a8cSapatard@mandriva.com { 512, CS42L51_SSM_MODE, 0 }, { 768, CS42L51_SSM_MODE, 0 }, 30972ed5a8cSapatard@mandriva.com { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, 31072ed5a8cSapatard@mandriva.com { 256, CS42L51_DSM_MODE, 0 }, { 384, CS42L51_DSM_MODE, 0 }, 31172ed5a8cSapatard@mandriva.com }; 31272ed5a8cSapatard@mandriva.com 31372ed5a8cSapatard@mandriva.com static struct cs42l51_ratios slave_auto_ratios[] = { 31472ed5a8cSapatard@mandriva.com { 1024, CS42L51_QSM_MODE, 0 }, { 1536, CS42L51_QSM_MODE, 0 }, 31572ed5a8cSapatard@mandriva.com { 2048, CS42L51_QSM_MODE, 1 }, { 3072, CS42L51_QSM_MODE, 1 }, 31672ed5a8cSapatard@mandriva.com { 512, CS42L51_HSM_MODE, 0 }, { 768, CS42L51_HSM_MODE, 0 }, 31772ed5a8cSapatard@mandriva.com { 1024, CS42L51_HSM_MODE, 1 }, { 1536, CS42L51_HSM_MODE, 1 }, 31872ed5a8cSapatard@mandriva.com { 256, CS42L51_SSM_MODE, 0 }, { 384, CS42L51_SSM_MODE, 0 }, 31972ed5a8cSapatard@mandriva.com { 512, CS42L51_SSM_MODE, 1 }, { 768, CS42L51_SSM_MODE, 1 }, 32072ed5a8cSapatard@mandriva.com { 128, CS42L51_DSM_MODE, 0 }, { 192, CS42L51_DSM_MODE, 0 }, 32172ed5a8cSapatard@mandriva.com { 256, CS42L51_DSM_MODE, 1 }, { 384, CS42L51_DSM_MODE, 1 }, 32272ed5a8cSapatard@mandriva.com }; 32372ed5a8cSapatard@mandriva.com 32472ed5a8cSapatard@mandriva.com static int cs42l51_set_dai_sysclk(struct snd_soc_dai *codec_dai, 32572ed5a8cSapatard@mandriva.com int clk_id, unsigned int freq, int dir) 32672ed5a8cSapatard@mandriva.com { 32772ed5a8cSapatard@mandriva.com struct snd_soc_codec *codec = codec_dai->codec; 32872ed5a8cSapatard@mandriva.com struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); 32972ed5a8cSapatard@mandriva.com 33072ed5a8cSapatard@mandriva.com cs42l51->mclk = freq; 33172ed5a8cSapatard@mandriva.com return 0; 33272ed5a8cSapatard@mandriva.com } 33372ed5a8cSapatard@mandriva.com 33472ed5a8cSapatard@mandriva.com static int cs42l51_hw_params(struct snd_pcm_substream *substream, 33572ed5a8cSapatard@mandriva.com struct snd_pcm_hw_params *params, 33672ed5a8cSapatard@mandriva.com struct snd_soc_dai *dai) 33772ed5a8cSapatard@mandriva.com { 338e6968a17SMark Brown struct snd_soc_codec *codec = dai->codec; 33972ed5a8cSapatard@mandriva.com struct cs42l51_private *cs42l51 = snd_soc_codec_get_drvdata(codec); 34072ed5a8cSapatard@mandriva.com int ret; 34172ed5a8cSapatard@mandriva.com unsigned int i; 34272ed5a8cSapatard@mandriva.com unsigned int rate; 34372ed5a8cSapatard@mandriva.com unsigned int ratio; 34472ed5a8cSapatard@mandriva.com struct cs42l51_ratios *ratios = NULL; 34572ed5a8cSapatard@mandriva.com int nr_ratios = 0; 34672ed5a8cSapatard@mandriva.com int intf_ctl, power_ctl, fmt; 34772ed5a8cSapatard@mandriva.com 34872ed5a8cSapatard@mandriva.com switch (cs42l51->func) { 34972ed5a8cSapatard@mandriva.com case MODE_MASTER: 35072ed5a8cSapatard@mandriva.com return -EINVAL; 35172ed5a8cSapatard@mandriva.com case MODE_SLAVE: 35272ed5a8cSapatard@mandriva.com ratios = slave_ratios; 35372ed5a8cSapatard@mandriva.com nr_ratios = ARRAY_SIZE(slave_ratios); 35472ed5a8cSapatard@mandriva.com break; 35572ed5a8cSapatard@mandriva.com case MODE_SLAVE_AUTO: 35672ed5a8cSapatard@mandriva.com ratios = slave_auto_ratios; 35772ed5a8cSapatard@mandriva.com nr_ratios = ARRAY_SIZE(slave_auto_ratios); 35872ed5a8cSapatard@mandriva.com break; 35972ed5a8cSapatard@mandriva.com } 36072ed5a8cSapatard@mandriva.com 36172ed5a8cSapatard@mandriva.com /* Figure out which MCLK/LRCK ratio to use */ 36272ed5a8cSapatard@mandriva.com rate = params_rate(params); /* Sampling rate, in Hz */ 36372ed5a8cSapatard@mandriva.com ratio = cs42l51->mclk / rate; /* MCLK/LRCK ratio */ 36472ed5a8cSapatard@mandriva.com for (i = 0; i < nr_ratios; i++) { 36572ed5a8cSapatard@mandriva.com if (ratios[i].ratio == ratio) 36672ed5a8cSapatard@mandriva.com break; 36772ed5a8cSapatard@mandriva.com } 36872ed5a8cSapatard@mandriva.com 36972ed5a8cSapatard@mandriva.com if (i == nr_ratios) { 37072ed5a8cSapatard@mandriva.com /* We did not find a matching ratio */ 37172ed5a8cSapatard@mandriva.com dev_err(codec->dev, "could not find matching ratio\n"); 37272ed5a8cSapatard@mandriva.com return -EINVAL; 37372ed5a8cSapatard@mandriva.com } 37472ed5a8cSapatard@mandriva.com 37572ed5a8cSapatard@mandriva.com intf_ctl = snd_soc_read(codec, CS42L51_INTF_CTL); 37672ed5a8cSapatard@mandriva.com power_ctl = snd_soc_read(codec, CS42L51_MIC_POWER_CTL); 37772ed5a8cSapatard@mandriva.com 37872ed5a8cSapatard@mandriva.com intf_ctl &= ~(CS42L51_INTF_CTL_MASTER | CS42L51_INTF_CTL_ADC_I2S 37972ed5a8cSapatard@mandriva.com | CS42L51_INTF_CTL_DAC_FORMAT(7)); 38072ed5a8cSapatard@mandriva.com power_ctl &= ~(CS42L51_MIC_POWER_CTL_SPEED(3) 38172ed5a8cSapatard@mandriva.com | CS42L51_MIC_POWER_CTL_MCLK_DIV2); 38272ed5a8cSapatard@mandriva.com 38372ed5a8cSapatard@mandriva.com switch (cs42l51->func) { 38472ed5a8cSapatard@mandriva.com case MODE_MASTER: 38572ed5a8cSapatard@mandriva.com intf_ctl |= CS42L51_INTF_CTL_MASTER; 38672ed5a8cSapatard@mandriva.com power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); 38772ed5a8cSapatard@mandriva.com break; 38872ed5a8cSapatard@mandriva.com case MODE_SLAVE: 38972ed5a8cSapatard@mandriva.com power_ctl |= CS42L51_MIC_POWER_CTL_SPEED(ratios[i].speed_mode); 39072ed5a8cSapatard@mandriva.com break; 39172ed5a8cSapatard@mandriva.com case MODE_SLAVE_AUTO: 39272ed5a8cSapatard@mandriva.com power_ctl |= CS42L51_MIC_POWER_CTL_AUTO; 39372ed5a8cSapatard@mandriva.com break; 39472ed5a8cSapatard@mandriva.com } 39572ed5a8cSapatard@mandriva.com 39672ed5a8cSapatard@mandriva.com switch (cs42l51->audio_mode) { 39772ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_I2S: 39872ed5a8cSapatard@mandriva.com intf_ctl |= CS42L51_INTF_CTL_ADC_I2S; 39972ed5a8cSapatard@mandriva.com intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_I2S); 40072ed5a8cSapatard@mandriva.com break; 40172ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_LEFT_J: 40272ed5a8cSapatard@mandriva.com intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(CS42L51_DAC_DIF_LJ24); 40372ed5a8cSapatard@mandriva.com break; 40472ed5a8cSapatard@mandriva.com case SND_SOC_DAIFMT_RIGHT_J: 4051b6b0dfaSMark Brown switch (params_width(params)) { 4061b6b0dfaSMark Brown case 16: 40772ed5a8cSapatard@mandriva.com fmt = CS42L51_DAC_DIF_RJ16; 40872ed5a8cSapatard@mandriva.com break; 4091b6b0dfaSMark Brown case 18: 41072ed5a8cSapatard@mandriva.com fmt = CS42L51_DAC_DIF_RJ18; 41172ed5a8cSapatard@mandriva.com break; 4121b6b0dfaSMark Brown case 20: 41372ed5a8cSapatard@mandriva.com fmt = CS42L51_DAC_DIF_RJ20; 41472ed5a8cSapatard@mandriva.com break; 4151b6b0dfaSMark Brown case 24: 41672ed5a8cSapatard@mandriva.com fmt = CS42L51_DAC_DIF_RJ24; 41772ed5a8cSapatard@mandriva.com break; 41872ed5a8cSapatard@mandriva.com default: 41972ed5a8cSapatard@mandriva.com dev_err(codec->dev, "unknown format\n"); 42072ed5a8cSapatard@mandriva.com return -EINVAL; 42172ed5a8cSapatard@mandriva.com } 42272ed5a8cSapatard@mandriva.com intf_ctl |= CS42L51_INTF_CTL_DAC_FORMAT(fmt); 42372ed5a8cSapatard@mandriva.com break; 42472ed5a8cSapatard@mandriva.com default: 42572ed5a8cSapatard@mandriva.com dev_err(codec->dev, "unknown format\n"); 42672ed5a8cSapatard@mandriva.com return -EINVAL; 42772ed5a8cSapatard@mandriva.com } 42872ed5a8cSapatard@mandriva.com 42972ed5a8cSapatard@mandriva.com if (ratios[i].mclk) 43072ed5a8cSapatard@mandriva.com power_ctl |= CS42L51_MIC_POWER_CTL_MCLK_DIV2; 43172ed5a8cSapatard@mandriva.com 43272ed5a8cSapatard@mandriva.com ret = snd_soc_write(codec, CS42L51_INTF_CTL, intf_ctl); 43372ed5a8cSapatard@mandriva.com if (ret < 0) 43472ed5a8cSapatard@mandriva.com return ret; 43572ed5a8cSapatard@mandriva.com 43672ed5a8cSapatard@mandriva.com ret = snd_soc_write(codec, CS42L51_MIC_POWER_CTL, power_ctl); 43772ed5a8cSapatard@mandriva.com if (ret < 0) 43872ed5a8cSapatard@mandriva.com return ret; 43972ed5a8cSapatard@mandriva.com 44072ed5a8cSapatard@mandriva.com return 0; 44172ed5a8cSapatard@mandriva.com } 44272ed5a8cSapatard@mandriva.com 44372ed5a8cSapatard@mandriva.com static int cs42l51_dai_mute(struct snd_soc_dai *dai, int mute) 44472ed5a8cSapatard@mandriva.com { 44572ed5a8cSapatard@mandriva.com struct snd_soc_codec *codec = dai->codec; 44672ed5a8cSapatard@mandriva.com int reg; 44772ed5a8cSapatard@mandriva.com int mask = CS42L51_DAC_OUT_CTL_DACA_MUTE|CS42L51_DAC_OUT_CTL_DACB_MUTE; 44872ed5a8cSapatard@mandriva.com 44972ed5a8cSapatard@mandriva.com reg = snd_soc_read(codec, CS42L51_DAC_OUT_CTL); 45072ed5a8cSapatard@mandriva.com 45172ed5a8cSapatard@mandriva.com if (mute) 45272ed5a8cSapatard@mandriva.com reg |= mask; 45372ed5a8cSapatard@mandriva.com else 45472ed5a8cSapatard@mandriva.com reg &= ~mask; 45572ed5a8cSapatard@mandriva.com 45672ed5a8cSapatard@mandriva.com return snd_soc_write(codec, CS42L51_DAC_OUT_CTL, reg); 45772ed5a8cSapatard@mandriva.com } 45872ed5a8cSapatard@mandriva.com 45985e7652dSLars-Peter Clausen static const struct snd_soc_dai_ops cs42l51_dai_ops = { 46072ed5a8cSapatard@mandriva.com .hw_params = cs42l51_hw_params, 46172ed5a8cSapatard@mandriva.com .set_sysclk = cs42l51_set_dai_sysclk, 46272ed5a8cSapatard@mandriva.com .set_fmt = cs42l51_set_dai_fmt, 46372ed5a8cSapatard@mandriva.com .digital_mute = cs42l51_dai_mute, 46472ed5a8cSapatard@mandriva.com }; 46572ed5a8cSapatard@mandriva.com 466f0fba2adSLiam Girdwood static struct snd_soc_dai_driver cs42l51_dai = { 467f0fba2adSLiam Girdwood .name = "cs42l51-hifi", 46872ed5a8cSapatard@mandriva.com .playback = { 46972ed5a8cSapatard@mandriva.com .stream_name = "Playback", 47072ed5a8cSapatard@mandriva.com .channels_min = 1, 47172ed5a8cSapatard@mandriva.com .channels_max = 2, 47272ed5a8cSapatard@mandriva.com .rates = SNDRV_PCM_RATE_8000_96000, 47372ed5a8cSapatard@mandriva.com .formats = CS42L51_FORMATS, 47472ed5a8cSapatard@mandriva.com }, 47572ed5a8cSapatard@mandriva.com .capture = { 47672ed5a8cSapatard@mandriva.com .stream_name = "Capture", 47772ed5a8cSapatard@mandriva.com .channels_min = 1, 47872ed5a8cSapatard@mandriva.com .channels_max = 2, 47972ed5a8cSapatard@mandriva.com .rates = SNDRV_PCM_RATE_8000_96000, 48072ed5a8cSapatard@mandriva.com .formats = CS42L51_FORMATS, 48172ed5a8cSapatard@mandriva.com }, 48272ed5a8cSapatard@mandriva.com .ops = &cs42l51_dai_ops, 48372ed5a8cSapatard@mandriva.com }; 48472ed5a8cSapatard@mandriva.com 485a1253ef6SBrian Austin static int cs42l51_codec_probe(struct snd_soc_codec *codec) 48672ed5a8cSapatard@mandriva.com { 487f0fba2adSLiam Girdwood int ret, reg; 48872ed5a8cSapatard@mandriva.com 489f0fba2adSLiam Girdwood /* 490f0fba2adSLiam Girdwood * DAC configuration 491f0fba2adSLiam Girdwood * - Use signal processor 492f0fba2adSLiam Girdwood * - auto mute 493f0fba2adSLiam Girdwood * - vol changes immediate 494f0fba2adSLiam Girdwood * - no de-emphasize 495f0fba2adSLiam Girdwood */ 496f0fba2adSLiam Girdwood reg = CS42L51_DAC_CTL_DATA_SEL(1) 497f0fba2adSLiam Girdwood | CS42L51_DAC_CTL_AMUTE | CS42L51_DAC_CTL_DACSZ(0); 498f0fba2adSLiam Girdwood ret = snd_soc_write(codec, CS42L51_DAC_CTL, reg); 499f0fba2adSLiam Girdwood if (ret < 0) 500f0fba2adSLiam Girdwood return ret; 501f0fba2adSLiam Girdwood 50272ed5a8cSapatard@mandriva.com return 0; 50372ed5a8cSapatard@mandriva.com } 50472ed5a8cSapatard@mandriva.com 505f0fba2adSLiam Girdwood static struct snd_soc_codec_driver soc_codec_device_cs42l51 = { 506a1253ef6SBrian Austin .probe = cs42l51_codec_probe, 5073f7cec04SAxel Lin 5083f7cec04SAxel Lin .controls = cs42l51_snd_controls, 5093f7cec04SAxel Lin .num_controls = ARRAY_SIZE(cs42l51_snd_controls), 5103f7cec04SAxel Lin .dapm_widgets = cs42l51_dapm_widgets, 5113f7cec04SAxel Lin .num_dapm_widgets = ARRAY_SIZE(cs42l51_dapm_widgets), 5123f7cec04SAxel Lin .dapm_routes = cs42l51_routes, 5133f7cec04SAxel Lin .num_dapm_routes = ARRAY_SIZE(cs42l51_routes), 514f0fba2adSLiam Girdwood }; 51572ed5a8cSapatard@mandriva.com 516a1253ef6SBrian Austin const struct regmap_config cs42l51_regmap = { 517da071489SMark Brown .max_register = CS42L51_CHARGE_FREQ, 518da071489SMark Brown .cache_type = REGCACHE_RBTREE, 519da071489SMark Brown }; 520a1253ef6SBrian Austin EXPORT_SYMBOL_GPL(cs42l51_regmap); 521da071489SMark Brown 522a1253ef6SBrian Austin int cs42l51_probe(struct device *dev, struct regmap *regmap) 52372ed5a8cSapatard@mandriva.com { 524f0fba2adSLiam Girdwood struct cs42l51_private *cs42l51; 525da071489SMark Brown unsigned int val; 526f0fba2adSLiam Girdwood int ret; 52772ed5a8cSapatard@mandriva.com 528a1253ef6SBrian Austin if (IS_ERR(regmap)) 529a1253ef6SBrian Austin return PTR_ERR(regmap); 530a1253ef6SBrian Austin 531a1253ef6SBrian Austin cs42l51 = devm_kzalloc(dev, sizeof(struct cs42l51_private), 532a1253ef6SBrian Austin GFP_KERNEL); 533a1253ef6SBrian Austin if (!cs42l51) 534a1253ef6SBrian Austin return -ENOMEM; 535a1253ef6SBrian Austin 536a1253ef6SBrian Austin dev_set_drvdata(dev, cs42l51); 537da071489SMark Brown 538f0fba2adSLiam Girdwood /* Verify that we have a CS42L51 */ 539da071489SMark Brown ret = regmap_read(regmap, CS42L51_CHIP_REV_ID, &val); 540f0fba2adSLiam Girdwood if (ret < 0) { 541a1253ef6SBrian Austin dev_err(dev, "failed to read I2C\n"); 542f0fba2adSLiam Girdwood goto error; 543f0fba2adSLiam Girdwood } 54472ed5a8cSapatard@mandriva.com 545da071489SMark Brown if ((val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_A)) && 546da071489SMark Brown (val != CS42L51_MK_CHIP_REV(CS42L51_CHIP_ID, CS42L51_CHIP_REV_B))) { 547a1253ef6SBrian Austin dev_err(dev, "Invalid chip id: %x\n", val); 548f0fba2adSLiam Girdwood ret = -ENODEV; 549f0fba2adSLiam Girdwood goto error; 550f0fba2adSLiam Girdwood } 551a1253ef6SBrian Austin dev_info(dev, "Cirrus Logic CS42L51, Revision: %02X\n", val & 0xFF); 552f0fba2adSLiam Girdwood 553a1253ef6SBrian Austin ret = snd_soc_register_codec(dev, 554f0fba2adSLiam Girdwood &soc_codec_device_cs42l51, &cs42l51_dai, 1); 555f0fba2adSLiam Girdwood error: 556f0fba2adSLiam Girdwood return ret; 557f0fba2adSLiam Girdwood } 558a1253ef6SBrian Austin EXPORT_SYMBOL_GPL(cs42l51_probe); 559f0fba2adSLiam Girdwood 560dfd72a68SThomas Petazzoni static const struct of_device_id cs42l51_of_match[] = { 561dfd72a68SThomas Petazzoni { .compatible = "cirrus,cs42l51", }, 562dfd72a68SThomas Petazzoni { } 563dfd72a68SThomas Petazzoni }; 564dfd72a68SThomas Petazzoni MODULE_DEVICE_TABLE(of, cs42l51_of_match); 56569737897SArnaud Patard (Rtp) MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); 56672ed5a8cSapatard@mandriva.com MODULE_DESCRIPTION("Cirrus Logic CS42L51 ALSA SoC Codec Driver"); 56772ed5a8cSapatard@mandriva.com MODULE_LICENSE("GPL"); 568