Alsa-devel
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
October 2011
- 82 participants
- 323 discussions
26 Oct '11
This driver implements basic functionality, using I²C for the control channel.
Signed-off-by: Leon Romanovsky <leon(a)leon.nu>
---
v2: Free from checkpatch warnings
v1: Initial code drop
---
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/alc5632.c | 1190 ++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/alc5632.h | 243 +++++++++
4 files changed, 1439 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/alc5632.c
create mode 100644 sound/soc/codecs/alc5632.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4584514..e78ece8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -26,6 +26,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_ALC5623 if I2C
+ select SND_SOC_ALC5632 if I2C
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
@@ -169,6 +170,9 @@ config SND_SOC_AK4671
config SND_SOC_ALC5623
tristate
+config SND_SOC_ALC5632
+ tristate
+
config SND_SOC_CQ0093VC
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a2c7842..98bf1b6 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -29,6 +29,7 @@ snd-soc-pcm3008-objs := pcm3008.o
snd-soc-rt5631-objs := rt5631.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
+snd-soc-alc5632-objs := alc5632.0
snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
@@ -113,6 +114,7 @@ obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
+obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c
new file mode 100644
index 0000000..96d15b0
--- /dev/null
+++ b/sound/soc/codecs/alc5632.c
@@ -0,0 +1,1190 @@
+/*
+* alc5632.c -- ALC5632 ALSA SoC Audio Codec
+*
+* Copyright (C) 2011 The AC100 Kernel Team <ac100(a)lists.lauchpad.net>
+*
+* Authors: Leon Romanovsky <leon(a)leon.nu>
+* Andrey Danin <danindrey(a)mail.ru>
+* Ilya Petrov <ilya.muromec(a)gmail.com>
+* Marc Dietrich <marvin24(a)gmx.de>
+*
+* Based on alc5623.c by Arnaud Patard
+*
+* 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.
+*/
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/alc5632.h>
+
+#include "alc5632.h"
+
+#define ALC5632_VERSION "0.1"
+
+/*
+ * ALC5632 register cache
+ */
+static const u16 alc5632_reg_defaults[] = {
+ 0x59B4, 0x0000, 0x8080, 0x0000, /* 0 */
+ 0x8080, 0x0000, 0x8080, 0x0000, /* 4 */
+ 0xC800, 0x0000, 0xE808, 0x0000, /* 8 */
+ 0x1010, 0x0000, 0x0808, 0x0000, /* 12 */
+ 0xEE0F, 0x0000, 0xCBCB, 0x0000, /* 16 */
+ 0x7F7F, 0x0000, 0x0000, 0x0000, /* 20 */
+ 0xE010, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x8008, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 32 */
+ 0x00C0, 0x0000, 0xEF00, 0x0000, /* 36 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 40 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 44 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 48 */
+ 0x8000, 0x0000, 0x0000, 0x0000, /* 52 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+ 0x0000, 0x0000, 0x8000, 0x0000, /* 60 */
+ 0x0C0A, 0x0000, 0x0000, 0x0000, /* 64 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 68 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 72 */
+ 0xBE3E, 0x0000, 0xBE3E, 0x0000, /* 76 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 80 */
+ 0x803A, 0x0000, 0x0000, 0x0000, /* 84 */
+ 0x0000, 0x0000, 0x0009, 0x0000, /* 88 */
+ 0x0000, 0x0000, 0x3000, 0x0000, /* 92 */
+ 0x3075, 0x0000, 0x1010, 0x0000, /* 96 */
+ 0x3110, 0x0000, 0x0000, 0x0000, /* 100 */
+ 0x0553, 0x0000, 0x0000, 0x0000, /* 104 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 108 */
+};
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "ALC5632 cap charge time (msecs)");
+
+/* codec private data */
+struct alc5632_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ struct mutex mutex;
+ u8 id;
+ unsigned int sysclk;
+ unsigned int add_ctrl;
+ unsigned int jack_det_ctrl;
+};
+
+static int alc5632_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case ALC5632_RESET:
+ case ALC5632_PWR_DOWN_CTRL_STATUS:
+ case ALC5632_GPIO_PIN_STATUS:
+ case ALC5632_OVER_CURR_STATUS:
+ case ALC5632_HID_CTRL_DATA:
+ case ALC5632_EQ_CTRL:
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline int alc5632_reset(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, ALC5632_RESET, 0);
+ return snd_soc_read(codec, ALC5632_RESET);
+}
+
+static int amp_mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ /* to power-on/off class-d amp generators/speaker */
+ /* need to write to 'index-46h' register : */
+ /* so write index num (here 0x46) to reg 0x6a */
+ /* and then 0xffff/0 to reg 0x6c */
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_INDEX, 0x46);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_DATA, 0xFFFF);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_DATA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * ALC5632 Controls
+ */
+
+/* -34.5db min scale, 1.5db steps, no mute */
+static const DECLARE_TLV_DB_SCALE(vol_tlv, -3450, 150, 0);
+/* -46.5db min scale, 1.5db steps, no mute */
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -4650, 150, 0);
+/* -16.5db min scale, 1.5db steps, no mute */
+static const DECLARE_TLV_DB_SCALE(adc_rec_tlv, -1650, 150, 0);
+static const unsigned int boost_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+/* 0db min scale, 6 db steps, no mute */
+static const DECLARE_TLV_DB_SCALE(dig_tlv, 0, 600, 0);
+/* 0db min scalem 0.75db steps, no mute */
+static const DECLARE_TLV_DB_SCALE(vdac_tlv, -3525, 075, 0);
+
+static const struct snd_kcontrol_new alc5632_vol_snd_controls[] = {
+ /* left starts at bit 8, right at bit 0 */
+ /* 31 steps (5 bit), -46.5db scale */
+ SOC_DOUBLE_TLV("Line Playback Volume",
+ ALC5632_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ /* bit 15 mutes left, bit 7 right */
+ SOC_DOUBLE("Line Playback Switch",
+ ALC5632_SPK_OUT_VOL, 15, 7, 1, 1),
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ ALC5632_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Headphone Playback Switch",
+ ALC5632_HP_OUT_VOL, 15, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_snd_controls[] = {
+ SOC_DOUBLE_TLV("Auxout Playback Volume",
+ ALC5632_AUX_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Auxout Playback Switch",
+ ALC5632_AUX_OUT_VOL, 15, 7, 1, 1),
+ SOC_SINGLE_TLV("Voice DAC Playback Volume",
+ ALC5632_VOICE_DAC_VOL, 0, 63, 0, vdac_tlv),
+ SOC_SINGLE_TLV("Phone Capture Volume",
+ ALC5632_PHONE_IN_VOL, 8, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("LineIn Capture Volume",
+ ALC5632_LINE_IN_VOL, 8, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("Stereo DAC Capture Volume",
+ ALC5632_STEREO_DAC_IN_VOL, 8, 0, 63, 1, vdac_tlv),
+ SOC_DOUBLE("Stereo DAC Capture Switch",
+ ALC5632_STEREO_DAC_IN_VOL, 15, 7, 1, 1),
+ SOC_SINGLE_TLV("Mic1 Capture Volume",
+ ALC5632_MIC_VOL, 8, 31, 1, vol_tlv),
+ SOC_SINGLE_TLV("Mic2 Capture Volume",
+ ALC5632_MIC_VOL, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("Rec Capture Volume",
+ ALC5632_ADC_REC_GAIN, 8, 0, 31, 0, adc_rec_tlv),
+ SOC_SINGLE_TLV("Mic 1 Boost Volume",
+ ALC5632_MIC_CTRL, 10, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Mic 2 Boost Volume",
+ ALC5632_MIC_CTRL, 8, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Digital Boost Volume",
+ ALC5632_DIGI_BOOST_CTRL, 0, 7, 0, dig_tlv),
+};
+
+/*
+ * DAPM Controls
+ */
+static const struct snd_kcontrol_new alc5632_hp_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2HP Playback Switch", ALC5632_LINE_IN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("PHONE2HP Playback Switch", ALC5632_PHONE_IN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC12HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC22HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 11, 1, 1),
+SOC_DAPM_SINGLE("DACL2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 3, 1, 1),
+SOC_DAPM_SINGLE("DACR2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 2, 1, 1),
+SOC_DAPM_SINGLE("VOICE2HP Playback Switch", ALC5632_VOICE_DAC_VOL, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_L Playback Switch", ALC5632_ADC_REC_GAIN, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_R Playback Switch", ALC5632_ADC_REC_GAIN, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2MONO_L Playback Switch", ALC5632_ADC_REC_GAIN, 14, 1, 1),
+SOC_DAPM_SINGLE("ADC2MONO_R Playback Switch", ALC5632_ADC_REC_GAIN, 6, 1, 1),
+SOC_DAPM_SINGLE("LI2MONO Playback Switch", ALC5632_LINE_IN_VOL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC12MONO Playback Switch",
+ ALC5632_MIC_ROUTING_CTRL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC22MONO Playback Switch",
+ ALC5632_MIC_ROUTING_CTRL, 9, 1, 1),
+SOC_DAPM_SINGLE("DAC2MONO Playback Switch", ALC5632_MIC_ROUTING_CTRL, 0, 1, 1),
+SOC_DAPM_SINGLE("VOICE2MONO Playback Switch", ALC5632_VOICE_DAC_VOL, 13, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2SPK Playback Switch", ALC5632_LINE_IN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("PHONE2SPK Playback Switch", ALC5632_PHONE_IN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC12SPK Playback Switch",
+ ALC5632_MIC_ROUTING_CTRL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC22SPK Playback Switch",
+ ALC5632_MIC_ROUTING_CTRL, 10, 1, 1),
+SOC_DAPM_SINGLE("DAC2SPK Playback Switch", ALC5632_MIC_ROUTING_CTRL, 1, 1, 1),
+SOC_DAPM_SINGLE("VOICE2SPK Playback Switch", ALC5632_VOICE_DAC_VOL, 14, 1, 1),
+};
+
+/* Left Record Mixer */
+static const struct snd_kcontrol_new alc5632_captureL_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5632_ADC_REC_MIXER, 14, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5632_ADC_REC_MIXER, 13, 1, 1),
+SOC_DAPM_SINGLE("LineInL Capture Switch", ALC5632_ADC_REC_MIXER, 12, 1, 1),
+SOC_DAPM_SINGLE("Left Phone Capture Switch", ALC5632_ADC_REC_MIXER, 11, 1, 1),
+SOC_DAPM_SINGLE("HPMixerL Capture Switch", ALC5632_ADC_REC_MIXER, 10, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5632_ADC_REC_MIXER, 9, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5632_ADC_REC_MIXER, 8, 1, 1),
+};
+
+/* Right Record Mixer */
+static const struct snd_kcontrol_new alc5632_captureR_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5632_ADC_REC_MIXER, 6, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5632_ADC_REC_MIXER, 5, 1, 1),
+SOC_DAPM_SINGLE("LineInR Capture Switch", ALC5632_ADC_REC_MIXER, 4, 1, 1),
+SOC_DAPM_SINGLE("Right Phone Capture Switch", ALC5632_ADC_REC_MIXER, 3, 1, 1),
+SOC_DAPM_SINGLE("HPMixerR Capture Switch", ALC5632_ADC_REC_MIXER, 2, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5632_ADC_REC_MIXER, 1, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5632_ADC_REC_MIXER, 0, 1, 1),
+};
+
+static const char *alc5632_spk_n_sour_sel[] = {
+ "RN/-R", "RP/+R", "LN/-R", "Mute"};
+static const char *alc5632_hpl_out_input_sel[] = {
+ "Vmid", "HP Left Mix"};
+static const char *alc5632_hpr_out_input_sel[] = {
+ "Vmid", "HP Right Mix"};
+static const char *alc5632_spkout_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+static const char *alc5632_aux_out_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+
+/* auxout output mux */
+static const struct soc_enum alc5632_aux_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 6, 4, alc5632_aux_out_input_sel);
+static const struct snd_kcontrol_new alc5632_auxout_mux_controls =
+SOC_DAPM_ENUM("AuxOut Mux", alc5632_aux_out_input_enum);
+
+/* speaker output mux */
+static const struct soc_enum alc5632_spkout_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 10, 4, alc5632_spkout_input_sel);
+static const struct snd_kcontrol_new alc5632_spkout_mux_controls =
+SOC_DAPM_ENUM("SpeakerOut Mux", alc5632_spkout_input_enum);
+
+/* headphone left output mux */
+static const struct soc_enum alc5632_hpl_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 9, 2, alc5632_hpl_out_input_sel);
+static const struct snd_kcontrol_new alc5632_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Left Headphone Mux", alc5632_hpl_out_input_enum);
+
+/* headphone right output mux */
+static const struct soc_enum alc5632_hpr_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 8, 2, alc5632_hpr_out_input_sel);
+static const struct snd_kcontrol_new alc5632_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Right Headphone Mux", alc5632_hpr_out_input_enum);
+
+/* speaker output N select */
+static const struct soc_enum alc5632_spk_n_sour_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 14, 4, alc5632_spk_n_sour_sel);
+static const struct snd_kcontrol_new alc5632_spkoutn_mux_controls =
+SOC_DAPM_ENUM("SpeakerOut N Mux", alc5632_spk_n_sour_enum);
+
+static const struct snd_soc_dapm_widget alc5632_dapm_widgets[] = {
+/* Muxes */
+SND_SOC_DAPM_MUX("AuxOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_auxout_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_spkout_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut N Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_spkoutn_mux_controls),
+
+/* output mixers */
+SND_SOC_DAPM_MIXER("HP Mix", SND_SOC_NOPM, 0, 0,
+ &alc5632_hp_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hp_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPR Mix", ALC5632_PWR_MANAG_ADD2, 4, 0,
+ &alc5632_hpr_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hpr_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPL Mix", ALC5632_PWR_MANAG_ADD2, 5, 0,
+ &alc5632_hpl_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hpl_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPOut Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Mono Mix", ALC5632_PWR_MANAG_ADD2, 2, 0,
+ &alc5632_mono_mixer_controls[0],
+ ARRAY_SIZE(alc5632_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mix", ALC5632_PWR_MANAG_ADD2, 3, 0,
+ &alc5632_speaker_mixer_controls[0],
+ ARRAY_SIZE(alc5632_speaker_mixer_controls)),
+
+/* input mixers */
+SND_SOC_DAPM_MIXER("Left Capture Mix", ALC5632_PWR_MANAG_ADD2, 1, 0,
+ &alc5632_captureL_mixer_controls[0],
+ ARRAY_SIZE(alc5632_captureL_mixer_controls)),
+SND_SOC_DAPM_MIXER("Right Capture Mix", ALC5632_PWR_MANAG_ADD2, 0, 0,
+ &alc5632_captureR_mixer_controls[0],
+ ARRAY_SIZE(alc5632_captureR_mixer_controls)),
+
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ ALC5632_PWR_MANAG_ADD2, 9, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ ALC5632_PWR_MANAG_ADD2, 8, 0),
+SND_SOC_DAPM_MIXER("I2S Mix", ALC5632_PWR_MANAG_ADD1, 11, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Phone Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+ ALC5632_PWR_MANAG_ADD2, 7, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+ ALC5632_PWR_MANAG_ADD2, 6, 0),
+SND_SOC_DAPM_PGA("Left Headphone", ALC5632_PWR_MANAG_ADD3, 11, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", ALC5632_PWR_MANAG_ADD3, 10, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", ALC5632_PWR_MANAG_ADD3, 13, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", ALC5632_PWR_MANAG_ADD3, 12, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Aux Out", ALC5632_PWR_MANAG_ADD3, 14, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left LineIn", ALC5632_PWR_MANAG_ADD3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right LineIn", ALC5632_PWR_MANAG_ADD3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Phone", ALC5632_PWR_MANAG_ADD3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Phone ADMix", ALC5632_PWR_MANAG_ADD3, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 PGA", ALC5632_PWR_MANAG_ADD3, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 PGA", ALC5632_PWR_MANAG_ADD3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 Pre Amp", ALC5632_PWR_MANAG_ADD3, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 Pre Amp", ALC5632_PWR_MANAG_ADD3, 0, 0, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias1", ALC5632_PWR_MANAG_ADD1, 3, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias2", ALC5632_PWR_MANAG_ADD1, 2, 0),
+
+SND_SOC_DAPM_OUTPUT("AUXOUT"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONEP"),
+SND_SOC_DAPM_INPUT("PHONEN"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("Vmid"),
+};
+
+static const char *alc5632_amp_names[] = {"AB Amp", "D Amp"};
+static const struct soc_enum alc5632_amp_enum =
+ SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 13, 2, alc5632_amp_names);
+static const struct snd_kcontrol_new alc5632_amp_mux_controls =
+ SOC_DAPM_ENUM("Route", alc5632_amp_enum);
+
+static const struct snd_soc_dapm_widget alc5632_dapm_amp_widgets[] = {
+SND_SOC_DAPM_PGA_E("D Amp", ALC5632_PWR_MANAG_ADD2, 14, 0, NULL, 0,
+ amp_mixer_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA("AB Amp", ALC5632_PWR_MANAG_ADD2, 15, 0, NULL, 0),
+SND_SOC_DAPM_MUX("AB-D Amp Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_amp_mux_controls),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* virtual mixer - mixes left & right channels */
+ {"I2S Mix", NULL, "Left DAC"},
+ {"I2S Mix", NULL, "Right DAC"},
+ {"Line Mix", NULL, "Right LineIn"},
+ {"Line Mix", NULL, "Left LineIn"},
+ {"Phone Mix", NULL, "Phone"},
+ {"Phone Mix", NULL, "Phone ADMix"},
+ {"AUXOUT", NULL, "Aux Out"},
+
+ /* HP mixer */
+ {"HPL Mix", "ADC2HP_L Playback Switch", "Left Capture Mix"},
+ {"HPL Mix", NULL, "HP Mix"},
+ {"HPR Mix", "ADC2HP_R Playback Switch", "Right Capture Mix"},
+ {"HPR Mix", NULL, "HP Mix"},
+ {"HP Mix", "LI2HP Playback Switch", "Line Mix"},
+ {"HP Mix", "PHONE2HP Playback Switch", "Phone Mix"},
+ {"HP Mix", "MIC12HP Playback Switch", "MIC1 PGA"},
+ {"HP Mix", "MIC22HP Playback Switch", "MIC2 PGA"},
+
+ {"HP Mix", "DACR2HP Playback Switch", "I2S Mix"},
+ {"HP Mix", "DACL2HP Playback Switch", "I2S Mix"},
+
+
+ /* speaker mixer */
+ {"Speaker Mix", "LI2SPK Playback Switch", "Line Mix"},
+ {"Speaker Mix", "PHONE2SPK Playback Switch", "Phone Mix"},
+ {"Speaker Mix", "MIC12SPK Playback Switch", "MIC1 PGA"},
+ {"Speaker Mix", "MIC22SPK Playback Switch", "MIC2 PGA"},
+ {"Speaker Mix", "DAC2SPK Playback Switch", "I2S Mix"},
+
+ /* mono mixer */
+ {"Mono Mix", "ADC2MONO_L Playback Switch", "Left Capture Mix"},
+ {"Mono Mix", "ADC2MONO_R Playback Switch", "Right Capture Mix"},
+ {"Mono Mix", "LI2MONO Playback Switch", "Line Mix"},
+ {"Mono Mix", "VOICE2MONO Playback Switch", "Phone Mix"},
+ {"Mono Mix", "MIC12MONO Playback Switch", "MIC1 PGA"},
+ {"Mono Mix", "MIC22MONO Playback Switch", "MIC2 PGA"},
+ {"Mono Mix", "DAC2MONO Playback Switch", "I2S Mix"},
+
+ /* Left record mixer */
+ {"Left Capture Mix", "LineInL Capture Switch", "LINEINL"},
+ {"Left Capture Mix", "Left Phone Capture Switch", "PHONEN"},
+ {"Left Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Left Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Left Capture Mix", "HPMixerL Capture Switch", "HPL Mix"},
+ {"Left Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Left Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /*Right record mixer */
+ {"Right Capture Mix", "LineInR Capture Switch", "LINEINR"},
+ {"Right Capture Mix", "Right Phone Capture Switch", "PHONEP"},
+ {"Right Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Right Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Right Capture Mix", "HPMixerR Capture Switch", "HPR Mix"},
+ {"Right Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Right Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /* headphone left mux */
+ {"Left Headphone Mux", "HP Left Mix", "HPL Mix"},
+ {"Left Headphone Mux", "Vmid", "Vmid"},
+
+ /* headphone right mux */
+ {"Right Headphone Mux", "HP Right Mix", "HPR Mix"},
+ {"Right Headphone Mux", "Vmid", "Vmid"},
+
+ /* speaker out mux */
+ {"SpeakerOut Mux", "Vmid", "Vmid"},
+ {"SpeakerOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"SpeakerOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"SpeakerOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* Mono/Aux Out mux */
+ {"AuxOut Mux", "Vmid", "Vmid"},
+ {"AuxOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"AuxOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"AuxOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Mux"},
+ {"Aux Out", NULL, "AuxOut Mux"},
+
+ /* input pga */
+ {"Left LineIn", NULL, "LINEINL"},
+ {"Right LineIn", NULL, "LINEINR"},
+ {"Phone", NULL, "PHONEP"},
+ {"MIC1 Pre Amp", NULL, "MIC1"},
+ {"MIC2 Pre Amp", NULL, "MIC2"},
+ {"MIC1 PGA", NULL, "MIC1 Pre Amp"},
+ {"MIC2 PGA", NULL, "MIC2 Pre Amp"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Mix"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Mix"},
+
+ {"SpeakerOut N Mux", "RN/-R", "Left Speaker"},
+ {"SpeakerOut N Mux", "RP/+R", "Left Speaker"},
+ {"SpeakerOut N Mux", "LN/-R", "Left Speaker"},
+ {"SpeakerOut N Mux", "Mute", "Vmid"},
+
+ {"SpeakerOut N Mux", "RN/-R", "Right Speaker"},
+ {"SpeakerOut N Mux", "RP/+R", "Right Speaker"},
+ {"SpeakerOut N Mux", "LN/-R", "Right Speaker"},
+ {"SpeakerOut N Mux", "Mute", "Vmid"},
+
+
+ {"SPKOUT", NULL, "Left Speaker"},
+ {"SPKOUT", NULL, "Right Speaker"},
+
+ {"SPKOUTN", NULL, "SpeakerOut N Mux"},
+};
+
+static const struct snd_soc_dapm_route intercon_spk[] = {
+ {"Right Speaker", NULL, "SpeakerOut Mux"},
+ {"Left Speaker", NULL, "SpeakerOut Mux"},
+
+};
+
+static const struct snd_soc_dapm_route intercon_amp_spk[] = {
+ {"AB Amp", NULL, "SpeakerOut Mux"},
+ {"D Amp", NULL, "SpeakerOut Mux"},
+ {"AB-D Amp Mux", "AB Amp", "AB Amp"},
+ {"AB-D Amp Mux", "D Amp", "D Amp"},
+ {"SpeakerOut", NULL, "AB-D Amp Mux"},
+};
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pll_in;
+ u32 pll_out;
+ u16 regvalue;
+};
+
+/* Note : pll code from original alc5632 driver. Not sure of how good it is */
+/* usefull only for master mode */
+static const struct _pll_div codec_master_pll_div[] = {
+
+ { 2048000, 8192000, 0x0ea0},
+ { 3686400, 8192000, 0x4e27},
+ { 12000000, 8192000, 0x456b},
+ { 13000000, 8192000, 0x495f},
+ { 13100000, 8192000, 0x0320},
+ { 2048000, 11289600, 0xf637},
+ { 3686400, 11289600, 0x2f22},
+ { 12000000, 11289600, 0x3e2f},
+ { 13000000, 11289600, 0x4d5b},
+ { 13100000, 11289600, 0x363b},
+ { 2048000, 16384000, 0x1ea0},
+ { 3686400, 16384000, 0x9e27},
+ { 12000000, 16384000, 0x452b},
+ { 13000000, 16384000, 0x542f},
+ { 13100000, 16384000, 0x03a0},
+ { 2048000, 16934400, 0xe625},
+ { 3686400, 16934400, 0x9126},
+ { 12000000, 16934400, 0x4d2c},
+ { 13000000, 16934400, 0x742f},
+ { 13100000, 16934400, 0x3c27},
+ { 2048000, 22579200, 0x2aa0},
+ { 3686400, 22579200, 0x2f20},
+ { 12000000, 22579200, 0x7e2f},
+ { 13000000, 22579200, 0x742f},
+ { 13100000, 22579200, 0x3c27},
+ { 2048000, 24576000, 0x2ea0},
+ { 3686400, 24576000, 0xee27},
+ { 12000000, 24576000, 0x2915},
+ { 13000000, 24576000, 0x772e},
+ { 13100000, 24576000, 0x0d20},
+};
+
+/* FOUT = MCLK*(N+2)/((M+2)*(K+2))
+ N: bit 15:8 (div 2 .. div 257)
+ K: bit 6:4 typical 2
+ M: bit 3:0 (div 2 .. div 17)
+
+ same as for 5623 - thanks!
+*/
+
+static const struct _pll_div codec_slave_pll_div[] = {
+
+ { 1024000, 16384000, 0x3ea0},
+ { 1411200, 22579200, 0x3ea0},
+ { 1536000, 24576000, 0x3ea0},
+ { 2048000, 16384000, 0x1ea0},
+ { 2822400, 22579200, 0x1ea0},
+ { 3072000, 24576000, 0x1ea0},
+
+};
+
+static int alc5632_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ int i;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int gbl_clk = 0, pll_div = 0;
+ u16 reg;
+
+ if (pll_id < ALC5632_PLL_FR_MCLK || pll_id > ALC5632_PLL_FR_VBCLK)
+ return -ENODEV;
+
+ /* Disable PLL power */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL1,
+ 0);
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL2,
+ 0);
+
+ /* pll is not used in slave mode */
+ reg = snd_soc_read(codec, ALC5632_DAI_CONTROL);
+ if (reg & ALC5632_DAI_SDP_SLAVE_MODE)
+ return 0;
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ switch (pll_id) {
+ case ALC5632_PLL_FR_MCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) {
+ if (codec_master_pll_div[i].pll_in == freq_in
+ && codec_master_pll_div[i].pll_out == freq_out) {
+ /* PLL source from MCLK */
+ pll_div = codec_master_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ case ALC5632_PLL_FR_BCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) {
+ if (codec_slave_pll_div[i].pll_in == freq_in
+ && codec_slave_pll_div[i].pll_out == freq_out) {
+ /* PLL source from Bitclk */
+ gbl_clk = ALC5632_PLL_FR_BCLK;
+ pll_div = codec_slave_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ case ALC5632_PLL_FR_VBCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) {
+ if (codec_slave_pll_div[i].pll_in == freq_in
+ && codec_slave_pll_div[i].pll_out == freq_out) {
+ /* PLL source from voice clock */
+ gbl_clk = ALC5632_PLL_FR_VBCLK;
+ pll_div = codec_slave_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!pll_div)
+ return -EINVAL;
+
+/* choose MCLK/BCLK/VBCLK */
+ snd_soc_write(codec, ALC5632_GPCR2, gbl_clk);
+/* choose PLL1 clock rate */
+ snd_soc_write(codec, ALC5632_PLL1_CTRL, pll_div);
+/* enable PLL1 */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL1,
+ ALC5632_PWR_ADD2_PLL1);
+/* enable PLL2 */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL2,
+ ALC5632_PWR_ADD2_PLL2);
+/* use PLL1 as main SYSCLK */
+ snd_soc_update_bits(codec, ALC5632_GPCR1,
+ ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1,
+ ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1);
+
+ return 0;
+}
+
+struct _coeff_div {
+ u16 fs;
+ u16 regvalue;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+/* values inspired from column BCLK=32Fs of Appendix A table */
+static const struct _coeff_div coeff_div[] = {
+ {512*1, 0x3075},
+};
+
+static int get_coeff(struct snd_soc_codec *codec, int rate)
+{
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].fs * rate == alc5632->sysclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int alc5632_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 8192000:
+ case 11289600:
+ case 12288000:
+ case 16384000:
+ case 16934400:
+ case 18432000:
+ case 22579200:
+ case 24576000:
+ alc5632->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int alc5632_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = ALC5632_DAI_SDP_MASTER_MODE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iface = ALC5632_DAI_SDP_SLAVE_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= ALC5632_DAI_I2S_DF_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= ALC5632_DAI_I2S_DF_LEFT;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= ALC5632_DAI_I2S_DF_PCM_A;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= ALC5632_DAI_I2S_DF_PCM_B;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, ALC5632_DAI_CONTROL, iface);
+}
+
+static int alc5632_pcm_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_codec *codec = rtd->codec;
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ int coeff, rate;
+ u16 iface;
+
+ iface = snd_soc_read(codec, ALC5632_DAI_CONTROL);
+ iface &= ~ALC5632_DAI_I2S_DL_MASK;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iface |= ALC5632_DAI_I2S_DL_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= ALC5632_DAI_I2S_DL_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= ALC5632_DAI_I2S_DL_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, ALC5632_DAI_CONTROL, iface);
+ rate = params_rate(params);
+ coeff = get_coeff(codec, rate);
+ if (coeff < 0)
+ return -EINVAL;
+
+ coeff = coeff_div[coeff].regvalue;
+ dev_dbg(codec->dev, "%s: sysclk=%d,rate=%d,coeff=0x%04x\n",
+ __func__, alc5632->sysclk, rate, coeff);
+ snd_soc_write(codec, ALC5632_DAC_CLK_CTRL1, coeff);
+
+ return 0;
+}
+
+static int alc5632_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 hp_mute = ALC5632_MISC_HP_DEPOP_MUTE_L \
+ |ALC5632_MISC_HP_DEPOP_MUTE_R;
+ u16 mute_reg = snd_soc_read(codec, ALC5632_MISC_CTRL) & ~hp_mute;
+
+ if (mute)
+ mute_reg |= hp_mute;
+
+ return snd_soc_write(codec, ALC5632_MISC_CTRL, mute_reg);
+}
+
+#define ALC5632_ADD2_POWER_EN (ALC5632_PWR_ADD2_VREF)
+
+#define ALC5632_ADD3_POWER_EN (ALC5632_PWR_ADD3_MIC1_BOOST_AD)
+
+#define ALC5632_ADD1_POWER_EN \
+ (ALC5632_PWR_ADD1_SPK_AMP_EN \
+ | ALC5632_PWR_ADD1_DAC_REF \
+ | ALC5632_PWR_ADD1_DAC_L_EN \
+ | ALC5632_PWR_ADD1_DAC_R_EN \
+ | ALC5632_PWR_ADD1_SOFTGEN_EN \
+ | ALC5632_PWR_ADD1_MAIN_I2S_EN \
+ | ALC5632_PWR_ADD1_HP_OUT_AMP \
+ | ALC5632_PWR_ADD1_HP_OUT_ENH_AMP \
+ | ALC5632_PWR_ADD1_MAIN_BIAS)
+
+static void enable_power_depop(struct snd_soc_codec *codec)
+{
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD1,
+ ALC5632_PWR_ADD1_SOFTGEN_EN,
+ ALC5632_PWR_ADD1_SOFTGEN_EN);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD3, ALC5632_ADD3_POWER_EN);
+
+ snd_soc_update_bits(codec, ALC5632_MISC_CTRL,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN);
+
+ msleep(500);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2, ALC5632_ADD2_POWER_EN);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1, ALC5632_ADD1_POWER_EN);
+
+ /* disable HP Depop2 */
+ snd_soc_update_bits(codec, ALC5632_MISC_CTRL,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN,
+ 0);
+
+}
+
+static int alc5632_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ enable_power_depop(codec);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1,
+ ALC5632_PWR_ADD1_MAIN_BIAS);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_VREF);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2, 0);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD3, 0);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define ALC5632_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+ | SNDRV_PCM_FMTBIT_S24_LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops alc5632_dai_ops = {
+ .hw_params = alc5632_pcm_hw_params,
+ .digital_mute = alc5632_mute,
+ .set_fmt = alc5632_set_dai_fmt,
+ .set_sysclk = alc5632_set_dai_sysclk,
+ .set_pll = alc5632_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver alc5632_dai = {
+ .name = "alc5632-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5632_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5632_FORMATS,},
+
+ .ops = &alc5632_dai_ops,
+};
+
+static int alc5632_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int alc5632_resume(struct snd_soc_codec *codec)
+{
+ int i, step = codec->driver->reg_cache_step;
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 2 ; i < codec->driver->reg_cache_size ; i += step)
+ snd_soc_write(codec, i, cache[i]);
+
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge alc5632 caps */
+ if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ codec->dapm.bias_level = SND_SOC_BIAS_ON;
+ alc5632_set_bias_level(codec, codec->dapm.bias_level);
+ }
+
+ return 0;
+}
+
+#define ALC5632_REC_UNMUTE (ALC5632_ADC_REC_MIC2 \
+ | ALC5632_ADC_REC_LINE_IN | ALC5632_ADC_REC_AUX \
+ | ALC5632_ADC_REC_HP | ALC5632_ADC_REC_SPK \
+ | ALC5632_ADC_REC_MONOMIX)
+
+#define ALC5632_MIC_ROUTE (ALC5632_MIC_ROUTE_HP \
+ | ALC5632_MIC_ROUTE_SPK \
+ | ALC5632_MIC_ROUTE_MONOMIX)
+
+#define ALC5632_PWR_DEFAULT (ALC5632_PWR_ADC_STATUS \
+ | ALC5632_PWR_DAC_STATUS \
+ | ALC5632_PWR_AMIX_STATUS \
+ | ALC5632_PWR_VREF_STATUS)
+
+#define ALC5632_ADC_REC_GAIN_COMP(x) (int)((x - ALC5632_ADC_REC_GAIN_BASE) \
+ / ALC5632_ADC_REC_GAIN_STEP)
+
+#define ALC5632_MIC_BOOST_COMP(x) (int)(x / ALC5632_MIC_BOOST_STEP)
+
+#define ALC5632_SPK_OUT_VOL_COMP(x) (int)(x / ALC5632_SPK_OUT_VOL_STEP)
+
+static int alc5632_probe(struct snd_soc_codec *codec)
+{
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ dev_dbg(codec->dev, "ALC5632 Audio Codec Ver. %s\n", ALC5632_VERSION);
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, alc5632->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ alc5632_reset(codec);
+
+ /* power on device */
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (alc5632->add_ctrl) {
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1,
+ alc5632->add_ctrl);
+ }
+
+ /* spk amp pwr enable 3A | 0x0400 @ 3A */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD1,
+ 0, ALC5632_PWR_ADD1_SPK_AMP_EN);
+
+ /* "normal" mode: 0 @ 26 */
+ snd_soc_write(codec, ALC5632_PWR_DOWN_CTRL_STATUS, 0);
+
+ /* power on VREF on all analog circuits 0x2000 @ 3C */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ 0, ALC5632_PWR_ADD2_VREF);
+
+ /* enable slave mode 0x8000 @ 34 */
+ snd_soc_write(codec, ALC5632_DAI_CONTROL, ALC5632_DAI_SDP_SLAVE_MODE);
+
+ switch (alc5632->id) {
+ case 0x5c:
+ snd_soc_add_controls(codec, alc5632_vol_snd_controls,
+ ARRAY_SIZE(alc5632_vol_snd_controls));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_add_controls(codec, alc5632_snd_controls,
+ ARRAY_SIZE(alc5632_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, alc5632_dapm_widgets,
+ ARRAY_SIZE(alc5632_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return ret;
+}
+
+/* power down chip */
+static int alc5632_remove(struct snd_soc_codec *codec)
+{
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_alc5632 = {
+ .probe = alc5632_probe,
+ .remove = alc5632_remove,
+ .suspend = alc5632_suspend,
+ .resume = alc5632_resume,
+ .set_bias_level = alc5632_set_bias_level,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = alc5632_reg_defaults,
+ .reg_cache_size = ARRAY_SIZE(alc5632_reg_defaults),
+ .volatile_register = alc5632_volatile_register,
+};
+
+/*
+ * alc5632 2 wire address is determined by A1 pin
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int alc5632_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct alc5632_platform_data *pdata;
+ struct alc5632_priv *alc5632;
+ int ret, vid1, vid2;
+
+ dev_dbg(&client->dev, "in i2c probe...\n");
+
+ vid1 = i2c_smbus_read_word_data(client, ALC5632_VENDOR_ID1);
+ if (vid1 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ } else {
+ dev_err(&client->dev, "got vid1: %x\n", vid1);
+ }
+ vid1 = ((vid1 & 0xff) << 8) | (vid1 >> 8);
+
+ vid2 = i2c_smbus_read_word_data(client, ALC5632_VENDOR_ID2);
+ if (vid2 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ } else {
+ dev_err(&client->dev, "got vid2: %x\n", vid2);
+ }
+ vid2 = (vid2 & 0xff);
+
+ if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) {
+ dev_err(&client->dev, "unknown or wrong codec\n");
+ dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n",
+ 0x10ec, id->driver_data,
+ vid1, vid2);
+ return -ENODEV;
+ }
+
+ dev_dbg(&client->dev, "Found codec: ALC5632 (ID: %x)\n", vid2);
+
+ alc5632 = kzalloc(sizeof(struct alc5632_priv), GFP_KERNEL);
+ if (alc5632 == NULL)
+ return -ENOMEM;
+
+ pdata = client->dev.platform_data;
+ if (pdata) {
+ alc5632->add_ctrl = pdata->add_ctrl;
+ alc5632->jack_det_ctrl = pdata->jack_det_ctrl;
+ }
+
+ alc5632->id = vid2;
+ switch (alc5632->id) {
+ case 0x5c:
+ alc5632_dai.name = "alc5632-hifi";
+ break;
+ default:
+ kfree(alc5632);
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, alc5632);
+ alc5632->control_data = client;
+ alc5632->control_type = SND_SOC_I2C;
+ mutex_init(&alc5632->mutex);
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_device_alc5632, &alc5632_dai, 1);
+ if (ret != 0) {
+ dev_err(&client->dev, "Failed to register codec: %d\n", ret);
+ kfree(alc5632);
+ }
+
+ return ret;
+}
+
+static int alc5632_i2c_remove(struct i2c_client *client)
+{
+ struct alc5632_priv *alc5632 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ kfree(alc5632);
+ return 0;
+}
+
+static const struct i2c_device_id alc5632_i2c_table[] = {
+ {"alc5632", 0x5c},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, alc5632_i2c_table);
+
+/* i2c codec control layer */
+static struct i2c_driver alc5632_i2c_driver = {
+ .driver = {
+ .name = "alc5632",
+ .owner = THIS_MODULE,
+ },
+ .probe = alc5632_i2c_probe,
+ .remove = __devexit_p(alc5632_i2c_remove),
+ .id_table = alc5632_i2c_table,
+};
+
+static int __init alc5632_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&alc5632_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "%s: can't add i2c driver", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+module_init(alc5632_modinit);
+
+static void __exit alc5632_modexit(void)
+{
+ i2c_del_driver(&alc5632_i2c_driver);
+}
+module_exit(alc5632_modexit);
+
+MODULE_DESCRIPTION("ASoC ALC5632 driver");
+MODULE_AUTHOR("Leon Romanovsky <leon(a)leon.nu>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/alc5632.h b/sound/soc/codecs/alc5632.h
new file mode 100644
index 0000000..9747016
--- /dev/null
+++ b/sound/soc/codecs/alc5632.h
@@ -0,0 +1,243 @@
+/*
+* alc5632.h -- ALC5632 ALSA SoC Audio Codec
+*
+* Copyright (C) 2011 The AC100 Kernel Team <ac100(a)lists.lauchpad.net>
+*
+* Authors: Leon Romanovsky <leon(a)leon.nu>
+* Andrey Danin <danindrey(a)mail.ru>
+* Ilya Petrov <ilya.muromec(a)gmail.com>
+* Marc Dietrich <marvin24(a)gmx.de>
+*
+* Based on alc5623.h by Arnaud Patard
+*
+* 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 _ALC5632_H
+#define _ALC5632_H
+
+#define ALC5632_RESET 0x00
+/* speaker output vol 2 2 */
+/* line output vol 4 2 */
+/* HP output vol 4 0 4 */
+#define ALC5632_SPK_OUT_VOL 0x02 /* spe out vol */
+#define ALC5632_SPK_OUT_VOL_STEP 1.5
+#define ALC5632_HP_OUT_VOL 0x04 /* hp out vol */
+#define ALC5632_AUX_OUT_VOL 0x06 /* aux out vol */
+#define ALC5632_PHONE_IN_VOL 0x08 /* phone in vol */
+#define ALC5632_LINE_IN_VOL 0x0A /* line in vol */
+#define ALC5632_STEREO_DAC_IN_VOL 0x0C /* stereo dac in vol */
+#define ALC5632_MIC_VOL 0x0E /* mic in vol */
+/* stero dac/mic routing */
+#define ALC5632_MIC_ROUTING_CTRL 0x10
+#define ALC5632_MIC_ROUTE_MONOMIX (1 << 0)
+#define ALC5632_MIC_ROUTE_SPK (1 << 1)
+#define ALC5632_MIC_ROUTE_HP (1 << 2)
+
+#define ALC5632_ADC_REC_GAIN 0x12 /* rec gain */
+#define ALC5632_ADC_REC_GAIN_RANGE 0x1F1F
+#define ALC5632_ADC_REC_GAIN_BASE (-16.5)
+#define ALC5632_ADC_REC_GAIN_STEP 1.5
+
+#define ALC5632_ADC_REC_MIXER 0x14 /* mixer control */
+#define ALC5632_ADC_REC_MIC1 (1 << 6)
+#define ALC5632_ADC_REC_MIC2 (1 << 5)
+#define ALC5632_ADC_REC_LINE_IN (1 << 4)
+#define ALC5632_ADC_REC_AUX (1 << 3)
+#define ALC5632_ADC_REC_HP (1 << 2)
+#define ALC5632_ADC_REC_SPK (1 << 1)
+#define ALC5632_ADC_REC_MONOMIX (1 << 0)
+
+#define ALC5632_VOICE_DAC_VOL 0x18 /* voice dac vol */
+/* ALC5632_OUTPUT_MIXER_CTRL : */
+/* same remark as for reg 2 line vs speaker */
+#define ALC5632_OUTPUT_MIXER_CTRL 0x1C /* out mix ctrl */
+#define ALC5632_OUTPUT_MIXER_RP (1 << 14)
+#define ALC5632_OUTPUT_MIXER_WEEK (1 << 12)
+#define ALC5632_OUTPUT_MIXER_HP (1 << 10)
+#define ALC5632_OUTPUT_MIXER_AUX_SPK (2 << 6)
+#define ALC5632_OUTPUT_MIXER_AUX_HP_LR (1 << 6)
+#define ALC5632_OUTPUT_MIXER_HP_R (1 << 8)
+#define ALC5632_OUTPUT_MIXER_HP_L (1 << 9)
+
+#define ALC5632_MIC_CTRL 0x22 /* mic phone ctrl */
+#define ALC5632_MIC_BOOST_BYPASS 0
+#define ALC5632_MIC_BOOST_20DB 1
+#define ALC5632_MIC_BOOST_30DB 2
+#define ALC5632_MIC_BOOST_40DB 3
+
+#define ALC5632_DIGI_BOOST_CTRL 0x24 /* digi mic / bost ctl */
+#define ALC5632_MIC_BOOST_RANGE 7
+#define ALC5632_MIC_BOOST_STEP 6
+#define ALC5632_PWR_DOWN_CTRL_STATUS 0x26
+#define ALC5632_PWR_VREF_STATUS (1 << 3)
+#define ALC5632_PWR_AMIX_STATUS (1 << 2)
+#define ALC5632_PWR_DAC_STATUS (1 << 1)
+#define ALC5632_PWR_ADC_STATUS (1 << 0)
+/* stereo/voice DAC / stereo adc func ctrl */
+#define ALC5632_DAC_FUNC_SELECT 0x2E
+
+/* Main serial data port ctrl (i2s) */
+#define ALC5632_DAI_CONTROL 0x34
+
+#define ALC5632_DAI_SDP_MASTER_MODE (0 << 15)
+#define ALC5632_DAI_SDP_SLAVE_MODE (1 << 15)
+#define ALC5632_DAI_SADLRCK_MODE (1 << 14)
+/* 0:voice, 1:main */
+#define ALC5632_DAI_MAIN_I2S_SYSCLK_SEL (1 << 8)
+#define ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL (1 << 7)
+/* 0:normal, 1:invert */
+#define ALC5632_DAI_MAIN_I2S_LRCK_INV (1 << 6)
+#define ALC5632_DAI_I2S_DL_MASK (3 << 2)
+#define ALC5632_DAI_I2S_DL_8 (3 << 2)
+#define ALC5632_DAI_I2S_DL_24 (2 << 2)
+#define ALC5632_DAI_I2S_DL_20 (1 << 2)
+#define ALC5632_DAI_I2S_DL_16 (0 << 2)
+#define ALC5632_DAI_I2S_DF_MASK (3 << 0)
+#define ALC5632_DAI_I2S_DF_PCM_B (3 << 0)
+#define ALC5632_DAI_I2S_DF_PCM_A (2 << 0)
+#define ALC5632_DAI_I2S_DF_LEFT (1 << 0)
+#define ALC5632_DAI_I2S_DF_I2S (0 << 0)
+/* extend serial data port control (VoDAC_i2c/pcm) */
+#define ALC5632_DAI_CONTROL2 0x36
+/* 0:gpio func, 1:voice pcm */
+#define ALC5632_DAI_VOICE_PCM_ENABLE (1 << 15)
+/* 0:master, 1:slave */
+#define ALC5632_DAI_VOICE_MODE_SEL (1 << 14)
+/* 0:disable, 1:enable */
+#define ALC5632_DAI_HPF_CLK_CTRL (1 << 13)
+/* 0:main, 1:voice */
+#define ALC5632_DAI_VOICE_I2S_SYSCLK_SEL (1 << 8)
+/* 0:normal, 1:invert */
+#define ALC5632_DAI_VOICE_VBCLK_SYSCLK_SEL (1 << 7)
+/* 0:normal, 1:invert */
+#define ALC5632_DAI_VOICE_I2S_LR_INV (1 << 6)
+#define ALC5632_DAI_VOICE_DL_MASK (3 << 2)
+#define ALC5632_DAI_VOICE_DL_16 (0 << 2)
+#define ALC5632_DAI_VOICE_DL_20 (1 << 2)
+#define ALC5632_DAI_VOICE_DL_24 (2 << 2)
+#define ALC5632_DAI_VOICE_DL_8 (3 << 2)
+#define ALC5632_DAI_VOICE_DF_MASK (3 << 0)
+#define ALC5632_DAI_VOICE_DF_I2S (0 << 0)
+#define ALC5632_DAI_VOICE_DF_LEFT (1 << 0)
+#define ALC5632_DAI_VOICE_DF_PCM_A (2 << 0)
+#define ALC5632_DAI_VOICE_DF_PCM_B (3 << 0)
+
+#define ALC5632_PWR_MANAG_ADD1 0x3A
+#define ALC5632_PWR_ADD1_DAC_L_EN (1 << 15)
+#define ALC5632_PWR_ADD1_DAC_R_EN (1 << 14)
+#define ALC5632_PWR_ADD1_ZERO_CROSS (1 << 13)
+#define ALC5632_PWR_ADD1_MAIN_I2S_EN (1 << 11)
+#define ALC5632_PWR_ADD1_SPK_AMP_EN (1 << 10)
+#define ALC5632_PWR_ADD1_HP_OUT_AMP (1 << 9)
+#define ALC5632_PWR_ADD1_HP_OUT_ENH_AMP (1 << 8)
+#define ALC5632_PWR_ADD1_VOICE_DAC_MIX (1 << 7)
+#define ALC5632_PWR_ADD1_SOFTGEN_EN (1 << 6)
+#define ALC5632_PWR_ADD1_MIC1_SHORT_CURR (1 << 5)
+#define ALC5632_PWR_ADD1_MIC2_SHORT_CURR (1 << 4)
+#define ALC5632_PWR_ADD1_MIC1_EN (1 << 3)
+#define ALC5632_PWR_ADD1_MIC2_EN (1 << 2)
+#define ALC5632_PWR_ADD1_MAIN_BIAS (1 << 1)
+#define ALC5632_PWR_ADD1_DAC_REF (1 << 0)
+
+#define ALC5632_PWR_MANAG_ADD2 0x3C
+#define ALC5632_PWR_ADD2_PLL1 (1 << 15)
+#define ALC5632_PWR_ADD2_PLL2 (1 << 14)
+#define ALC5632_PWR_ADD2_VREF (1 << 13)
+#define ALC5632_PWR_ADD2_OVT_DET (1 << 12)
+#define ALC5632_PWR_ADD2_VOICE_DAC (1 << 10)
+#define ALC5632_PWR_ADD2_L_DAC_CLK (1 << 9)
+#define ALC5632_PWR_ADD2_R_DAC_CLK (1 << 8)
+#define ALC5632_PWR_ADD2_L_ADC_CLK_GAIN (1 << 7)
+#define ALC5632_PWR_ADD2_R_ADC_CLK_GAIN (1 << 6)
+#define ALC5632_PWR_ADD2_L_HP_MIXER (1 << 5)
+#define ALC5632_PWR_ADD2_R_HP_MIXER (1 << 4)
+#define ALC5632_PWR_ADD2_SPK_MIXER (1 << 3)
+#define ALC5632_PWR_ADD2_MONO_MIXER (1 << 2)
+#define ALC5632_PWR_ADD2_L_ADC_REC_MIXER (1 << 1)
+#define ALC5632_PWR_ADD2_R_ADC_REC_MIXER (1 << 0)
+
+#define ALC5632_PWR_MANAG_ADD3 0x3E
+#define ALC5632_PWR_ADD3_AUXOUT_VOL (1 << 14)
+#define ALC5632_PWR_ADD3_SPK_L_OUT (1 << 13)
+#define ALC5632_PWR_ADD3_SPK_R_OUT (1 << 12)
+#define ALC5632_PWR_ADD3_HP_L_OUT_VOL (1 << 11)
+#define ALC5632_PWR_ADD3_HP_R_OUT_VOL (1 << 10)
+#define ALC5632_PWR_ADD3_LINEIN_L_VOL (1 << 7)
+#define ALC5632_PWR_ADD3_LINEIN_R_VOL (1 << 6)
+#define ALC5632_PWR_ADD3_AUXIN_VOL (1 << 5)
+#define ALC5632_PWR_ADD3_AUXIN_MIX (1 << 4)
+#define ALC5632_PWR_ADD3_MIC1_VOL (1 << 3)
+#define ALC5632_PWR_ADD3_MIC2_VOL (1 << 2)
+#define ALC5632_PWR_ADD3_MIC1_BOOST_AD (1 << 1)
+#define ALC5632_PWR_ADD3_MIC2_BOOST_AD (1 << 0)
+
+#define ALC5632_GPCR1 0x40
+#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1 (1 << 15)
+#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_MCLK (0 << 15)
+#define ALC5632_GPCR1_DAC_HI_FLT_EN (1 << 10)
+#define ALC5632_GPCR1_SPK_AMP_CTRL (7 << 1)
+#define ALC5632_GPCR1_VDD_100 (5 << 1)
+#define ALC5632_GPCR1_VDD_125 (4 << 1)
+#define ALC5632_GPCR1_VDD_150 (3 << 1)
+#define ALC5632_GPCR1_VDD_175 (2 << 1)
+#define ALC5632_GPCR1_VDD_200 (1 << 1)
+#define ALC5632_GPCR1_VDD_225 (0 << 1)
+
+#define ALC5632_GPCR2 0x42
+#define ALC5632_GPCR2_PLL1_SOUR_SEL (3 << 12)
+#define ALC5632_PLL_FR_MCLK (0 << 12)
+#define ALC5632_PLL_FR_BCLK (2 << 12)
+#define ALC5632_PLL_FR_VBCLK (3 << 12)
+#define ALC5632_GPCR2_CLK_PLL_PRE_DIV1 (0 << 0)
+
+#define ALC5632_PLL1_CTRL 0x44
+#define ALC5632_PLL1_CTRL_N_VAL(n) (((n) & 0x0f) << 8)
+#define ALC5632_PLL1_M_BYPASS (1 << 7)
+#define ALC5632_PLL1_CTRL_K_VAL(k) (((k) & 0x07) << 4)
+#define ALC5632_PLL1_CTRL_M_VAL(m) (((m) & 0x0f) << 0)
+
+#define ALC5632_PLL2_CTRL 0x46
+#define ALC5632_PLL2_EN (1 << 15)
+#define ALC5632_PLL2_RATIO (0 << 15)
+
+#define ALC5632_GPIO_PIN_CONFIG 0x4C
+#define ALC5632_GPIO_PIN_POLARITY 0x4E
+#define ALC5632_GPIO_PIN_STICKY 0x50
+#define ALC5632_GPIO_PIN_WAKEUP 0x52
+#define ALC5632_GPIO_PIN_STATUS 0x54
+#define ALC5632_GPIO_PIN_SHARING 0x56
+#define ALC5632_OVER_CURR_STATUS 0x58
+#define ALC5632_SOFTVOL_CTRL 0x5A
+#define ALC5632_GPIO_OUPUT_PIN_CTRL 0x5C
+
+#define ALC5632_MISC_CTRL 0x5E
+#define ALC5632_MISC_DISABLE_FAST_VREG (1 << 15)
+#define ALC5632_MISC_AVC_TRGT_SEL (3 << 12)
+#define ALC5632_MISC_AVC_TRGT_RIGHT (1 << 12)
+#define ALC5632_MISC_AVC_TRGT_LEFT (2 << 12)
+#define ALC5632_MISC_AVC_TRGT_BOTH (3 << 12)
+#define ALC5632_MISC_HP_DEPOP_MODE1_EN (1 << 9)
+#define ALC5632_MISC_HP_DEPOP_MODE2_EN (1 << 8)
+#define ALC5632_MISC_HP_DEPOP_MUTE_L (1 << 7)
+#define ALC5632_MISC_HP_DEPOP_MUTE_R (1 << 6)
+#define ALC5632_MISC_HP_DEPOP_MUTE (1 << 5)
+#define ALC5632_MISC_GPIO_WAKEUP_CTRL (1 << 1)
+#define ALC5632_MISC_IRQOUT_INV_CTRL (1 << 0)
+
+#define ALC5632_DAC_CLK_CTRL1 0x60
+#define ALC5632_DAC_CLK_CTRL2 0x62
+#define ALC5632_DAC_CLK_CTRL2_DIV1_2 (1 << 0)
+#define ALC5632_VOICE_DAC_PCM_CLK_CTRL1 0x64
+#define ALC5632_PSEUDO_SPATIAL_CTRL 0x68
+#define ALC5632_HID_CTRL_INDEX 0x6A
+#define ALC5632_HID_CTRL_DATA 0x6C
+#define ALC5632_EQ_CTRL 0x6E
+
+/* undocumented */
+#define ALC5632_VENDOR_ID1 0x7C
+#define ALC5632_VENDOR_ID2 0x7E
+
+#endif
--
1.7.3.4
2
1
Re: [alsa-devel] [PATCH 1/1] intel8x0: [v2] Improve performance in virtual environment
by Takashi Iwai 26 Oct '11
by Takashi Iwai 26 Oct '11
26 Oct '11
At Wed, 26 Oct 2011 17:29:37 +0400,
Konstantin Ozerkov wrote:
>
> On 26.10.11 17:03, Takashi Iwai wrote:
> > Are the changes specific to x86, right?
> > boot_cpu_has() seems x86 only, and kvm_para_*() isn't provided for all
> > architectures.
> >
> > The best would be to provide a standard API for such detection. But,
> > as a workaround, we may need a simple ifdef.
> Is this really needed ? This is driver for controllers which is used only (AFIK) with x86 and x86_64 CPUs.
I remember vaguely the implementation on PPC and others. Who knows.
(And you can run a kernel in VM, right? :)
Takashi
1
0
[alsa-devel] ASoC PCM drivers without progress counters breaks snd_pcm_status
by Magnus Olsson 26 Oct '11
by Magnus Olsson 26 Oct '11
26 Oct '11
Hi,
To determine the timestamp of captured audio, we do something along the lines:
ts = snd_pcm_status_get_htstamp() - time(snd_pcm_status_get_delay())
snd_pcm_readi(buf)
process(buf, ts)
However for PCM drivers that only update its hw_pointer in steps of periods (in irq-handler), the attributes in snd_pcm_status() struct will not be in sync even though ALSA thinks so. For example, the avail property will correspond to the time of the last IRQ, not at the specified timestamp returned by snd_pcm_status_get_htstamp(). This will introduce timestamp jitter which depends on system IRQ latency and also when the userspace application issues it syscall to read status. For an embedded system under heavy load, the A/V sync is unacceptable when using ALSA timestamps.
Several mainline PCM drivers do this kind of hw-pointer caching in their irq handlers, perhaps because the DMA HW does not support progress counting.
Possible solutions:
1) If DMA HW supports progress counter, implement it.
2) If DMA HW is not supported, implement it in software (estimate position using elapsed time since last irq). Only requires driver changes.
3) If DMA HW is not supported, return use last irq timestamp as the timestamp returned by snd_pcm_status_get_htstamp() . Probably requires ALSA Core changes.
I've tested 1) and results in much improved A/V sync on our systems. If would be kind of convenient if ALSA SoC Core could provide a generic pointercallback for solution 2), if the PCM driver specifies that it does not support HW progress counter. Otherwise we will see a lot of duplicate code for estimating position. AFAIK, ALSA already maintains a timestamp for last irq.
Magnus Olsson
Axis Communications / Product Platforms
2
1
Re: [alsa-devel] [PATCH 1/1] intel8x0: [v2] Improve performance in virtual environment
by Takashi Iwai 26 Oct '11
by Takashi Iwai 26 Oct '11
26 Oct '11
At Wed, 26 Oct 2011 16:00:15 +0400,
Konstantin Ozerkov wrote:
>
> v2: add detection for virtual environments (KVM and Parallels)
>
> This patch is intended to improve performance in virtualized environments
> like Parallels Desktop or KVM/VirtualBox/QEMU (virtual ICH/AC97 audio).
>
> I/O access is very time-expensive operation in virtual world: VCPU
> can be rescheduled and in the worst case we get more than 10ms delay on
> each I/O access.
>
> In the virtual environment loop exit rule
> (old_civ == current_civ && old_picb == current_picb) is never satisfied,
> because old_picb is never the same as current_picb due to delay inspired
> by reading current_civ. As a result loop ended by timeout and we get 10x
> more I/O operations.
>
> Experimental data from Prallels Desktop 7, RHEL6 guest (I/O ops per
> second):
>
> Original code:
> In Port Counter Callback
> f014 41550 fffff00000179d00 ac97_bm_read_civ+0x000
> f018 41387 fffff0000017a580 ac97_bm_read_picb+0x000
>
> With patch:
> In Port Counter Callback
> f014 4090 fffff00000179d00 ac97_bm_read_civ+0x000
> f018 1964 fffff0000017a580 ac97_bm_read_picb+0x000
>
> Signed-off-by: Konstantin Ozerkov <kozerkov(a)parallels.com>
> Signed-off-by: Denis V. Lunev <den(a)openvz.org>
Are the changes specific to x86, right?
boot_cpu_has() seems x86 only, and kvm_para_*() isn't provided for all
architectures.
The best would be to provide a standard API for such detection. But,
as a workaround, we may need a simple ifdef.
thanks,
Takashi
> diff --git a/pci/intel8x0.c b/pci/intel8x0.c
> index 6a5b387..0974ae5 100644
> --- a/pci/intel8x0.c
> +++ b/pci/intel8x0.c
> @@ -27,6 +27,7 @@
> */
>
> #include <asm/io.h>
> +#include <asm/kvm_para.h>
> #include <linux/delay.h>
> #include <linux/interrupt.h>
> #include <linux/init.h>
> @@ -77,6 +78,7 @@ static int buggy_semaphore;
> static int buggy_irq = -1; /* auto-check */
> static int xbox;
> static int spdif_aclink = -1;
> +static int inside_vm = -1;
>
> module_param(index, int, 0444);
> MODULE_PARM_DESC(index, "Index value for Intel i8x0 soundcard.");
> @@ -94,6 +96,8 @@ module_param(xbox, bool, 0444);
> MODULE_PARM_DESC(xbox, "Set to 1 for Xbox, if you have problems with the AC'97 codec detection.");
> module_param(spdif_aclink, int, 0444);
> MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link.");
> +module_param(inside_vm, bool, 0444);
> +MODULE_PARM_DESC(inside_vm, "KVM/Parallels optimization.");
>
> /* just for backward compatibility */
> static int enable;
> @@ -400,6 +404,7 @@ struct intel8x0 {
> unsigned buggy_irq: 1; /* workaround for buggy mobos */
> unsigned xbox: 1; /* workaround for Xbox AC'97 detection */
> unsigned buggy_semaphore: 1; /* workaround for buggy codec semaphore */
> + unsigned inside_vm: 1; /* enable VM optimization */
>
> int spdif_idx; /* SPDIF BAR index; *_SPBAR or -1 if use PCMOUT */
> unsigned int sdm_saved; /* SDM reg value */
> @@ -1065,8 +1070,11 @@ static snd_pcm_uframes_t snd_intel8x0_pcm_pointer(struct snd_pcm_substream *subs
> udelay(10);
> continue;
> }
> - if (civ == igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV) &&
> - ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
> + if (civ != igetbyte(chip, ichdev->reg_offset + ICH_REG_OFF_CIV))
> + continue;
> + if (chip->inside_vm)
> + break;
> + if (ptr1 == igetword(chip, ichdev->reg_offset + ichdev->roff_picb))
> break;
> } while (timeout--);
> ptr = ichdev->last_pos;
> @@ -2984,6 +2992,10 @@ static int __devinit snd_intel8x0_create(struct snd_card *card,
> if (xbox)
> chip->xbox = 1;
>
> + chip->inside_vm = inside_vm;
> + if (inside_vm)
> + printk(KERN_INFO "intel8x0: enable KVM optimization\n");
> +
> if (pci->vendor == PCI_VENDOR_ID_INTEL &&
> pci->device == PCI_DEVICE_ID_INTEL_440MX)
> chip->fix_nocache = 1; /* enable workaround */
> @@ -3226,6 +3238,11 @@ static int __devinit snd_intel8x0_probe(struct pci_dev *pci,
> buggy_irq = 0;
> }
>
> + if (inside_vm < 0) {
> + /* detect KVM and Parallels virtual environments */
> + inside_vm = kvm_para_available() || boot_cpu_has(X86_FEATURE_HYPERVISOR);
> + }
> +
> if ((err = snd_intel8x0_create(card, pci, pci_id->driver_data,
> &chip)) < 0) {
> snd_card_free(card);
> --
> 1.7.7
>
1
0
26 Oct '11
At Wed, 26 Oct 2011 09:48:12 +0200,
Alexander Stein wrote:
>
> If a line in the firmware file is larger than the given buffer size (and
> so the firmware file size), size is set to a value larger than the actual
> buffer size. This results in an overflow in the buffer passed.
> Fix this by copying only up to 127 chars per line.
Actually this check should have been
if (size > fw->size)
size = fw->size;
Otherwise it doesn't make sense.
If the change is OK, could you resend the patch with it?
thanks,
Takashi
> Signed-off-by: Alexander Stein <alexander.stein(a)systec-electronic.com>
> ---
> sound/pci/hda/hda_hwdep.c | 6 +++---
> 1 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c
> index bf3ced5..61da08d 100644
> --- a/sound/pci/hda/hda_hwdep.c
> +++ b/sound/pci/hda/hda_hwdep.c
> @@ -745,6 +745,7 @@ static int parse_line_mode(char *buf, struct hda_bus *bus)
> * if successfully copied a line
> *
> * the spaces at the beginning and the end of the line are stripped
> + * lines read are clamped to 127 chars
> */
> static int get_line_from_fw(char *buf, int size, struct firmware *fw)
> {
> @@ -756,8 +757,6 @@ static int get_line_from_fw(char *buf, int size, struct firmware *fw)
> }
> if (!fw->size)
> return 0;
> - if (size < fw->size)
> - size = fw->size;
>
> for (len = 0; len < fw->size; len++) {
> if (!*p)
> @@ -768,7 +767,8 @@ static int get_line_from_fw(char *buf, int size, struct firmware *fw)
> break;
> }
> if (len < size)
> - *buf++ = *p++;
> + *buf++ = *p;
> + p++;
> }
> *buf = 0;
> fw->size -= len;
> --
> 1.7.3.4
>
1
0
Hi Takashi and Mark,
When I use ad1836 and ad1938 with alsa lib 1.0.24, there is an error:
root:/> arecord -t wav -c 2 -d 3 -r 48000 -f S32_LE 1.wav
Recording WAVE '1.wav' : Signed 32 bit Little Endian, Rate 48000 Hz, Stereo
arecord: pcm_read:1773: read error: Input/output error
If I use S16_LE and S24_LE there is no error.
The drivers for ad1836 and ad1938 only support S32_LE.
My card config file lists below:
bfin-ad1836.pcm.default {
@args [ CARD ]
@args.CARD {
type string
}
type plug
slave.pcm {
type mmap_emul
slave.pcm {
type hw
card $CARD
format S32_LE
rate 48000
}
}
}
If I used alsa lib 1.0.18, S16_LE, S24_LE and S32_LE are all ok.
Is my config file wrong in new version lib?
It seems if the format is the same as driver supports it will get an error.
Regards,
Scott
1
0
2011/10/25 Nenad Sljivic <nenad.sljivic(a)rt-rk.com>:
> Hello Scott,
>
> Thanks a lot for your reply. My project is totally blocked due to this
> issue. I would really appreciate your help.
>
> Are you referring to this entry:
> http://mailman.alsa-project.org/pipermail/alsa-devel/2011-September/043393.h
> tml
> ?
>
> Could you elaborate your solution a little more ?
>
> What do you mean by:
> " I add driver_name in my snd_soc_card to solve this bug. "
>
Mark has committed a patch to fix this in asoc core.
you can cat /proc/asound/cards to find the card driver name, then you
must modify your config file to match this.
Scott
1
0
25 Oct '11
This driver implements basic functionality, using I²C for the control channel.
Signed-off-by: Leon Romanovsky <leon(a)leon.nu>
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/alc5632.c | 1175 ++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/alc5632.h | 233 +++++++++
4 files changed, 1414 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/alc5632.c
create mode 100644 sound/soc/codecs/alc5632.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4584514..e78ece8 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -26,6 +26,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4642 if I2C
select SND_SOC_AK4671 if I2C
select SND_SOC_ALC5623 if I2C
+ select SND_SOC_ALC5632 if I2C
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
@@ -169,6 +170,9 @@ config SND_SOC_AK4671
config SND_SOC_ALC5623
tristate
+config SND_SOC_ALC5632
+ tristate
+
config SND_SOC_CQ0093VC
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a2c7842..98bf1b6 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -29,6 +29,7 @@ snd-soc-pcm3008-objs := pcm3008.o
snd-soc-rt5631-objs := rt5631.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
+snd-soc-alc5632-objs := alc5632.0
snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
@@ -113,6 +114,7 @@ obj-$(CONFIG_SND_SOC_AK4641) += snd-soc-ak4641.o
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
+obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
diff --git a/sound/soc/codecs/alc5632.c b/sound/soc/codecs/alc5632.c
new file mode 100644
index 0000000..b3670a3
--- /dev/null
+++ b/sound/soc/codecs/alc5632.c
@@ -0,0 +1,1175 @@
+/*
+* alc5632.c -- ALC5632 ALSA SoC Audio Codec
+*
+* Copyright (C) 2011 The AC100 Kernel Team <ac100(a)lists.lauchpad.net>
+*
+* Authors: Leon Romanovsky <leon(a)leon.nu>
+* Andrey Danin <danindrey(a)mail.ru>
+* Ilya Petrov <ilya.muromec(a)gmail.com>
+* Marc Dietrich <marvin24(a)gmx.de>
+*
+* Based on alc5623.c by Arnaud Patard
+*
+* 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.
+*/
+
+/* #define DEBUG */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/alc5632.h>
+
+#include "alc5632.h"
+
+#define ALC5632_VERSION "0.1"
+
+/*
+ * ALC5632 register cache
+ */
+static const u16 alc5632_reg_defaults[] = {
+ 0x59B4, 0x0000, 0x8080, 0x0000, /* 0 */
+ 0x8080, 0x0000, 0x8080, 0x0000, /* 4 */
+ 0xC800, 0x0000, 0xE808, 0x0000, /* 8 */
+ 0x1010, 0x0000, 0x0808, 0x0000, /* 12 */
+ 0xEE0F, 0x0000, 0xCBCB, 0x0000, /* 16 */
+ 0x7F7F, 0x0000, 0x0000, 0x0000, /* 20 */
+ 0xE010, 0x0000, 0x0000, 0x0000, /* 24 */
+ 0x8008, 0x0000, 0x0000, 0x0000, /* 28 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 32 */
+ 0x00C0, 0x0000, 0xEF00, 0x0000, /* 36 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 40 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 44 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 48 */
+ 0x8000, 0x0000, 0x0000, 0x0000, /* 52 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */
+ 0x0000, 0x0000, 0x8000, 0x0000, /* 60 */
+ 0x0C0A, 0x0000, 0x0000, 0x0000, /* 64 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 68 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 72 */
+ 0xBE3E, 0x0000, 0xBE3E, 0x0000, /* 76 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 80 */
+ 0x803A, 0x0000, 0x0000, 0x0000, /* 84 */
+ 0x0000, 0x0000, 0x0009, 0x0000, /* 88 */
+ 0x0000, 0x0000, 0x3000, 0x0000, /* 92 */
+ 0x3075, 0x0000, 0x1010, 0x0000, /* 96 */
+ 0x3110, 0x0000, 0x0000, 0x0000, /* 100 */
+ 0x0553, 0x0000, 0x0000, 0x0000, /* 104 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* 108 */
+};
+
+static int caps_charge = 2000;
+module_param(caps_charge, int, 0);
+MODULE_PARM_DESC(caps_charge, "ALC5632 cap charge time (msecs)");
+
+/* codec private data */
+struct alc5632_priv {
+ enum snd_soc_control_type control_type;
+ void *control_data;
+ struct mutex mutex;
+ u8 id;
+ unsigned int sysclk;
+ unsigned int add_ctrl;
+ unsigned int jack_det_ctrl;
+};
+
+static int alc5632_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
+{
+ switch (reg) {
+ case ALC5632_RESET:
+ case ALC5632_PWR_DOWN_CTRL_STATUS:
+ case ALC5632_GPIO_PIN_STATUS:
+ case ALC5632_OVER_CURR_STATUS:
+ case ALC5632_HID_CTRL_DATA:
+ case ALC5632_EQ_CTRL:
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline int alc5632_reset(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, ALC5632_RESET, 0);
+ return snd_soc_read(codec, ALC5632_RESET);
+}
+
+static int amp_mixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ /* to power-on/off class-d amp generators/speaker */
+ /* need to write to 'index-46h' register : */
+ /* so write index num (here 0x46) to reg 0x6a */
+ /* and then 0xffff/0 to reg 0x6c */
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_INDEX, 0x46);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_DATA, 0xFFFF);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ snd_soc_write(w->codec, ALC5632_HID_CTRL_DATA, 0);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * ALC5632 Controls
+ */
+
+static const DECLARE_TLV_DB_SCALE(vol_tlv, -3450, 150, 0); // -34.5db min scale, 1.5db steps, no mute
+static const DECLARE_TLV_DB_SCALE(hp_tlv, -4650, 150, 0); // -46.5db min scale, 1.5db steps, no mute
+static const DECLARE_TLV_DB_SCALE(adc_rec_tlv, -1650, 150, 0); // -16.5db min scale, 1.5db steps, no mute
+static const unsigned int boost_tlv[] = {
+ TLV_DB_RANGE_HEAD(3),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 1, TLV_DB_SCALE_ITEM(2000, 0, 0),
+ 2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+static const DECLARE_TLV_DB_SCALE(dig_tlv, 0, 600, 0); // 0db min scale, 6 db steps, no mute
+static const DECLARE_TLV_DB_SCALE(vdac_tlv, -3525, 075, 0); // 0db min scalem 0.75db steps, no mute
+
+static const struct snd_kcontrol_new alc5632_vol_snd_controls[] = {
+ SOC_DOUBLE_TLV("Line Playback Volume",
+ ALC5632_SPK_OUT_VOL, 8, 0, 31, 1, hp_tlv), // left starts at bit 8, right at bit 0
+ // 31 steps (5 bit), -46.5db scale
+ SOC_DOUBLE("Line Playback Switch",
+ ALC5632_SPK_OUT_VOL, 15, 7, 1, 1), // bit 15 mutes left, bit 7 right
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ ALC5632_HP_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Headphone Playback Switch",
+ ALC5632_HP_OUT_VOL, 15, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_snd_controls[] = {
+ SOC_DOUBLE_TLV("Auxout Playback Volume",
+ ALC5632_AUX_OUT_VOL, 8, 0, 31, 1, hp_tlv),
+ SOC_DOUBLE("Auxout Playback Switch",
+ ALC5632_AUX_OUT_VOL, 15, 7, 1, 1),
+ SOC_SINGLE_TLV("Voice DAC Playback Volume",
+ ALC5632_VOICE_DAC_VOL, 0, 63, 0, vdac_tlv),
+ SOC_SINGLE_TLV("Phone Capture Volume",
+ ALC5632_PHONE_IN_VOL, 8, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("LineIn Capture Volume",
+ ALC5632_LINE_IN_VOL, 8, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("Stereo DAC Capture Volume",
+ ALC5632_STEREO_DAC_IN_VOL, 8, 0, 63, 1, vdac_tlv),
+ SOC_DOUBLE("Stereo DAC Capture Switch",
+ ALC5632_STEREO_DAC_IN_VOL, 15, 7, 1, 1),
+ SOC_SINGLE_TLV("Mic1 Capture Volume",
+ ALC5632_MIC_VOL, 8, 31, 1, vol_tlv),
+ SOC_SINGLE_TLV("Mic2 Capture Volume",
+ ALC5632_MIC_VOL, 0, 31, 1, vol_tlv),
+ SOC_DOUBLE_TLV("Rec Capture Volume",
+ ALC5632_ADC_REC_GAIN, 8, 0, 31, 0, adc_rec_tlv),
+ SOC_SINGLE_TLV("Mic 1 Boost Volume",
+ ALC5632_MIC_CTRL, 10, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Mic 2 Boost Volume",
+ ALC5632_MIC_CTRL, 8, 2, 0, boost_tlv),
+ SOC_SINGLE_TLV("Digital Boost Volume",
+ ALC5632_DIGI_BOOST_CTRL, 0, 7, 0, dig_tlv),
+};
+
+/*
+ * DAPM Controls
+ */
+static const struct snd_kcontrol_new alc5632_hp_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2HP Playback Switch", ALC5632_LINE_IN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("PHONE2HP Playback Switch", ALC5632_PHONE_IN_VOL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC12HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 15, 1, 1),
+SOC_DAPM_SINGLE("MIC22HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 11, 1, 1),
+SOC_DAPM_SINGLE("DACL2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 3, 1, 1),
+SOC_DAPM_SINGLE("DACR2HP Playback Switch", ALC5632_MIC_ROUTING_CTRL, 2, 1, 1),
+SOC_DAPM_SINGLE("VOICE2HP Playback Switch", ALC5632_VOICE_DAC_VOL, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_hpl_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_L Playback Switch", ALC5632_ADC_REC_GAIN, 15, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_hpr_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2HP_R Playback Switch", ALC5632_ADC_REC_GAIN, 7, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_mono_mixer_controls[] = {
+SOC_DAPM_SINGLE("ADC2MONO_L Playback Switch", ALC5632_ADC_REC_GAIN, 14, 1, 1),
+SOC_DAPM_SINGLE("ADC2MONO_R Playback Switch", ALC5632_ADC_REC_GAIN, 6, 1, 1),
+SOC_DAPM_SINGLE("LI2MONO Playback Switch", ALC5632_LINE_IN_VOL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC12MONO Playback Switch", ALC5632_MIC_ROUTING_CTRL, 13, 1, 1),
+SOC_DAPM_SINGLE("MIC22MONO Playback Switch", ALC5632_MIC_ROUTING_CTRL, 9, 1, 1),
+SOC_DAPM_SINGLE("DAC2MONO Playback Switch", ALC5632_MIC_ROUTING_CTRL, 0, 1, 1),
+SOC_DAPM_SINGLE("VOICE2MONO Playback Switch", ALC5632_VOICE_DAC_VOL, 13, 1, 1),
+};
+
+static const struct snd_kcontrol_new alc5632_speaker_mixer_controls[] = {
+SOC_DAPM_SINGLE("LI2SPK Playback Switch", ALC5632_LINE_IN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("PHONE2SPK Playback Switch", ALC5632_PHONE_IN_VOL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC12SPK Playback Switch", ALC5632_MIC_ROUTING_CTRL, 14, 1, 1),
+SOC_DAPM_SINGLE("MIC22SPK Playback Switch", ALC5632_MIC_ROUTING_CTRL, 10, 1, 1),
+SOC_DAPM_SINGLE("DAC2SPK Playback Switch", ALC5632_MIC_ROUTING_CTRL, 1, 1, 1),
+SOC_DAPM_SINGLE("VOICE2SPK Playback Switch", ALC5632_VOICE_DAC_VOL, 14, 1, 1),
+};
+
+/* Left Record Mixer */
+static const struct snd_kcontrol_new alc5632_captureL_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5632_ADC_REC_MIXER, 14, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5632_ADC_REC_MIXER, 13, 1, 1),
+SOC_DAPM_SINGLE("LineInL Capture Switch", ALC5632_ADC_REC_MIXER, 12, 1, 1),
+SOC_DAPM_SINGLE("Left Phone Capture Switch", ALC5632_ADC_REC_MIXER, 11, 1, 1),
+SOC_DAPM_SINGLE("HPMixerL Capture Switch", ALC5632_ADC_REC_MIXER, 10, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5632_ADC_REC_MIXER, 9, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5632_ADC_REC_MIXER, 8, 1, 1),
+};
+
+/* Right Record Mixer */
+static const struct snd_kcontrol_new alc5632_captureR_mixer_controls[] = {
+SOC_DAPM_SINGLE("Mic1 Capture Switch", ALC5632_ADC_REC_MIXER, 6, 1, 1),
+SOC_DAPM_SINGLE("Mic2 Capture Switch", ALC5632_ADC_REC_MIXER, 5, 1, 1),
+SOC_DAPM_SINGLE("LineInR Capture Switch", ALC5632_ADC_REC_MIXER, 4, 1, 1),
+SOC_DAPM_SINGLE("Right Phone Capture Switch", ALC5632_ADC_REC_MIXER, 3, 1, 1),
+SOC_DAPM_SINGLE("HPMixerR Capture Switch", ALC5632_ADC_REC_MIXER, 2, 1, 1),
+SOC_DAPM_SINGLE("SPKMixer Capture Switch", ALC5632_ADC_REC_MIXER, 1, 1, 1),
+SOC_DAPM_SINGLE("MonoMixer Capture Switch", ALC5632_ADC_REC_MIXER, 0, 1, 1),
+};
+
+static const char *alc5632_spk_n_sour_sel[] = {
+ "RN/-R", "RP/+R", "LN/-R", "Mute"};
+static const char *alc5632_hpl_out_input_sel[] = {
+ "Vmid", "HP Left Mix"};
+static const char *alc5632_hpr_out_input_sel[] = {
+ "Vmid", "HP Right Mix"};
+static const char *alc5632_spkout_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+static const char *alc5632_aux_out_input_sel[] = {
+ "Vmid", "HPOut Mix", "Speaker Mix", "Mono Mix"};
+
+/* auxout output mux */
+static const struct soc_enum alc5632_aux_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 6, 4, alc5632_aux_out_input_sel);
+static const struct snd_kcontrol_new alc5632_auxout_mux_controls =
+SOC_DAPM_ENUM("AuxOut Mux", alc5632_aux_out_input_enum);
+
+/* speaker output mux */
+static const struct soc_enum alc5632_spkout_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 10, 4, alc5632_spkout_input_sel);
+static const struct snd_kcontrol_new alc5632_spkout_mux_controls =
+SOC_DAPM_ENUM("SpeakerOut Mux", alc5632_spkout_input_enum);
+
+/* headphone left output mux */
+static const struct soc_enum alc5632_hpl_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 9, 2, alc5632_hpl_out_input_sel);
+static const struct snd_kcontrol_new alc5632_hpl_out_mux_controls =
+SOC_DAPM_ENUM("Left Headphone Mux", alc5632_hpl_out_input_enum);
+
+/* headphone right output mux */
+static const struct soc_enum alc5632_hpr_out_input_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 8, 2, alc5632_hpr_out_input_sel);
+static const struct snd_kcontrol_new alc5632_hpr_out_mux_controls =
+SOC_DAPM_ENUM("Right Headphone Mux", alc5632_hpr_out_input_enum);
+
+/* speaker output N select */
+static const struct soc_enum alc5632_spk_n_sour_enum =
+SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 14, 4, alc5632_spk_n_sour_sel);
+static const struct snd_kcontrol_new alc5632_spkoutn_mux_controls =
+SOC_DAPM_ENUM("SpeakerOut N Mux", alc5632_spk_n_sour_enum);
+
+static const struct snd_soc_dapm_widget alc5632_dapm_widgets[] = {
+/* Muxes */
+SND_SOC_DAPM_MUX("AuxOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_auxout_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_spkout_mux_controls),
+SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_hpl_out_mux_controls),
+SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_hpr_out_mux_controls),
+SND_SOC_DAPM_MUX("SpeakerOut N Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_spkoutn_mux_controls),
+
+/* output mixers */
+SND_SOC_DAPM_MIXER("HP Mix", SND_SOC_NOPM, 0, 0,
+ &alc5632_hp_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hp_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPR Mix", ALC5632_PWR_MANAG_ADD2, 4, 0,
+ &alc5632_hpr_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hpr_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPL Mix", ALC5632_PWR_MANAG_ADD2, 5, 0,
+ &alc5632_hpl_mixer_controls[0],
+ ARRAY_SIZE(alc5632_hpl_mixer_controls)),
+SND_SOC_DAPM_MIXER("HPOut Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Mono Mix", ALC5632_PWR_MANAG_ADD2, 2, 0,
+ &alc5632_mono_mixer_controls[0],
+ ARRAY_SIZE(alc5632_mono_mixer_controls)),
+SND_SOC_DAPM_MIXER("Speaker Mix", ALC5632_PWR_MANAG_ADD2, 3, 0,
+ &alc5632_speaker_mixer_controls[0],
+ ARRAY_SIZE(alc5632_speaker_mixer_controls)),
+
+/* input mixers */
+SND_SOC_DAPM_MIXER("Left Capture Mix", ALC5632_PWR_MANAG_ADD2, 1, 0,
+ &alc5632_captureL_mixer_controls[0],
+ ARRAY_SIZE(alc5632_captureL_mixer_controls)),
+SND_SOC_DAPM_MIXER("Right Capture Mix", ALC5632_PWR_MANAG_ADD2, 0, 0,
+ &alc5632_captureR_mixer_controls[0],
+ ARRAY_SIZE(alc5632_captureR_mixer_controls)),
+
+SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback",
+ ALC5632_PWR_MANAG_ADD2, 9, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right HiFi Playback",
+ ALC5632_PWR_MANAG_ADD2, 8, 0),
+SND_SOC_DAPM_MIXER("I2S Mix", ALC5632_PWR_MANAG_ADD1, 11, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Phone Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_MIXER("Line Mix", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_ADC("Left ADC", "Left HiFi Capture",
+ ALC5632_PWR_MANAG_ADD2, 7, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right HiFi Capture",
+ ALC5632_PWR_MANAG_ADD2, 6, 0),
+SND_SOC_DAPM_PGA("Left Headphone", ALC5632_PWR_MANAG_ADD3, 11, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Headphone", ALC5632_PWR_MANAG_ADD3, 10, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left Speaker", ALC5632_PWR_MANAG_ADD3, 13, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right Speaker", ALC5632_PWR_MANAG_ADD3, 12, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Aux Out", ALC5632_PWR_MANAG_ADD3, 14, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Left LineIn", ALC5632_PWR_MANAG_ADD3, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Right LineIn", ALC5632_PWR_MANAG_ADD3, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Phone", ALC5632_PWR_MANAG_ADD3, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Phone ADMix", ALC5632_PWR_MANAG_ADD3, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 PGA", ALC5632_PWR_MANAG_ADD3, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 PGA", ALC5632_PWR_MANAG_ADD3, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC1 Pre Amp", ALC5632_PWR_MANAG_ADD3, 1, 0, NULL, 0),
+SND_SOC_DAPM_PGA("MIC2 Pre Amp", ALC5632_PWR_MANAG_ADD3, 0, 0, NULL, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias1", ALC5632_PWR_MANAG_ADD1, 3, 0),
+SND_SOC_DAPM_MICBIAS("Mic Bias2", ALC5632_PWR_MANAG_ADD1, 2, 0),
+
+SND_SOC_DAPM_OUTPUT("AUXOUT"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_OUTPUT("SPKOUT"),
+SND_SOC_DAPM_OUTPUT("SPKOUTN"),
+SND_SOC_DAPM_INPUT("LINEINL"),
+SND_SOC_DAPM_INPUT("LINEINR"),
+SND_SOC_DAPM_INPUT("PHONEP"),
+SND_SOC_DAPM_INPUT("PHONEN"),
+SND_SOC_DAPM_INPUT("MIC1"),
+SND_SOC_DAPM_INPUT("MIC2"),
+SND_SOC_DAPM_VMID("Vmid"),
+};
+
+static const char *alc5632_amp_names[] = {"AB Amp", "D Amp"};
+static const struct soc_enum alc5632_amp_enum =
+ SOC_ENUM_SINGLE(ALC5632_OUTPUT_MIXER_CTRL, 13, 2, alc5632_amp_names);
+static const struct snd_kcontrol_new alc5632_amp_mux_controls =
+ SOC_DAPM_ENUM("Route", alc5632_amp_enum);
+
+static const struct snd_soc_dapm_widget alc5632_dapm_amp_widgets[] = {
+SND_SOC_DAPM_PGA_E("D Amp", ALC5632_PWR_MANAG_ADD2, 14, 0, NULL, 0,
+ amp_mixer_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+SND_SOC_DAPM_PGA("AB Amp", ALC5632_PWR_MANAG_ADD2, 15, 0, NULL, 0),
+SND_SOC_DAPM_MUX("AB-D Amp Mux", SND_SOC_NOPM, 0, 0,
+ &alc5632_amp_mux_controls),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* virtual mixer - mixes left & right channels */
+ {"I2S Mix", NULL, "Left DAC"},
+ {"I2S Mix", NULL, "Right DAC"},
+ {"Line Mix", NULL, "Right LineIn"},
+ {"Line Mix", NULL, "Left LineIn"},
+ {"Phone Mix", NULL, "Phone"},
+ {"Phone Mix", NULL, "Phone ADMix"},
+ {"AUXOUT", NULL, "Aux Out"},
+
+ /* HP mixer */
+ {"HPL Mix", "ADC2HP_L Playback Switch", "Left Capture Mix"},
+ {"HPL Mix", NULL, "HP Mix"},
+ {"HPR Mix", "ADC2HP_R Playback Switch", "Right Capture Mix"},
+ {"HPR Mix", NULL, "HP Mix"},
+ {"HP Mix", "LI2HP Playback Switch", "Line Mix"},
+ {"HP Mix", "PHONE2HP Playback Switch", "Phone Mix"},
+ {"HP Mix", "MIC12HP Playback Switch", "MIC1 PGA"},
+ {"HP Mix", "MIC22HP Playback Switch", "MIC2 PGA"},
+
+ {"HP Mix", "DACR2HP Playback Switch", "I2S Mix"},
+ {"HP Mix", "DACL2HP Playback Switch", "I2S Mix"},
+
+
+ /* speaker mixer */
+ {"Speaker Mix", "LI2SPK Playback Switch", "Line Mix"},
+ {"Speaker Mix", "PHONE2SPK Playback Switch", "Phone Mix"},
+ {"Speaker Mix", "MIC12SPK Playback Switch", "MIC1 PGA"},
+ {"Speaker Mix", "MIC22SPK Playback Switch", "MIC2 PGA"},
+ {"Speaker Mix", "DAC2SPK Playback Switch", "I2S Mix"},
+
+ /* mono mixer */
+ {"Mono Mix", "ADC2MONO_L Playback Switch", "Left Capture Mix"},
+ {"Mono Mix", "ADC2MONO_R Playback Switch", "Right Capture Mix"},
+ {"Mono Mix", "LI2MONO Playback Switch", "Line Mix"},
+ {"Mono Mix", "VOICE2MONO Playback Switch", "Phone Mix"},
+ {"Mono Mix", "MIC12MONO Playback Switch", "MIC1 PGA"},
+ {"Mono Mix", "MIC22MONO Playback Switch", "MIC2 PGA"},
+ {"Mono Mix", "DAC2MONO Playback Switch", "I2S Mix"},
+
+ /* Left record mixer */
+ {"Left Capture Mix", "LineInL Capture Switch", "LINEINL"},
+ {"Left Capture Mix", "Left Phone Capture Switch", "PHONEN"},
+ {"Left Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Left Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Left Capture Mix", "HPMixerL Capture Switch", "HPL Mix"},
+ {"Left Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Left Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /*Right record mixer */
+ {"Right Capture Mix", "LineInR Capture Switch", "LINEINR"},
+ {"Right Capture Mix", "Right Phone Capture Switch", "PHONEP"},
+ {"Right Capture Mix", "Mic1 Capture Switch", "MIC1 Pre Amp"},
+ {"Right Capture Mix", "Mic2 Capture Switch", "MIC2 Pre Amp"},
+ {"Right Capture Mix", "HPMixerR Capture Switch", "HPR Mix"},
+ {"Right Capture Mix", "SPKMixer Capture Switch", "Speaker Mix"},
+ {"Right Capture Mix", "MonoMixer Capture Switch", "Mono Mix"},
+
+ /* headphone left mux */
+ {"Left Headphone Mux", "HP Left Mix", "HPL Mix"},
+ {"Left Headphone Mux", "Vmid", "Vmid"},
+
+ /* headphone right mux */
+ {"Right Headphone Mux", "HP Right Mix", "HPR Mix"},
+ {"Right Headphone Mux", "Vmid", "Vmid"},
+
+ /* speaker out mux */
+ {"SpeakerOut Mux", "Vmid", "Vmid"},
+ {"SpeakerOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"SpeakerOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"SpeakerOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* Mono/Aux Out mux */
+ {"AuxOut Mux", "Vmid", "Vmid"},
+ {"AuxOut Mux", "HPOut Mix", "HPOut Mix"},
+ {"AuxOut Mux", "Speaker Mix", "Speaker Mix"},
+ {"AuxOut Mux", "Mono Mix", "Mono Mix"},
+
+ /* output pga */
+ {"HPL", NULL, "Left Headphone"},
+ {"Left Headphone", NULL, "Left Headphone Mux"},
+ {"HPR", NULL, "Right Headphone"},
+ {"Right Headphone", NULL, "Right Headphone Mux"},
+ {"Aux Out", NULL, "AuxOut Mux"},
+
+ /* input pga */
+ {"Left LineIn", NULL, "LINEINL"},
+ {"Right LineIn", NULL, "LINEINR"},
+ {"Phone", NULL, "PHONEP"},
+ {"MIC1 Pre Amp", NULL, "MIC1"},
+ {"MIC2 Pre Amp", NULL, "MIC2"},
+ {"MIC1 PGA", NULL, "MIC1 Pre Amp"},
+ {"MIC2 PGA", NULL, "MIC2 Pre Amp"},
+
+ /* left ADC */
+ {"Left ADC", NULL, "Left Capture Mix"},
+
+ /* right ADC */
+ {"Right ADC", NULL, "Right Capture Mix"},
+
+ {"SpeakerOut N Mux", "RN/-R", "Left Speaker"},
+ {"SpeakerOut N Mux", "RP/+R", "Left Speaker"},
+ {"SpeakerOut N Mux", "LN/-R", "Left Speaker"},
+ {"SpeakerOut N Mux", "Mute", "Vmid"},
+
+ {"SpeakerOut N Mux", "RN/-R", "Right Speaker"},
+ {"SpeakerOut N Mux", "RP/+R", "Right Speaker"},
+ {"SpeakerOut N Mux", "LN/-R", "Right Speaker"},
+ {"SpeakerOut N Mux", "Mute", "Vmid"},
+
+
+ {"SPKOUT", NULL, "Left Speaker"},
+ {"SPKOUT", NULL, "Right Speaker"},
+
+ {"SPKOUTN", NULL, "SpeakerOut N Mux"},
+};
+
+static const struct snd_soc_dapm_route intercon_spk[] = {
+ {"Right Speaker", NULL, "SpeakerOut Mux"},
+ {"Left Speaker", NULL, "SpeakerOut Mux"},
+
+};
+
+static const struct snd_soc_dapm_route intercon_amp_spk[] = {
+ {"AB Amp", NULL, "SpeakerOut Mux"},
+ {"D Amp", NULL, "SpeakerOut Mux"},
+ {"AB-D Amp Mux", "AB Amp", "AB Amp"},
+ {"AB-D Amp Mux", "D Amp", "D Amp"},
+ {"SpeakerOut", NULL, "AB-D Amp Mux"},
+};
+
+/* PLL divisors */
+struct _pll_div {
+ u32 pll_in;
+ u32 pll_out;
+ u16 regvalue;
+};
+
+/* Note : pll code from original alc5632 driver. Not sure of how good it is */
+/* usefull only for master mode */
+static const struct _pll_div codec_master_pll_div[] = {
+
+ { 2048000, 8192000, 0x0ea0},
+ { 3686400, 8192000, 0x4e27},
+ { 12000000, 8192000, 0x456b},
+ { 13000000, 8192000, 0x495f},
+ { 13100000, 8192000, 0x0320},
+ { 2048000, 11289600, 0xf637},
+ { 3686400, 11289600, 0x2f22},
+ { 12000000, 11289600, 0x3e2f},
+ { 13000000, 11289600, 0x4d5b},
+ { 13100000, 11289600, 0x363b},
+ { 2048000, 16384000, 0x1ea0},
+ { 3686400, 16384000, 0x9e27},
+ { 12000000, 16384000, 0x452b},
+ { 13000000, 16384000, 0x542f},
+ { 13100000, 16384000, 0x03a0},
+ { 2048000, 16934400, 0xe625},
+ { 3686400, 16934400, 0x9126},
+ { 12000000, 16934400, 0x4d2c},
+ { 13000000, 16934400, 0x742f},
+ { 13100000, 16934400, 0x3c27},
+ { 2048000, 22579200, 0x2aa0},
+ { 3686400, 22579200, 0x2f20},
+ { 12000000, 22579200, 0x7e2f},
+ { 13000000, 22579200, 0x742f},
+ { 13100000, 22579200, 0x3c27},
+ { 2048000, 24576000, 0x2ea0},
+ { 3686400, 24576000, 0xee27},
+ { 12000000, 24576000, 0x2915},
+ { 13000000, 24576000, 0x772e},
+ { 13100000, 24576000, 0x0d20},
+};
+
+/* FOUT = MCLK*(N+2)/((M+2)*(K+2))
+ N: bit 15:8 (div 2 .. div 257)
+ K: bit 6:4 typical 2
+ M: bit 3:0 (div 2 .. div 17)
+
+ same as for 5623 - thanks!
+*/
+
+static const struct _pll_div codec_slave_pll_div[] = {
+
+ { 1024000, 16384000, 0x3ea0},
+ { 1411200, 22579200, 0x3ea0},
+ { 1536000, 24576000, 0x3ea0},
+ { 2048000, 16384000, 0x1ea0},
+ { 2822400, 22579200, 0x1ea0},
+ { 3072000, 24576000, 0x1ea0},
+
+};
+
+static int alc5632_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ int source, unsigned int freq_in, unsigned int freq_out)
+{
+ int i;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int gbl_clk = 0, pll_div = 0;
+ u16 reg;
+
+ if (pll_id < ALC5632_PLL_FR_MCLK || pll_id > ALC5632_PLL_FR_VBCLK)
+ return -ENODEV;
+
+ /* Disable PLL power */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL1,
+ 0);
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL2,
+ 0);
+
+ /* pll is not used in slave mode */
+ reg = snd_soc_read(codec, ALC5632_DAI_CONTROL);
+ if (reg & ALC5632_DAI_SDP_SLAVE_MODE)
+ return 0;
+
+ if (!freq_in || !freq_out)
+ return 0;
+
+ switch (pll_id) {
+ case ALC5632_PLL_FR_MCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_master_pll_div); i++) {
+ if (codec_master_pll_div[i].pll_in == freq_in
+ && codec_master_pll_div[i].pll_out == freq_out) {
+ /* PLL source from MCLK */
+ pll_div = codec_master_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ case ALC5632_PLL_FR_BCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) {
+ if (codec_slave_pll_div[i].pll_in == freq_in
+ && codec_slave_pll_div[i].pll_out == freq_out) {
+ /* PLL source from Bitclk */
+ gbl_clk = ALC5632_PLL_FR_BCLK;
+ pll_div = codec_slave_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ case ALC5632_PLL_FR_VBCLK:
+ for (i = 0; i < ARRAY_SIZE(codec_slave_pll_div); i++) {
+ if (codec_slave_pll_div[i].pll_in == freq_in
+ && codec_slave_pll_div[i].pll_out == freq_out) {
+ /* PLL source from voice clock */
+ gbl_clk = ALC5632_PLL_FR_VBCLK;
+ pll_div = codec_slave_pll_div[i].regvalue;
+ break;
+ }
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!pll_div)
+ return -EINVAL;
+
+/* choose MCLK/BCLK/VBCLK */
+ snd_soc_write(codec, ALC5632_GPCR2, gbl_clk);
+/* choose PLL1 clock rate */
+ snd_soc_write(codec, ALC5632_PLL1_CTRL, pll_div);
+/* enable PLL1 */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL1,
+ ALC5632_PWR_ADD2_PLL1);
+/* enable PLL2 */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_PLL2,
+ ALC5632_PWR_ADD2_PLL2);
+/* use PLL1 as main SYSCLK */
+ snd_soc_update_bits(codec, ALC5632_GPCR1,
+ ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1,
+ ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1);
+
+ return 0;
+}
+
+struct _coeff_div {
+ u16 fs;
+ u16 regvalue;
+};
+
+/* codec hifi mclk (after PLL) clock divider coefficients */
+/* values inspired from column BCLK=32Fs of Appendix A table */
+static const struct _coeff_div coeff_div[] = {
+ {512*1, 0x3075},
+};
+
+static int get_coeff(struct snd_soc_codec *codec, int rate)
+{
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_div); i++) {
+ if (coeff_div[i].fs * rate == alc5632->sysclk)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*
+ * Clock after PLL and dividers
+ */
+static int alc5632_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case 8192000:
+ case 11289600:
+ case 12288000:
+ case 16384000:
+ case 16934400:
+ case 18432000:
+ case 22579200:
+ case 24576000:
+ alc5632->sysclk = freq;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int alc5632_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ iface = ALC5632_DAI_SDP_MASTER_MODE;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ iface = ALC5632_DAI_SDP_SLAVE_MODE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= ALC5632_DAI_I2S_DF_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= ALC5632_DAI_I2S_DF_LEFT;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= ALC5632_DAI_I2S_DF_PCM_A;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= ALC5632_DAI_I2S_DF_PCM_B;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return snd_soc_write(codec, ALC5632_DAI_CONTROL, iface);
+}
+
+static int alc5632_pcm_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_codec *codec = rtd->codec;
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ int coeff, rate;
+ u16 iface;
+
+ iface = snd_soc_read(codec, ALC5632_DAI_CONTROL);
+ iface &= ~ALC5632_DAI_I2S_DL_MASK;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ iface |= ALC5632_DAI_I2S_DL_16;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= ALC5632_DAI_I2S_DL_20;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= ALC5632_DAI_I2S_DL_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set iface & srate */
+ snd_soc_write(codec, ALC5632_DAI_CONTROL, iface);
+ rate = params_rate(params);
+ coeff = get_coeff(codec, rate);
+ if (coeff < 0)
+ return -EINVAL;
+
+ coeff = coeff_div[coeff].regvalue;
+ dev_dbg(codec->dev, "%s: sysclk=%d,rate=%d,coeff=0x%04x\n",
+ __func__, alc5632->sysclk, rate, coeff);
+ snd_soc_write(codec, ALC5632_DAC_CLK_CTRL1, coeff);
+
+ return 0;
+}
+
+static int alc5632_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 hp_mute = ALC5632_MISC_HP_DEPOP_MUTE_L | ALC5632_MISC_HP_DEPOP_MUTE_R;
+ u16 mute_reg = snd_soc_read(codec, ALC5632_MISC_CTRL) & ~hp_mute;
+
+ if (mute)
+ mute_reg |= hp_mute;
+
+ return snd_soc_write(codec, ALC5632_MISC_CTRL, mute_reg);
+}
+
+#define ALC5632_ADD2_POWER_EN ( ALC5632_PWR_ADD2_VREF )
+
+#define ALC5632_ADD3_POWER_EN ( \
+ ALC5632_PWR_ADD3_MIC1_BOOST_AD )
+
+#define ALC5632_ADD1_POWER_EN \
+ ( ALC5632_PWR_ADD1_SPK_AMP_EN \
+ | ALC5632_PWR_ADD1_DAC_REF \
+ | ALC5632_PWR_ADD1_DAC_L_EN \
+ | ALC5632_PWR_ADD1_DAC_R_EN \
+ | ALC5632_PWR_ADD1_SOFTGEN_EN \
+ | ALC5632_PWR_ADD1_MAIN_I2S_EN | ALC5632_PWR_ADD1_HP_OUT_AMP \
+ | ALC5632_PWR_ADD1_HP_OUT_ENH_AMP \
+ | ALC5632_PWR_ADD1_MAIN_BIAS )
+
+static void enable_power_depop(struct snd_soc_codec *codec)
+{
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD1,
+ ALC5632_PWR_ADD1_SOFTGEN_EN,
+ ALC5632_PWR_ADD1_SOFTGEN_EN);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD3, ALC5632_ADD3_POWER_EN);
+
+ snd_soc_update_bits(codec, ALC5632_MISC_CTRL,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN);
+
+ msleep(500);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2, ALC5632_ADD2_POWER_EN);
+
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1, ALC5632_ADD1_POWER_EN);
+
+ /* disable HP Depop2 */
+ snd_soc_update_bits(codec, ALC5632_MISC_CTRL,
+ ALC5632_MISC_HP_DEPOP_MODE2_EN,
+ 0);
+
+}
+
+static int alc5632_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ enable_power_depop(codec);
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* everything off except vref/vmid, */
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1,
+ ALC5632_PWR_ADD1_MAIN_BIAS);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2,
+ ALC5632_PWR_ADD2_VREF);
+ break;
+ case SND_SOC_BIAS_OFF:
+ /* everything off, dac mute, inactive */
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD2, 0);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD3, 0);
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1, 0);
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define ALC5632_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \
+ | SNDRV_PCM_FMTBIT_S24_LE \
+ | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops alc5632_dai_ops = {
+ .hw_params = alc5632_pcm_hw_params,
+ .digital_mute = alc5632_mute,
+ .set_fmt = alc5632_set_dai_fmt,
+ .set_sysclk = alc5632_set_dai_sysclk,
+ .set_pll = alc5632_set_dai_pll,
+};
+
+static struct snd_soc_dai_driver alc5632_dai = {
+ .name = "alc5632-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5632_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rate_min = 8000,
+ .rate_max = 48000,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = ALC5632_FORMATS,},
+
+ .ops = &alc5632_dai_ops,
+};
+
+static int alc5632_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int alc5632_resume(struct snd_soc_codec *codec)
+{
+ int i, step = codec->driver->reg_cache_step;
+ u16 *cache = codec->reg_cache;
+
+ /* Sync reg_cache with the hardware */
+ for (i = 2 ; i < codec->driver->reg_cache_size ; i += step)
+ snd_soc_write(codec, i, cache[i]);
+
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* charge alc5632 caps */
+ if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) {
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ codec->dapm.bias_level = SND_SOC_BIAS_ON;
+ alc5632_set_bias_level(codec, codec->dapm.bias_level);
+ }
+
+ return 0;
+}
+
+#define ALC5632_REC_UNMUTE ( ALC5632_ADC_REC_MIC2 | \
+ ALC5632_ADC_REC_LINE_IN | ALC5632_ADC_REC_AUX | \
+ ALC5632_ADC_REC_HP | ALC5632_ADC_REC_SPK | \
+ ALC5632_ADC_REC_MONOMIX )
+
+#define ALC5632_MIC_ROUTE ( ALC5632_MIC_ROUTE_HP | \
+ ALC5632_MIC_ROUTE_SPK | ALC5632_MIC_ROUTE_MONOMIX )
+
+#define ALC5632_PWR_DEFAULT ( ALC5632_PWR_ADC_STATUS | \
+ ALC5632_PWR_DAC_STATUS | ALC5632_PWR_AMIX_STATUS | \
+ ALC5632_PWR_VREF_STATUS )
+
+#define ALC5632_ADC_REC_GAIN_COMP(x) (int)(( x - ALC5632_ADC_REC_GAIN_BASE ) \
+ / ALC5632_ADC_REC_GAIN_STEP)
+
+#define ALC5632_MIC_BOOST_COMP(x) (int)( x / ALC5632_MIC_BOOST_STEP )
+
+#define ALC5632_SPK_OUT_VOL_COMP(x) (int)( x / ALC5632_SPK_OUT_VOL_STEP )
+
+static int alc5632_probe(struct snd_soc_codec *codec)
+{
+ struct alc5632_priv *alc5632 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ dev_dbg(codec->dev, "ALC5632 Audio Codec Version %s\n", ALC5632_VERSION);
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, alc5632->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ alc5632_reset(codec);
+
+ /* power on device */
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (alc5632->add_ctrl) {
+ snd_soc_write(codec, ALC5632_PWR_MANAG_ADD1,
+ alc5632->add_ctrl);
+ }
+
+ /* spk amp pwr enable 3A | 0x0400 @ 3A */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD1,
+ 0, ALC5632_PWR_ADD1_SPK_AMP_EN);
+
+ /* "normal" mode: 0 @ 26 */
+ snd_soc_write(codec, ALC5632_PWR_DOWN_CTRL_STATUS, 0);
+
+ /* power on VREF on all analog circuits 0x2000 @ 3C */
+ snd_soc_update_bits(codec, ALC5632_PWR_MANAG_ADD2,
+ 0, ALC5632_PWR_ADD2_VREF);
+
+ /* enable slave mode 0x8000 @ 34 */
+ snd_soc_write(codec, ALC5632_DAI_CONTROL, ALC5632_DAI_SDP_SLAVE_MODE);
+
+ switch (alc5632->id) {
+ case 0x5c:
+ snd_soc_add_controls(codec, alc5632_vol_snd_controls,
+ ARRAY_SIZE(alc5632_vol_snd_controls));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_add_controls(codec, alc5632_snd_controls,
+ ARRAY_SIZE(alc5632_snd_controls));
+
+ snd_soc_dapm_new_controls(dapm, alc5632_dapm_widgets,
+ ARRAY_SIZE(alc5632_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ return ret;
+}
+
+/* power down chip */
+static int alc5632_remove(struct snd_soc_codec *codec)
+{
+ alc5632_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_alc5632 = {
+ .probe = alc5632_probe,
+ .remove = alc5632_remove,
+ .suspend = alc5632_suspend,
+ .resume = alc5632_resume,
+ .set_bias_level = alc5632_set_bias_level,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = alc5632_reg_defaults,
+ .reg_cache_size = ARRAY_SIZE(alc5632_reg_defaults),
+ .volatile_register = alc5632_volatile_register,
+};
+
+/*
+ * alc5632 2 wire address is determined by A1 pin
+ * state during powerup.
+ * low = 0x1a
+ * high = 0x1b
+ */
+static int alc5632_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct alc5632_platform_data *pdata;
+ struct alc5632_priv *alc5632;
+ int ret, vid1, vid2;
+
+ dev_dbg(&client->dev, "in i2c probe...\n");
+
+ vid1 = i2c_smbus_read_word_data(client, ALC5632_VENDOR_ID1);
+ if (vid1 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ } else {
+ dev_err(&client->dev, "got vid1: %x\n", vid1);
+ }
+ vid1 = ((vid1 & 0xff) << 8) | (vid1 >> 8);
+
+ vid2 = i2c_smbus_read_word_data(client, ALC5632_VENDOR_ID2);
+ if (vid2 < 0) {
+ dev_err(&client->dev, "failed to read I2C\n");
+ return -EIO;
+ } else {
+ dev_err(&client->dev, "got vid2: %x\n", vid2);
+ }
+ vid2 = (vid2 & 0xff);
+
+ if ((vid1 != 0x10ec) || (vid2 != id->driver_data)) {
+ dev_err(&client->dev, "unknown or wrong codec\n");
+ dev_err(&client->dev, "Expected %x:%lx, got %x:%x\n",
+ 0x10ec, id->driver_data,
+ vid1, vid2);
+ return -ENODEV;
+ }
+
+ dev_dbg(&client->dev, "Found codec: ALC5632 (ID: %x)\n", vid2);
+
+ alc5632 = kzalloc(sizeof(struct alc5632_priv), GFP_KERNEL);
+ if (alc5632 == NULL)
+ return -ENOMEM;
+
+ pdata = client->dev.platform_data;
+ if (pdata) {
+ alc5632->add_ctrl = pdata->add_ctrl;
+ alc5632->jack_det_ctrl = pdata->jack_det_ctrl;
+ }
+
+ alc5632->id = vid2;
+ switch (alc5632->id) {
+ case 0x5c:
+ alc5632_dai.name = "alc5632-hifi";
+ break;
+ default:
+ kfree(alc5632);
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, alc5632);
+ alc5632->control_data = client;
+ alc5632->control_type = SND_SOC_I2C;
+ mutex_init(&alc5632->mutex);
+
+ ret = snd_soc_register_codec(&client->dev,
+ &soc_codec_device_alc5632, &alc5632_dai, 1);
+ if (ret != 0) {
+ dev_err(&client->dev, "Failed to register codec: %d\n", ret);
+ kfree(alc5632);
+ }
+
+ return ret;
+}
+
+static int alc5632_i2c_remove(struct i2c_client *client)
+{
+ struct alc5632_priv *alc5632 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+ kfree(alc5632);
+ return 0;
+}
+
+static const struct i2c_device_id alc5632_i2c_table[] = {
+ {"alc5632", 0x5c},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, alc5632_i2c_table);
+
+/* i2c codec control layer */
+static struct i2c_driver alc5632_i2c_driver = {
+ .driver = {
+ .name = "alc5632",
+ .owner = THIS_MODULE,
+ },
+ .probe = alc5632_i2c_probe,
+ .remove = __devexit_p(alc5632_i2c_remove),
+ .id_table = alc5632_i2c_table,
+};
+
+static int __init alc5632_modinit(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&alc5632_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "%s: can't add i2c driver", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+module_init(alc5632_modinit);
+
+static void __exit alc5632_modexit(void)
+{
+ i2c_del_driver(&alc5632_i2c_driver);
+}
+module_exit(alc5632_modexit);
+
+MODULE_DESCRIPTION("ASoC ALC5632 driver");
+MODULE_AUTHOR("Leon Romanovsky <leon(a)leon.nu>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/alc5632.h b/sound/soc/codecs/alc5632.h
new file mode 100644
index 0000000..b28ae24
--- /dev/null
+++ b/sound/soc/codecs/alc5632.h
@@ -0,0 +1,233 @@
+/*
+* alc5632.h -- ALC5632 ALSA SoC Audio Codec
+*
+* Copyright (C) 2011 The AC100 Kernel Team <ac100(a)lists.lauchpad.net>
+*
+* Authors: Leon Romanovsky <leon(a)leon.nu>
+* Andrey Danin <danindrey(a)mail.ru>
+* Ilya Petrov <ilya.muromec(a)gmail.com>
+* Marc Dietrich <marvin24(a)gmx.de>
+*
+* Based on alc5623.h by Arnaud Patard
+*
+* 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 _ALC5632_H
+#define _ALC5632_H
+
+#define ALC5632_RESET 0x00
+/* speaker output vol 2 2 */
+/* line output vol 4 2 */
+/* HP output vol 4 0 4 */
+#define ALC5632_SPK_OUT_VOL 0x02 // spe out vol
+#define ALC5632_SPK_OUT_VOL_STEP 1.5
+#define ALC5632_HP_OUT_VOL 0x04 // hp out vol
+#define ALC5632_AUX_OUT_VOL 0x06 // aux out vol
+#define ALC5632_PHONE_IN_VOL 0x08 // phone in vol
+#define ALC5632_LINE_IN_VOL 0x0A // line in vol
+#define ALC5632_STEREO_DAC_IN_VOL 0x0C // stereo dac in vol
+#define ALC5632_MIC_VOL 0x0E // mic in vol
+#define ALC5632_MIC_ROUTING_CTRL 0x10 // stero dac/mic routing
+#define ALC5632_MIC_ROUTE_MONOMIX (1 << 0)
+#define ALC5632_MIC_ROUTE_SPK (1 << 1)
+#define ALC5632_MIC_ROUTE_HP (1 << 2)
+
+#define ALC5632_ADC_REC_GAIN 0x12 // rec gain
+#define ALC5632_ADC_REC_GAIN_RANGE 0x1F1F
+#define ALC5632_ADC_REC_GAIN_BASE -16.5
+#define ALC5632_ADC_REC_GAIN_STEP 1.5
+
+#define ALC5632_ADC_REC_MIXER 0x14 // mixer control
+#define ALC5632_ADC_REC_MIC1 (1 << 6)
+#define ALC5632_ADC_REC_MIC2 (1 << 5)
+#define ALC5632_ADC_REC_LINE_IN (1 << 4)
+#define ALC5632_ADC_REC_AUX (1 << 3)
+#define ALC5632_ADC_REC_HP (1 << 2)
+#define ALC5632_ADC_REC_SPK (1 << 1)
+#define ALC5632_ADC_REC_MONOMIX (1 << 0)
+
+#define ALC5632_VOICE_DAC_VOL 0x18 // voice dac vol
+/* ALC5632_OUTPUT_MIXER_CTRL : */
+/* same remark as for reg 2 line vs speaker */
+#define ALC5632_OUTPUT_MIXER_CTRL 0x1C // out mix ctrl
+#define ALC5632_OUTPUT_MIXER_RP (1 << 14)
+#define ALC5632_OUTPUT_MIXER_WEEK (1 << 12)
+#define ALC5632_OUTPUT_MIXER_HP (1 << 10)
+#define ALC5632_OUTPUT_MIXER_AUX_SPK (2 << 6)
+#define ALC5632_OUTPUT_MIXER_AUX_HP_LR (1 << 6)
+#define ALC5632_OUTPUT_MIXER_HP_R (1 << 8)
+#define ALC5632_OUTPUT_MIXER_HP_L (1 << 9)
+
+#define ALC5632_MIC_CTRL 0x22 // mic phone ctrl
+#define ALC5632_MIC_BOOST_BYPASS 0
+#define ALC5632_MIC_BOOST_20DB 1
+#define ALC5632_MIC_BOOST_30DB 2
+#define ALC5632_MIC_BOOST_40DB 3
+
+#define ALC5632_DIGI_BOOST_CTRL 0x24 // digi mic / bost ctl
+#define ALC5632_MIC_BOOST_RANGE 7
+#define ALC5632_MIC_BOOST_STEP 6
+#define ALC5632_PWR_DOWN_CTRL_STATUS 0x26
+#define ALC5632_PWR_VREF_STATUS (1 << 3)
+#define ALC5632_PWR_AMIX_STATUS (1 << 2)
+#define ALC5632_PWR_DAC_STATUS (1 << 1)
+#define ALC5632_PWR_ADC_STATUS (1 << 0)
+#define ALC5632_DAC_FUNC_SELECT 0x2E // stereo/voice DAC / stereo adc func ctrl
+
+#define ALC5632_DAI_CONTROL 0x34 // Main serial data port ctrl (i2s)
+
+#define ALC5632_DAI_SDP_MASTER_MODE (0 << 15)
+#define ALC5632_DAI_SDP_SLAVE_MODE (1 << 15)
+#define ALC5632_DAI_SADLRCK_MODE (1 << 14)
+#define ALC5632_DAI_MAIN_I2S_SYSCLK_SEL (1 << 8) // 0:voice, 1:main
+#define ALC5632_DAI_MAIN_I2S_BCLK_POL_CTRL (1 << 7)
+#define ALC5632_DAI_MAIN_I2S_LRCK_INV (1 << 6) // 0:normal, 1:invert
+#define ALC5632_DAI_I2S_DL_MASK (3 << 2)
+#define ALC5632_DAI_I2S_DL_8 (3 << 2)
+#define ALC5632_DAI_I2S_DL_24 (2 << 2)
+#define ALC5632_DAI_I2S_DL_20 (1 << 2)
+#define ALC5632_DAI_I2S_DL_16 (0 << 2)
+#define ALC5632_DAI_I2S_DF_MASK (3 << 0)
+#define ALC5632_DAI_I2S_DF_PCM_B (3 << 0)
+#define ALC5632_DAI_I2S_DF_PCM_A (2 << 0)
+#define ALC5632_DAI_I2S_DF_LEFT (1 << 0)
+#define ALC5632_DAI_I2S_DF_I2S (0 << 0)
+
+#define ALC5632_DAI_CONTROL2 0x36 // extend serial data port control (VoDAC_i2c/pcm)
+
+#define ALC5632_DAI_VOICE_PCM_ENABLE (1 << 15) // 0:gpio func, 1:voice pcm
+#define ALC5632_DAI_VOICE_MODE_SEL (1 << 14) // 0:master, 1:slave
+#define ALC5632_DAI_HPF_CLK_CTRL (1 << 13) // 0:disable, 1:enable
+#define ALC5632_DAI_VOICE_I2S_SYSCLK_SEL (1 << 8) // 0:main, 1:voice
+#define ALC5632_DAI_VOICE_VBCLK_SYSCLK_SEL (1 << 7) // 0:normal, 1:invert
+#define ALC5632_DAI_VOICE_I2S_LR_INV (1 << 6) // 0:normal, 1:invert
+#define ALC5632_DAI_VOICE_DL_MASK (3 << 2)
+#define ALC5632_DAI_VOICE_DL_16 (0 << 2)
+#define ALC5632_DAI_VOICE_DL_20 (1 << 2)
+#define ALC5632_DAI_VOICE_DL_24 (2 << 2)
+#define ALC5632_DAI_VOICE_DL_8 (3 << 2)
+#define ALC5632_DAI_VOICE_DF_MASK (3 << 0)
+#define ALC5632_DAI_VOICE_DF_I2S (0 << 0)
+#define ALC5632_DAI_VOICE_DF_LEFT (1 << 0)
+#define ALC5632_DAI_VOICE_DF_PCM_A (2 << 0)
+#define ALC5632_DAI_VOICE_DF_PCM_B (3 << 0)
+
+#define ALC5632_PWR_MANAG_ADD1 0x3A
+#define ALC5632_PWR_ADD1_DAC_L_EN (1 << 15)
+#define ALC5632_PWR_ADD1_DAC_R_EN (1 << 14)
+#define ALC5632_PWR_ADD1_ZERO_CROSS (1 << 13)
+#define ALC5632_PWR_ADD1_MAIN_I2S_EN (1 << 11)
+#define ALC5632_PWR_ADD1_SPK_AMP_EN (1 << 10)
+#define ALC5632_PWR_ADD1_HP_OUT_AMP (1 << 9)
+#define ALC5632_PWR_ADD1_HP_OUT_ENH_AMP (1 << 8)
+#define ALC5632_PWR_ADD1_VOICE_DAC_MIX (1 << 7)
+#define ALC5632_PWR_ADD1_SOFTGEN_EN (1 << 6)
+#define ALC5632_PWR_ADD1_MIC1_SHORT_CURR (1 << 5)
+#define ALC5632_PWR_ADD1_MIC2_SHORT_CURR (1 << 4)
+#define ALC5632_PWR_ADD1_MIC1_EN (1 << 3)
+#define ALC5632_PWR_ADD1_MIC2_EN (1 << 2)
+#define ALC5632_PWR_ADD1_MAIN_BIAS (1 << 1)
+#define ALC5632_PWR_ADD1_DAC_REF (1 << 0)
+
+#define ALC5632_PWR_MANAG_ADD2 0x3C
+#define ALC5632_PWR_ADD2_PLL1 (1 << 15)
+#define ALC5632_PWR_ADD2_PLL2 (1 << 14)
+#define ALC5632_PWR_ADD2_VREF (1 << 13)
+#define ALC5632_PWR_ADD2_OVT_DET (1 << 12)
+#define ALC5632_PWR_ADD2_VOICE_DAC (1 << 10)
+#define ALC5632_PWR_ADD2_L_DAC_CLK (1 << 9)
+#define ALC5632_PWR_ADD2_R_DAC_CLK (1 << 8)
+#define ALC5632_PWR_ADD2_L_ADC_CLK_GAIN (1 << 7)
+#define ALC5632_PWR_ADD2_R_ADC_CLK_GAIN (1 << 6)
+#define ALC5632_PWR_ADD2_L_HP_MIXER (1 << 5)
+#define ALC5632_PWR_ADD2_R_HP_MIXER (1 << 4)
+#define ALC5632_PWR_ADD2_SPK_MIXER (1 << 3)
+#define ALC5632_PWR_ADD2_MONO_MIXER (1 << 2)
+#define ALC5632_PWR_ADD2_L_ADC_REC_MIXER (1 << 1)
+#define ALC5632_PWR_ADD2_R_ADC_REC_MIXER (1 << 0)
+
+#define ALC5632_PWR_MANAG_ADD3 0x3E
+#define ALC5632_PWR_ADD3_AUXOUT_VOL (1 << 14)
+#define ALC5632_PWR_ADD3_SPK_L_OUT (1 << 13)
+#define ALC5632_PWR_ADD3_SPK_R_OUT (1 << 12)
+#define ALC5632_PWR_ADD3_HP_L_OUT_VOL (1 << 11)
+#define ALC5632_PWR_ADD3_HP_R_OUT_VOL (1 << 10)
+#define ALC5632_PWR_ADD3_LINEIN_L_VOL (1 << 7)
+#define ALC5632_PWR_ADD3_LINEIN_R_VOL (1 << 6)
+#define ALC5632_PWR_ADD3_AUXIN_VOL (1 << 5)
+#define ALC5632_PWR_ADD3_AUXIN_MIX (1 << 4)
+#define ALC5632_PWR_ADD3_MIC1_VOL (1 << 3)
+#define ALC5632_PWR_ADD3_MIC2_VOL (1 << 2)
+#define ALC5632_PWR_ADD3_MIC1_BOOST_AD (1 << 1)
+#define ALC5632_PWR_ADD3_MIC2_BOOST_AD (1 << 0)
+
+#define ALC5632_GPCR1 0x40
+#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_PLL1 (1 << 15)
+#define ALC5632_GPCR1_CLK_SYS_SRC_SEL_MCLK (0 << 15)
+#define ALC5632_GPCR1_DAC_HI_FLT_EN (1 << 10)
+#define ALC5632_GPCR1_SPK_AMP_CTRL (7 << 1)
+#define ALC5632_GPCR1_VDD_100 (5 << 1)
+#define ALC5632_GPCR1_VDD_125 (4 << 1)
+#define ALC5632_GPCR1_VDD_150 (3 << 1)
+#define ALC5632_GPCR1_VDD_175 (2 << 1)
+#define ALC5632_GPCR1_VDD_200 (1 << 1)
+#define ALC5632_GPCR1_VDD_225 (0 << 1)
+
+#define ALC5632_GPCR2 0x42
+#define ALC5632_GPCR2_PLL1_SOUR_SEL (3 << 12)
+#define ALC5632_PLL_FR_MCLK (0 << 12)
+#define ALC5632_PLL_FR_BCLK (2 << 12)
+#define ALC5632_PLL_FR_VBCLK (3 << 12)
+#define ALC5632_GPCR2_CLK_PLL_PRE_DIV1 (0 << 0)
+
+#define ALC5632_PLL1_CTRL 0x44
+#define ALC5632_PLL1_CTRL_N_VAL(n) (((n) & 0x0f) << 8)
+#define ALC5632_PLL1_M_BYPASS (1 << 7)
+#define ALC5632_PLL1_CTRL_K_VAL(k) (((k) & 0x07) << 4)
+#define ALC5632_PLL1_CTRL_M_VAL(m) (((m) & 0x0f) << 0)
+
+#define ALC5632_PLL2_CTRL 0x46
+#define ALC5632_PLL2_EN (1 << 15)
+#define ALC5632_PLL2_RATIO (0 << 15)
+
+#define ALC5632_GPIO_PIN_CONFIG 0x4C
+#define ALC5632_GPIO_PIN_POLARITY 0x4E
+#define ALC5632_GPIO_PIN_STICKY 0x50
+#define ALC5632_GPIO_PIN_WAKEUP 0x52
+#define ALC5632_GPIO_PIN_STATUS 0x54
+#define ALC5632_GPIO_PIN_SHARING 0x56
+#define ALC5632_OVER_CURR_STATUS 0x58
+#define ALC5632_SOFTVOL_CTRL 0x5A
+#define ALC5632_GPIO_OUPUT_PIN_CTRL 0x5C
+
+#define ALC5632_MISC_CTRL 0x5E
+#define ALC5632_MISC_DISABLE_FAST_VREG (1 << 15)
+#define ALC5632_MISC_AVC_TRGT_SEL (3 << 12)
+#define ALC5632_MISC_AVC_TRGT_RIGHT (1 << 12)
+#define ALC5632_MISC_AVC_TRGT_LEFT (2 << 12)
+#define ALC5632_MISC_AVC_TRGT_BOTH (3 << 12)
+#define ALC5632_MISC_HP_DEPOP_MODE1_EN (1 << 9)
+#define ALC5632_MISC_HP_DEPOP_MODE2_EN (1 << 8)
+#define ALC5632_MISC_HP_DEPOP_MUTE_L (1 << 7)
+#define ALC5632_MISC_HP_DEPOP_MUTE_R (1 << 6)
+#define ALC5632_MISC_HP_DEPOP_MUTE (1 << 5)
+#define ALC5632_MISC_GPIO_WAKEUP_CTRL (1 << 1)
+#define ALC5632_MISC_IRQOUT_INV_CTRL (1 << 0)
+
+#define ALC5632_DAC_CLK_CTRL1 0x60
+#define ALC5632_DAC_CLK_CTRL2 0x62
+#define ALC5632_DAC_CLK_CTRL2_DIV1_2 (1 << 0)
+#define ALC5632_VOICE_DAC_PCM_CLK_CTRL1 0x64
+#define ALC5632_PSEUDO_SPATIAL_CTRL 0x68
+#define ALC5632_HID_CTRL_INDEX 0x6A
+#define ALC5632_HID_CTRL_DATA 0x6C
+#define ALC5632_EQ_CTRL 0x6E
+
+/* undocumented */
+#define ALC5632_VENDOR_ID1 0x7C
+#define ALC5632_VENDOR_ID2 0x7E
+
+#endif
--
1.7.3.4
1
0
Re: [alsa-devel] ALSA PulseAudio plugin: snd_pcm_rewindable() returns >0, but snd_pcm_rewind() is broken
by Nikolay Nikolov 25 Oct '11
by Nikolay Nikolov 25 Oct '11
25 Oct '11
On 10/24/2011 06:34 PM, Pierre-Louis Bossart wrote:
> You want to avoid rewinding completely. Your audio hardware might have
> prefetched data with the DMA subsystem. Rewinding completely might result in
> an inconsistent configuration and possibly underflows. If you look at the
> PulseAudio code, we've introduced some thresholds beyond which we don't
> rewind (128 bytes or 1ms off the top of my head).
> You might argue that snd_pcm_rewindable is broken, but it's somewhat
> difficult to fix as the amount of prefetched data isn't modeled in the
> driver and it's very much hardware-specific. Using a less aggressive
> approach works fine on most hardware.
> -Pierre
Yes, I figured this out while experimenting with real hw alsa devices
and that's why I actually rewind snd_pcm_rewindable() minus some
threshold, that can be configured. However with the alsa pulseaudio
plugin, rewinding seems to be broken completely, even with a very large
threshold.
2
1
[alsa-devel] ALSA PulseAudio plugin: snd_pcm_rewindable() returns >0, but snd_pcm_rewind() is broken
by Nikolay Nikolov 24 Oct '11
by Nikolay Nikolov 24 Oct '11
24 Oct '11
Hi all,
I submitted a bug report for this here:
https://bugtrack.alsa-project.org/alsa-bug/view.php?id=5464
I got a comment, which said I should post this on the mailing list, so
I'm doing this now:
I'm writing a program that tries to use the snd_pcm_rewind() function if
it's available and I'm having serious trouble with the alsa-pulseaudio
plugin.
I'm using the max buffer size, which is 1048576 frames (4MB buffer,
16-bit stereo). The buffer is almost full when I try to rewind. I first
call snd_pcm_rewindable() and it returns a greater than zero value. Then
I call snd_pcm_rewind() with this value and it returns success, i.e. a
positive value of frames actually rewound. I then call snd_pcm_writei
with the number of frames rewound and I get a weird pause (several
seconds) in the audio on the next poll. Audio then continues from the
middle; the beginning of the audio that was rewritten immediately after
rewind() has been dropped. Also, pulseaudio reports several times the
following error:
protocol-native.c: Failed to push data into queue
After looking at the alsa-lib and alsa-plugins sources, it seems that
snd_pcm_rewind() for ioplug devices only updates an internal pointer and
doesn't notify pulseaudio, so on the next write we just try to push too
much data to the pulseaudio stream. After googling a bit, I found this
email from 17 Feb 2010 from Lennart Poettering, which says that
snd_pcm_rewind() is broken for the alsa-pulseaudio plugin.
http://ns.spinics.net/lists/alsa-devel/msg31536.html
Also, this mail:
http://lists.freedesktop.org/archives/pulseaudio-bugs/2010-July/004051.html
says that snd_pcm_rewindable() should always return 0 frames on ioplug
devices, and if that's not the case, then it's a bug in alsa.
So, if I'm getting this right, either snd_pcm_rewindable() should be
fixed to return 0, or snd_pcm_rewind() should be fixed to work properly
with the alsa-pulseaudio plugin.
Thanks,
Nikolay
2
1