[alsa-devel] [PATCH] ASoC: Add max98088 CODEC driver
This patch adds the MAX98088 CODEC driver.
Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com> --- include/sound/max98088.h | 87 ++ sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/max98088.c | 2871 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/max98088.h | 161 +++ 5 files changed, 3125 insertions(+), 0 deletions(-) create mode 100644 include/sound/max98088.h create mode 100644 sound/soc/codecs/max98088.c create mode 100644 sound/soc/codecs/max98088.h
diff --git a/include/sound/max98088.h b/include/sound/max98088.h new file mode 100644 index 0000000..7a6c53c --- /dev/null +++ b/include/sound/max98088.h @@ -0,0 +1,87 @@ +/* + * Platform data for MAX98088 + * + * Copyright 2010 Maxim Integrated Products + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef __SOUND_MAX98088_PDATA_H__ +#define __SOUND_MAX98088_PDATA_H__ + +#define EQ_CFG_MAX 32 + +/* Equalizer filter response configuration */ +struct max98088_eq_cfg { + const char *name; + unsigned int rate; + u16 band1[5]; + u16 band2[5]; + u16 band3[5]; + u16 band4[5]; + u16 band5[5]; +}; + +/* Speaker excursion limiter filter response configurations */ +#define EX_CFG_MAX 32 + +struct max98088_excursion_cfg { + const char *name; + unsigned int rate; + u16 resp[5]; +}; + +/* codec platform data */ +struct max98088_pdata { + + /* Equalizers for DAI1 and DAI2 */ + struct max98088_eq_cfg *eq1_cfg; + struct max98088_eq_cfg *eq2_cfg; + unsigned int eq1_cfgcnt; + unsigned int eq2_cfgcnt; + + /* Excursion limiters for DAI1 and DAI2 */ + struct max98088_excursion_cfg *ex1_cfg; + struct max98088_excursion_cfg *ex2_cfg; + unsigned int ex1_cfgcnt; + unsigned int ex2_cfgcnt; + + /* Receiver output can be configured as power amplifier or LINE out */ + /* Set receiver_mode to: + * 0 = amplifier output, or + * 1 = LINE level output + */ + unsigned int receiver_mode; + + /* Analog/digital microphone configuration: + * 0 = analog microphone input (normal setting) + * 1 = digital microhpone input + */ + unsigned int digmic_left_enable; + unsigned int digmic_right_enable; + + /* Normal microphone connection, or external connection through INA + * 0 = normal connection + * 1 = INA1 input + * 2 = INA2 input + */ + unsigned int extmic_mode; + + /* Bypass option for INA to MIC1 connection + * 0 = normal setting + * 1 = bypass enabled + */ + unsigned int ina_to_mic1_bypass; + + /* Bypass option for MIC1 to MIC2 connection + * 0 = normal setting + * 1 = bypass enabled + */ + unsigned int mic1_to_mic2_bypass; +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 155c127..e9a3c74 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7210 if I2C select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C + select SND_SOC_MAX98088 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -156,6 +157,9 @@ config SND_SOC_L3 config SND_SOC_DA7210 tristate
+config SND_SOC_MAX98088 + tristate + config SND_SOC_PCM3008 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 10d468e..fe9d104 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-da7210-objs := da7210.o snd-soc-l3-objs := l3.o +snd-soc-max98088-objs := max98088.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o @@ -87,6 +88,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o +obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c new file mode 100644 index 0000000..72f599f --- /dev/null +++ b/sound/soc/codecs/max98088.c @@ -0,0 +1,2871 @@ +/* + * max98088.c -- MAX98088 ALSA SoC Audio driver + * + * Copyright 2010 Maxim Integrated Products + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.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 <linux/slab.h> +#include <sound/max98088.h> +#include "max98088.h" + +static struct snd_soc_codec *max98088_codec; +struct snd_soc_codec_device soc_codec_dev_max98088; + +/* configurations associated with each channel of DAI stream */ +struct max98088_cdata { + unsigned int rate; + unsigned int fmt; + + /* Equalizer parameters */ + int eq_textcnt; + const char *eq_texts[EQ_CFG_MAX]; + int eq_sel; + struct soc_enum eq_enum; + + /* Excursion limiter parameters */ + int ex_textcnt; + const char *ex_texts[EX_CFG_MAX]; + int ex_sel; + struct soc_enum ex_enum; +}; + +/* codec private data */ +struct max98088_priv { + struct snd_soc_codec codec; + struct max98088_pdata *pdata; + u8 reg_cache[M98088_REG_CNT]; + unsigned int sysclk; + struct max98088_cdata dai[2]; + u8 power_state; + unsigned int ex_mode; + unsigned int digmic_mode; +}; + +static const u8 max98088_reg[M98088_REG_CNT] = { + 0x00, /* 00 IRQ status */ + 0x00, /* 01 MIC status */ + 0x00, /* 02 jack status */ + 0x00, /* 03 battery voltage */ + 0x00, /* 04 */ + 0x00, /* 05 */ + 0x00, /* 06 */ + 0x00, /* 07 */ + 0x00, /* 08 */ + 0x00, /* 09 */ + 0x00, /* 0A */ + 0x00, /* 0B */ + 0x00, /* 0C */ + 0x00, /* 0D */ + 0x00, /* 0E */ + 0x00, /* 0F interrupt enable */ + + 0x00, /* 10 master clock */ + 0x00, /* 11 DAI1 clock mode */ + 0x00, /* 12 DAI1 clock control */ + 0x00, /* 13 DAI1 clock control */ + 0x00, /* 14 DAI1 format */ + 0x00, /* 15 DAI1 clock */ + 0x00, /* 16 DAI1 config */ + 0x00, /* 17 DAI1 TDM */ + 0x00, /* 18 DAI1 filters */ + 0x00, /* 19 DAI2 clock mode */ + 0x00, /* 1A DAI2 clock control */ + 0x00, /* 1B DAI2 clock control */ + 0x00, /* 1C DAI2 format */ + 0x00, /* 1D DAI2 clock */ + 0x00, /* 1E DAI2 config */ + 0x00, /* 1F DAI2 TDM */ + + 0x00, /* 20 DAI2 filters */ + 0x00, /* 21 data config */ + 0x00, /* 22 DAC mixer */ + 0x00, /* 23 left ADC mixer */ + 0x00, /* 24 right ADC mixer */ + 0x00, /* 25 left HP mixer */ + 0x00, /* 26 right HP mixer */ + 0x00, /* 27 HP control */ + 0x00, /* 28 left REC mixer */ + 0x00, /* 29 right REC mixer */ + 0x00, /* 2A REC control */ + 0x00, /* 2B left SPK mixer */ + 0x00, /* 2C right SPK mixer */ + 0x00, /* 2D SPK control */ + 0x00, /* 2E sidetone */ + 0x00, /* 2F DAI1 playback level */ + + 0x00, /* 30 DAI1 playback level */ + 0x00, /* 31 DAI2 playback level */ + 0x00, /* 32 DAI2 playbakc level */ + 0x00, /* 33 left ADC level */ + 0x00, /* 34 right ADC level */ + 0x00, /* 35 MIC1 level */ + 0x00, /* 36 MIC2 level */ + 0x00, /* 37 INA level */ + 0x00, /* 38 INB level */ + 0x00, /* 39 left HP volume */ + 0x00, /* 3A right HP volume */ + 0x00, /* 3B left REC volume */ + 0x00, /* 3C right REC volume */ + 0x00, /* 3D left SPK volume */ + 0x00, /* 3E right SPK volume */ + 0x00, /* 3F MIC config */ + + 0x00, /* 40 MIC threshold */ + 0x00, /* 41 excursion limiter filter */ + 0x00, /* 42 excursion limiter threshold */ + 0x00, /* 43 ALC */ + 0x00, /* 44 power limiter threshold */ + 0x00, /* 45 power limiter config */ + 0x00, /* 46 distortion limiter config */ + 0x00, /* 47 audio input */ + 0x00, /* 48 microphone */ + 0x00, /* 49 level control */ + 0x00, /* 4A bypass switches */ + 0x00, /* 4B jack detect */ + 0x00, /* 4C input enable */ + 0x00, /* 4D output enable */ + 0xF0, /* 4E bias control */ + 0x00, /* 4F DAC power */ + + 0x0F, /* 50 DAC power */ + 0x00, /* 51 system */ + 0x00, /* 52 DAI1 EQ1 */ + 0x00, /* 53 DAI1 EQ1 */ + 0x00, /* 54 DAI1 EQ1 */ + 0x00, /* 55 DAI1 EQ1 */ + 0x00, /* 56 DAI1 EQ1 */ + 0x00, /* 57 DAI1 EQ1 */ + 0x00, /* 58 DAI1 EQ1 */ + 0x00, /* 59 DAI1 EQ1 */ + 0x00, /* 5A DAI1 EQ1 */ + 0x00, /* 5B DAI1 EQ1 */ + 0x00, /* 5C DAI1 EQ2 */ + 0x00, /* 5D DAI1 EQ2 */ + 0x00, /* 5E DAI1 EQ2 */ + 0x00, /* 5F DAI1 EQ2 */ + + 0x00, /* 60 DAI1 EQ2 */ + 0x00, /* 61 DAI1 EQ2 */ + 0x00, /* 62 DAI1 EQ2 */ + 0x00, /* 63 DAI1 EQ2 */ + 0x00, /* 64 DAI1 EQ2 */ + 0x00, /* 65 DAI1 EQ2 */ + 0x00, /* 66 DAI1 EQ3 */ + 0x00, /* 67 DAI1 EQ3 */ + 0x00, /* 68 DAI1 EQ3 */ + 0x00, /* 69 DAI1 EQ3 */ + 0x00, /* 6A DAI1 EQ3 */ + 0x00, /* 6B DAI1 EQ3 */ + 0x00, /* 6C DAI1 EQ3 */ + 0x00, /* 6D DAI1 EQ3 */ + 0x00, /* 6E DAI1 EQ3 */ + 0x00, /* 6F DAI1 EQ3 */ + + 0x00, /* 70 DAI1 EQ4 */ + 0x00, /* 71 DAI1 EQ4 */ + 0x00, /* 72 DAI1 EQ4 */ + 0x00, /* 73 DAI1 EQ4 */ + 0x00, /* 74 DAI1 EQ4 */ + 0x00, /* 75 DAI1 EQ4 */ + 0x00, /* 76 DAI1 EQ4 */ + 0x00, /* 77 DAI1 EQ4 */ + 0x00, /* 78 DAI1 EQ4 */ + 0x00, /* 79 DAI1 EQ4 */ + 0x00, /* 7A DAI1 EQ5 */ + 0x00, /* 7B DAI1 EQ5 */ + 0x00, /* 7C DAI1 EQ5 */ + 0x00, /* 7D DAI1 EQ5 */ + 0x00, /* 7E DAI1 EQ5 */ + 0x00, /* 7F DAI1 EQ5 */ + + 0x00, /* 80 DAI1 EQ5 */ + 0x00, /* 81 DAI1 EQ5 */ + 0x00, /* 82 DAI1 EQ5 */ + 0x00, /* 83 DAI1 EQ5 */ + 0x00, /* 84 DAI2 EQ1 */ + 0x00, /* 85 DAI2 EQ1 */ + 0x00, /* 86 DAI2 EQ1 */ + 0x00, /* 87 DAI2 EQ1 */ + 0x00, /* 88 DAI2 EQ1 */ + 0x00, /* 89 DAI2 EQ1 */ + 0x00, /* 8A DAI2 EQ1 */ + 0x00, /* 8B DAI2 EQ1 */ + 0x00, /* 8C DAI2 EQ1 */ + 0x00, /* 8D DAI2 EQ1 */ + 0x00, /* 8E DAI2 EQ2 */ + 0x00, /* 8F DAI2 EQ2 */ + + 0x00, /* 90 DAI2 EQ2 */ + 0x00, /* 91 DAI2 EQ2 */ + 0x00, /* 92 DAI2 EQ2 */ + 0x00, /* 93 DAI2 EQ2 */ + 0x00, /* 94 DAI2 EQ2 */ + 0x00, /* 95 DAI2 EQ2 */ + 0x00, /* 96 DAI2 EQ2 */ + 0x00, /* 97 DAI2 EQ2 */ + 0x00, /* 98 DAI2 EQ3 */ + 0x00, /* 99 DAI2 EQ3 */ + 0x00, /* 9A DAI2 EQ3 */ + 0x00, /* 9B DAI2 EQ3 */ + 0x00, /* 9C DAI2 EQ3 */ + 0x00, /* 9D DAI2 EQ3 */ + 0x00, /* 9E DAI2 EQ3 */ + 0x00, /* 9F DAI2 EQ3 */ + + 0x00, /* A0 DAI2 EQ3 */ + 0x00, /* A1 DAI2 EQ3 */ + 0x00, /* A2 DAI2 EQ4 */ + 0x00, /* A3 DAI2 EQ4 */ + 0x00, /* A4 DAI2 EQ4 */ + 0x00, /* A5 DAI2 EQ4 */ + 0x00, /* A6 DAI2 EQ4 */ + 0x00, /* A7 DAI2 EQ4 */ + 0x00, /* A8 DAI2 EQ4 */ + 0x00, /* A9 DAI2 EQ4 */ + 0x00, /* AA DAI2 EQ4 */ + 0x00, /* AB DAI2 EQ4 */ + 0x00, /* AC DAI2 EQ5 */ + 0x00, /* AD DAI2 EQ5 */ + 0x00, /* AE DAI2 EQ5 */ + 0x00, /* AF DAI2 EQ5 */ + + 0x00, /* B0 DAI2 EQ5 */ + 0x00, /* B1 DAI2 EQ5 */ + 0x00, /* B2 DAI2 EQ5 */ + 0x00, /* B3 DAI2 EQ5 */ + 0x00, /* B4 DAI2 EQ5 */ + 0x00, /* B5 DAI2 EQ5 */ + 0x00, /* B6 DAI1 biquad */ + 0x00, /* B7 DAI1 biquad */ + 0x00, /* B8 DAI1 biquad */ + 0x00, /* B9 DAI1 biquad */ + 0x00, /* BA DAI1 biquad */ + 0x00, /* BB DAI1 biquad */ + 0x00, /* BC DAI1 biquad */ + 0x00, /* BD DAI1 biquad */ + 0x00, /* BE DAI1 biquad */ + 0x00, /* BF DAI1 biquad */ + + 0x00, /* C0 DAI2 biquad */ + 0x00, /* C1 DAI2 biquad */ + 0x00, /* C2 DAI2 biquad */ + 0x00, /* C3 DAI2 biquad */ + 0x00, /* C4 DAI2 biquad */ + 0x00, /* C5 DAI2 biquad */ + 0x00, /* C6 DAI2 biquad */ + 0x00, /* C7 DAI2 biquad */ + 0x00, /* C8 DAI2 biquad */ + 0x00, /* C9 DAI2 biquad */ + 0x00, /* CA */ + 0x00, /* CB */ + 0x00, /* CC */ + 0x00, /* CD */ + 0x00, /* CE */ + 0x00, /* CF */ + + 0x00, /* D0 */ + 0x00, /* D1 */ + 0x00, /* D2 */ + 0x00, /* D3 */ + 0x00, /* D4 */ + 0x00, /* D5 */ + 0x00, /* D6 */ + 0x00, /* D7 */ + 0x00, /* D8 */ + 0x00, /* D9 */ + 0x00, /* DA */ + 0x70, /* DB */ + 0x00, /* DC */ + 0x00, /* DD */ + 0x00, /* DE */ + 0x00, /* DF */ + + 0x00, /* E0 */ + 0x00, /* E1 */ + 0x00, /* E2 */ + 0x00, /* E3 */ + 0x00, /* E4 */ + 0x00, /* E5 */ + 0x00, /* E6 */ + 0x00, /* E7 */ + 0x00, /* E8 */ + 0x00, /* E9 */ + 0x00, /* EA */ + 0x00, /* EB */ + 0x00, /* EC */ + 0x00, /* ED */ + 0x00, /* EE */ + 0x00, /* EF */ + + 0x00, /* F0 */ + 0x00, /* F1 */ + 0x00, /* F2 */ + 0x00, /* F3 */ + 0x00, /* F4 */ + 0x00, /* F5 */ + 0x00, /* F6 */ + 0x00, /* F7 */ + 0x00, /* F8 */ + 0x00, /* F9 */ + 0x00, /* FA */ + 0x00, /* FB */ + 0x00, /* FC */ + 0x00, /* FD */ + 0x00, /* FE */ + 0x00, /* FF */ +}; + +static struct { + int readable; + int writable; + int vol; +} max98088_access[M98088_REG_CNT] = { + { 0xFF, 0xFF, 1 }, /* 00 IRQ status */ + { 0xFF, 0x00, 1 }, /* 01 MIC status */ + { 0xFF, 0x00, 1 }, /* 02 jack status */ + { 0x1F, 0x1F, 1 }, /* 03 battery voltage */ + { 0xFF, 0xFF, 0 }, /* 04 */ + { 0xFF, 0xFF, 0 }, /* 05 */ + { 0xFF, 0xFF, 0 }, /* 06 */ + { 0xFF, 0xFF, 0 }, /* 07 */ + { 0xFF, 0xFF, 0 }, /* 08 */ + { 0xFF, 0xFF, 0 }, /* 09 */ + { 0xFF, 0xFF, 0 }, /* 0A */ + { 0xFF, 0xFF, 0 }, /* 0B */ + { 0xFF, 0xFF, 0 }, /* 0C */ + { 0xFF, 0xFF, 0 }, /* 0D */ + { 0xFF, 0xFF, 0 }, /* 0E */ + { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */ + + { 0xFF, 0xFF, 0 }, /* 10 master clock */ + { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */ + { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */ + { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */ + { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */ + { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */ + { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */ + { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */ + { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */ + { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */ + { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */ + { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */ + { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */ + { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */ + { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */ + { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */ + + { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */ + { 0xFF, 0xFF, 0 }, /* 21 data config */ + { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */ + { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */ + { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */ + { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */ + { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */ + { 0xFF, 0xFF, 0 }, /* 27 HP control */ + { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */ + { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */ + { 0xFF, 0xFF, 0 }, /* 2A REC control */ + { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */ + { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */ + { 0xFF, 0xFF, 0 }, /* 2D SPK control */ + { 0xFF, 0xFF, 0 }, /* 2E sidetone */ + { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */ + + { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */ + { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */ + { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */ + { 0xFF, 0xFF, 0 }, /* 33 left ADC level */ + { 0xFF, 0xFF, 0 }, /* 34 right ADC level */ + { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */ + { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */ + { 0xFF, 0xFF, 0 }, /* 37 INA level */ + { 0xFF, 0xFF, 0 }, /* 38 INB level */ + { 0xFF, 0xFF, 0 }, /* 39 left HP volume */ + { 0xFF, 0xFF, 0 }, /* 3A right HP volume */ + { 0xFF, 0xFF, 0 }, /* 3B left REC volume */ + { 0xFF, 0xFF, 0 }, /* 3C right REC volume */ + { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */ + { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */ + { 0xFF, 0xFF, 0 }, /* 3F MIC config */ + + { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */ + { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */ + { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */ + { 0xFF, 0xFF, 0 }, /* 43 ALC */ + { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */ + { 0xFF, 0xFF, 0 }, /* 45 power limiter config */ + { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */ + { 0xFF, 0xFF, 0 }, /* 47 audio input */ + { 0xFF, 0xFF, 0 }, /* 48 microphone */ + { 0xFF, 0xFF, 0 }, /* 49 level control */ + { 0xFF, 0xFF, 0 }, /* 4A bypass switches */ + { 0xFF, 0xFF, 0 }, /* 4B jack detect */ + { 0xFF, 0xFF, 0 }, /* 4C input enable */ + { 0xFF, 0xFF, 0 }, /* 4D output enable */ + { 0xFF, 0xFF, 0 }, /* 4E bias control */ + { 0xFF, 0xFF, 0 }, /* 4F DAC power */ + + { 0xFF, 0xFF, 0 }, /* 50 DAC power */ + { 0xFF, 0xFF, 0 }, /* 51 system */ + { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */ + + { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */ + + { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */ + { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */ + + { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */ + { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */ + { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */ + + { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */ + { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */ + + { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */ + { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */ + { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */ + + { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */ + { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */ + { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */ + + { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */ + { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */ + { 0x00, 0x00, 0 }, /* CA */ + { 0x00, 0x00, 0 }, /* CB */ + { 0x00, 0x00, 0 }, /* CC */ + { 0x00, 0x00, 0 }, /* CD */ + { 0x00, 0x00, 0 }, /* CE */ + { 0x00, 0x00, 0 }, /* CF */ + + { 0x00, 0x00, 0 }, /* D0 */ + { 0x00, 0x00, 0 }, /* D1 */ + { 0x00, 0x00, 0 }, /* D2 */ + { 0x00, 0x00, 0 }, /* D3 */ + { 0x00, 0x00, 0 }, /* D4 */ + { 0x00, 0x00, 0 }, /* D5 */ + { 0x00, 0x00, 0 }, /* D6 */ + { 0x00, 0x00, 0 }, /* D7 */ + { 0x00, 0x00, 0 }, /* D8 */ + { 0x00, 0x00, 0 }, /* D9 */ + { 0x00, 0x00, 0 }, /* DA */ + { 0x70, 0x70, 0 }, /* DB */ + { 0x00, 0x00, 0 }, /* DC */ + { 0x00, 0x00, 0 }, /* DD */ + { 0x00, 0x00, 0 }, /* DE */ + { 0x00, 0x00, 0 }, /* DF */ + + { 0x00, 0x00, 0 }, /* E0 */ + { 0x00, 0x00, 0 }, /* E1 */ + { 0x00, 0x00, 0 }, /* E2 */ + { 0x00, 0x00, 0 }, /* E3 */ + { 0x00, 0x00, 0 }, /* E4 */ + { 0x00, 0x00, 0 }, /* E5 */ + { 0x00, 0x00, 0 }, /* E6 */ + { 0x00, 0x00, 0 }, /* E7 */ + { 0x00, 0x00, 0 }, /* E8 */ + { 0x00, 0x00, 0 }, /* E9 */ + { 0x00, 0x00, 0 }, /* EA */ + { 0x00, 0x00, 0 }, /* EB */ + { 0x00, 0x00, 0 }, /* EC */ + { 0x00, 0x00, 0 }, /* ED */ + { 0x00, 0x00, 0 }, /* EE */ + { 0x00, 0x00, 0 }, /* EF */ + + { 0x00, 0x00, 0 }, /* F0 */ + { 0x00, 0x00, 0 }, /* F1 */ + { 0x00, 0x00, 0 }, /* F2 */ + { 0x00, 0x00, 0 }, /* F3 */ + { 0x00, 0x00, 0 }, /* F4 */ + { 0x00, 0x00, 0 }, /* F5 */ + { 0x00, 0x00, 0 }, /* F6 */ + { 0x00, 0x00, 0 }, /* F7 */ + { 0x00, 0x00, 0 }, /* F8 */ + { 0x00, 0x00, 0 }, /* F9 */ + { 0x00, 0x00, 0 }, /* FA */ + { 0x00, 0x00, 0 }, /* FB */ + { 0x00, 0x00, 0 }, /* FC */ + { 0x00, 0x00, 0 }, /* FD */ + { 0x00, 0x00, 0 }, /* FE */ + { 0xFF, 0x00, 1 }, /* FF */ +}; + + +static int max98088_volatile_register(unsigned int reg) +{ + return max98088_access[reg].vol; +} + +/* + * Read the MAX98088 I2C register space + * Note: this driver source code is backward compatible to kernel + * version 2.6.32. + */ +static unsigned int max98088_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct max98088_priv *max98088 = codec->private_data; + struct i2c_msg msg[2]; + struct i2c_client *client; + u8 data[2]; + int ret; + + client = (struct i2c_client *)codec->control_data; + + if (max98088_volatile_register(reg)) { + data[0] = reg; + msg[0].addr = client->addr; + msg[0].flags = 0; + msg[0].buf = &data[0]; + msg[0].len = 1; + + msg[1].addr = client->addr; + msg[1].flags = I2C_M_RD; + msg[1].buf = &data[1]; + msg[1].len = 1; + + ret = i2c_transfer(client->adapter, &msg[0], 2); + return (ret == 2) ? data[1] : -EIO; + } else { + if (reg > (ARRAY_SIZE(max98088_reg))) + return -EIO; + return max98088->reg_cache[reg]; + } +} + +/* Write to the MAX98088 register space (cached) */ +static int max98088_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + struct max98088_priv *max98088 = codec->private_data; + + data[0] = reg; + data[1] = value; + if (codec->hw_write(codec->control_data, data, 2) == 2) { + max98088->reg_cache[reg] = + (value & max98088_access[reg].writable); + return 0; + } else { + return -EIO; + } +} + +/* Write to the MAX98088 I2C register space */ +static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + data[0] = reg; + data[1] = value; + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +/* + * For kernels compiled without unsigned long long int division + */ +unsigned long long int ulldiv(unsigned long long int dividend, + unsigned long long int divisor) +{ + unsigned long long int quotient = 0; + int shift = 1; + + if (divisor == 0) + return 0; + + /* result is 1.0 if divisor and dividend are equal */ + if (divisor == dividend) + return 1; + + /* Normalize divisor */ + while (!(divisor & 0x8000000000000000ULL)) { + divisor <<= 1; + ++shift; + } + + /* Shift and subtract */ + while (shift--) { + quotient <<= 1; + + if (divisor <= dividend) { + dividend -= divisor; + ++quotient; + } + divisor >>= 1; + } + + /* Round up */ + if (dividend > divisor) + ++quotient; + + return quotient; +} + +#define INA1_PGA_BIT 0x01 +#define INA2_PGA_BIT 0x02 +#define INB1_PGA_BIT 0x04 +#define INB2_PGA_BIT 0x08 +/* + * The INx1 and INx2 PGAs share a power control signal. + * This function OR's the two power events to keep an unpowered INx + * from turning off it's counterpart. + * The control names are used to identify the PGA. + */ +static int max98088_pga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct max98088_priv *max98088 = codec->private_data; + u8 *state = &max98088->power_state; + unsigned int val; + unsigned int pga; + unsigned int mask; + + BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN); + + if (strncmp(w->name, "INA1", 4) == 0) { + pga = INA1_PGA_BIT; + mask = INA1_PGA_BIT | INA2_PGA_BIT; + } else if (strncmp(w->name, "INA2", 4) == 0) { + pga = INA2_PGA_BIT; + mask = INA1_PGA_BIT | INA2_PGA_BIT; + } else if (strncmp(w->name, "INB1", 4) == 0) { + pga = INB1_PGA_BIT; + mask = INB1_PGA_BIT | INB2_PGA_BIT; + } else if (strncmp(w->name, "INB2", 4) == 0) { + pga = INB2_PGA_BIT; + mask = INB1_PGA_BIT | INB2_PGA_BIT; + } else { + return -EINVAL; + } + + if (event == SND_SOC_DAPM_POST_PMU) { + /* ON */ + *state |= pga; + + /* Turn on, avoiding unnecessary writes */ + val = max98088_read(codec, w->reg); + if (!(val & (1 << w->shift))) { + val |= (1 << w->shift); + max98088_write(codec, w->reg, val); + } + } else if (event == SND_SOC_DAPM_POST_PMD) { + /* OFF */ + *state &= ~pga; + + /* Turn off if both are off, avoiding unnecessary writes */ + if (!(*state & mask)) { + val = max98088_read(codec, w->reg); + if (val & (1 << w->shift)) { + val &= ~(1 << w->shift); + max98088_write(codec, w->reg, val); + } + } + } else { + return -EINVAL; + } + + return 0; +} + +/* + * Load equalizer DSP coefficient configurations registers + */ +void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai, + unsigned int band, u16 *coefs) +{ + unsigned int eq_reg; + unsigned int i; + + if (band > 4) + return; + + if (dai > 1) + return; + + /* Load the base register address */ + eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE; + + /* Add the band address offset, note adjustment for word address */ + eq_reg += band * (M98088_COEFS_PER_BAND << 1); + + /* Step through the registers and coefs */ + for (i = 0; i < M98088_COEFS_PER_BAND; i++) { + max98088_write(codec, eq_reg++, M98088_BYTE1(coefs[i])); + max98088_write(codec, eq_reg++, M98088_BYTE0(coefs[i])); + } + + return; +} + +/* + * Excursion limiter modes + */ +static const char *max98088_ex_mode[] = { + "Off", + "100Hz", + "400Hz", + "600Hz", + "800Hz", + "1000Hz", + "200-400Hz", + "400-600Hz", + "400-800Hz", + "user-400Hz", + "user-600Hz", + "user-800Hz", + "user-1000Hz" +}; + +static const unsigned int ex_mode_table[] = { + 0x00, /* disabled */ + (0<<4)|3, /* 100Hz */ + (1<<4)|0, /* 400Hz */ + (2<<4)|0, /* 600Hz */ + (3<<4)|0, /* 800Hz */ + (4<<4)|0, /* 1000Hz */ + (1<<4)|1, /* 200-400Hz */ + (2<<4)|2, /* 400-600Hz */ + (3<<4)|2, /* 400-800Hz */ + (1<<4)|3, /* user-400Hz */ + (2<<4)|3, /* user-600Hz */ + (3<<4)|3, /* user-800Hz */ + (4<<4)|3 /* user-1000Hz */ +}; + +static const struct soc_enum max98088_ex_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(13, max98088_ex_mode), +}; + +/* + * Excursion limiter mode - set mode + */ +static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + unsigned int *mode = &max98088->ex_mode; + + *mode = ucontrol->value.integer.value[0]; + + if (*mode <= ARRAY_SIZE(ex_mode_table)) + max98088_write(codec, M98088_REG_41_SPKDHP, + ex_mode_table[*mode]); + + return 0; +} + +/* + * Excursion limiter mode - get mode + */ +static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + unsigned int *mode = &max98088->ex_mode; + + ucontrol->value.integer.value[0] = *mode; + return 0; +} + +/* + * Load user programmable mode excursion limiter filter coefficients + */ +static void max98088_ex_resp_control(struct snd_soc_codec *codec, + int reg, u16 *param) +{ + max98088_write(codec, reg, M98088_BYTE1(param[0])); + max98088_write(codec, ++reg, M98088_BYTE0(param[0])); + max98088_write(codec, ++reg, M98088_BYTE1(param[1])); + max98088_write(codec, ++reg, M98088_BYTE0(param[1])); + max98088_write(codec, ++reg, M98088_BYTE1(param[2])); + max98088_write(codec, ++reg, M98088_BYTE0(param[2])); + max98088_write(codec, ++reg, M98088_BYTE1(param[3])); + max98088_write(codec, ++reg, M98088_BYTE0(param[3])); + max98088_write(codec, ++reg, M98088_BYTE1(param[4])); + max98088_write(codec, ++reg, M98088_BYTE0(param[4])); +} + +static const char *max98088_hp_spk_mute[] = {"disable", "enable"}; +static const struct soc_enum max98088_hp_spk_mute_enum[] = { + SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute), +}; +/* These defines correlate to the position of the like named value in + * max98088_hp_spk_mute[] */ +#define MAX98088_HP_SPK_MUTE_DISABLE 0 +#define MAX98088_HP_SPK_MUTE_ENABLE 1 + +/* + * Mute the speaker + */ +static int max98088_spk_mute_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 lReg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L); + u8 rReg = max98088_read(codec, M98088_REG_3E_LVL_SPK_R); + + if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) { + lReg |= M98088_SP_MUTE; + rReg |= M98088_SP_MUTE; + } else { + lReg &= ~M98088_SP_MUTE; + rReg &= ~M98088_SP_MUTE; + } + + max98088_write(codec, M98088_REG_3D_LVL_SPK_L, lReg); + max98088_write(codec, M98088_REG_3E_LVL_SPK_R, rReg); + + return 0; +} + +/* + * Get speaker mute status + */ +static int max98088_spk_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 reg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L); + + ucontrol->value.integer.value[0] = (reg & M98088_SP_MUTE) ? 1 : 0; + + return 0; +} + +/* + * Mute the headphone + */ +static int max98088_hp_mute_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 lReg = max98088_read(codec, M98088_REG_39_LVL_HP_L); + u8 rReg = max98088_read(codec, M98088_REG_3A_LVL_HP_R); + + if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) { + lReg |= M98088_HP_MUTE; + rReg |= M98088_HP_MUTE; + } else { + lReg &= ~M98088_HP_MUTE; + rReg &= ~M98088_HP_MUTE; + } + + max98088_write(codec, M98088_REG_39_LVL_HP_L, lReg); + max98088_write(codec, M98088_REG_3A_LVL_HP_R, rReg); + + return 0; +} + +/* + * Get current headphone mute status + */ +static int max98088_hp_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 reg = max98088_read(codec, M98088_REG_39_LVL_HP_L); + + ucontrol->value.integer.value[0] = (reg & M98088_HP_MUTE) ? 1 : 0; + + return 0; +} + +/* + * Mute the receiver + */ +static int max98088_rec_mute_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 lReg = max98088_read(codec, M98088_REG_3B_LVL_REC_L); + u8 rReg = max98088_read(codec, M98088_REG_3C_LVL_REC_R); + + if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) { + lReg |= M98088_REC_MUTE; + rReg |= M98088_REC_MUTE; + } else { + lReg &= ~M98088_REC_MUTE; + rReg &= ~M98088_REC_MUTE; + } + + max98088_write(codec, M98088_REG_3B_LVL_REC_L, lReg); + max98088_write(codec, M98088_REG_3C_LVL_REC_R, rReg); + + return 0; +} + +/* + * Get current receiver mute status + */ +static int max98088_rec_mute_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u8 reg = max98088_read(codec, M98088_REG_3B_LVL_REC_L); + + ucontrol->value.integer.value[0] = (reg & M98088_REC_MUTE) ? 1 : 0; + return 0; +} + +static const char *max98088_ex_thresh[] = { /* volts PP */ + "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"}; +static const struct soc_enum max98088_ex_thresh_enum[] = { + SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8, + max98088_ex_thresh), +}; + +static const char *max98088_fltr_mode[] = {"Voice", "Music" }; +static const struct soc_enum max98088_filter_mode_enum[] = { + SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode), +}; + +static const char *max98088_dai1_fltr[] = { + "Off", "fc=258/fs=16k", "fc=500/fs=16k", + "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"}; +static const struct soc_enum max98088_dai1_dac_filter_enum[] = { + SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr), +}; +static const struct soc_enum max98088_dai1_adc_filter_enum[] = { + SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr), +}; + +static const char *max98088_dcblk[] = {"Off", "On"}; +static const struct soc_enum max98088_dcblk_enum[] = { + SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk), +}; + +/* amixer controls (non-dapm) */ +static const struct snd_kcontrol_new max98088_snd_controls[] = { + + /* analog output levels */ + + SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L, + M98088_REG_3A_LVL_HP_R, 0, 31, 0), + SOC_DOUBLE_R("Speaker volume", M98088_REG_3D_LVL_SPK_L, + M98088_REG_3E_LVL_SPK_R, 0, 31, 0), + SOC_DOUBLE_R("Receiver volume", M98088_REG_3B_LVL_REC_L, + M98088_REG_3C_LVL_REC_R, 0, 31, 0), + + SOC_ENUM_EXT("Headphone mute", max98088_hp_spk_mute_enum, + max98088_hp_mute_get, max98088_hp_mute_set), + SOC_ENUM_EXT("Speaker mute", max98088_hp_spk_mute_enum, + max98088_spk_mute_get, max98088_spk_mute_set), + SOC_ENUM_EXT("Receiver mute", max98088_hp_spk_mute_enum, + max98088_rec_mute_get, max98088_rec_mute_set), + + /* analog input levels */ + + SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1), + SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1), + + SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0), + SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0), + + SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1), + SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1), + + /* ADC input digital gains and volume control */ + + SOC_SINGLE("ADCL volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0), + SOC_SINGLE("ADCR volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0), + + SOC_SINGLE("ADCL gain", M98088_REG_33_LVL_ADC_L, 4, 3, 0), + SOC_SINGLE("ADCR gain", M98088_REG_34_LVL_ADC_R, 4, 3, 0), + + /* equalizer */ + + SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0), + SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0), + + /* excursion limiter */ + + SOC_ENUM_EXT("EX limiter mode", max98088_ex_mode_enum, + max98088_ex_mode_get, max98088_ex_mode_set), + SOC_ENUM("EX limiter threshold", max98088_ex_thresh_enum), + + /* voice/music filters */ + + SOC_ENUM("DAI1 filter mode", max98088_filter_mode_enum), + SOC_ENUM("DAI1 DAC filter", max98088_dai1_dac_filter_enum), + SOC_ENUM("DAI1 ADC filter", max98088_dai1_adc_filter_enum), + SOC_ENUM("DAI2 DC block", max98088_dcblk_enum), + + /* automatic level control (for both DAI1/DAI2) */ + + SOC_SINGLE("ALC switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0), + SOC_SINGLE("ALC threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0), + SOC_SINGLE("ALC multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0), + SOC_SINGLE("ALC release time", M98088_REG_43_SPKALC_COMP, 4, 7, 0), + + /* power limiter */ + + SOC_SINGLE("PWR limiter threshold", M98088_REG_44_PWRLMT_CFG, + 4, 15, 0), + SOC_SINGLE("PWR limiter weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0), + SOC_SINGLE("PWR limiter time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0), + SOC_SINGLE("PWR limiter time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0), + + /* THD distortion limiter */ + + SOC_SINGLE("THD limiter thresh", M98088_REG_46_THDLMT_CFG, 4, 15, 0), + SOC_SINGLE("THD limiter time", M98088_REG_46_THDLMT_CFG, 0, 7, 0), +}; + +/* DAPM sub-tables of the main dapm_widgets */ + +/* Left speaker mixer switch */ +static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0), +}; + +/* Right speaker mixer switch */ +static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0), +}; + +/* Left headphone mixer switch */ +static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0), +}; + +/* Right headphone mixer switch */ +static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0), +}; + +/* Left earpiece/receiver mixer switch */ +static const struct snd_kcontrol_new max98088_left_receiver_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0), +}; + +/* Right earpiece/receiver mixer switch */ +static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = { + SOC_DAPM_SINGLE("Left DAC1", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC1", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("Left DAC2", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("Right DAC2", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0), + SOC_DAPM_SINGLE("MIC1", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0), +}; + +/* Left ADC mixer switch */ +static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0), +}; + +/* Right ADC mixer switch */ +static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC1", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0), + SOC_DAPM_SINGLE("MIC2", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0), + SOC_DAPM_SINGLE("INA1", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0), + SOC_DAPM_SINGLE("INA2", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0), + SOC_DAPM_SINGLE("INB1", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0), + SOC_DAPM_SINGLE("INB2", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0), +}; + +static int max98088_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 status; + + BUG_ON(event != SND_SOC_DAPM_PRE_PMD); + + /* powering down headphone gracefully */ + status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT); + if ((status & M98088_HPEN) == M98088_HPEN) { + max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT, + (status & ~M98088_HPEN)); + } + schedule_timeout_interruptible(msecs_to_jiffies(20)); + + return 0; +} + +/* DAPM widgets top level */ +static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = { + + SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0), + SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0), + + SND_SOC_DAPM_DAC("DACL1", "HiFi Playback", + M98088_REG_4D_PWR_EN_OUT, 1, 0), + SND_SOC_DAPM_DAC("DACR1", "HiFi Playback", + M98088_REG_4D_PWR_EN_OUT, 0, 0), + SND_SOC_DAPM_DAC("DACL2", "Aux Playback", + M98088_REG_4D_PWR_EN_OUT, 1, 0), + SND_SOC_DAPM_DAC("DACR2", "Aux Playback", + M98088_REG_4D_PWR_EN_OUT, 0, 0), + + SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT, + 7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT, + 6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT, + 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT, + 4, 0, NULL, 0), + + SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT, + 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT, + 2, 0, NULL, 0), + + SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_hp_mixer_controls[0], + ARRAY_SIZE(max98088_left_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_hp_mixer_controls[0], + ARRAY_SIZE(max98088_right_hp_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_speaker_mixer_controls[0], + ARRAY_SIZE(max98088_left_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_speaker_mixer_controls[0], + ARRAY_SIZE(max98088_right_speaker_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_receiver_mixer_controls[0], + ARRAY_SIZE(max98088_left_receiver_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_rec_mixer_controls[0], + ARRAY_SIZE(max98088_right_rec_mixer_controls)), + + SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_left_ADC_mixer_controls[0], + ARRAY_SIZE(max98088_left_ADC_mixer_controls)), + + SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0, + &max98088_right_ADC_mixer_controls[0], + ARRAY_SIZE(max98088_right_ADC_mixer_controls)), + + SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN, + 7, 0, NULL, 0, max98088_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN, + 7, 0, NULL, 0, max98088_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN, + 6, 0, NULL, 0, max98088_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN, + 6, 0, NULL, 0, max98088_pga_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MICBIAS("Mic Bias", M98088_REG_4C_PWR_EN_IN, 3, 0), + + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("RECL"), + SND_SOC_DAPM_OUTPUT("RECR"), + + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("INA1"), + SND_SOC_DAPM_INPUT("INA2"), + SND_SOC_DAPM_INPUT("INB1"), + SND_SOC_DAPM_INPUT("INB2"), +}; + +/* DAPM AUDIO_MAP: */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Left headphone output mixer */ + {"Left HP Mixer", "Left DAC1", "DACL1"}, + {"Left HP Mixer", "Left DAC2", "DACL2"}, + {"Left HP Mixer", "Right DAC1", "DACR1"}, + {"Left HP Mixer", "Right DAC2", "DACR2"}, + {"Left HP Mixer", "MIC1", "Mic Bias"}, + {"Left HP Mixer", "MIC2", "Mic Bias"}, + {"Left HP Mixer", "INA1", "INA1 Input"}, + {"Left HP Mixer", "INA2", "INA2 Input"}, + {"Left HP Mixer", "INB1", "INB1 Input"}, + {"Left HP Mixer", "INB2", "INB2 Input"}, + + /* Right headphone output mixer */ + {"Right HP Mixer", "Left DAC1", "DACL1"}, + {"Right HP Mixer", "Left DAC2", "DACL2" }, + {"Right HP Mixer", "Right DAC1", "DACR1"}, + {"Right HP Mixer", "Right DAC2", "DACR2"}, + {"Right HP Mixer", "MIC1", "Mic Bias"}, + {"Right HP Mixer", "MIC2", "Mic Bias"}, + {"Right HP Mixer", "INA1", "INA1 Input"}, + {"Right HP Mixer", "INA2", "INA2 Input"}, + {"Right HP Mixer", "INB1", "INB1 Input"}, + {"Right HP Mixer", "INB2", "INB2 Input"}, + + /* Left speaker output mixer */ + {"Left SPK Mixer", "Left DAC1", "DACL1"}, + {"Left SPK Mixer", "Left DAC2", "DACL2"}, + {"Left SPK Mixer", "Right DAC1", "DACR1"}, + {"Left SPK Mixer", "Right DAC2", "DACR2"}, + {"Left SPK Mixer", "MIC1", "Mic Bias"}, + {"Left SPK Mixer", "MIC2", "Mic Bias"}, + {"Left SPK Mixer", "INA1", "INA1 Input"}, + {"Left SPK Mixer", "INA2", "INA2 Input"}, + {"Left SPK Mixer", "INB1", "INB1 Input"}, + {"Left SPK Mixer", "INB2", "INB2 Input"}, + + /* Right speaker output mixer */ + {"Right SPK Mixer", "Left DAC1", "DACL1"}, + {"Right SPK Mixer", "Left DAC2", "DACL2"}, + {"Right SPK Mixer", "Right DAC1", "DACR1"}, + {"Right SPK Mixer", "Right DAC2", "DACR2"}, + {"Right SPK Mixer", "MIC1", "Mic Bias"}, + {"Right SPK Mixer", "MIC2", "Mic Bias"}, + {"Right SPK Mixer", "INA1", "INA1 Input"}, + {"Right SPK Mixer", "INA2", "INA2 Input"}, + {"Right SPK Mixer", "INB1", "INB1 Input"}, + {"Right SPK Mixer", "INB2", "INB2 Input"}, + + /* Earpiece/Receiver output mixer */ + {"Left REC Mixer", "Left DAC1", "DACL1"}, + {"Left REC Mixer", "Left DAC2", "DACL2"}, + {"Left REC Mixer", "Right DAC1", "DACR1"}, + {"Left REC Mixer", "Right DAC2", "DACR2"}, + {"Left REC Mixer", "MIC1", "Mic Bias"}, + {"Left REC Mixer", "MIC2", "Mic Bias"}, + {"Left REC Mixer", "INA1", "INA1 Input"}, + {"Left REC Mixer", "INA2", "INA2 Input"}, + {"Left REC Mixer", "INB1", "INB1 Input"}, + {"Left REC Mixer", "INB2", "INB2 Input"}, + + /* Earpiece/Receiver output mixer */ + {"Right REC Mixer", "Left DAC1", "DACL1"}, + {"Right REC Mixer", "Left DAC2", "DACL2"}, + {"Right REC Mixer", "Right DAC1", "DACR1"}, + {"Right REC Mixer", "Right DAC2", "DACR2"}, + {"Right REC Mixer", "MIC1", "Mic Bias"}, + {"Right REC Mixer", "MIC2", "Mic Bias"}, + {"Right REC Mixer", "INA1", "INA1 Input"}, + {"Right REC Mixer", "INA2", "INA2 Input"}, + {"Right REC Mixer", "INB1", "INB1 Input"}, + {"Right REC Mixer", "INB2", "INB2 Input"}, + + {"HP Left Out", NULL, "Left HP Mixer"}, + {"HP Right Out", NULL, "Right HP Mixer"}, + {"SPK Left Out", NULL, "Left SPK Mixer"}, + {"SPK Right Out", NULL, "Right SPK Mixer"}, + {"REC Left Out", NULL, "Left REC Mixer"}, + {"REC Right Out", NULL, "Right REC Mixer"}, + + {"HPL", NULL, "HP Left Out"}, + {"HPR", NULL, "HP Right Out"}, + {"SPKL", NULL, "SPK Left Out"}, + {"SPKR", NULL, "SPK Right Out"}, + {"RECL", NULL, "REC Left Out"}, + {"RECR", NULL, "REC Right Out"}, + + /* Left ADC input mixer */ + {"Left ADC Mixer", "MIC1", "Mic Bias"}, + {"Left ADC Mixer", "MIC2", "Mic Bias"}, + {"Left ADC Mixer", "INA1", "INA1 Input"}, + {"Left ADC Mixer", "INA2", "INA2 Input"}, + {"Left ADC Mixer", "INB1", "INB1 Input"}, + {"Left ADC Mixer", "INB2", "INB2 Input"}, + + /* Right ADC input mixer */ + {"Right ADC Mixer", "MIC1", "Mic Bias"}, + {"Right ADC Mixer", "MIC2", "Mic Bias"}, + {"Right ADC Mixer", "INA1", "INA1 Input"}, + {"Right ADC Mixer", "INA2", "INA2 Input"}, + {"Right ADC Mixer", "INB1", "INB1 Input"}, + {"Right ADC Mixer", "INB2", "INB2 Input"}, + + /* inputs */ + {"ADCL", NULL, "Left ADC Mixer"}, + {"ADCR", NULL, "Right ADC Mixer"}, + + {"INA1 Input", NULL, "INA1"}, + {"INA2 Input", NULL, "INA2"}, + {"INB1 Input", NULL, "INB1"}, + {"INB2 Input", NULL, "INB2"}, + + {"Mic Bias", NULL, "MIC1"}, + {"Mic Bias", NULL, "MIC2"}, +}; + +/* + * Add widgets + */ +static int max98088_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, max98088_dapm_widgets, + ARRAY_SIZE(max98088_dapm_widgets)); + + /* set up audio path interconnects */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* + * Setup DAI1 format + */ +static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + u8 regval; + + cdata = &max98088->dai[0]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + /* DAI clock master/slave wrt the codec */ + switch (fmt & SND_SOC_DAIFMT_CBS_CFS) { + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + /* MAS: slave */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_MAS, 0); + /* slave mode PLL */ + max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI, + 0x80); + max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk, frm master */ + /* MAS: master */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + 0, M98088_DAI_MAS); + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv, frm mas */ + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas, frm slv */ + dev_err(codec->dev, "Clock mode unsupported"); + return -EINVAL;; + } + + /* I2S or TDM */ + if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) { + /* TDM: I2S */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_TDM, 0); + } else { + /* TDM: PCM/TDM */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + 0, M98088_DAI_TDM); + } + + /* DAI hardware signal inversions */ + switch (fmt & SND_SOC_DAIFMT_NB_NF) { + case SND_SOC_DAIFMT_NB_NF: + /* BCI: normal bclk (rise) */ + /* WCI: normal frame */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_BCI|M98088_DAI_WCI, 0); + break; + case SND_SOC_DAIFMT_NB_IF: + /* BCI: normal bclk (rise) */ + /* WCI: invert frame */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_BCI, M98088_DAI_WCI); + break; + case SND_SOC_DAIFMT_IB_NF: + /* BCI: invert bclk (fall) */ + /* WCI: normal frame */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_WCI, M98088_DAI_BCI); + break; + case SND_SOC_DAIFMT_IB_IF: + /* BCI: invert bclk (fall) */ + /* WCI: invert frame */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + 0, M98088_DAI_BCI|M98088_DAI_WCI); + break; + } + + regval = (1<<0); /* BSEL : 64*LRCLK (for master mode only) */ + if (max98088->digmic_mode) + regval |= (1<<6); /* OSR : oversample ratio */ + max98088_write(codec, M98088_REG_15_DAI1_CLOCK, regval); + + max98088_write(codec, M98088_REG_16_DAI1_IOCFG, + (1<<6) | /* SEL : map DAI1 to S1 */ + (0<<5) | /* LTEN : ADC->DAC loop-through enable */ + (0<<4) | /* LBEN : loopback (0=disable, 1=enable) */ + (0<<3) | /* DMONO : DAC SDIN (0=stereo, 1=mono) */ + (0<<2) | /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */ + (1<<1) | /* SDOEN : serial data out ENABLE */ + (1<<0)); /* SDIEN : serial data in ENABLE */ + + max98088_write(codec, M98088_REG_17_DAI1_TDM, + (0<<6) | /* SLOTL : L in 1st slot */ + (1<<4) | /* SLOTR : R in 2nd slot */ + (0<<0)); /* SLOTDLY : no delay */ + } + + return 0; +} + +/* + * Setup DAI2 format + */ +static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[1]; + + if (fmt != cdata->fmt) { + cdata->fmt = fmt; + + /* DAI clock master/slave wrt the codec */ + switch (fmt & SND_SOC_DAIFMT_CBS_CFS) { + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + /* MAS: slave */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_MAS, 0); + /* slave mode PLL */ + max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI, + 0x80); + max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO, + 0x00); + break; + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + /* MAS: master */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + 0, M98088_DAI_MAS); + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv & frm mas */ + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas & frm slv */ + dev_err(codec->dev, "Clock mode unsupported"); + return -EINVAL;; + } + + /* I2S or TDM */ + if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) { + /* TDM: I2S */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_TDM, 0); + } else { + /* TDM: PCM/TDM */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + 0, M98088_DAI_TDM); + } + + /* DAI hardware signal inversions */ + switch (fmt & SND_SOC_DAIFMT_NB_NF) { + case SND_SOC_DAIFMT_NB_NF: + /* BCI: normal bclk (rise) */ + /* WCI: normal frame */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_BCI|M98088_DAI_WCI, 0); + break; + case SND_SOC_DAIFMT_NB_IF: + /* BCI: normal bclk (rise) */ + /* WCI: invert frame */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_BCI, M98088_DAI_WCI); + break; + case SND_SOC_DAIFMT_IB_NF: + /* BCI: invert bclk (fall) */ + /* WCI: normal frame */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_WCI, M98088_DAI_BCI); + break; + case SND_SOC_DAIFMT_IB_IF: + /* BCI: invert bclk (fall) */ + /* WCI: invert frame */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + 0, M98088_DAI_BCI|M98088_DAI_WCI); + break; + } + + max98088_write(codec, M98088_REG_1D_DAI2_CLOCK, + (1<<0)); /* BSEL: 64*LRCLK (for master mode only) */ + + max98088_write(codec, M98088_REG_1E_DAI2_IOCFG, + (2<<6) | /* SEL : map DAI2 to S2 */ + (0<<4) | /* LBEN : loopback (0=disable) */ + (0<<3) | /* DMONO : DAC SDIN input */ + (0<<2) | /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */ + (1<<1) | /* SDOEN : serial data out ENABLE */ + (1<<0)); /* SDIEN : serial data in ENABLE */ + + max98088_write(codec, M98088_REG_1F_DAI2_TDM, + (0<<6) | /* SLOTL : L in 1st slot */ + (1<<4) | /* SLOTR : R in 2nd slot */ + (0<<0)); /* SLOTDLY : no delay */ + } + + return 0; +} + +static int max98088_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct max98088_priv *max98088 = codec->private_data; + + if (freq != max98088->sysclk) { + max98088->sysclk = freq; /* remember current sysclk */ + + /* setup clocks for slave mode, and using the PLL + * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) + * 0x02 (when master clk is 20MHz to 30MHz).. + */ + if ((freq >= 10000000) && (freq < 20000000)) { + max98088_write(codec, M98088_REG_10_SYS_CLK, 0x10); + } else if ((freq >= 20000000) && (freq < 30000000)) { + max98088_write(codec, M98088_REG_10_SYS_CLK, 0x20); + } else { + dev_err(codec->dev, "Invalid master clock frequency\n"); + return -EINVAL; + } + + /* If codec is currently running, toggle reset */ + if (max98088_read(codec, M98088_REG_51_PWR_SYS) + & M98088_SHDNRUN) { + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, + M98088_SHDNRUN, 0); + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, + 0, M98088_SHDNRUN); + } + } + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); + + max98088->sysclk = freq; + return 0; +} + + +struct rate_table_struct { + u32 rate; + u8 sr1; +}; + +/* codec mclk clock divider coefficients */ +static const struct rate_table_struct rate_table[] = { + {8000, 0x1}, + {11025, 0x2}, + {16000, 0x3}, + {22050, 0x4}, + {24000, 0x5}, + {32000, 0x6}, + {44100, 0x7}, + {48000, 0x8}, + {88200, 0x9}, + {96000, 0xA}, +}; + +static inline int rate_index(int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rate_table); i++) { + if (rate_table[i].rate >= rate) + return i; + } + return 0; +} + +/* Setup hw params and sample rate */ +static int max98088_dai1_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + unsigned int rate; + u8 reg11val; + u16 ni; + + cdata = &max98088->dai[0]; + + rate = params_rate(params); + + /* data 16/24 bit width */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + /* WS: 16bit */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + M98088_DAI_WS, 0); + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* WS: 24bit */ + snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT, + 0, M98088_DAI_WS); + break; + } + + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0); + + if (rate != cdata->rate) { + /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */ + reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0; + max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val); + cdata->rate = rate; + } + + /* Configure NI when operating as master */ + if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT) + & M98088_DAI_MAS) { + BUG_ON(max98088->sysclk == 0); + ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate, + (unsigned long long int)max98088->sysclk); + max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI, + (ni >> 8) & 0x7f); + max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO, + ni & 0xff); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS, + (1<<3), 0); + else + snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS, + 0, (1<<3)); + + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN); + + return 0; +} + +/* Setup hw params and sample rate */ +static int max98088_dai2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + unsigned int rate; + u8 reg19val; + u16 ni; + + cdata = &max98088->dai[1]; + + rate = params_rate(params); + + /* data 16/24 bit width */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + /* WS: 16bit */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + M98088_DAI_WS, 0); + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* WS: 24bit */ + snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT, + 0, M98088_DAI_WS); + break; + } + + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0); + + if (rate != cdata->rate) { + /* set DAI2 SR2 value for the DSP; FREQ1:0=anyclock */ + reg19val = (rate_table[rate_index(rate)].sr1 << 4) | 0; + max98088_write(codec, M98088_REG_19_DAI2_CLKMODE, reg19val); + cdata->rate = rate; + } + + /* Configure NI when operating as master */ + if (max98088_read(codec, M98088_REG_1C_DAI2_FORMAT) + & M98088_DAI_MAS) { + BUG_ON(max98088->sysclk == 0); + ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL) + * (unsigned long long int)rate, + (unsigned long long int)max98088->sysclk); + max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI, + (ni >> 8) & 0x7f); + max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO, + ni & 0xff); + } + + /* Update sample rate mode */ + if (rate < 50000) + snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS, + (1<<3), 0); + else + snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS, + 0, (1<<3)); + + snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN); + + return 0; +} + +static void max98088_sync_cache(struct snd_soc_codec *codec) +{ + struct max98088_priv *max98088 = codec->private_data; + int i; + + /* write back cached values if they're writeable and + * different from the hardware default. + */ + for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) { + if (!max98088_access[i].writable) + continue; + + if (max98088->reg_cache[i] == max98088_reg[i]) + continue; + + snd_soc_write(codec, i, max98088->reg_cache[i]); + } +} + +static int max98088_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN); + break; + + case SND_SOC_BIAS_PREPARE: + max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN); + break; + + case SND_SOC_BIAS_STANDBY: + max98088_sync_cache(codec); + max98088_write(codec, M98088_REG_51_PWR_SYS, + M98088_SHDNRUN|M98088_PWRSV); + break; + case SND_SOC_BIAS_OFF: + max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV); + break; + } + codec->bias_level = level; + return 0; +} + +#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000 +#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops max98088_dai1_ops = { + .set_sysclk = max98088_dai_set_sysclk, + .set_fmt = max98088_dai1_set_fmt, + .hw_params = max98088_dai1_hw_params, +}; + +static struct snd_soc_dai_ops max98088_dai2_ops = { + .set_sysclk = max98088_dai_set_sysclk, + .set_fmt = max98088_dai2_set_fmt, + .hw_params = max98088_dai2_hw_params, +}; + +struct snd_soc_dai max98088_dai[] = { +{ + .name = "HiFi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .ops = &max98088_dai1_ops, +}, +{ + .name = "Aux", + .playback = { + .stream_name = "Aux Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98088_RATES, + .formats = MAX98088_FORMATS, + }, + .ops = &max98088_dai2_ops, +} +}; +EXPORT_SYMBOL_GPL(max98088_dai); + + +static void max98088_setup_eq1(struct snd_soc_codec *codec) +{ + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_eq_cfg *coef_set; + int best, best_val, save, i, sel, fs; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[0]; + + if (!pdata || !cdata->eq_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + sel = cdata->eq_sel; + + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->eq1_cfgcnt; i++) { + if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 && + abs(pdata->eq1_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->eq1_cfg[i].rate - fs); + } + } + + dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->eq1_cfg[best].name, + pdata->eq1_cfg[best].rate, fs); + + /* Disable EQ while configuring, and save current on/off state */ + save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL); + snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0); + + coef_set = &pdata->eq1_cfg[sel]; + + m98088_eq_band(codec, 0, 0, coef_set->band1); + m98088_eq_band(codec, 0, 1, coef_set->band2); + m98088_eq_band(codec, 0, 2, coef_set->band3); + m98088_eq_band(codec, 0, 3, coef_set->band4); + m98088_eq_band(codec, 0, 4, coef_set->band5); + + /* restore original on/off state */ + snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save); +} + +static void max98088_setup_eq2(struct snd_soc_codec *codec) +{ + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_eq_cfg *coef_set; + int best, best_val, save, i, sel, fs; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[1]; + + if (!pdata || !cdata->eq_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + + sel = cdata->eq_sel; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->eq2_cfgcnt; i++) { + if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 && + abs(pdata->eq2_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->eq2_cfg[i].rate - fs); + } + } + + dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->eq2_cfg[best].name, + pdata->eq2_cfg[best].rate, fs); + + /* Disable EQ while configuring, and save current on/off state */ + save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL); + snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0); + + coef_set = &pdata->eq2_cfg[sel]; + + m98088_eq_band(codec, 1, 0, coef_set->band1); + m98088_eq_band(codec, 1, 1, coef_set->band2); + m98088_eq_band(codec, 1, 2, coef_set->band3); + m98088_eq_band(codec, 1, 3, coef_set->band4); + m98088_eq_band(codec, 1, 4, coef_set->band5); + + /* restore original on/off state */ + snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, + save); +} + + +static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int sel = ucontrol->value.integer.value[0]; + + cdata = &max98088->dai[0]; + + if (sel >= pdata->eq1_cfgcnt) + return -EINVAL; + + cdata->eq_sel = sel; + max98088_setup_eq1(codec); + return 0; +} + +static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int sel = ucontrol->value.integer.value[0]; + + cdata = &max98088->dai[1]; + + if (sel >= pdata->eq2_cfgcnt) + return -EINVAL; + + cdata->eq_sel = sel; + max98088_setup_eq2(codec); + return 0; +} + +static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[0]; + + ucontrol->value.enumerated.item[0] = cdata->eq_sel; + return 0; +} + +static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[1]; + + ucontrol->value.enumerated.item[0] = cdata->eq_sel; + return 0; +} + +static void max98088_handle_eq1_pdata(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int ret, i, j; + + struct snd_kcontrol_new eq1control = + SOC_ENUM_EXT("EQ1 Mode", + max98088->dai[0].eq_enum, + max98088_get_eq1_enum, + max98088_put_eq1_enum); + + cdata = &max98088->dai[0]; + + /* Build an array of texts for the enum API. The number + * of texts is likely fewer than the number of configurations + * due to multiple sample rates for the same text name. */ + cdata->eq_textcnt = 0; + for (i = 0; i < pdata->eq1_cfgcnt; i++) { + for (j = 0; j < cdata->eq_textcnt; j++) { + if (strcmp(pdata->eq1_cfg[i].name, + cdata->eq_texts[j]) == 0) { + break; + } + } + + if (j != cdata->eq_textcnt) + continue; + + /* ...and remember the new version. */ + cdata->eq_texts[i] = pdata->eq1_cfg[i].name; + cdata->eq_textcnt++; + + if (cdata->eq_textcnt >= EQ_CFG_MAX) { + dev_err(codec->dev, "Too many EQ config entries\n"); + cdata->eq_textcnt--; + break; + } + } + + dev_dbg(codec->dev, "Installed %d EQ1 configurations\n", + cdata->eq_textcnt); + + /* now point the soc_enum to .texts array items */ + cdata->eq_enum.texts = cdata->eq_texts; + cdata->eq_enum.max = cdata->eq_textcnt; + + BUG_ON(&max98088->codec == 0); + BUG_ON((max98088->codec).card == 0); + + ret = snd_soc_add_controls(&max98088->codec, &eq1control, 1); + if (ret != 0) + dev_err(codec->dev, "Failed to add EQ control: %d\n", ret); +} + +static void max98088_handle_eq2_pdata(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int ret, i, j; + + struct snd_kcontrol_new eq2control = + SOC_ENUM_EXT("EQ2 Mode", + max98088->dai[1].eq_enum, + max98088_get_eq2_enum, + max98088_put_eq2_enum); + + cdata = &max98088->dai[1]; + + /* Build an array of texts for the enum API. The number + * of texts is likely fewer than the number of configurations + * due to multiple sample rates for the same text name. */ + cdata->eq_textcnt = 0; + for (i = 0; i < pdata->eq2_cfgcnt; i++) { + for (j = 0; j < cdata->eq_textcnt; j++) { + if (strcmp(pdata->eq2_cfg[i].name, + cdata->eq_texts[j]) == 0) { + break; + } + } + + if (j != cdata->eq_textcnt) + continue; + + cdata->eq_texts[i] = pdata->eq2_cfg[i].name; + cdata->eq_textcnt++; + + if (cdata->eq_textcnt >= EQ_CFG_MAX) { + dev_err(codec->dev, "Too many EQ config entries\n"); + cdata->eq_textcnt--; + break; + } + } + + dev_dbg(codec->dev, "Installed %d EQ2 configurations\n", + cdata->eq_textcnt); + + /* now point the soc_enum to .texts array items */ + cdata->eq_enum.texts = cdata->eq_texts; + cdata->eq_enum.max = cdata->eq_textcnt; + + ret = snd_soc_add_controls(&max98088->codec, &eq2control, 1); + if (ret != 0) + printk(KERN_ERR "Failed to add EQ control: %d\n", ret); +} + + +static void max98088_setup_ex1(struct snd_soc_codec *codec) +{ + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + struct max98088_excursion_cfg *coef_set; + int best, best_val, i, sel, fs; + + cdata = &max98088->dai[0]; + + if (!pdata || !cdata->ex_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + + sel = cdata->ex_sel; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->ex1_cfgcnt; i++) { + if (strcmp(pdata->ex1_cfg[i].name, + cdata->ex_texts[sel]) == 0 && + abs(pdata->ex1_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->ex1_cfg[i].rate - fs); + } + } + + dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->ex1_cfg[best].name, + pdata->ex1_cfg[best].rate, fs); + + coef_set = &pdata->ex1_cfg[sel]; + + max98088_ex_resp_control(codec, M98088_REG_B6_DAI1_BIQUAD_BASE, + coef_set->resp); +} + +static int max98088_put_ex1_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int sel = ucontrol->value.integer.value[0]; + + cdata = &max98088->dai[0]; + + if (sel >= pdata->ex1_cfgcnt) + return -EINVAL; + + cdata->ex_sel = sel; + max98088_setup_ex1(codec); + return 0; +} + + +static int max98088_get_ex1_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[0]; + ucontrol->value.enumerated.item[0] = cdata->ex_sel; + + return 0; +} + +static void max98088_handle_ex1_pdata(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int ret, i, j; + + struct snd_kcontrol_new ex1control = + SOC_ENUM_EXT("EX1 Limiter Config", + max98088->dai[0].ex_enum, + max98088_get_ex1_enum, + max98088_put_ex1_enum); + + cdata = &max98088->dai[0]; + + /* Build an array of texts for the enum API. The number + * of texts is likely fewer than the number of configurations + * due to multiple sample rates for the same text name. */ + cdata->ex_textcnt = 0; + for (i = 0; i < pdata->ex1_cfgcnt; i++) { + for (j = 0; j < cdata->ex_textcnt; j++) { + if (strcmp(pdata->ex1_cfg[i].name, + cdata->ex_texts[j]) == 0) { + break; + } + } + + if (j != cdata->ex_textcnt) + continue; + + /* ...and remember the new version. */ + cdata->ex_texts[i] = pdata->ex1_cfg[i].name; + cdata->ex_textcnt++; + + if (cdata->ex_textcnt >= EX_CFG_MAX) { + dev_err(codec->dev, "Too many limiter configs\n"); + cdata->ex_textcnt--; + break; + } + } + + dev_dbg(codec->dev, "Installed %d EX1 configurations\n", + cdata->ex_textcnt); + + /* now point the soc_enum to .texts array */ + cdata->ex_enum.texts = cdata->ex_texts; + cdata->ex_enum.max = cdata->ex_textcnt; + ret = snd_soc_add_controls(&max98088->codec, &ex1control, 1); + if (ret != 0) + dev_err(codec->dev, "Failed to add limiter control\n"); +} + + +static void max98088_setup_ex2(struct snd_soc_codec *codec) +{ + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + struct max98088_excursion_cfg *coef_set; + int best, best_val, i, sel, fs; + + cdata = &max98088->dai[1]; + + if (!pdata || !cdata->ex_textcnt) + return; + + /* Find the selected configuration with nearest sample rate */ + fs = cdata->rate; + + sel = cdata->ex_sel; + best = 0; + best_val = INT_MAX; + for (i = 0; i < pdata->ex2_cfgcnt; i++) { + if (strcmp(pdata->ex2_cfg[i].name, + cdata->ex_texts[sel]) == 0 && + abs(pdata->ex2_cfg[i].rate - fs) < best_val) { + best = i; + best_val = abs(pdata->ex2_cfg[i].rate - fs); + } + } + + dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n", + pdata->ex2_cfg[best].name, + pdata->ex2_cfg[best].rate, fs); + + coef_set = &pdata->ex2_cfg[sel]; + + max98088_ex_resp_control(codec, M98088_REG_C0_DAI2_BIQUAD_BASE, + coef_set->resp); +} + +static int max98088_put_ex2_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int sel = ucontrol->value.integer.value[0]; + + cdata = &max98088->dai[1]; + + if (sel >= pdata->ex2_cfgcnt) + return -EINVAL; + + cdata->ex_sel = sel; + max98088_setup_ex2(codec); + return 0; +} + +static int max98088_get_ex2_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct max98088_priv *max98088 = codec->private_data; + struct max98088_cdata *cdata; + + cdata = &max98088->dai[1]; + ucontrol->value.enumerated.item[0] = cdata->ex_sel; + + return 0; +} + +static void max98088_handle_ex2_pdata(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_pdata *pdata = max98088->pdata; + struct max98088_cdata *cdata; + int ret, i, j; + + struct snd_kcontrol_new ex2control = + SOC_ENUM_EXT("EX2 Limiter Config", + max98088->dai[1].ex_enum, + max98088_get_ex2_enum, + max98088_put_ex2_enum); + + cdata = &max98088->dai[1]; + + /* Build an array of texts for the enum API. The number + * of texts is likely fewer than the number of configurations + * due to multiple sample rates for the same text name. */ + cdata->ex_textcnt = 0; + for (i = 0; i < pdata->ex2_cfgcnt; i++) { + for (j = 0; j < cdata->ex_textcnt; j++) { + if (strcmp(pdata->ex2_cfg[i].name, + cdata->ex_texts[j]) == 0) { + break; + } + } + + if (j != cdata->ex_textcnt) + continue; + + /* ...and remember the new version. */ + cdata->ex_texts[i] = pdata->ex2_cfg[i].name; + cdata->ex_textcnt++; + + if (cdata->ex_textcnt >= EX_CFG_MAX) { + dev_err(codec->dev, "Too many limiter configs\n"); + cdata->ex_textcnt--; + break; + } + } + + dev_dbg(codec->dev, "Installed %d EX2 configurations\n", + cdata->ex_textcnt); + + /* now point the soc_enum to .texts array items */ + cdata->ex_enum.texts = cdata->ex_texts; + cdata->ex_enum.max = cdata->ex_textcnt; + + ret = snd_soc_add_controls(&max98088->codec, &ex2control, 1); + if (ret != 0) + dev_err(codec->dev, "Failed to add limiter control\n"); +} + +static void max98088_handle_pdata(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_pdata *pdata = max98088->pdata; + u8 regval; + + if (!pdata) + return; + + /* Configure digital mic / external mic */ + /* Digital mic needs REG_15 OSR1=1 */ + regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */ + (0<<5) | /* DIGMICL enable */ + (0<<4) | /* DIGMICR enable */ + (0<<0)); /* external mic enable */ + + if (pdata->digmic_left_enable) + regval |= (1<<5); /* digital left mic enabled */ + + if (pdata->digmic_right_enable) + regval |= (1<<4); /* digital right mic enabled */ + + max98088->digmic_mode = (regval ? 1 : 0); + + if (pdata->extmic_mode < 3) + regval |= (pdata->extmic_mode << 0); /* external mic enabled */ + + max98088_write(codec, M98088_REG_48_CFG_MIC, regval); + + /* configure external mic to mic1 bypass, mic1 to mic2 bypass */ + regval = 0; + + if (pdata->ina_to_mic1_bypass) + regval |= (1<<7); + + if (pdata->mic1_to_mic2_bypass) + regval |= (1<<4); + + max98088_write(codec, M98088_REG_4A_CFG_BYPASS, regval); + + /* configure receiver output */ + regval = ((pdata->receiver_mode) ? (1<<7) : 0); + snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL, + (1<<7), regval); + + /* configure equalizers */ + if (pdata->eq1_cfgcnt) + max98088_handle_eq1_pdata(max98088); + + if (pdata->eq2_cfgcnt) + max98088_handle_eq2_pdata(max98088); + + /* configure excursion limiters */ + if (pdata->ex1_cfgcnt) + max98088_handle_ex1_pdata(max98088); + + if (pdata->ex2_cfgcnt) + max98088_handle_ex2_pdata(max98088); +} + +static int max98088_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int max98088_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 *cache = codec->reg_cache; + + /* toggle reset the shutdown bit of the codec hardware */ + max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); + + /* Sync reg_cache with the hardware */ + for (i = 0; i < M98088_REG_CNT; i++) { + if (i == M98088_REG_51_PWR_SYS) + continue; + + if (!max98088_access[i].writable) + continue; + + max98088_hw_write(codec, i, cache[i]); + } + + /* now enter into the resume mode bias level */ + max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +/* + * Make sure that a max98088 is attached to the I2C bus. + */ +static int max98088_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct max98088_priv *max98088; + int ret = 0; + + dev_printk(KERN_INFO, &pdev->dev, "MAX98088 Audio CODEC\n"); + + if (!max98088_codec) { + dev_err(&pdev->dev, "Codec device is not registered\n"); + return -EINVAL; + } + + socdev->card->codec = max98088_codec; + codec = max98088_codec; + max98088 = codec->private_data; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, max98088_snd_controls, + ARRAY_SIZE(max98088_snd_controls)); + + /* install additional amixer controls: EQ and excursion limiter */ + if (max98088->pdata) + max98088_handle_pdata(max98088); + else + dev_err(&pdev->dev, "No platform data\n"); + + max98088_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return 0; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + +pcm_err: + return ret; +} + +static int max98088_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->control_data) + max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +/* expose to machine driver */ +struct snd_soc_codec_device soc_codec_dev_max98088 = { + .probe = max98088_probe, + .remove = max98088_remove, + .suspend = max98088_suspend, + .resume = max98088_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_max98088); + + +static int max98088_register(struct max98088_priv *max98088, + enum snd_soc_control_type control) + +{ + int ret, i, version; + struct snd_soc_codec *codec = &max98088->codec; + struct max98088_cdata *cdata; + + if (max98088_codec) { + dev_err(codec->dev, "Another MAX98088 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "MAX98088"; + codec->owner = THIS_MODULE; + codec->read = max98088_read; + codec->write = max98088_write; + + /* setup DAPM event bias level and handler function */ + codec->bias_level = SND_SOC_BIAS_STANDBY; + codec->set_bias_level = max98088_set_bias_level; + + codec->dai = max98088_dai; + codec->num_dai = ARRAY_SIZE(max98088_dai); + codec->private_data = max98088; + + codec->reg_cache_size = M98088_REG_CNT; + codec->reg_cache = &max98088->reg_cache; + codec->volatile_register = max98088_volatile_register; + + memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg)); + + version = max98088_read(codec, M98088_REG_FF_REV_ID); + dev_dbg(codec->dev, "MAX98088 revision 0x%02X\n", version); + + /* initalize private data */ + + max98088->sysclk = (unsigned)-1; + + cdata = &max98088->dai[0]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_textcnt = 0; + cdata->eq_sel = 0; + cdata->ex_textcnt = 0; + cdata->ex_sel = 0; + + cdata = &max98088->dai[1]; + cdata->rate = (unsigned)-1; + cdata->fmt = (unsigned)-1; + cdata->eq_textcnt = 0; + cdata->eq_sel = 0; + cdata->ex_textcnt = 0; + cdata->ex_sel = 0; + + max98088_write(codec, M98088_REG_14_DAI1_FORMAT, M98088_DAI_DLY); + max98088_write(codec, M98088_REG_1C_DAI2_FORMAT, M98088_DAI_DLY); + + max98088->power_state = 0; /* INA INB power enable state */ + max98088->ex_mode = 0; /* excursion limiter mode */ + max98088->digmic_mode = 0; /* 0=normal, 1=digital mic used */ + + /* disable device via dapm interface */ + max98088_set_bias_level(codec, SND_SOC_BIAS_OFF); + + /* initialize registers cache to hardware default */ + max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + max98088_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00); + + max98088_write(codec, M98088_REG_22_MIX_DAC, + M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL| + M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR); + + max98088_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0); + max98088_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F); + + /* power on device */ + max98088_codec = codec; + for (i = 0; i < codec->num_dai; i++) + max98088_dai[i].dev = codec->dev; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(&max98088_dai[0], codec->num_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } + + return ret; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(max98088); + + return ret; +} + +static void max98088_unregister(struct max98088_priv *max98088) +{ + struct snd_soc_codec *codec = &max98088->codec; + snd_soc_unregister_dais(max98088_dai, codec->num_dai); + snd_soc_unregister_codec(codec); + kfree(max98088); + max98088_codec = NULL; +} + +static int max98088_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct snd_soc_codec *codec; + struct max98088_priv *max98088; + + max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL); + if (max98088 == NULL) + return -ENOMEM; + + /* codec structure is inside the private data */ + codec = &(max98088->codec); + + codec->private_data = max98088; + + codec->hw_write = (hw_write_t) i2c_master_send; + i2c_set_clientdata(i2c, max98088); + codec->control_data = i2c; + + max98088->pdata = i2c->dev.platform_data; + + codec->dev = &i2c->dev; + + return max98088_register(max98088, SND_SOC_I2C); +} + +static int max98088_i2c_remove(struct i2c_client *client) +{ + struct max98088_priv *max98088 = i2c_get_clientdata(client); + max98088_unregister(max98088); + return 0; +} + +static const struct i2c_device_id max98088_i2c_id[] = { + { "max98088", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max98088_i2c_id); + + +static struct i2c_driver max98088_i2c_driver = { + .driver = { + .name = "MAX98088 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = max98088_i2c_probe, + .remove = __devexit_p(max98088_i2c_remove), + .id_table = max98088_i2c_id, +}; + +static int __init max98088_init(void) +{ + int ret; + + ret = i2c_add_driver(&max98088_i2c_driver); + if (ret) + pr_err("Failed to register max98088 I2C driver: %d\n", ret); + + return ret; +} + +static void __exit max98088_exit(void) +{ + i2c_del_driver(&max98088_i2c_driver); +} + +module_init(max98088_init); +module_exit(max98088_exit); + +MODULE_DESCRIPTION("ALSA SoC MAX98088 driver"); +MODULE_AUTHOR("Peter Hsiang peter.hsiang@maxim-ic.com"); +MODULE_AUTHOR("Jesse Marroquin jesse.marroquin@maxim-ic.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h new file mode 100644 index 0000000..f7ef4c2 --- /dev/null +++ b/sound/soc/codecs/max98088.h @@ -0,0 +1,161 @@ +/* + * max98088.h -- MAX98088 ALSA SoC Audio driver + * + * Copyright 2010 Maxim Integrated Products + * + * 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 _MAX98088_H +#define _MAX98088_H + +/* + * MAX98088 Registers Definition + */ +#define M98088_REG_00_IRQ_STATUS 0x00 +#define M98088_REG_01_MIC_STATUS 0x01 +#define M98088_REG_02_JACK_STAUS 0x02 +#define M98088_REG_03_BATTERY_VOLTAGE 0x03 +#define M98088_REG_0F_IRQ_ENABLE 0x0F +#define M98088_REG_10_SYS_CLK 0x10 +#define M98088_REG_11_DAI1_CLKMODE 0x11 +#define M98088_REG_12_DAI1_CLKCFG_HI 0x12 +#define M98088_REG_13_DAI1_CLKCFG_LO 0x13 +#define M98088_REG_14_DAI1_FORMAT 0x14 +#define M98088_REG_15_DAI1_CLOCK 0x15 +#define M98088_REG_16_DAI1_IOCFG 0x16 +#define M98088_REG_17_DAI1_TDM 0x17 +#define M98088_REG_18_DAI1_FILTERS 0x18 +#define M98088_REG_19_DAI2_CLKMODE 0x19 +#define M98088_REG_1A_DAI2_CLKCFG_HI 0x1A +#define M98088_REG_1B_DAI2_CLKCFG_LO 0x1B +#define M98088_REG_1C_DAI2_FORMAT 0x1C +#define M98088_REG_1D_DAI2_CLOCK 0x1D +#define M98088_REG_1E_DAI2_IOCFG 0x1E +#define M98088_REG_1F_DAI2_TDM 0x1F +#define M98088_REG_20_DAI2_FILTERS 0x20 +#define M98088_REG_21_SRC 0x21 +#define M98088_REG_22_MIX_DAC 0x22 +#define M98088_REG_23_MIX_ADC_LEFT 0x23 +#define M98088_REG_24_MIX_ADC_RIGHT 0x24 +#define M98088_REG_25_MIX_HP_LEFT 0x25 +#define M98088_REG_26_MIX_HP_RIGHT 0x26 +#define M98088_REG_27_MIX_HP_CNTL 0x27 +#define M98088_REG_28_MIX_REC_LEFT 0x28 +#define M98088_REG_29_MIX_REC_RIGHT 0x29 +#define M98088_REG_2A_MIC_REC_CNTL 0x2A +#define M98088_REG_2B_MIX_SPK_LEFT 0x2B +#define M98088_REG_2C_MIX_SPK_RIGHT 0x2C +#define M98088_REG_2D_MIX_SPK_CNTL 0x2D +#define M98088_REG_2E_LVL_SIDETONE 0x2E +#define M98088_REG_2F_LVL_DAI1_PLAY 0x2F +#define M98088_REG_30_LVL_DAI1_PLAY_EQ 0x30 +#define M98088_REG_31_LVL_DAI2_PLAY 0x31 +#define M98088_REG_32_LVL_DAI2_PLAY_EQ 0x32 +#define M98088_REG_33_LVL_ADC_L 0x33 +#define M98088_REG_34_LVL_ADC_R 0x34 +#define M98088_REG_35_LVL_MIC1 0x35 +#define M98088_REG_36_LVL_MIC2 0x36 +#define M98088_REG_37_LVL_INA 0x37 +#define M98088_REG_38_LVL_INB 0x38 +#define M98088_REG_39_LVL_HP_L 0x39 +#define M98088_REG_3A_LVL_HP_R 0x3A +#define M98088_REG_3B_LVL_REC_L 0x3B +#define M98088_REG_3C_LVL_REC_R 0x3C +#define M98088_REG_3D_LVL_SPK_L 0x3D +#define M98088_REG_3E_LVL_SPK_R 0x3E +#define M98088_REG_3F_MICAGC_CFG 0x3F +#define M98088_REG_40_MICAGC_THRESH 0x40 +#define M98088_REG_41_SPKDHP 0x41 +#define M98088_REG_42_SPKDHP_THRESH 0x42 +#define M98088_REG_43_SPKALC_COMP 0x43 +#define M98088_REG_44_PWRLMT_CFG 0x44 +#define M98088_REG_45_PWRLMT_TIME 0x45 +#define M98088_REG_46_THDLMT_CFG 0x46 +#define M98088_REG_47_CFG_AUDIO_IN 0x47 +#define M98088_REG_48_CFG_MIC 0x48 +#define M98088_REG_49_CFG_LEVEL 0x49 +#define M98088_REG_4A_CFG_BYPASS 0x4A +#define M98088_REG_4B_CFG_JACKDET 0x4B +#define M98088_REG_4C_PWR_EN_IN 0x4C +#define M98088_REG_4D_PWR_EN_OUT 0x4D +#define M98088_REG_4E_BIAS_CNTL 0x4E +#define M98088_REG_4F_DAC_BIAS1 0x4F +#define M98088_REG_50_DAC_BIAS2 0x50 +#define M98088_REG_51_PWR_SYS 0x51 +#define M98088_REG_52_DAI1_EQ_BASE 0x52 +#define M98088_REG_84_DAI2_EQ_BASE 0x84 +#define M98088_REG_B6_DAI1_BIQUAD_BASE 0xB6 +#define M98088_REG_C0_DAI2_BIQUAD_BASE 0xC0 +#define M98088_REG_FF_REV_ID 0xFF + +#define M98088_REG_CNT (0xFF+1) + +/* MAX98088 Registers Bit Fields */ + +/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */ + #define M98088_DAI_MAS (1<<7) + #define M98088_DAI_WCI (1<<6) + #define M98088_DAI_BCI (1<<5) + #define M98088_DAI_DLY (1<<4) + #define M98088_DAI_TDM (1<<2) + #define M98088_DAI_FSW (1<<1) + #define M98088_DAI_WS (1<<0) + +/* M98088_REG_22_MIX_DAC */ + #define M98088_DAI1L_TO_DACL (1<<7) + #define M98088_DAI1R_TO_DACL (1<<6) + #define M98088_DAI2L_TO_DACL (1<<5) + #define M98088_DAI2R_TO_DACL (1<<4) + #define M98088_DAI1L_TO_DACR (1<<3) + #define M98088_DAI1R_TO_DACR (1<<2) + #define M98088_DAI2L_TO_DACR (1<<1) + #define M98088_DAI2R_TO_DACR (1<<0) + +/* M98088_REG_3A_LVL_HEADPHONE_R */ + #define M98088_HP_MUTE (1<<7) + +/* M98088_REG_3C_LVL_RECEIVER_R */ + #define M98088_REC_MUTE (1<<7) + +/* M98088_REG_3E_LVL_SPEAKER_R */ + #define M98088_SP_MUTE (1<<7) + +/* M98088_REG_49_CFG_LEVEL */ + #define M98088_VSEN (1<<6) + #define M98088_ZDEN (1<<5) + #define M98088_EQ2EN (1<<1) + #define M98088_EQ1EN (1<<0) + +/* M98088_REG_4D_PWR_EN_OUT */ + #define M98088_HPLEN (1<<7) + #define M98088_HPREN (1<<6) + #define M98088_HPEN ((1<<7)|(1<<6)) + #define M98088_SPLEN (1<<5) + #define M98088_SPREN (1<<4) + #define M98088_RECEN (1<<3) + #define M98088_DALEN (1<<1) + #define M98088_DAREN (1<<0) + +/* M98088_REG_51_PWR_SYS */ + #define M98088_SHDNRUN (1<<7) + #define M98088_PERFMODE (1<<3) + #define M98088_HPPLYBACK (1<<2) + #define M98088_PWRSV8K (1<<1) + #define M98088_PWRSV (1<<0) + +#define M98088_COEFS_PER_BAND 5 + +#define M98088_BYTE1(w) ((w >> 8) & 0xff) +#define M98088_BYTE0(w) (w & 0xff) + +struct max98088_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai max98088_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_max98088; + +#endif -- 1.6.3.3
On Tue, Aug 31, 2010 at 02:08:30PM -0700, Peter Hsiang wrote:
This patch adds the MAX98088 CODEC driver.
The major issue with this is that the driver needs to be updated to current ASoC versions - you should always be working against the trees that are in linux-next for new drivers. For ASoC you should be looking at:
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>
Please write your e-mail address properly - it'll end up in the git logs anyway. It'd also be nice to credit your sources when you've taken code from other drivers :)
+#define EQ_CFG_MAX 32
+/* Speaker excursion limiter filter response configurations */ +#define EX_CFG_MAX 32
Multiple definitions of this.
/* Set receiver_mode to:
* 0 = amplifier output, or
* 1 = LINE level output
*/
unsigned int receiver_mode;
Better to use bools or bitfields.
/* Bypass option for INA to MIC1 connection
* 0 = normal setting
* 1 = bypass enabled
*/
unsigned int ina_to_mic1_bypass;
/* Bypass option for MIC1 to MIC2 connection
What are these - they sound like something I'd expect to be configurable at runtime?
@@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7210 if I2C select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C
select SND_SOC_MAX98088 if I2C
The sorting should be lexical.
+/*
- Read the MAX98088 I2C register space
- Note: this driver source code is backward compatible to kernel
- version 2.6.32.
- */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
unsigned int reg)
Just use soc-cache. Compatibility code for older kernels isn't really acceptable for mainline due to the increased maintainance burden and it will at best result in a driver that doesn't work as well as possible.
client = (struct i2c_client *)codec->control_data;
No need to cast away from void.
+/*
- For kernels compiled without unsigned long long int division
- */
+unsigned long long int ulldiv(unsigned long long int dividend,
unsigned long long int divisor)
+{
unsigned long long int quotient = 0;
int shift = 1;
if (divisor == 0)
return 0;
/* result is 1.0 if divisor and dividend are equal */
if (divisor == dividend)
return 1;
/* Normalize divisor */
while (!(divisor & 0x8000000000000000ULL)) {
divisor <<= 1;
++shift;
}
/* Shift and subtract */
while (shift--) {
quotient <<= 1;
if (divisor <= dividend) {
dividend -= divisor;
++quotient;
}
divisor >>= 1;
}
/* Round up */
if (dividend > divisor)
++quotient;
return quotient;
+}
+/*
- Load equalizer DSP coefficient configurations registers
- */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
unsigned int band, u16 *coefs)
+{
unsigned int eq_reg;
unsigned int i;
if (band > 4)
return;
if (dai > 1)
return;
These look like they should be BUG_ON().
+/*
- Excursion limiter modes
- */
+static const char *max98088_ex_mode[] = {
"Off",
"100Hz",
"400Hz",
"600Hz",
"800Hz",
"1000Hz",
"200-400Hz",
"400-600Hz",
"400-800Hz",
"user-400Hz",
"user-600Hz",
"user-800Hz",
"user-1000Hz"
These user options look very suspicous.
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct max98088_priv *max98088 = codec->private_data;
unsigned int *mode = &max98088->ex_mode;
*mode = ucontrol->value.integer.value[0];
if (*mode <= ARRAY_SIZE(ex_mode_table))
max98088_write(codec, M98088_REG_41_SPKDHP,
ex_mode_table[*mode]);
Report the error if there's an invalid input.
+static const char *max98088_hp_spk_mute[] = {"disable", "enable"}; +static const struct soc_enum max98088_hp_spk_mute_enum[] = {
SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),
Why are you doing this rather than just using a standard switch control? Even if there's a reason to not use one of the standard register controls this should be presented to the application layer as a switch rather than as an enumeration.
The same issue applies to the other outputs.
+static const char *max98088_dcblk[] = {"Off", "On"}; +static const struct soc_enum max98088_dcblk_enum[] = {
SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk),
+};
Again, present a switch to userspace.
SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
M98088_REG_3A_LVL_HP_R, 0, 31, 0),
Volume, not volume.
SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),
SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),
These should all be named " Volume", as should all your other " gain" or " pre" controls.
SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
Capitalise switch.
+/* Left speaker mixer switch */ +static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
All of these should be " Switch".
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
struct snd_soc_codec *codec = w->codec;
u16 status;
BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
/* powering down headphone gracefully */
status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
if ((status & M98088_HPEN) == M98088_HPEN) {
max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
(status & ~M98088_HPEN));
}
schedule_timeout_interruptible(msecs_to_jiffies(20));
This should be non-interruptible since there's no handling of any actual interruptions.
+/* DAPM AUDIO_MAP: */ +static const struct snd_soc_dapm_route audio_map[] = {
/* Left headphone output mixer */
{"Left HP Mixer", "Left DAC1", "DACL1"},
{"Left HP Mixer", "Left DAC2", "DACL2"},
{"Left HP Mixer", "Right DAC1", "DACR1"},
{"Left HP Mixer", "Right DAC2", "DACR2"},
{"Left HP Mixer", "MIC1", "Mic Bias"},
{"Left HP Mixer", "MIC2", "Mic Bias"},
This looks wrong - I'd expect this to be switching the microphone input to the mixer, not the bias. Mixing the bias in would just present a DC level which isn't going to be great. Similarly for all your other microphone input switching.
/* DAI clock master/slave wrt the codec */
switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
You should be using the masks for your switch statements. Using specific values is at best harder to read and at worst will lead to false positives. This applies to all your comparisons in this function.
/* BCI: normal bclk (rise) */
/* WCI: invert frame */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
M98088_DAI_BCI, M98088_DAI_WCI);
break;
case SND_SOC_DAIFMT_IB_NF:
/* BCI: invert bclk (fall) */
/* WCI: normal frame */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
M98088_DAI_WCI, M98088_DAI_BCI);
This code (and most of the other update_bits() usages round here) looks wrong - I'd expect to see a constant bitmask being supplied as the mask argument then the value changing to set different values.
max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
(1<<6) | /* SEL : map DAI1 to S1 */
(0<<5) | /* LTEN : ADC->DAC loop-through enable */
(0<<4) | /* LBEN : loopback (0=disable, 1=enable) */
(0<<3) | /* DMONO : DAC SDIN (0=stereo, 1=mono) */
These look like things that should be configured by the user, especially LTEN.
(0<<2) | /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
(1<<1) | /* SDOEN : serial data out ENABLE */
(1<<0)); /* SDIEN : serial data in ENABLE */
These look like they ought to be DAPM widgets for the AIF.
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int dir)
+{
struct snd_soc_codec *codec = dai->codec;
struct max98088_priv *max98088 = codec->private_data;
if (freq != max98088->sysclk) {
It would be clearer to just return in this case so you don't have so much indentation.
/* If codec is currently running, toggle reset */
if (max98088_read(codec, M98088_REG_51_PWR_SYS)
& M98088_SHDNRUN) {
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN, 0);
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
0, M98088_SHDNRUN);
Will this reset the whole chip?
+static inline int rate_index(int rate) +{
int i;
for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
if (rate_table[i].rate >= rate)
return i;
}
return 0;
This won't indicate if there's a failure to find a match.
/* data 16/24 bit width */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
/* WS: 16bit */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
M98088_DAI_WS, 0);
break;
case SNDRV_PCM_FORMAT_S24_LE:
/* WS: 24bit */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
0, M98088_DAI_WS);
break;
}
Again, your snd_soc_update_bits() usage seems confused. You're also not checking for invalid inputs. There are other instances of this in the driver, I'll not comment them all.
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
if (rate != cdata->rate) {
/* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val);
cdata->rate = rate;
Just directly use snd_soc_update_bits() - it will suppress null changes.
/* Configure NI when operating as master */
if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
& M98088_DAI_MAS) {
BUG_ON(max98088->sysclk == 0);
BUG_ON() is unfriendly here - this is a machine driver configuration error and normally BUG_ON() would be an internal error in the driver. Better error reporting please.
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
switch (level) {
case SND_SOC_BIAS_ON:
max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
break;
case SND_SOC_BIAS_PREPARE:
max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
break;
Drop the _ON cacse if it's the same. Also, how will this interact with the management of SHDNRUN in hw_params()?
case SND_SOC_BIAS_STANDBY:
max98088_sync_cache(codec);
max98088_write(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN|M98088_PWRSV);
break;
Do you really want to sync the cache every time we drop into standby?
/* Configure digital mic / external mic */
/* Digital mic needs REG_15 OSR1=1 */
regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
(0<<5) | /* DIGMICL enable */
(0<<4) | /* DIGMICR enable */
(0<<0)); /* external mic enable */
These look like they ought to be managed via DAPM?
On Wed, Sep 01, 2010, Mark Brown wrote:
On Tue, Aug 31, 2010 at 02:08:30PM -0700, Peter Hsiang wrote:
This patch adds the MAX98088 CODEC driver.
The major issue with this is that the driver needs to be updated to current ASoC versions - you should always be working against the trees that are in linux-next for new drivers. For ASoC you should be looking at:
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Thanks for the git location you provided. What version of Linux kernel does this git/broonie/sound-2.6.git version correspond to? Is it newer than even the latest kernel-next?
Some of the ASoC API functions and features in the latest version of the kernel you require us to use are not in the popular stable kernel version 2.6.32 that people are using today to build products. Our intent is to release driver code that will work in both the current and latest kernel versions.
Would it not be a good idea to use the API functions that exist in both the very latest and the currently popular kernel version?
Does kernel.org provide a channel for releasing codec drivers that can be backward compatible to a popular earlier kernel version?
Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>
Please write your e-mail address properly - it'll end up in the git logs anyway. It'd also be nice to credit your sources when you've taken code from other drivers :)
+#define EQ_CFG_MAX 32
+/* Speaker excursion limiter filter response configurations */ +#define EX_CFG_MAX 32
Multiple definitions of this.
These are not multiple definitions. They are 2 different ones - the equalizer, and excursion limiter.
/* Set receiver_mode to:
* 0 = amplifier output, or
* 1 = LINE level output
*/
unsigned int receiver_mode;
Better to use bools or bitfields.
/* Bypass option for INA to MIC1 connection
* 0 = normal setting
* 1 = bypass enabled
*/
unsigned int ina_to_mic1_bypass;
/* Bypass option for MIC1 to MIC2 connection
What are these - they sound like something I'd expect to be configurable at runtime?
@@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7210 if I2C select SND_SOC_JZ4740 if SOC_JZ4740 select SND_SOC_MAX9877 if I2C
select SND_SOC_MAX98088 if I2C
The sorting should be lexical.
Ok, thanks
+/*
- Read the MAX98088 I2C register space
- Note: this driver source code is backward compatible to kernel
- version 2.6.32.
- */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
unsigned int reg)
Just use soc-cache. Compatibility code for older kernels isn't really acceptable for mainline due to the increased maintainance burden and it will at best result in a driver that doesn't work as well as possible.
This integrated registers feature is not available in 2.6.32 Is it ok with you to keep it this way for this release? Thanks.
client = (struct i2c_client *)codec->control_data;
No need to cast away from void.
Ok will do - but no harm in being informative :)
+/*
- For kernels compiled without unsigned long long int division
- */
+unsigned long long int ulldiv(unsigned long long int dividend,
unsigned long long int divisor)
+{
unsigned long long int quotient = 0;
int shift = 1;
if (divisor == 0)
return 0;
/* result is 1.0 if divisor and dividend are equal */
if (divisor == dividend)
return 1;
/* Normalize divisor */
while (!(divisor & 0x8000000000000000ULL)) {
divisor <<= 1;
++shift;
}
/* Shift and subtract */
while (shift--) {
quotient <<= 1;
if (divisor <= dividend) {
dividend -= divisor;
++quotient;
}
divisor >>= 1;
}
/* Round up */
if (dividend > divisor)
++quotient;
return quotient;
+}
+/*
- Load equalizer DSP coefficient configurations registers
- */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
unsigned int band, u16 *coefs)
+{
unsigned int eq_reg;
unsigned int i;
if (band > 4)
return;
if (dai > 1)
return;
These look like they should be BUG_ON().
Yes, good idea.
+/*
- Excursion limiter modes
- */
+static const char *max98088_ex_mode[] = {
"Off",
"100Hz",
"400Hz",
"600Hz",
"800Hz",
"1000Hz",
"200-400Hz",
"400-600Hz",
"400-800Hz",
"user-400Hz",
"user-600Hz",
"user-800Hz",
"user-1000Hz"
These user options look very suspicous.
The first 8 options are settings pre-defined in hardware that the user can choose from. The last 4 are user programmable options, and thus the name 'user'.
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
struct max98088_priv *max98088 = codec->private_data;
unsigned int *mode = &max98088->ex_mode;
*mode = ucontrol->value.integer.value[0];
if (*mode <= ARRAY_SIZE(ex_mode_table))
max98088_write(codec, M98088_REG_41_SPKDHP,
ex_mode_table[*mode]);
Report the error if there's an invalid input.
Ok
+static const char *max98088_hp_spk_mute[] = {"disable", "enable"}; +static const struct soc_enum max98088_hp_spk_mute_enum[] = {
SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),
Why are you doing this rather than just using a standard switch control? Even if there's a reason to not use one of the standard register controls this should be presented to the application layer as a switch rather than as an enumeration.
We will use a switch control. Thanks
The same issue applies to the other outputs.
+static const char *max98088_dcblk[] = {"Off", "On"}; +static const struct soc_enum max98088_dcblk_enum[] = {
SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2,
max98088_dcblk),
+};
Again, present a switch to userspace.
Yes
SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
M98088_REG_3A_LVL_HP_R, 0, 31, 0),
Volume, not volume.
SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),
SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),
These should all be named " Volume", as should all your other " gain" or " pre" controls.
Ok
SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
Capitalise switch.
I noticed that by capitalizing the first letter of the 2nd word, the 2nd word is dropped out by the system when amixer lists them. "EQ1 Switch" becomes just "EQ1", while "EQ1 switch" is "EQ1 switch". Have you seen this behavior before? Is this a known issue with some version of amixer or ASoC code base?
+/* Left speaker mixer switch */ +static const struct snd_kcontrol_new
max98088_left_speaker_mixer_controls[] = {
SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1,
0),
All of these should be " Switch".
Ok
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
+{
struct snd_soc_codec *codec = w->codec;
u16 status;
BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
/* powering down headphone gracefully */
status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
if ((status & M98088_HPEN) == M98088_HPEN) {
max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
(status & ~M98088_HPEN));
}
schedule_timeout_interruptible(msecs_to_jiffies(20));
This should be non-interruptible since there's no handling of any actual interruptions.
Ok
+/* DAPM AUDIO_MAP: */ +static const struct snd_soc_dapm_route audio_map[] = {
/* Left headphone output mixer */
{"Left HP Mixer", "Left DAC1", "DACL1"},
{"Left HP Mixer", "Left DAC2", "DACL2"},
{"Left HP Mixer", "Right DAC1", "DACR1"},
{"Left HP Mixer", "Right DAC2", "DACR2"},
{"Left HP Mixer", "MIC1", "Mic Bias"},
{"Left HP Mixer", "MIC2", "Mic Bias"},
This looks wrong - I'd expect this to be switching the microphone input to the mixer, not the bias. Mixing the bias in would just present a DC level which isn't going to be great. Similarly for all your other microphone input switching.
I'll look at switching the microphone input like you suggested. Thanks. No worries, when turning on the mic bias, the hardware will not add DC into the digital samples. It's handled well.
/* DAI clock master/slave wrt the codec */
switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
You should be using the masks for your switch statements. Using specific values is at best harder to read and at worst will lead to false positives. This applies to all your comparisons in this function.
This method was inherited from another released driver. We will adopt the new method.
/* BCI: normal bclk (rise) */
/* WCI: invert frame */
snd_soc_update_bits(codec,
M98088_REG_14_DAI1_FORMAT,
M98088_DAI_BCI, M98088_DAI_WCI);
break;
case SND_SOC_DAIFMT_IB_NF:
/* BCI: invert bclk (fall) */
/* WCI: normal frame */
snd_soc_update_bits(codec,
M98088_REG_14_DAI1_FORMAT,
M98088_DAI_WCI, M98088_DAI_BCI);
This code (and most of the other update_bits() usages round here) looks wrong - I'd expect to see a constant bitmask being supplied as the mask argument then the value changing to set different values.
The M98088_DAI_BCI and M98088_DAI_WCI are bit field masks. Is this what you expect? If not, could you clarify? Thanks!
max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
(1<<6) | /* SEL : map DAI1 to S1 */
(0<<5) | /* LTEN : ADC->DAC loop-through enable
*/
(0<<4) | /* LBEN : loopback (0=disable,
1=enable) */
(0<<3) | /* DMONO : DAC SDIN (0=stereo, 1=mono)
*/
These look like things that should be configured by the user, especially LTEN.
(0<<2) | /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive)
*/
(1<<1) | /* SDOEN : serial data out ENABLE */
(1<<0)); /* SDIEN : serial data in ENABLE */
These look like they ought to be DAPM widgets for the AIF.
Good idea. Which widget(s) will drive SDOEN, or SDIEN?
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
int clk_id, unsigned int freq, int
dir)
+{
struct snd_soc_codec *codec = dai->codec;
struct max98088_priv *max98088 = codec->private_data;
if (freq != max98088->sysclk) {
It would be clearer to just return in this case so you don't have so much indentation.
Good point. Thanks for this suggestion on the source indentation.
/* If codec is currently running, toggle reset */
if (max98088_read(codec, M98088_REG_51_PWR_SYS)
& M98088_SHDNRUN) {
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN, 0);
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
0, M98088_SHDNRUN);
Will this reset the whole chip?
It's not a chip reset. I will correct the comments to make it clearer. Thanks.
+static inline int rate_index(int rate) +{
int i;
for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
if (rate_table[i].rate >= rate)
return i;
}
return 0;
This won't indicate if there's a failure to find a match.
You are right. This will default to the hardware default setting of 0 if the sample rate specified by the user were invalid. I'll take a look this.
/* data 16/24 bit width */
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
/* WS: 16bit */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
M98088_DAI_WS, 0);
break;
case SNDRV_PCM_FORMAT_S24_LE:
/* WS: 24bit */
snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
0, M98088_DAI_WS);
break;
}
Again, your snd_soc_update_bits() usage seems confused. You're also not checking for invalid inputs. There are other instances of this in the driver, I'll not comment them all.
It uses the bit field mask to control the desired bits.
snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
0);
if (rate != cdata->rate) {
/* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
max98088_write(codec, M98088_REG_11_DAI1_CLKMODE,
reg11val);
cdata->rate = rate;
Just directly use snd_soc_update_bits() - it will suppress null changes.
Good idea.
/* Configure NI when operating as master */
if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
& M98088_DAI_MAS) {
BUG_ON(max98088->sysclk == 0);
BUG_ON() is unfriendly here - this is a machine driver configuration error and normally BUG_ON() would be an internal error in the driver. Better error reporting please.
Ok
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
switch (level) {
case SND_SOC_BIAS_ON:
max98088_write(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN);
break;
case SND_SOC_BIAS_PREPARE:
max98088_write(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN);
break;
Drop the _ON cacse if it's the same. Also, how will this interact with the management of SHDNRUN in hw_params()?
Ok
case SND_SOC_BIAS_STANDBY:
max98088_sync_cache(codec);
max98088_write(codec, M98088_REG_51_PWR_SYS,
M98088_SHDNRUN|M98088_PWRSV);
break;
Do you really want to sync the cache every time we drop into standby?
Good point.
/* Configure digital mic / external mic */
/* Digital mic needs REG_15 OSR1=1 */
regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
(0<<5) | /* DIGMICL enable */
(0<<4) | /* DIGMICR enable */
(0<<0)); /* external mic enable */
These look like they ought to be managed via DAPM?
On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
On Wed, Sep 01, 2010, Mark Brown wrote:
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
Thanks for the git location you provided. What version of Linux kernel does this git/broonie/sound-2.6.git version correspond to? Is it newer than even the latest kernel-next?
This is what's going into -next, it will often be trivially ahead of -next with the patches addded since -next was last built but not meaningfully so.
Some of the ASoC API functions and features in the latest version of the kernel you require us to use are not in the popular stable kernel version 2.6.32 that people are using today to build products. Our intent is to release driver code that will work in both the current and latest kernel versions.
This is not how Linux development works, and in many cases will be impossible as internal APIs are frequently removed from the Linux kernel or changed incompatibly.
Would it not be a good idea to use the API functions that exist in both the very latest and the currently popular kernel version?
Like I say this is just not possible in many cases.
Does kernel.org provide a channel for releasing codec drivers that can be backward compatible to a popular earlier kernel version?
No, and it's not clear that a single approach can be adopted since newer devices often drive enhancements to the core APIs so the approaches that work best to integrate with older kernels vary depending on the particular devices and kernel versions involved.
/* Bypass option for INA to MIC1 connection
* 0 = normal setting
* 1 = bypass enabled
*/
unsigned int ina_to_mic1_bypass;
/* Bypass option for MIC1 to MIC2 connection
What are these - they sound like something I'd expect to be configurable at runtime?
Please engage with questions like this.
+/*
- Read the MAX98088 I2C register space
- Note: this driver source code is backward compatible to kernel
- version 2.6.32.
- */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
unsigned int reg)
Just use soc-cache. Compatibility code for older kernels isn't really acceptable for mainline due to the increased maintainance burden and it will at best result in a driver that doesn't work as well as possible.
This integrated registers feature is not available in 2.6.32 Is it ok with you to keep it this way for this release? Thanks.
No, you should use the features of the current kernel. For your backport you can do things like supply soc-cache.c as well.
client = (struct i2c_client *)codec->control_data;
No need to cast away from void.
Ok will do - but no harm in being informative :)
There is actually - if you have a cast then if you have done something broken by mistake the compiler is much less likely to generate a warning since the cast says you know what you're doing.
"user-400Hz",
"user-600Hz",
"user-800Hz",
"user-1000Hz"
These user options look very suspicous.
The first 8 options are settings pre-defined in hardware that the user can choose from. The last 4 are user programmable options, and thus the name 'user'.
What happens if the user didn't supply any programmable options? Would it not be better to allow the user to specify meaningful text here?
SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
Capitalise switch.
I noticed that by capitalizing the first letter of the 2nd word, the 2nd word is dropped out by the system when amixer lists them. "EQ1 Switch" becomes just "EQ1", while "EQ1 switch" is "EQ1 switch". Have you seen this behavior before? Is this a known issue with some version of amixer or ASoC code base?
This is not a problem, this is intended behaviour. ALSA applications interpret the control names and use this naming information to present an appropriate UI to users.
+static const struct snd_soc_dapm_route audio_map[] = {
/* Left headphone output mixer */
{"Left HP Mixer", "Left DAC1", "DACL1"},
{"Left HP Mixer", "Left DAC2", "DACL2"},
{"Left HP Mixer", "Right DAC1", "DACR1"},
{"Left HP Mixer", "Right DAC2", "DACR2"},
{"Left HP Mixer", "MIC1", "Mic Bias"},
{"Left HP Mixer", "MIC2", "Mic Bias"},
This looks wrong - I'd expect this to be switching the microphone input to the mixer, not the bias. Mixing the bias in would just present a DC level which isn't going to be great. Similarly for all your other microphone input switching.
I'll look at switching the microphone input like you suggested. Thanks. No worries, when turning on the mic bias, the hardware will not add DC into the digital samples. It's handled well.
As I said I rather suspect that this is because the microphone input is being switched into the path rather than the microphone bias.
/* BCI: normal bclk (rise) */
/* WCI: invert frame */
snd_soc_update_bits(codec,
M98088_REG_14_DAI1_FORMAT,
M98088_DAI_BCI, M98088_DAI_WCI);
break;
case SND_SOC_DAIFMT_IB_NF:
/* BCI: invert bclk (fall) */
/* WCI: normal frame */
snd_soc_update_bits(codec,
M98088_REG_14_DAI1_FORMAT,
M98088_DAI_WCI, M98088_DAI_BCI);
This code (and most of the other update_bits() usages round here) looks wrong - I'd expect to see a constant bitmask being supplied as the mask argument then the value changing to set different values.
The M98088_DAI_BCI and M98088_DAI_WCI are bit field masks. Is this what you expect? If not, could you clarify? Thanks!
As I said above I'd expect to see a constant mask being supplied and varying values. Could you please explain what you think the code above does?
(0<<2) | /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive)
*/
(1<<1) | /* SDOEN : serial data out ENABLE */
(1<<0)); /* SDIEN : serial data in ENABLE */
These look like they ought to be DAPM widgets for the AIF.
Good idea. Which widget(s) will drive SDOEN, or SDIEN?
The AIF widgets.
On Fri, Sep 03, 2010, Mark Brown wrote:
On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
On Wed, Sep 01, 2010, Mark Brown wrote:
+/*
- Read the MAX98088 I2C register space
- Note: this driver source code is backward compatible to kernel
- version 2.6.32.
- */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
unsigned int reg)
Just use soc-cache. Compatibility code for older kernels isn't really acceptable for mainline due to the increased maintainance burden and it will at best result in a driver that doesn't work as well as possible.
This integrated registers feature is not available in 2.6.32 Is it ok with you to keep it this way for this release? Thanks.
No, you should use the features of the current kernel. For your backport you can do things like supply soc-cache.c as well.
The soc-cache.c in the latest kernel-next version supports codecs with volatile registers in 16 bit mode, but not for the 8 bit mode. See snd_soc_8_8_write.
The source code comments confirmed this: "Note at present this code can not be used by CODECs with volatile registers."
For this codec (with volatile registers), should we use the read and write functions in the codec driver until the 8-bit mode is supported by Linux soc-cache.c?
On Tue, Sep 21, 2010 at 07:49:30PM -0700, Peter Hsiang wrote:
On Fri, Sep 03, 2010, Mark Brown wrote:
No, you should use the features of the current kernel. For your backport you can do things like supply soc-cache.c as well.
The soc-cache.c in the latest kernel-next version supports codecs with volatile registers in 16 bit mode, but not for the 8 bit mode. See snd_soc_8_8_write.
The source code comments confirmed this: "Note at present this code can not be used by CODECs with volatile registers."
For this codec (with volatile registers), should we use the read and write functions in the codec driver until the 8-bit mode is supported by Linux soc-cache.c?
You should modify soc-cache.c to support volatile registers if you need this; it's not been implemented because nobody needed it yet (someone may get to it before you get round to resubmitting, but if they haven't then just extend the core functionality).
participants (2)
-
Mark Brown
-
Peter Hsiang