[alsa-devel] [PATCH] ASoC: Add support for CS42L52 Codec
This patch adds support for Cirrus Logic CS42L52 Low Power Stereo Codec
Signed-off-by: Brian Austin brian.austin@cirrus.com Signed-off-by: Georgi Vlaev joe@nucleusys.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs42l52.c | 1079 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cs42l52.h | 264 +++++++++++ 4 files changed, 1349 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/cs42l52.c create mode 100644 sound/soc/codecs/cs42l52.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2e51eb0..0235532 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -29,6 +29,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_ALC5632 if I2C select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC select SND_SOC_CS42L51 if I2C + select SND_SOC_CS42L52 if I2C select SND_SOC_CS42L73 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI @@ -183,6 +184,9 @@ config SND_SOC_CQ0093VC config SND_SOC_CS42L51 tristate
+config SND_SOC_CS42L52 + tristate + config SND_SOC_CS42L73 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index db61c44..d11ab90 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -15,6 +15,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs42l51-objs := cs42l51.o +snd-soc-cs42l52-objs := cs42l52.o snd-soc-cs42l73-objs := cs42l73.o snd-soc-cs4270-objs := cs4270.o snd-soc-cs4271-objs := cs4271.o @@ -123,6 +124,7 @@ obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o +obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o diff --git a/sound/soc/codecs/cs42l52.c b/sound/soc/codecs/cs42l52.c new file mode 100644 index 0000000..a7a5ff5 --- /dev/null +++ b/sound/soc/codecs/cs42l52.c @@ -0,0 +1,1079 @@ +/* + * cs42l52.c -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev joe@nucleusys.com + * Author: Brian Austin brian.austin@cirrus.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include "cs42l52.h" + +struct sp_config { + u8 spc, format, spfs; + u32 srate; +}; + +struct cs42l52_private { + struct regmap *regmap; + struct sp_config config; + u32 sysclk; + u8 mclksel; + u32 mclk; + u8 flags; +}; + +static const struct reg_default cs42l52_reg_defaults[] = { + { 1, 0xE0 }, /* r01 ChipID */ + { 2, 0x9F }, /* r02 PWRCTL 1 */ + { 3, 0x07 }, /* r03 PWRCTL 2 */ + { 4, 0xFF }, /* r04 PWRCTL 3 */ + { 5, 0xA0 }, /* r05 Clocking Ctl */ + { 6, 0x00 }, /* r06 Interface Ctl 1 */ + { 7, 0x00 }, /* r07 Interface Ctl 2 */ + { 8, 0x80 }, /* r08 Input A Select */ + { 9, 0x80 }, /* r09 Input B Select */ + { 10, 0xA5 }, /* r0A Analog HPF Ctl */ + { 11, 0x00 }, /* r0B ADC HPF Corner Freq */ + { 12, 0x00 }, /* r0C Misc. ADC Ctl */ + { 13, 0x60 }, /* r0D Playback Ctl 1 */ + { 14, 0x02 }, /* r0E Misc. Ctl */ + { 15, 0x00 }, /* r0F Playback Ctl 2 */ + { 16, 0x00 }, /* r10 MICA Amp Ctl */ + { 17, 0x00 }, /* r11 MICB Amp Ctl */ + { 18, 0x00 }, /* r12 PGAA Vol, Misc. */ + { 19, 0x00 }, /* r13 PGAB Vol, Misc. */ + { 20, 0x00 }, /* r14 Bypass A Vol */ + { 21, 0x00 }, /* r15 Bypass B Vol */ + { 22, 0x00 }, /* r16 ADCA Volume */ + { 23, 0x00 }, /* r17 ADCB Volume */ + { 24, 0x80 }, /* r18 ADCA Mixer Volume */ + { 25, 0x80 }, /* r19 ADCB Mixer Volume */ + { 26, 0x00 }, /* r1A PCMA Mixer Volume */ + { 27, 0x00 }, /* r1B PCMB Mixer Volume */ + { 28, 0x00 }, /* r1C Beep Freq on Time */ + { 29, 0x00 }, /* r1D Beep Volume off Time */ + { 30, 0x00 }, /* r1E Beep Tone Cfg. */ + { 31, 0x00 }, /* r1F Tone Ctl */ + { 32, 0x88 }, /* r20 Master A Volume */ + { 33, 0x00 }, /* r21 Master B Volume */ + { 34, 0x00 }, /* r22 Headphone A Volume */ + { 35, 0x00 }, /* r23 Headphone B Volume */ + { 36, 0x00 }, /* r24 Speaker A Volume */ + { 37, 0x00 }, /* r25 Speaker B Volume */ + { 38, 0x00 }, /* r26 Channel Mixer and Swap */ + { 39, 0x00 }, /* r27 Limit Ctl 1 Thresholds */ + { 40, 0x7F }, /* r28 Limit Ctl 2 Release Rate */ + { 41, 0xC0 }, /* r29 Limiter Attack Rate */ + { 42, 0x00 }, /* r2A ALC Ctl 1 Attack Rate */ + { 43, 0x3F }, /* r2B ALC Release Rate */ + { 44, 0x3f }, /* r2C ALC Thresholds */ + { 45, 0x00 }, /* r2D Noise Gate Ctl */ + { 46, 0x00 }, /* r2E Overflow and Clock Status */ + { 47, 0x00 }, /* r2F battery Compensation */ + { 48, 0x00 }, /* r30 VP Battery Level */ + { 49, 0x00 }, /* r31 Speaker Status */ + { 50, 0x3B }, /* r32 Temp Ctl */ + { 51, 0x00 }, /* r33 Foldback */ + { 52, 0x5F }, /* r34 Change Pump Freq */ +}; + +static bool cs42l52_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS42L52_CHIP: + case CS42L52_PWRCTL1: + case CS42L52_PWRCTL2: + case CS42L52_PWRCTL3: + case CS42L52_CLK_CTL: + case CS42L52_IFACE_CTL1: + case CS42L52_IFACE_CTL2: + case CS42L52_ADC_PGA_A: + case CS42L52_ADC_PGA_B: + case CS42L52_ANALOG_HPF_CTL: + case CS42L52_ADC_HPF_FREQ: + case CS42L52_ADC_MISC_CTL: + case CS42L52_PB_CTL1: + case CS42L52_MISC_CTL: + case CS42L52_PB_CTL2: + case CS42L52_MICA_CTL: + case CS42L52_MICB_CTL: + case CS42L52_PGAA_CTL: + case CS42L52_PGAB_CTL: + case CS42L52_PASSTHRUA_VOL: + case CS42L52_PASSTHRUB_VOL: + case CS42L52_ADCA_VOL: + case CS42L52_ADCB_VOL: + case CS42L52_ADCA_MIXER_VOL: + case CS42L52_ADCB_MIXER_VOL: + case CS42L52_PCMA_MIXER_VOL: + case CS42L52_PCMB_MIXER_VOL: + case CS42L52_BEEP_FREQ: + case CS42L52_BEEP_VOL: + case CS42L52_BEEP_TONE_CTL: + case CS42L52_TONE_CTL: + case CS42L52_MASTERA_VOL: + case CS42L52_MASTERB_VOL: + case CS42L52_HPA_VOL: + case CS42L52_HPB_VOL: + case CS42L52_SPKA_VOL: + case CS42L52_SPKB_VOL: + case CS42L52_ADC_PCM_MIXER: + case CS42L52_LIMITER_CTL1: + case CS42L52_LIMITER_CTL2: + case CS42L52_LIMITER_AT_RATE: + case CS42L52_ALC_CTL: + case CS42L52_ALC_RATE: + case CS42L52_ALC_THRESHOLD: + case CS42L52_NOISE_GATE_CTL: + case CS42L52_CLK_STATUS: + case CS42L52_BATT_COMPEN: + case CS42L52_BATT_LEVEL: + case CS42L52_SPK_STATUS: + case CS42L52_TEM_CTL: + case CS42L52_THE_FOLDBACK: + case CS42L52_CHARGE_PUMP: + return true; + default: + return false; + } +} + +static DECLARE_TLV_DB_SCALE(hl_tlv, -10200, 50, 0); + +static DECLARE_TLV_DB_SCALE(hpd_tlv, -9600, 50, 1); + +static DECLARE_TLV_DB_SCALE(ipd_tlv, -9600, 100, 0); + +static DECLARE_TLV_DB_SCALE(mic_tlv, 1600, 100, 0); + +static DECLARE_TLV_DB_SCALE(pga_tlv, -600, 50, 0); + +static const unsigned int limiter_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 2, TLV_DB_SCALE_ITEM(-3000, 600, 0), + 3, 7, TLV_DB_SCALE_ITEM(-1200, 300, 0), +}; + +static const char * const cs42l52_adca_text[] = { + "Input1A", "Input2A", "Input3A", "Input4A", "PGA Input Left"}; + +static const char * const cs42l52_adcb_text[] = { + "Input1B", "Input2B", "Input3B", "Input4B", "PGA Input Right"}; + +static const struct soc_enum adca_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_PGA_A, 5, + ARRAY_SIZE(cs42l52_adca_text), cs42l52_adca_text); + +static const struct soc_enum adcb_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_PGA_B, 5, + ARRAY_SIZE(cs42l52_adcb_text), cs42l52_adcb_text); + +static const struct snd_kcontrol_new adca_mux = + SOC_DAPM_ENUM("Left ADC Input Capture Mux", adca_enum); + +static const struct snd_kcontrol_new adcb_mux = + SOC_DAPM_ENUM("Right ADC Input Capture Mux", adcb_enum); + +static const char * const mic_bias_level_text[] = { + "0.5 +VA", "0.6 +VA", "0.7 +VA", + "0.8 +VA", "0.83 +VA", "0.91 +VA" +}; + +static const struct soc_enum mic_bias_level_enum = + SOC_ENUM_SINGLE(CS42L52_IFACE_CTL1, 0, + ARRAY_SIZE(mic_bias_level_text), mic_bias_level_text); + +static const char * const cs42l52_mic_text[] = { "Single", "Differential" }; + +static const struct soc_enum mica_enum = + SOC_ENUM_SINGLE(CS42L52_MICA_CTL, 5, + ARRAY_SIZE(cs42l52_mic_text), cs42l52_mic_text); + +static const struct soc_enum micb_enum = + SOC_ENUM_SINGLE(CS42L52_MICB_CTL, 5, + ARRAY_SIZE(cs42l52_mic_text), cs42l52_mic_text); + +static const struct snd_kcontrol_new mica_mux = + SOC_DAPM_ENUM("Left Mic Input Capture Mux", mica_enum); + +static const struct snd_kcontrol_new micb_mux = + SOC_DAPM_ENUM("Right Mic Input Capture Mux", micb_enum); + +static const char * const digital_output_mux_text[] = {"ADC", "DSP"}; + +static const struct soc_enum digital_output_mux_enum = + SOC_ENUM_SINGLE(CS42L52_ADC_MISC_CTL, 6, + ARRAY_SIZE(digital_output_mux_text), + digital_output_mux_text); + +static const struct snd_kcontrol_new digital_output_mux = + SOC_DAPM_ENUM("Digital Output Mux", digital_output_mux_enum); + +static const char * const hp_gain_num_text[] = { + "0.3959", "0.4571", "0.5111", "0.6047", + "0.7099", "0.8399", "1.000", "1.1430" +}; + +static const struct soc_enum hp_gain_enum = + SOC_ENUM_SINGLE(CS42L52_PB_CTL1, 4, + ARRAY_SIZE(hp_gain_num_text), hp_gain_num_text); + +static const char * const beep_pitch_text[] = { + "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5", + "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7" +}; + +static const struct soc_enum beep_pitch_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 4, + ARRAY_SIZE(beep_pitch_text), beep_pitch_text); + +static const char * const beep_ontime_text[] = { + "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s", + "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s", + "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s" +}; + +static const struct soc_enum beep_ontime_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 0, + ARRAY_SIZE(beep_ontime_text), beep_ontime_text); + +static const char * const beep_offtime_text[] = { + "1.23 s", "2.58 s", "3.90 s", "5.20 s", + "6.60 s", "8.05 s", "9.35 s", "10.80 s" +}; + +static const struct soc_enum beep_offtime_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_VOL, 5, + ARRAY_SIZE(beep_offtime_text), beep_offtime_text); + +static const char * const beep_config_text[] = { + "Off", "Single", "Multiple", "Continuous" +}; + +static const struct soc_enum beep_config_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 6, + ARRAY_SIZE(beep_config_text), beep_config_text); + +static const char * const beep_bass_text[] = { + "50 Hz", "100 Hz", "200 Hz", "250 Hz" +}; + +static const struct soc_enum beep_bass_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 1, + ARRAY_SIZE(beep_bass_text), beep_bass_text); + +static const char * const beep_treble_text[] = { + "5 kHz", "7 kHz", "10 kHz", " 15 kHz" +}; + +static const struct soc_enum beep_treble_enum = + SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 3, + ARRAY_SIZE(beep_treble_text), beep_treble_text); + +static const char * const ng_threshold_text[] = { + "-34dB", "-37dB", "-40dB", "-43dB", + "-46dB", "-52dB", "-58dB", "-64dB" +}; + +static const struct soc_enum ng_threshold_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 2, + ARRAY_SIZE(ng_threshold_text), ng_threshold_text); + +static const char * const cs42l52_ng_delay_text[] = { + "50ms", "100ms", "150ms", "200ms"}; + +static const struct soc_enum ng_delay_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 0, + ARRAY_SIZE(cs42l52_ng_delay_text), cs42l52_ng_delay_text); + +static const char * const cs42l52_ng_type_text[] = { + "Apply Specific", "Apply All" +}; + +static const struct soc_enum ng_type_enum = + SOC_ENUM_SINGLE(CS42L52_NOISE_GATE_CTL, 6, + ARRAY_SIZE(cs42l52_ng_type_text), cs42l52_ng_type_text); + +static const char * const charge_pump_freq_text[] = { + "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9", + "10", "11", "12", "13", "14", "15" }; + +static const struct soc_enum charge_pump_enum = + SOC_ENUM_SINGLE(CS42L52_CHARGE_PUMP, 4, + ARRAY_SIZE(charge_pump_freq_text), charge_pump_freq_text); + +static const char * const left_swap_text[] = { + "Left", "LR 2", "Right"}; + +static const char * const right_swap_text[] = { + "Right", "LR 2", "Left"}; + +static const unsigned int swap_values[] = { 0, 1, 3 }; + +static const struct soc_enum adca_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 2, 1, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct soc_enum pcma_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 6, 1, + ARRAY_SIZE(left_swap_text), + left_swap_text, + swap_values); + +static const struct soc_enum adcb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 0, 1, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct soc_enum pcmb_swap_enum = + SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 4, 1, + ARRAY_SIZE(right_swap_text), + right_swap_text, + swap_values); + +static const struct snd_kcontrol_new passthrul_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 6, 1, 0); + +static const struct snd_kcontrol_new passthrur_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_MISC_CTL, 7, 1, 0); + +static const struct snd_kcontrol_new spkl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 0, 1, 1); + +static const struct snd_kcontrol_new spkr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 2, 1, 1); + +static const struct snd_kcontrol_new hpl_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 4, 1, 1); + +static const struct snd_kcontrol_new hpr_ctl = + SOC_DAPM_SINGLE("Switch", CS42L52_PWRCTL3, 6, 1, 1); + +static const struct snd_kcontrol_new cs42l52_snd_controls[] = { + + SOC_DOUBLE_R_SX_TLV("Master Volume", CS42L52_MASTERA_VOL, + CS42L52_MASTERB_VOL, 0, 0x34, 0xE4, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Headphone Volume", CS42L52_HPA_VOL, + CS42L52_HPB_VOL, 0, 0x34, 0xCC, hpd_tlv), + + SOC_ENUM("Headphone Analog Gain", hp_gain_enum), + + SOC_DOUBLE_R_SX_TLV("Speaker Volume", CS42L52_SPKA_VOL, + CS42L52_SPKB_VOL, 7, 0x1, 0xff, hl_tlv), + + SOC_DOUBLE_R_SX_TLV("Bypass Volume", CS42L52_PASSTHRUA_VOL, + CS42L52_PASSTHRUB_VOL, 6, 0x18, 0x90, pga_tlv), + + SOC_DOUBLE("Bypass Mute", CS42L52_MISC_CTL, 4, 5, 1, 0), + + SOC_DOUBLE_R_TLV("MIC Gain Volume", CS42L52_MICA_CTL, + CS42L52_MICB_CTL, 0, 0x10, 0, mic_tlv), + + SOC_ENUM("MIC Bias Level", mic_bias_level_enum), + + SOC_DOUBLE_R_SX_TLV("ADC Volume", CS42L52_ADCA_VOL, + CS42L52_ADCB_VOL, 7, 0x80, 0xA0, ipd_tlv), + SOC_DOUBLE_R_SX_TLV("ADC Mixer Volume", + CS42L52_ADCA_MIXER_VOL, CS42L52_ADCB_MIXER_VOL, + 6, 0x7f, 0x19, ipd_tlv), + + SOC_DOUBLE("ADC Switch", CS42L52_ADC_MISC_CTL, 0, 1, 1, 0), + + SOC_DOUBLE_R("ADC Mixer Switch", CS42L52_ADCA_MIXER_VOL, + CS42L52_ADCB_MIXER_VOL, 7, 1, 1), + + SOC_ENUM("ADC Left Swap", adca_swap_enum), + SOC_ENUM("ADC Right Swap", adcb_swap_enum), + + SOC_DOUBLE_R_SX_TLV("PGA Volume", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 0, 0x28, 0x30, pga_tlv), + + SOC_DOUBLE_R_SX_TLV("PCM Mixer Volume", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, + 6, 0x7f, 0x19, hl_tlv), + SOC_DOUBLE_R("PCM Mixer Switch", + CS42L52_PCMA_MIXER_VOL, CS42L52_PCMB_MIXER_VOL, 7, 1, 1), + + SOC_ENUM("PCM Left Swap", pcma_swap_enum), + SOC_ENUM("PCM Right Swap", pcmb_swap_enum), + + SOC_ENUM("Beep Pitch", beep_pitch_enum), + SOC_ENUM("Beep on Time", beep_ontime_enum), + SOC_ENUM("Beep off Time", beep_offtime_enum), + SOC_SINGLE_TLV("Beep Volume", CS42L52_BEEP_VOL, 0, 0x1f, 0x07, hl_tlv), + SOC_ENUM("Beep Config", beep_config_enum), + SOC_SINGLE("Beep Mixer Switch", CS42L52_BEEP_TONE_CTL, 5, 1, 1), + SOC_ENUM("Beep Treble Corner Freq", beep_treble_enum), + SOC_ENUM("Beep Bass Corner Freq", beep_bass_enum), + + SOC_SINGLE("Tone Control Switch", CS42L52_BEEP_TONE_CTL, 0, 1, 1), + SOC_SINGLE_TLV("Treble Gain Volume", + CS42L52_TONE_CTL, 4, 15, 1, hl_tlv), + SOC_SINGLE_TLV("Bass Gain Volume", + CS42L52_TONE_CTL, 0, 15, 1, hl_tlv), + + /* Limiter */ + SOC_SINGLE_TLV("Limiter Max Threshold Volume", + CS42L52_LIMITER_CTL1, 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Cushion Threshold Volume", + CS42L52_LIMITER_CTL1, 2, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Release Rate Volume", + CS42L52_LIMITER_CTL2, 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("Limiter Attack Rate Volume", + CS42L52_LIMITER_AT_RATE, 0, 63, 0, limiter_tlv), + + SOC_SINGLE("Limiter SR Switch", CS42L52_LIMITER_CTL1, 1, 1, 0), + SOC_SINGLE("Limiter ZC Switch", CS42L52_LIMITER_CTL1, 0, 1, 0), + SOC_SINGLE("Limiter Switch", CS42L52_LIMITER_CTL2, 7, 1, 0), + + /* ALC */ + SOC_SINGLE_TLV("ALC Attack Rate Volume", CS42L52_ALC_CTL, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Release Rate Volume", CS42L52_ALC_RATE, + 0, 63, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Max Threshold Volume", CS42L52_ALC_THRESHOLD, + 5, 7, 0, limiter_tlv), + SOC_SINGLE_TLV("ALC Min Threshold Volume", CS42L52_ALC_THRESHOLD, + 2, 7, 0, limiter_tlv), + + SOC_DOUBLE_R("ALC SR Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 7, 1, 1), + SOC_DOUBLE_R("ALC ZC Capture Switch", CS42L52_PGAA_CTL, + CS42L52_PGAB_CTL, 6, 1, 1), + SOC_DOUBLE("ALC Capture Switch", CS42L52_ALC_CTL, 6, 7, 1, 0), + + /* Noise gate */ + SOC_ENUM("NG Type Switch", ng_type_enum), + SOC_SINGLE("NG Enable Switch", CS42L52_NOISE_GATE_CTL, 6, 1, 0), + SOC_SINGLE("NG Boost Switch", CS42L52_NOISE_GATE_CTL, 5, 1, 1), + SOC_ENUM("NG Threshold", ng_threshold_enum), + SOC_ENUM("NG Delay", ng_delay_enum), + + SOC_DOUBLE("HPF Switch", CS42L52_ANALOG_HPF_CTL, 5, 7, 1, 0), + + SOC_DOUBLE("Analog SR Switch", CS42L52_ANALOG_HPF_CTL, 1, 3, 1, 1), + SOC_DOUBLE("Analog ZC Switch", CS42L52_ANALOG_HPF_CTL, 0, 2, 1, 1), + SOC_SINGLE("Digital SR Switch", CS42L52_MISC_CTL, 1, 1, 0), + SOC_SINGLE("Digital ZC Switch", CS42L52_MISC_CTL, 0, 1, 0), + SOC_SINGLE("Deemphasis Switch", CS42L52_MISC_CTL, 2, 1, 0), + + SOC_SINGLE("Batt Compensation Switch", CS42L52_BATT_COMPEN, 7, 1, 0), + SOC_SINGLE("Batt VP Monitor Switch", CS42L52_BATT_COMPEN, 6, 1, 0), + SOC_SINGLE("Batt VP ref", CS42L52_BATT_COMPEN, 0, 0x0f, 0), + SOC_ENUM("Charge Pump Freq", charge_pump_enum), + + SOC_SINGLE("PGA AIN1L Switch", CS42L52_ADC_PGA_A, 0, 1, 0), + SOC_SINGLE("PGA AIN1R Switch", CS42L52_ADC_PGA_B, 0, 1, 0), + SOC_SINGLE("PGA AIN2L Switch", CS42L52_ADC_PGA_A, 1, 1, 0), + SOC_SINGLE("PGA AIN2R Switch", CS42L52_ADC_PGA_B, 1, 1, 0), + + SOC_SINGLE("PGA AIN3L Switch", CS42L52_ADC_PGA_A, 2, 1, 0), + SOC_SINGLE("PGA AIN3R Switch", CS42L52_ADC_PGA_B, 2, 1, 0), + + SOC_SINGLE("PGA AIN4L Switch", CS42L52_ADC_PGA_A, 3, 1, 0), + SOC_SINGLE("PGA AIN4R Switch", CS42L52_ADC_PGA_B, 3, 1, 0), + + SOC_SINGLE("PGA MICA Switch", CS42L52_ADC_PGA_A, 4, 1, 0), + SOC_SINGLE("PGA MICB Switch", CS42L52_ADC_PGA_B, 4, 1, 0), + +}; + +static const struct snd_soc_dapm_widget cs42l52_dapm_widgets[] = { + + SND_SOC_DAPM_INPUT("AIN1L"), + SND_SOC_DAPM_INPUT("AIN1R"), + SND_SOC_DAPM_INPUT("AIN2L"), + SND_SOC_DAPM_INPUT("AIN2R"), + SND_SOC_DAPM_INPUT("AIN3L"), + SND_SOC_DAPM_INPUT("AIN3R"), + SND_SOC_DAPM_INPUT("AIN4L"), + SND_SOC_DAPM_INPUT("AIN4R"), + SND_SOC_DAPM_INPUT("MICA"), + SND_SOC_DAPM_INPUT("MICB"), + + SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MUX("MICA Mux", SND_SOC_NOPM, 0, 0, &mica_mux), + SND_SOC_DAPM_MUX("MICB Mux", SND_SOC_NOPM, 0, 0, &micb_mux), + + SND_SOC_DAPM_ADC("ADC Left", NULL, CS42L52_PWRCTL1, 1, 1), + SND_SOC_DAPM_ADC("ADC Right", NULL, CS42L52_PWRCTL1, 2, 1), + SND_SOC_DAPM_PGA("PGA Left", CS42L52_PWRCTL1, 3, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA Right", CS42L52_PWRCTL1, 4, 1, NULL, 0), + + SND_SOC_DAPM_MUX("ADC Left Mux", SND_SOC_NOPM, 0, 0, &adca_mux), + SND_SOC_DAPM_MUX("ADC Right Mux", SND_SOC_NOPM, 0, 0, &adcb_mux), + + SND_SOC_DAPM_MUX("Output Mux", SND_SOC_NOPM, + 0, 0, &digital_output_mux), + + SND_SOC_DAPM_PGA("PGA MICA", CS42L52_PWRCTL2, 1, 1, NULL, 0), + SND_SOC_DAPM_PGA("PGA MICB", CS42L52_PWRCTL2, 2, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("Mic Bias", CS42L52_PWRCTL2, 0, 1, NULL, 0), + + SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, + SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 0, + SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_DAC("DAC Left", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Right", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_SWITCH("Bypass Left", CS42L52_MISC_CTL, + 6, 0, &passthrul_ctl), + SND_SOC_DAPM_SWITCH("Bypass Right", CS42L52_MISC_CTL, + 7, 0, &passthrur_ctl), + + SND_SOC_DAPM_SWITCH("HP Left Amp", SND_SOC_NOPM, 0, 0, &hpl_ctl), + SND_SOC_DAPM_SWITCH("HP Right Amp", SND_SOC_NOPM, 0, 0, &hpr_ctl), + + SND_SOC_DAPM_SWITCH("SPK Left Amp", SND_SOC_NOPM, 0, 0, &spkl_ctl), + SND_SOC_DAPM_SWITCH("SPK Right Amp", SND_SOC_NOPM, 0, 0, &spkr_ctl), + + SND_SOC_DAPM_OUTPUT("HPOUTA"), + SND_SOC_DAPM_OUTPUT("HPOUTB"), + SND_SOC_DAPM_OUTPUT("SPKOUTA"), + SND_SOC_DAPM_OUTPUT("SPKOUTB"), + +}; + +static const struct snd_soc_dapm_route cs42l52_audio_map[] = { + + {"AIFOUTL", NULL, "Output Mux"}, + {"AIFOUTR", NULL, "Output Mux"}, + + {"Output Mux", "ADC", "ADC Left"}, + {"Output Mux", "ADC", "ADC Right"}, + + {"ADC Left", NULL, "ADC Left Mux"}, + {"ADC Right", NULL, "ADC Right Mux"}, + + {"ADC Left Mux", "Input1A", "AIN1L"}, + {"ADC Right Mux", "Input1B", "AIN1R"}, + {"ADC Left Mux", "Input2A", "AIN2L"}, + {"ADC Right Mux", "Input2B", "AIN2R"}, + {"ADC Left Mux", "Input3A", "AIN3L"}, + {"ADC Right Mux", "Input3B", "AIN3R"}, + {"ADC Left Mux", "Input4A", "AIN4L"}, + {"ADC Right Mux", "Input4B", "AIN4R"}, + {"ADC Left Mux", "PGA Input Left", "PGA Left"}, + {"ADC Right Mux", "PGA Input Right" , "PGA Right"}, + + {"PGA Left", "Switch", "AIN1L"}, + {"PGA Right", "Switch", "AIN1R"}, + {"PGA Left", "Switch", "AIN2L"}, + {"PGA Right", "Switch", "AIN2R"}, + {"PGA Left", "Switch", "AIN3L"}, + {"PGA Right", "Switch", "AIN3R"}, + {"PGA Left", "Switch", "AIN4L"}, + {"PGA Right", "Switch", "AIN4R"}, + + {"PGA Left", "Switch", "PGA MICA"}, + {"PGA MICA", NULL, "MICA Mux"}, + {"MICA Mux", "Single", "MICA"}, + {"MICA Mux", "Differential", "MICA"}, + + {"PGA Right", "Switch", "PGA MICB"}, + {"PGA MICB", NULL, "MICB Mux"}, + {"MICB Mux", "Single", "MICB"}, + {"MICB Mux", "Differential", "MICB"}, + + {"HPOUTA", NULL, "HP Left Amp"}, + {"HPOUTB", NULL, "HP Right Amp"}, + {"HP Left Amp", NULL, "Bypass Left"}, + {"HP Right Amp", NULL, "Bypass Right"}, + {"Bypass Left", "Switch", "PGA Left"}, + {"Bypass Right", "Switch", "PGA Right"}, + {"HP Left Amp", "Switch", "DAC Left"}, + {"HP Right Amp", "Switch", "DAC Right"}, + + {"SPKOUTA", NULL, "SPK Left Amp"}, + {"SPKOUTB", NULL, "SPK Right Amp"}, + {"SPK Left Amp", "Switch", "AIFINL"}, + {"SPK Right Amp", "Switch", "AIFINR"}, + + {"DAC Left", NULL, "AIFINL"}, + {"DAC Right", NULL, "AIFINR"}, + + {"Output Mux", "DSP", "AIFINL"}, + {"Output Mux", "DSP", "AIFINR"}, + +}; + +struct cs42l52_clk_para { + u32 mclk; + u32 rate; + u8 speed; + u8 group; + u8 videoclk; + u8 ratio; + u8 mclkdiv2; +}; + +static const struct cs42l52_clk_para clk_map_table[] = { + /*8k*/ + {12288000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 8000, CLK_QS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 8000, CLK_QS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /*11.025k*/ + {11289600, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 11025, CLK_QS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /*16k*/ + {12288000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 16000, CLK_HS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 16000, CLK_HS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /*22.05k*/ + {11289600, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 22050, CLK_HS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 32k */ + {12288000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 32000, CLK_SS_MODE, CLK_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 32000, CLK_SS_MODE, CLK_32K, CLK_27M_MCLK, CLK_R_125, 0}, + + /* 44.1k */ + {11289600, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 44100, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 48k */ + {12288000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, + {27000000, 48000, CLK_SS_MODE, CLK_NO_32K, CLK_27M_MCLK, CLK_R_125, 1}, + + /* 88.2k */ + {11289600, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {16934400, 88200, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + + /* 96k */ + {12288000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {18432000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_128, 0}, + {12000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 0}, + {24000000, 96000, CLK_DS_MODE, CLK_NO_32K, CLK_NO_27M, CLK_R_125, 1}, +}; + +static int cs42l52_get_clk(int mclk, int rate) +{ + int i, ret = 0; + u_int mclk1, mclk2 = 0; + + for (i = 0; i < ARRAY_SIZE(clk_map_table); i++) { + if (clk_map_table[i].rate == rate) { + mclk1 = clk_map_table[i].mclk; + if (abs(mclk - mclk1) < abs(mclk - mclk2)) { + mclk2 = mclk1; + ret = i; + } + } + } + if (ret > ARRAY_SIZE(clk_map_table)) + return -EINVAL; + return ret; +} + +static int cs42l52_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + if ((freq >= CS42L52_MIN_CLK) && (freq <= CS42L52_MAX_CLK)) { + cs42l52->sysclk = freq; + } else { + dev_err(codec->dev, "Invalid freq paramter\n"); + return -EINVAL; + } + return 0; +} + +static int cs42l52_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + u8 iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = CS42L52_IFACE_CTL1_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: + iface = CS42L52_IFACE_CTL1_SLAVE; + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_I2S | + CS42L52_IFACE_CTL1_DAC_FMT_I2S; + break; + case SND_SOC_DAIFMT_RIGHT_J: + iface |= CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J; + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J | + CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= CS42L52_IFACE_CTL1_DSP_MODE_EN; + break; + case SND_SOC_DAIFMT_DSP_B: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= CS42L52_IFACE_CTL1_INV_SCLK; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + default: + ret = -EINVAL; + } + cs42l52->config.format = iface; + snd_soc_write(codec, CS42L52_IFACE_CTL1, cs42l52->config.format); + + return 0; +} + +static int cs42l52_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + if (mute) + snd_soc_update_bits(codec, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, 0x03); + else + snd_soc_update_bits(codec, CS42L52_PB_CTL1, + CS42L52_PB_CTL1_MUTE_MASK, 0x00); + + return 0; +} + +static int cs42l52_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + u32 clk = 0; + int index; + + index = cs42l52_get_clk(cs42l52->sysclk, params_rate(params)); + if (index >= 0) { + cs42l52->sysclk = clk_map_table[index].mclk; + + clk |= (clk_map_table[index].speed << CLK_SPEED_SHIFT) | + (clk_map_table[index].group << CLK_32K_SR_SHIFT) | + (clk_map_table[index].videoclk << CLK_27M_MCLK_SHIFT) | + (clk_map_table[index].ratio << CLK_RATIO_SHIFT) | + clk_map_table[index].mclkdiv2; + + snd_soc_write(codec, CS42L52_CLK_CTL, clk); + } else { + dev_err(codec->dev, "can't get correct mclk\n"); + return -EINVAL; + } + + return 0; +} + +static int cs42l52_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_update_bits(codec, CS42L52_PWRCTL1, + CS42L52_PWRCTL1_PDN_CODEC | + CS42L52_PWRCTL1_PDN_CHRG, 0); + break; + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + regcache_cache_only(cs42l52->regmap, false); + regcache_sync(cs42l52->regmap); + } + snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + break; + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL); + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +#define CS42L52_RATES (SNDRV_PCM_RATE_8000_96000) + +#define CS42L52_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U24_LE) + +static struct snd_soc_dai_ops cs42l52_ops = { + .hw_params = cs42l52_pcm_hw_params, + .digital_mute = cs42l52_digital_mute, + .set_fmt = cs42l52_set_fmt, + .set_sysclk = cs42l52_set_sysclk, +}; + +struct snd_soc_dai_driver cs42l52_dai = { + .name = "cs42l52", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = CS42L52_RATES, + .formats = CS42L52_FORMATS, + }, + .ops = &cs42l52_ops, +}; + +static int cs42l52_suspend(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int cs42l52_resume(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +/* page 37 from cs42l52 datasheet */ +static void cs42l52_required_setup(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_ON); + + snd_soc_write(codec, CS42L52_FIX_BITS_CTL, 0x99); + snd_soc_write(codec, CS42L52_FIX_BITS1, 0xBA); + snd_soc_write(codec, CS42L52_FIX_BITS2, 0x80); + + snd_soc_update_bits(codec, CS42L52_TEM_CTL, CS42L52_TEM_CTL_SET, 1); + snd_soc_update_bits(codec, CS42L52_TEM_CTL, CS42L52_TEM_CTL_SET, 0); + + snd_soc_write(codec, CS42L52_FIX_BITS_CTL, 0x00); + + cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int cs42l52_probe(struct snd_soc_codec *codec) +{ + struct cs42l52_private *cs42l52 = snd_soc_codec_get_drvdata(codec); + int ret; + + codec->control_data = cs42l52->regmap; + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_REGMAP); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + regcache_cache_only(cs42l52->regmap, true); + + cs42l52_required_setup(codec); + + cs42l52->sysclk = CS42L52_DEFAULT_CLK; + cs42l52->config.format = CS42L52_DEFAULT_FORMAT; + + cs42l52_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return ret; +} + +static int cs42l52_remove(struct snd_soc_codec *codec) +{ + cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +struct snd_soc_codec_driver soc_codec_dev_cs42l52 = { + .probe = cs42l52_probe, + .remove = cs42l52_remove, + .suspend = cs42l52_suspend, + .resume = cs42l52_resume, + .set_bias_level = cs42l52_set_bias_level, + + .dapm_widgets = cs42l52_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs42l52_dapm_widgets), + .dapm_routes = cs42l52_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs42l52_audio_map), + + .controls = cs42l52_snd_controls, + .num_controls = ARRAY_SIZE(cs42l52_snd_controls), +}; + +static struct regmap_config cs42l52_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS42L52_MAX_REGISTER, + .reg_defaults = cs42l52_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cs42l52_reg_defaults), + .readable_reg = cs42l52_readable_register, + .cache_type = REGCACHE_RBTREE, +}; + +static int cs42l52_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs42l52_private *cs42l52; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs42l52 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l52_private), + GFP_KERNEL); + if (cs42l52 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs42l52); + + cs42l52->regmap = regmap_init_i2c(i2c_client, &cs42l52_regmap); + if (IS_ERR(cs42l52->regmap)) { + ret = PTR_ERR(cs42l52->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + ret = regmap_read(cs42l52->regmap, CS42L52_CHIP, ®); + devid = reg & CS42L52_CHIP_ID_MASK; + + if (devid != CS42L52_CHIP_ID) { + ret = -ENODEV; + dev_err(&i2c_client->dev, + "CS42L52 Device ID (%X). Expected %X\n", + devid, CS42L52_CHIP_ID); + goto err_regmap; + } + + regcache_cache_only(cs42l52->regmap, true); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_dev_cs42l52, &cs42l52_dai, 1); + if (ret < 0) + goto err_regmap; + return 0; + +err_regmap: + regmap_exit(cs42l52->regmap); + +err: + return ret; +} + +static int cs42l52_i2c_remove(struct i2c_client *client) +{ + struct cs42l52_private *cs42l52 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&client->dev); + regmap_exit(cs42l52->regmap); + + return 0; +} + +static const struct i2c_device_id cs42l52_id[] = { + { "cs42l52", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, cs42l52_id); + +static struct i2c_driver cs42l52_i2c_driver = { + .driver = { + .name = "cs42l52", + .owner = THIS_MODULE, + }, + .id_table = cs42l52_id, + .probe = cs42l52_i2c_probe, + .remove = __devexit_p(cs42l52_i2c_remove), +}; + +static int __init cs42l52_modinit(void) +{ + int ret; + ret = i2c_add_driver(&cs42l52_i2c_driver); + if (ret != 0) { + pr_err("Failed to register CS42L52 I2C driver: %d\n", ret); + return ret; + } + return 0; +} + +module_init(cs42l52_modinit); + +static void __exit cs42l52_exit(void) +{ + i2c_del_driver(&cs42l52_i2c_driver); +} + +module_exit(cs42l52_exit); + +MODULE_DESCRIPTION("ASoC CS42L52 driver"); +MODULE_AUTHOR("Georgi Vlaev, Nucleus Systems Ltd, joe@nucleusys.com"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, brian.austin@cirrus.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs42l52.h b/sound/soc/codecs/cs42l52.h new file mode 100644 index 0000000..6a598b5 --- /dev/null +++ b/sound/soc/codecs/cs42l52.h @@ -0,0 +1,264 @@ +/* + * cs42l52.h -- CS42L52 ALSA SoC audio driver + * + * Copyright 2012 CirrusLogic, Inc. + * + * Author: Georgi Vlaev joe@nucleusys.com + * Author: Brian Austin brian.austin@cirrus.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __CS42L52_H__ +#define __CS42L52_H__ + +#define CS42L52_NAME "CS42L52" +#define CS42L52_DEFAULT_CLK 12000000 +#define CS42L52_MIN_CLK 11000000 +#define CS42L52_MAX_CLK 27000000 +#define CS42L52_DEFAULT_FORMAT SNDRV_PCM_FMTBIT_S16_LE +#define CS42L52_DEFAULT_MAX_CHANS 2 +#define CS42L52_SYSCLK 1 + +#define CS42L52_CHIP_SWICTH (1 << 17) +#define CS42L52_ALL_IN_ONE (1 << 16) +#define CS42L52_CHIP_ONE 0x00 +#define CS42L52_CHIP_TWO 0x01 +#define CS42L52_CHIP_THR 0x02 +#define CS42L52_CHIP_MASK 0x0f + +#define CS42L52_FIX_BITS_CTL 0x00 +#define CS42L52_CHIP 0x01 +#define CS42L52_CHIP_ID 0xE0 +#define CS42L52_CHIP_ID_MASK 0xF8 +#define CS42L52_CHIP_REV_A0 0x00 +#define CS42L52_CHIP_REV_A1 0x01 +#define CS42L52_CHIP_REV_B0 0x02 +#define CS42L52_CHIP_REV_MASK 0x03 + +#define CS42L52_PWRCTL1 0x02 +#define CS42L52_PWRCTL1_PDN_ALL 0x9F +#define CS42L52_PWRCTL1_PDN_CHRG 0x80 +#define CS42L52_PWRCTL1_PDN_PGAB 0x10 +#define CS42L52_PWRCTL1_PDN_PGAA 0x08 +#define CS42L52_PWRCTL1_PDN_ADCB 0x04 +#define CS42L52_PWRCTL1_PDN_ADCA 0x02 +#define CS42L52_PWRCTL1_PDN_CODEC 0x01 + +#define CS42L52_PWRCTL2 0x03 +#define CS42L52_PWRCTL2_OVRDB (1 << 4) +#define CS42L52_PWRCTL2_OVRDA (1 << 3) +#define CS42L52_PWRCTL2_PDN_MICB (1 << 2) +#define CS42L52_PWRCTL2_PDN_MICB_SHIFT 2 +#define CS42L52_PWRCTL2_PDN_MICA (1 << 1) +#define CS42L52_PWRCTL2_PDN_MICA_SHIFT 1 +#define CS42L52_PWRCTL2_PDN_MICBIAS (1 << 0) +#define CS42L52_PWRCTL2_PDN_MICBIAS_SHIFT 0 + +#define CS42L52_PWRCTL3 0x04 +#define CS42L52_PWRCTL3_HPB_PDN_SHIFT 6 +#define CS42L52_PWRCTL3_HPB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPB_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_HPA_PDN_SHIFT 4 +#define CS42L52_PWRCTL3_HPA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_HPA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_HPA_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_HPA_ALWAYS_OFF 0x03 +#define CS42L52_PWRCTL3_SPKB_PDN_SHIFT 2 +#define CS42L52_PWRCTL3_SPKB_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKB_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKB_ALWAYS_ON 0x02 +#define CS42L52_PWRCTL3_PDN_SPKB (1 << 2) +#define CS42L52_PWRCTL3_PDN_SPKA (1 << 0) +#define CS42L52_PWRCTL3_SPKA_PDN_SHIFT 0 +#define CS42L52_PWRCTL3_SPKA_ON_LOW 0x00 +#define CS42L52_PWRCTL3_SPKA_ON_HIGH 0x01 +#define CS42L52_PWRCTL3_SPKA_ALWAYS_ON 0x02 + +#define CS42L52_DEFAULT_OUTPUT_STATE 0x05 +#define CS42L52_PWRCTL3_CONF_MASK 0x03 + +#define CS42L52_CLK_CTL 0x05 +#define CLK_AUTODECT_ENABLE (1 << 7) +#define CLK_SPEED_SHIFT 5 +#define CLK_DS_MODE 0x00 +#define CLK_SS_MODE 0x01 +#define CLK_HS_MODE 0x02 +#define CLK_QS_MODE 0x03 +#define CLK_32K_SR_SHIFT 4 +#define CLK_32K 0x01 +#define CLK_NO_32K 0x00 +#define CLK_27M_MCLK_SHIFT 3 +#define CLK_27M_MCLK 0x01 +#define CLK_NO_27M 0x00 +#define CLK_RATIO_SHIFT 1 +#define CLK_R_128 0x00 +#define CLK_R_125 0x01 +#define CLK_R_132 0x02 +#define CLK_R_136 0x03 + +#define CS42L52_IFACE_CTL1 0x06 +#define CS42L52_IFACE_CTL1_MASTER (1 << 7) +#define CS42L52_IFACE_CTL1_SLAVE (0 << 7) +#define CS42L52_IFACE_CTL1_INV_SCLK (1 << 6) +#define CS42L52_IFACE_CTL1_ADC_FMT_I2S (1 << 5) +#define CS42L52_IFACE_CTL1_ADC_FMT_LEFT_J (0 << 5) +#define CS42L52_IFACE_CTL1_DSP_MODE_EN (1 << 4) +#define CS42L52_IFACE_CTL1_DAC_FMT_LEFT_J (0 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_I2S (1 << 2) +#define CS42L52_IFACE_CTL1_DAC_FMT_RIGHT_J (2 << 2) +#define CS42L52_IFACE_CTL1_WL_32BIT (0x00) +#define CS42L52_IFACE_CTL1_WL_24BIT (0x01) +#define CS42L52_IFACE_CTL1_WL_20BIT (0x02) +#define CS42L52_IFACE_CTL1_WL_16BIT (0x03) +#define CS42L52_IFACE_CTL1_WL_MASK 0xFFFF + +#define CS42L52_IFACE_CTL2 0x07 +#define CS42L52_IFACE_CTL2_SC_MC_EQ (1 << 6) +#define CS42L52_IFACE_CTL2_LOOPBACK (1 << 5) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_EN (0 << 4) +#define CS42L52_IFACE_CTL2_S_MODE_OUTPUT_HIZ (1 << 4) +#define CS42L52_IFACE_CTL2_HP_SW_INV (1 << 3) + +#define CS42L52_ADC_PGA_A 0x08 +#define CS42L52_ADC_PGA_B 0x09 +#define CS42L52_ADC_SEL_SHIFT 5 +#define CS42L52_ADC_SEL_AIN1 0x00 +#define CS42L52_ADC_SEL_AIN2 0x01 +#define CS42L52_ADC_SEL_AIN3 0x02 +#define CS42L52_ADC_SEL_AIN4 0x03 +#define CS42L52_ADC_SEL_PGA 0x04 + +#define CS42L52_ANALOG_HPF_CTL 0x0A +#define CS42L52_HPF_CTL_ANLGSFTB (1 << 3) +#define CS42L52_HPF_CTL_ANLGSFTA (1 << 0) + +#define CS42L52_ADC_HPF_FREQ 0x0B +#define CS42L52_ADC_MISC_CTL 0x0C +#define CS42L52_ADC_MISC_CTL_SOURCE_DSP (1 << 6) + +#define CS42L52_PB_CTL1 0x0D +#define CS42L52_PB_CTL1_HP_GAIN_SHIFT 5 +#define CS42L52_PB_CTL1_HP_GAIN_03959 0x00 +#define CS42L52_PB_CTL1_HP_GAIN_04571 0x01 +#define CS42L52_PB_CTL1_HP_GAIN_05111 0x02 +#define CS42L52_PB_CTL1_HP_GAIN_06047 0x03 +#define CS42L52_PB_CTL1_HP_GAIN_07099 0x04 +#define CS42L52_PB_CTL1_HP_GAIN_08399 0x05 +#define CS42L52_PB_CTL1_HP_GAIN_10000 0x06 +#define CS42L52_PB_CTL1_HP_GAIN_11430 0x07 +#define CS42L52_PB_CTL1_INV_PCMB (1 << 3) +#define CS42L52_PB_CTL1_INV_PCMA (1 << 2) +#define CS42L52_PB_CTL1_MSTB_MUTE (1 << 1) +#define CS42L52_PB_CTL1_MSTA_MUTE (1 << 0) +#define CS42L52_PB_CTL1_MUTE_MASK 0xFFFD + +#define CS42L52_MISC_CTL 0x0E +#define CS42L52_MISC_CTL_DEEMPH (1 << 2) +#define CS42L52_MISC_CTL_DIGSFT (1 << 1) +#define CS42L52_MISC_CTL_DIGZC (1 << 0) + +#define CS42L52_PB_CTL2 0x0F +#define CS42L52_PB_CTL2_HPB_MUTE (1 << 7) +#define CS42L52_PB_CTL2_HPA_MUTE (1 << 6) +#define CS42L52_PB_CTL2_SPKB_MUTE (1 << 5) +#define CS42L52_PB_CTL2_SPKA_MUTE (1 << 4) +#define CS42L52_PB_CTL2_SPK_SWAP (1 << 2) +#define CS42L52_PB_CTL2_SPK_MONO (1 << 1) +#define CS42L52_PB_CTL2_SPK_MUTE50 (1 << 0) + +#define CS42L52_MICA_CTL 0x10 +#define CS42L52_MICB_CTL 0x11 +#define CS42L52_MIC_CTL_SEL_MIC1 (0 << 6) +#define CS42L52_MIC_CTL_SEL_MIC2 (1 << 6) +#define CS42L52_MIC_CTL_SEL_DIFF (1 << 5) + +#define CS42L52_PGAA_CTL 0x12 +#define CS42L52_PGAB_CTL 0x13 +#define CS42L52_PGAX_CTL_VOL_12DB 24 +#define CS42L52_PGAX_CTL_VOL_6DB 12 /*step size 0.5db*/ + +#define CS42L52_PASSTHRUA_VOL 0x14 +#define CS42L52_PASSTHRUB_VOL 0x15 + +#define CS42L52_ADCA_VOL 0x16 +#define CS42L52_ADCB_VOL 0x17 +#define CS42L52_ADCX_VOL_24DB 24 /*step size 1db*/ +#define CS42L52_ADCX_VOL_12DB 12 +#define CS42L52_ADCX_VOL_6DB 6 + +#define CS42L52_ADCA_MIXER_VOL 0x18 +#define CS42L52_ADCB_MIXER_VOL 0x19 +#define CS42L52_ADC_MIXER_VOL_12DB 0x18 + +#define CS42L52_PCMA_MIXER_VOL 0x1A +#define CS42L52_PCMB_MIXER_VOL 0x1B + +#define CS42L52_BEEP_FREQ 0x1C +#define CS42L52_BEEP_VOL 0x1D +#define CS42L52_BEEP_TONE_CTL 0x1E + +#define CS42L52_TONE_CTL 0x1F + +#define CS42L52_MASTERA_VOL 0x20 +#define CS42L52_MASTERB_VOL 0x21 + +#define CS42L52_HPA_VOL 0x22 +#define CS42L52_HPB_VOL 0x23 +#define CS42L52_DEFAULT_HP_VOL 0xF0 + +#define CS42L52_SPKA_VOL 0x24 +#define CS42L52_SPKB_VOL 0x25 +#define CS42L52_DEFAULT_SPK_VOL 0xF0 + +#define CS42L52_ADC_PCM_MIXER 0x26 + +#define CS42L52_LIMITER_CTL1 0x27 +#define CS42L52_LIMITER_CTL2 0x28 +#define CS42L52_LIMITER_AT_RATE 0x29 + +#define CS42L52_ALC_CTL 0x2A +#define CS42L52_ALC_CTL_ALCB_ENABLE (1 << 7) +#define CS42L52_ALC_CTL_ALCA_ENABLE (1 << 6) +#define CS42L52_ALC_CTL_FASTEST_ATTACK 0 + +#define CS42L52_ALC_RATE 0x2B +#define CS42L52_ALC_SLOWEST_RELEASE 0x3F + +#define CS42L52_ALC_THRESHOLD 0x2C +#define CS42L52_ALC_MAX_RATE_SHIFT 5 +#define CS42L52_ALC_MIN_RATE_SHIFT 2 +#define CS42L52_ALC_RATE_0DB 0 +#define CS42L52_ALC_RATE_3DB 1 +#define CS42L52_ALC_RATE_6DB 2 + +#define CS42L52_NOISE_GATE_CTL 0x2D +#define CS42L52_NG_ENABLE (1 << 6) +#define CS42L52_NG_THRESHOLD_SHIFT 2 +#define CS42L52_NG_MIN_70DB 2 +#define CS42L52_NG_DELAY_SHIFT 0 +#define CS42L52_NG_DELAY_100MS 1 + +#define CS42L52_CLK_STATUS 0x2E +#define CS42L52_BATT_COMPEN 0x2F + +#define CS42L52_BATT_LEVEL 0x30 +#define CS42L52_SPK_STATUS 0x31 +#define CS42L52_SPK_STATUS_PIN_SHIFT 3 +#define CS42L52_SPK_STATUS_PIN_HIGH 1 + +#define CS42L52_TEM_CTL 0x32 +#define CS42L52_TEM_CTL_SET 0x80 +#define CS42L52_THE_FOLDBACK 0x33 +#define CS42L52_CHARGE_PUMP 0x34 +#define CS42L52_FIX_BITS1 0x3E +#define CS42L52_FIX_BITS2 0x47 + +#define CS42L52_MAX_REGISTER 0x34 + +#endif
On Mon, Apr 16, 2012 at 03:58:58PM -0500, Brian Austin wrote:
This patch adds support for Cirrus Logic CS42L52 Low Power Stereo Codec
Overall this looks good, a few issues below...
+static const struct reg_default cs42l52_reg_defaults[] = {
- { 1, 0xE0 }, /* r01 ChipID */
You shouldn't provide a default for this, the whole point with a register like this is that it gets read from the device so you can confirm you're talking to the right device. As things stand your code in probe will just check that the cache is initialised with the expected default.
- { 49, 0x00 }, /* r31 Speaker Status */
Going on the name this (and possibly some of the other registers) ought to be marked as volatile and not have a default.
+static const char * const mic_bias_level_text[] = {
- "0.5 +VA", "0.6 +VA", "0.7 +VA",
- "0.8 +VA", "0.83 +VA", "0.91 +VA"
+};
Normally this is a platform data type thing.
+static const char * const cs42l52_mic_text[] = { "Single", "Differential" };
This too - it's usually determined by the schematic, not something that gets varied at runtime.
+static const char * const beep_pitch_text[] = {
- "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5",
- "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7"
+};
+static const struct soc_enum beep_pitch_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 4,
ARRAY_SIZE(beep_pitch_text), beep_pitch_text);
+static const char * const beep_ontime_text[] = {
- "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s",
- "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s",
- "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s"
+};
+static const struct soc_enum beep_ontime_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 0,
ARRAY_SIZE(beep_ontime_text), beep_ontime_text);
+static const char * const beep_offtime_text[] = {
- "1.23 s", "2.58 s", "3.90 s", "5.20 s",
- "6.60 s", "8.05 s", "9.35 s", "10.80 s"
+};
+static const struct soc_enum beep_offtime_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_VOL, 5,
ARRAY_SIZE(beep_offtime_text), beep_offtime_text);
+static const char * const beep_config_text[] = {
- "Off", "Single", "Multiple", "Continuous"
+};
+static const struct soc_enum beep_config_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 6,
ARRAY_SIZE(beep_config_text), beep_config_text);
+static const char * const beep_bass_text[] = {
- "50 Hz", "100 Hz", "200 Hz", "250 Hz"
+};
All this beep stuff is really supposed to go through the input subsystem, or at least have the hookup in there to trigger it.
+static const char * const charge_pump_freq_text[] = {
- "0", "1", "2", "3", "4",
- "5", "6", "7", "8", "9",
- "10", "11", "12", "13", "14", "15" };
+static const struct soc_enum charge_pump_enum =
- SOC_ENUM_SINGLE(CS42L52_CHARGE_PUMP, 4,
ARRAY_SIZE(charge_pump_freq_text), charge_pump_freq_text);
Should this be runtime configurable?
+static const unsigned int swap_values[] = { 0, 1, 3 };
+static const struct soc_enum adca_swap_enum =
- SOC_VALUE_ENUM_SINGLE(CS42L52_ADC_PCM_MIXER, 2, 1,
ARRAY_SIZE(left_swap_text),
left_swap_text,
swap_values);
This L/R swap stuff should probably go into DAPM, it's not so useful right now but we're hopefully going to be able to get the ability to figure out which of the channels on the DAI are active and power manage appropriately.
- SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0,
SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 0,
SND_SOC_NOPM, 0, 0),
You should use DAPM routes to hook the AIF widgets to the DAIs - they're new as of 3.4 but ideally we'll be transitioning everything over to this. Some of the Wolfson drivers like wm8996 have been converted so provide examples.
- switch (level) {
- case SND_SOC_BIAS_ON:
snd_soc_update_bits(codec, CS42L52_PWRCTL1,
CS42L52_PWRCTL1_PDN_CODEC |
CS42L52_PWRCTL1_PDN_CHRG, 0);
break;
This looks odd, especially having it in _ON - perhaps a comment explaining why?
- case SND_SOC_BIAS_STANDBY:
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
regcache_cache_only(cs42l52->regmap, false);
regcache_sync(cs42l52->regmap);
}
snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL);
break;
- case SND_SOC_BIAS_OFF:
snd_soc_write(codec, CS42L52_PWRCTL1, CS42L52_PWRCTL1_PDN_ALL);
break;
Should be setting cache_only when entering _OFF I think?
+/* page 37 from cs42l52 datasheet */ +static void cs42l52_required_setup(struct snd_soc_codec *codec) +{
- cs42l52_set_bias_level(codec, SND_SOC_BIAS_ON);
- snd_soc_write(codec, CS42L52_FIX_BITS_CTL, 0x99);
- snd_soc_write(codec, CS42L52_FIX_BITS1, 0xBA);
- snd_soc_write(codec, CS42L52_FIX_BITS2, 0x80);
- snd_soc_update_bits(codec, CS42L52_TEM_CTL, CS42L52_TEM_CTL_SET, 1);
- snd_soc_update_bits(codec, CS42L52_TEM_CTL, CS42L52_TEM_CTL_SET, 0);
- snd_soc_write(codec, CS42L52_FIX_BITS_CTL, 0x00);
- cs42l52_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
You should use a regmap patch for this. It's intended for this sort of magic register write sequence on startup and is also integrated with the cache sync code so the fixes will be automatically applies whenver you sync. With the code as it stands if the CODEC is powered off over suspend then after resume these updates won't be applied.
- cs42l52->regmap = regmap_init_i2c(i2c_client, &cs42l52_regmap);
- if (IS_ERR(cs42l52->regmap)) {
ret = PTR_ERR(cs42l52->regmap);
dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
goto err;
- }
devm_regmap_init_i2c(), saves a small amount of error handling/cleanup code.
+static int __init cs42l52_modinit(void) +{
- int ret;
- ret = i2c_add_driver(&cs42l52_i2c_driver);
module_i2c_driver().
On Tue, 17 Apr 2012, Mark Brown wrote:
On Mon, Apr 16, 2012 at 03:58:58PM -0500, Brian Austin wrote:
This patch adds support for Cirrus Logic CS42L52 Low Power Stereo Codec
Overall this looks good, a few issues below...
+static const struct reg_default cs42l52_reg_defaults[] = {
- { 1, 0xE0 }, /* r01 ChipID */
You shouldn't provide a default for this, the whole point with a register like this is that it gets read from the device so you can confirm you're talking to the right device. As things stand your code in probe will just check that the cache is initialised with the expected default.
- { 49, 0x00 }, /* r31 Speaker Status */
Going on the name this (and possibly some of the other registers) ought to be marked as volatile and not have a default.
Yeah, that makes sense :)
Normally this is a platform data type thing.
+static const char * const cs42l52_mic_text[] = { "Single", "Differential" };
This too - it's usually determined by the schematic, not something that gets varied at runtime.
Got it. Thanks
All this beep stuff is really supposed to go through the input subsystem, or at least have the hookup in there to trigger it.
Thanks for the input... I'd add that functionality
- SND_SOC_DAPM_AIF_OUT("AIFOUTL", "Capture", 0,
SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("AIFOUTR", "Capture", 0,
SND_SOC_NOPM, 0, 0),
You should use DAPM routes to hook the AIF widgets to the DAIs - they're new as of 3.4 but ideally we'll be transitioning everything over to this. Some of the Wolfson drivers like wm8996 have been converted so provide examples.
Thanks, I'll add that
snd_soc_update_bits(codec, CS42L52_PWRCTL1,
CS42L52_PWRCTL1_PDN_CODEC |
CS42L52_PWRCTL1_PDN_CHRG, 0);
This looks odd, especially having it in _ON - perhaps a comment explaining why?
I'm just enabling the power for the codec. Are you refering to the Charge Pump enable?
Should be setting cache_only when entering _OFF I think?
Yep. thanks
You should use a regmap patch for this. It's intended for this sort of magic register write sequence on startup and is also integrated with the cache sync code so the fixes will be automatically applies whenver you sync. With the code as it stands if the CODEC is powered off over suspend then after resume these updates won't be applied.
I missed this. Yes, that is much cleaner, thanks
devm_regmap_init_i2c(), saves a small amount of error handling/cleanup code.
Will change thanks
module_i2c_driver().
I should have done this. Thanks
On Tue, Apr 17, 2012 at 10:55:59AM -0500, Brian Austin wrote:
On Tue, 17 Apr 2012, Mark Brown wrote:
On Mon, Apr 16, 2012 at 03:58:58PM -0500, Brian Austin wrote:
snd_soc_update_bits(codec, CS42L52_PWRCTL1,
CS42L52_PWRCTL1_PDN_CODEC |
CS42L52_PWRCTL1_PDN_CHRG, 0);
This looks odd, especially having it in _ON - perhaps a comment explaining why?
I'm just enabling the power for the codec. Are you refering to the Charge Pump enable?
Both, really - it's just generally incredibly unusual to have anything in _ON, and looking at the names these look like supplies for other bits of the CODEC so things that I'd expect to be turned on early rather than late in the sequence. If it were in _PREPARE it'd be less surprising.
On Tue, 17 Apr 2012, Mark Brown wrote:
On Tue, Apr 17, 2012 at 10:55:59AM -0500, Brian Austin wrote:
On Tue, 17 Apr 2012, Mark Brown wrote:
On Mon, Apr 16, 2012 at 03:58:58PM -0500, Brian Austin wrote:
snd_soc_update_bits(codec, CS42L52_PWRCTL1,
CS42L52_PWRCTL1_PDN_CODEC |
CS42L52_PWRCTL1_PDN_CHRG, 0);
This looks odd, especially having it in _ON - perhaps a comment explaining why?
I'm just enabling the power for the codec. Are you refering to the Charge Pump enable?
Both, really - it's just generally incredibly unusual to have anything in _ON, and looking at the names these look like supplies for other bits of the CODEC so things that I'd expect to be turned on early rather than late in the sequence. If it were in _PREPARE it'd be less surprising.
I can move the Codec Power bit into PREPARE. As for the Charge Pump, since it is only required for ADC, I can move it into an event for the ADC DAPM Widget. Make sense?
On Tue, Apr 17, 2012 at 11:25:42AM -0500, Brian Austin wrote:
I can move the Codec Power bit into PREPARE. As for the Charge Pump, since it is only required for ADC, I can move it into an event for the ADC DAPM Widget. Make sense?
I'd suggest making the charge pump a _SUPPLY widget instead - that'll be less code - but yes.
+static const char * const beep_pitch_text[] = {
- "C4", "C5", "D5", "E5", "F5", "G5", "A5", "B5",
- "C6", "D6", "E6", "F6", "G6", "A6", "B6", "C7"
+};
+static const struct soc_enum beep_pitch_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 4,
ARRAY_SIZE(beep_pitch_text), beep_pitch_text);
+static const char * const beep_ontime_text[] = {
- "86 ms", "430 ms", "780 ms", "1.20 s", "1.50 s",
- "1.80 s", "2.20 s", "2.50 s", "2.80 s", "3.20 s",
- "3.50 s", "3.80 s", "4.20 s", "4.50 s", "4.80 s", "5.20 s"
+};
+static const struct soc_enum beep_ontime_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_FREQ, 0,
ARRAY_SIZE(beep_ontime_text), beep_ontime_text);
+static const char * const beep_offtime_text[] = {
- "1.23 s", "2.58 s", "3.90 s", "5.20 s",
- "6.60 s", "8.05 s", "9.35 s", "10.80 s"
+};
+static const struct soc_enum beep_offtime_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_VOL, 5,
ARRAY_SIZE(beep_offtime_text), beep_offtime_text);
+static const char * const beep_config_text[] = {
- "Off", "Single", "Multiple", "Continuous"
+};
+static const struct soc_enum beep_config_enum =
- SOC_ENUM_SINGLE(CS42L52_BEEP_TONE_CTL, 6,
ARRAY_SIZE(beep_config_text), beep_config_text);
+static const char * const beep_bass_text[] = {
- "50 Hz", "100 Hz", "200 Hz", "250 Hz"
+};
All this beep stuff is really supposed to go through the input subsystem, or at least have the hookup in there to trigger it.
I have added basic functionality of the Beep to the input subsystem but have a few questions regarding the extra configs. I just used the same interface as the WM8962.
Would it be OK to keep the configurable parts of the Beep as Kcontrols? Maybe even Platform data?
participants (2)
-
Brian Austin
-
Mark Brown