Alsa-devel
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 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
- 16 participants
- 51106 discussions
This adds support for Moorestown ALSA Sound card driver.
This is an ALSA driver for supporting PCM playback/capture
in traditional ALSA way. Anyone who chooses not to use DSP
for decoding/encoding can use ALSA path to
play/capture (in non low power mode).
This driver registers the control interface and PCM
interface with the LPE driver which finally sends it to
the hardware. This driver allows any subsystem in OS
which wants to use the audio-subsystems to be routed
through the ALSA
Signed-off-by: Vinod Koul <vinod.koul(a)intel.com>
Signed-off-by: Harsha Priya <priya.harsha(a)intel.com>
new file: sound/pci/sst/intelmid.c
---
sound/pci/sst/intelmid.c | 1761 ++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1761 insertions(+), 0 deletions(-)
create mode 100644 sound/pci/sst/intelmid.c
diff --git a/sound/pci/sst/intelmid.c b/sound/pci/sst/intelmid.c
new file mode 100644
index 0000000..28a6dcc
--- /dev/null
+++ b/sound/pci/sst/intelmid.c
@@ -0,0 +1,1761 @@
+/*
+ * intelmid.c - Intel Sound card driver for MID
+ *
+ * Copyright (C) 2008-09 Intel Corp
+ * Authors: Harsha Priya <priya.harsha(a)intel.com>
+ * Vinod Koul <vinod.koul(a)intel.com>
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * ALSA driver for Intel MID sound card chipset
+ */
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/pcm-indirect.h>
+#include <sound/intel_sst.h>
+#include <sound/intel_sst_ioctl.h>
+#include "intelmid_snd_control.h"
+#include "intelmid.h"
+#include "intelmid_pvt.h"
+#include <linux/spi/spi.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/sched.h>
+#include <linux/gpe.h>
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+MODULE_AUTHOR("Harsha Priya <priya.harsha(a)intel.com>");
+MODULE_AUTHOR("Vinod Koul <vinod.koul(a)intel.com>");
+MODULE_DESCRIPTION("Intel MAD Sound card driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{Intel,Intel_MAD}");
+
+static int card_index = SNDRV_DEFAULT_IDX1;/* Index 0-MAX */
+static char *card_id = SNDRV_DEFAULT_STR1; /* ID for this card */
+static int vendor_id;
+static unsigned int audio_event_seqnum;
+static int cum_bytes;
+
+
+#ifdef FULL_CTRL
+static char *out_names[] = {"Headphones",
+ "Internal speakers",
+ "EarPiece"};
+static char *in_names[] = { "DMIC",
+ "MIC1",
+ "MIC2",
+ "Line_in"};
+#endif
+static struct genl_family audio_event_genl_family = {
+ .id = GENL_ID_GENERATE,
+ .name = "audio events",
+ .version = 0x01,
+ .maxattr = 0,
+};
+
+static struct genl_multicast_group audio_event_mcgrp = {
+ .name = "audio_group",
+};
+
+/*control path functionalities*/
+/**
+* snd_intelmad_mute_info - provides information about the mute controls
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_mute_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ WARN_ON(!uinfo);
+ WARN_ON(!kcontrol);
+
+ /*set up the mute as a boolean mono control with min-max values*/
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = MONO_CNTL;
+ uinfo->value.integer.min = MIN_MUTE;
+ uinfo->value.integer.max = MAX_MUTE;
+ return 0;
+}
+
+#ifdef FULL_CTRL
+/**
+* snd_intelmad_volume_line_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_line_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].linein_vol_max,
+ ctrl_val[vendor_id].linein_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_mic1_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_mic1_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].mic1_vol_max,
+ ctrl_val[vendor_id].mic1_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_mic2_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_mic2_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].mic2_vol_max,
+ ctrl_val[vendor_id].mic2_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_dmic_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_dmic_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].dmic_vol_max,
+ ctrl_val[vendor_id].dmic_vol_min);
+ return 0;
+}
+#endif
+/**
+* snd_intelmad_hp_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_hp_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].hp_vol_max,
+ ctrl_val[vendor_id].hp_vol_min);
+ return 0;
+}
+
+#ifdef FULL_CTRL
+/**
+* snd_intelmad_speaker_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_speaker_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, STEREO_CNTL,
+ ctrl_val[vendor_id].speaker_vol_max,
+ ctrl_val[vendor_id].speaker_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_earpiece_volume_info - provides information about the volume control
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the control's info need
+* to be filled
+* This function is called when a mixer application requests for control's info
+*/
+static int snd_intelmad_earpiece_volume_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ snd_intelmad_volume_info(uinfo, MONO_CNTL,
+ ctrl_val[vendor_id].earpiece_vol_max,
+ ctrl_val[vendor_id].earpiece_vol_min);
+ return 0;
+}
+
+/**
+* snd_intelmad_device_info - provides information about the devices available
+* @kcontrol: pointer to the control
+* @uinfo: pointer to the structure where the devices's info need
+* to be filled
+* This function is called when a mixer application requests for device's info
+*/
+static int snd_intelmad_device_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ WARN_ON(!kcontrol);
+ WARN_ON(!uinfo);
+ /*setup device select as drop down controls with different values*/
+ if (OUTPUT_SEL == kcontrol->id.numid)
+ uinfo->value.enumerated.items = ARRAY_SIZE(out_names);
+ else
+ uinfo->value.enumerated.items = ARRAY_SIZE(in_names);
+ uinfo->count = MONO_CNTL;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = 1;
+ if (OUTPUT_SEL == kcontrol->id.numid)
+ strncpy(uinfo->value.enumerated.name,
+ out_names[uinfo->value.enumerated.item],
+ strlen(out_names[uinfo->value.enumerated.item]));
+ else
+ strncpy(uinfo->value.enumerated.name,
+ in_names[uinfo->value.enumerated.item],
+ strlen(in_names[uinfo->value.enumerated.item]));
+ return 0;
+}
+#endif
+
+/**
+* snd_intelmad_volume_get - gets the current volume for the control
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info need
+* to be filled
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_volume_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ int ret_val = 0, cntl_list[2] = {0,}, value = 0;
+ struct snd_intelmad *intelmaddata = NULL;
+ struct snd_pmic_ops *scard_ops = NULL;
+
+ sst_dbg("called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case HEADPHONES_VOL:
+ cntl_list[0] = PMIC_SND_RIGHT_HP_VOL;
+ cntl_list[1] = PMIC_SND_LEFT_HP_VOL;
+ break;
+#ifdef FULL_CTRL
+ case INTERNAL_SPEAKERS_VOL:
+ cntl_list[0] = PMIC_SND_RIGHT_SPEAKER_VOL;
+ cntl_list[1] = PMIC_SND_LEFT_SPEAKER_VOL;
+ break;
+ case LINE_IN_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_RIGHT_LINE_IN;
+ cntl_list[1] = PMIC_SND_INPUT_VOL_LEFT_LINE_IN;
+ break;
+ case EARPIECE_VOL:
+ cntl_list[0] = PMIC_SND_MONO_EARPIECE_VOL;
+ break;
+ case MIC1_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_MIC1;
+ break;
+ case MIC2_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_MIC2;
+ break;
+ case DMIC_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_DMIC;
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->get_vol(cntl_list[0], &value);
+ uval->value.integer.value[0] = value;
+
+ if (0 != ret_val)
+ return ret_val;
+#ifdef FULL_CTRL
+ if (HEADPHONES_VOL == kcontrol->id.numid
+ || INTERNAL_SPEAKERS_VOL == kcontrol->id.numid ||
+ LINE_IN_VOL == kcontrol->id.numid) {
+#else
+ if (1) {
+#endif
+
+ ret_val = scard_ops->get_vol(cntl_list[1], &value);
+ uval->value.integer.value[1] = value;
+
+ if (0 != ret_val)
+ return ret_val;
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_mute_get - gets the current mute status for the control
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info need
+* to be filled
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+
+ int cntl_list = 0, ret_val = 0, value = 0;
+ struct snd_intelmad *intelmaddata = NULL;
+ struct snd_pmic_ops *scard_ops = NULL;
+
+ sst_dbg("called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case HEADPHONES_MUTE:
+ cntl_list = PMIC_SND_LEFT_HP_MUTE;
+ break;
+#ifdef FULL_CTRL
+ case INTERNAL_SPEAKERS_MUTE:
+ cntl_list = PMIC_SND_LEFT_SPEAKER_MUTE;
+ break;
+ case EARPIECE_MUTE:
+ cntl_list = PMIC_SND_MONO_EARPIECE_MUTE;
+ break;
+ case LINE_IN_MUTE:
+ cntl_list = PMIC_SND_INPUT_MUTE_LINE_IN;
+ break;
+ case MIC1_MUTE:
+ cntl_list = PMIC_SND_INPUT_MUTE_MIC1;
+ break;
+ case MIC2_MUTE:
+ cntl_list = PMIC_SND_INPUT_MUTE_MIC2;
+ break;
+ case DMIC_MUTE:
+ cntl_list = PMIC_SND_INPUT_MUTE_DMIC;
+ break;
+ case MASTER_MUTE:
+ uval->value.integer.value[0] = kcontrol->private_value;
+ return 0;
+#endif
+
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->get_mute(cntl_list, &value);
+ uval->value.integer.value[0] = value;
+ return ret_val;
+}
+
+/**
+* snd_intelmad_volume_set - sets the volume control's info
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_volume_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+
+ int ret_val = 0, cntl_list[2] = {0,};
+ struct snd_intelmad *intelmaddata = NULL;
+ struct snd_pmic_ops *scard_ops = NULL;
+
+ sst_dbg("volume set called\n:%ld %ld",
+ uval->value.integer.value[0],
+ uval->value.integer.value[1]);
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ switch (kcontrol->id.numid) {
+ case HEADPHONES_VOL:
+ cntl_list[0] = PMIC_SND_RIGHT_HP_VOL;
+ cntl_list[1] = PMIC_SND_LEFT_HP_VOL;
+ break;
+#ifdef FULL_CTRL
+ case INTERNAL_SPEAKERS_VOL:
+ cntl_list[0] = PMIC_SND_RIGHT_SPEAKER_VOL;
+ cntl_list[1] = PMIC_SND_LEFT_SPEAKER_VOL;
+ break;
+ case LINE_IN_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_RIGHT_LINE_IN;
+ cntl_list[1] = PMIC_SND_INPUT_VOL_LEFT_LINE_IN;
+ break;
+ case EARPIECE_VOL:
+ cntl_list[0] = PMIC_SND_MONO_EARPIECE_VOL;
+ break;
+ case MIC1_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_MIC1;
+ break;
+ case MIC2_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_MIC2;
+ break;
+ case DMIC_VOL:
+ cntl_list[0] = PMIC_SND_INPUT_VOL_DMIC;
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->set_vol(cntl_list[0],
+ uval->value.integer.value[0]);
+ if (0 != ret_val)
+ return ret_val;
+#ifdef FULL_CTRL
+ if (HEADPHONES_VOL == kcontrol->id.numid
+ || INTERNAL_SPEAKERS_VOL == kcontrol->id.numid ||
+ LINE_IN_VOL == kcontrol->id.numid) {
+#else
+ if (1) {
+#endif
+ ret_val = scard_ops->set_vol(cntl_list[1],
+ uval->value.integer.value[1]);
+ if (0 != ret_val)
+ return ret_val;
+ }
+ kcontrol->private_value = uval->value.integer.value[0];
+ return ret_val;
+}
+
+
+
+/**
+* snd_intelmad_mute_set - sets the mute control's info
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_mute_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ int cntl_list[2] = {0,}, ret_val = 0;
+ struct snd_intelmad *intelmaddata = NULL;
+ struct snd_pmic_ops *scard_ops = NULL;
+
+ sst_dbg("called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ kcontrol->private_value = uval->value.integer.value[0];
+
+ switch (kcontrol->id.numid) {
+ case HEADPHONES_MUTE:
+ cntl_list[0] = PMIC_SND_LEFT_HP_MUTE;
+ cntl_list[1] = PMIC_SND_RIGHT_HP_MUTE;
+ break;
+#ifdef FULL_CTRL
+ case INTERNAL_SPEAKERS_MUTE:
+ cntl_list[0] = PMIC_SND_LEFT_SPEAKER_MUTE;
+ cntl_list[1] = PMIC_SND_RIGHT_SPEAKER_MUTE;
+ break;
+ case EARPIECE_MUTE:
+ cntl_list[0] = PMIC_SND_MONO_EARPIECE_MUTE;
+ break;
+ case LINE_IN_MUTE:
+ cntl_list[0] = PMIC_SND_INPUT_MUTE_LINE_IN;
+ break;
+ case MIC1_MUTE:
+ cntl_list[0] = PMIC_SND_INPUT_MUTE_MIC1;
+ break;
+ case MIC2_MUTE:
+ cntl_list[0] = PMIC_SND_INPUT_MUTE_MIC2;
+ break;
+ case DMIC_MUTE:
+ cntl_list[0] = PMIC_SND_INPUT_MUTE_DMIC;
+ break;
+ case MASTER_MUTE:
+ mute_all(kcontrol, uval);
+ kcontrol->private_value = uval->value.integer.value[0];
+ return 0;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ ret_val = scard_ops->set_mute(cntl_list[0],
+ uval->value.integer.value[0]);
+ if (0 != ret_val)
+ return ret_val;
+
+#ifdef FULL_CTRL
+ if (HEADPHONES_MUTE == kcontrol->id.numid
+ || INTERNAL_SPEAKERS_MUTE == kcontrol->id.numid)
+#endif
+ ret_val = scard_ops->set_mute(cntl_list[1],
+ uval->value.integer.value[0]);
+ kcontrol->private_value = uval->value.integer.value[0];
+ return ret_val;
+}
+
+#ifdef FULL_CTRL
+/**
+* snd_intelmad_device_get - get the device select control's info
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* to be filled
+* This function is called when .get function of a control is invoked from app
+*/
+static int snd_intelmad_device_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ sst_dbg("called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ uval->value.enumerated.item[0] = kcontrol->private_value;
+ return 0;
+}
+
+/**
+* snd_intelmad_device_set - set the device select control's info
+* @kcontrol: pointer to the control
+* @uval: pointer to the structure where the control's info is
+* available to be set
+* This function is called when .set function of a control is invoked from app
+*/
+static int snd_intelmad_device_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_intelmad *intelmaddata = NULL;
+ struct snd_pmic_ops *scard_ops = NULL;
+ int ret_val = 0;
+
+ sst_dbg("called\n");
+
+ WARN_ON(!uval);
+ WARN_ON(!kcontrol);
+
+ intelmaddata = kcontrol->private_data;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ scard_ops = intelmaddata->sstdrv_ops->scard_ops;
+
+ WARN_ON(!scard_ops);
+
+ /*store value with driver*/
+ kcontrol->private_value = uval->value.enumerated.item[0];
+
+ switch (kcontrol->id.numid) {
+ case OUTPUT_SEL:
+ ret_val = scard_ops->set_output_dev(
+ uval->value.enumerated.item[0]);
+ break;
+ case INPUT_SEL:
+ ret_val = scard_ops->set_input_dev(
+ uval->value.enumerated.item[0]);
+ break;
+ default:
+ return -EINVAL;
+ }
+ kcontrol->private_value = uval->value.enumerated.item[0];
+ return ret_val;
+}
+#endif
+
+/*structure of all the controls of the sound card that is exposed*/
+static struct snd_kcontrol_new snd_intelmad_controls[] __devinitdata = {
+#ifdef FULL_CTRL
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_line_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Capture Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DMIC Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_dmic_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DMIC Capture Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic1 Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mic1_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic1 Capture Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic2 Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mic2_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic2 Capture Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Capture Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+#endif
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_hp_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+#ifdef FULL_CTRL
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Internal Speaker Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_speaker_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Internal Speaker Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mono Earpiece Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_earpiece_volume_info,
+ .get = snd_intelmad_volume_get,
+ .put = snd_intelmad_volume_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mono Earpiece Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Source",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_device_info,
+ .get = snd_intelmad_device_get,
+ .put = snd_intelmad_device_set,
+ .private_value = 0,
+},
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Mute Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_intelmad_mute_info,
+ .get = snd_intelmad_mute_get,
+ .put = snd_intelmad_mute_set,
+ .private_value = 0,
+},
+#endif
+};
+
+/*Data path functionalities*/
+static struct snd_pcm_hardware snd_intelmad_stream = {
+ .info = (SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_DOUBLE |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_MMAP|
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 |
+ /*SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |*/
+ SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24),
+ .rates = (SNDRV_PCM_RATE_8000|
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000),
+ .rate_min = MIN_RATE,
+
+ .rate_max = MAX_RATE,
+ .channels_min = MIN_CHANNEL,
+ .channels_max = MAX_CHANNEL,
+ .buffer_bytes_max = MAX_BUFFER,
+ .period_bytes_min = MIN_PERIOD_BYTES,
+ .period_bytes_max = MAX_PERIOD_BYTES,
+ .periods_min = MIN_PERIODS,
+ .periods_max = MAX_PERIODS,
+ .fifo_size = FIFO_SIZE,
+};
+
+static void send_buffer_to_sst(struct snd_pcm_substream *substream,
+ struct snd_pcm_indirect *rec, size_t bytes)
+{
+ struct snd_intelmad *intelmaddata = snd_pcm_substream_chip(substream);
+ struct stream_buffer buffer_to_sst = {0,};
+ struct mad_stream_pvt *stream = substream->runtime->private_data;
+ int ret_val = 0;
+ /*sends data to SST to be processed*/
+
+ cum_bytes += bytes;
+ sst_dbg("bytes = %d \n", bytes);
+ sst_dbg("cum_bytes = 0x%x, \n", cum_bytes);
+ buffer_to_sst.length = bytes;
+ buffer_to_sst.addr = (unsigned long) substream->runtime->dma_area +
+ rec->sw_data;
+ /*SST API to actually send the buffer to be played*/
+ ret_val = intelmaddata->sstdrv_ops->send_buffer(stream->stream_id,
+ &buffer_to_sst);
+ sst_dbg("send_buffer ret_val = 0x%x \n", ret_val);
+ return;
+}
+
+void start_capture(struct work_struct *work)
+{
+ int ret_val = 0;
+ struct mad_capture_wq *stream_work =
+ container_of(work, struct mad_capture_wq, wq);
+ struct stream_buffer *buffer_to_sst = &stream_work->buffer_to_sst;
+ struct snd_pcm_substream *substream = stream_work->substream;
+ struct mad_stream_pvt *stream = substream->runtime->private_data;
+ struct snd_intelmad *intelmaddata = snd_pcm_substream_chip(substream);
+
+ ret_val = intelmaddata->sstdrv_ops->send_buffer(stream->stream_id,
+ buffer_to_sst);
+ sst_dbg("send_buffer ret_val = 0x%x \n", ret_val);
+ return;
+}
+
+void snd_card_pcm_wq_function(struct work_struct *work)
+{
+ struct mad_wq *stream_work =
+ container_of(work, struct mad_wq, wq);
+ struct snd_pcm_substream *substream;
+ struct snd_pcm_indirect *rec = NULL;
+ struct mad_stream_pvt *stream;
+
+ mutex_lock(&stream_work->wq_lock);
+ substream = stream_work->substream;
+ if (substream == NULL || substream->runtime == NULL) {
+ sst_err("subsream structure is null \n");
+ mutex_unlock(&stream_work->wq_lock);
+ return;
+ }
+ stream = substream->runtime->private_data;
+ sst_dbg("called %d\n", stream->stream_status);
+ if (stream == NULL) {
+ sst_err("stream is null in wq function \n");
+ mutex_unlock(&stream_work->wq_lock);
+ return;
+ }
+
+ if (stream->stream_status != INIT) {
+ rec = &stream->pcm_indirect;
+ if (substream->stream == STREAM_OPS_PLAYBACK)
+ snd_pcm_indirect_playback_transfer(substream, rec,
+ send_buffer_to_sst);
+ else if (substream->stream == STREAM_OPS_CAPTURE) {
+ sst_dbg("calling indirect capture transfer\n");
+ snd_pcm_indirect_capture_transfer(substream, rec,
+ send_buffer_to_sst);
+ }
+ stream->stream_status = RUNNING;
+ }
+ mutex_unlock(&stream_work->wq_lock);
+ return;
+}
+
+static int snd_intelmad_pcm_ack(struct snd_pcm_substream *substream)
+{
+ struct mad_stream_pvt *stream;
+
+ stream = substream->runtime->private_data;
+ sst_dbg("...called\n");
+ stream->mad_msg_wq.substream = substream;
+ schedule_work(&stream->mad_msg_wq.wq);
+ return 0;
+}
+
+/**
+* snd_intelmad_pcm_trigger - stream activities are handled here
+* @substream:substream for which the stream function is called
+*@cmd:the stream commamd thats requested from upper layer
+* This function is called whenever an a stream activity is invoked
+*/
+static int snd_intelmad_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int ret_val = 0, stream_type = 0/*, sample_size = 0*/;
+ struct snd_intelmad *intelmaddata = NULL;
+ struct mad_stream_pvt *stream = NULL;
+
+ sst_dbg("called\n");
+
+ WARN_ON(!substream);
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ stream_type = substream->stream;
+ stream = substream->runtime->private_data;
+
+ if (PMIC_UNINIT == intelmaddata->pmic_status) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ }
+
+ if (0 != ret_val)
+ return ret_val;
+
+ WARN_ON(!intelmaddata->sstdrv_ops);
+ WARN_ON(!intelmaddata->sstdrv_ops->scard_ops);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /*invoked on a power managment -resume operation*/
+ /*stream parameters and context resuming to be done here*/
+ /*TBD*/
+ sst_dbg("in resume\n");
+ break;
+ case SNDRV_PCM_TRIGGER_START:
+ stream->substream = substream;
+ stream->pcm_size = snd_pcm_lib_buffer_bytes(substream);
+ sst_dbg("pcm_size=%d\n", stream->pcm_size);
+#ifdef TIMER_TS
+ bps = (substream->runtime->sample_bits/8)
+ * (substream->runtime->channels)
+ * (substream->runtime->rate);
+ stream->pcm_jiffie = ((bps * ELAPSED_PERIOD) / HZ);
+ stream->pcm_buf_pos = 0;
+ sst_dbg("pcm_jiffie=%d \n", stream->pcm_jiffie);
+#endif
+ stream->stream_status = STARTED;
+ if (substream->stream == STREAM_OPS_PLAYBACK)
+ snd_intelmad_pcm_ack(substream);
+ else if (substream->stream == STREAM_OPS_CAPTURE) {
+ stream->mad_capture_wq.substream = substream;
+ stream->mad_capture_wq.buffer_to_sst.length =
+ frames_to_bytes(substream->runtime,
+ substream->runtime->period_size);
+ stream->mad_capture_wq.buffer_to_sst.addr =
+ (unsigned long) substream->runtime->dma_area;
+ schedule_work(&stream->mad_capture_wq.wq);
+ cum_bytes = stream->mad_capture_wq.buffer_to_sst.length;
+ stream->stream_status = RUNNING;
+ }
+ break;
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ /*invoked on suspend - power management event*/
+ /*stream parameters and context to be saved*/
+ /*TBD*/
+ sst_dbg("in suspend\n");
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ sst_dbg("in stop\n");
+ /*ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_DROP,
+ &stream->stream_id);
+ if (0 != ret_val)
+ return ret_val;*/
+ stream->stream_status = DROPPED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ sst_dbg("in pause\n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_PAUSE,
+ &stream->stream_id);
+ if (0 != ret_val)
+ return ret_val;
+ stream->stream_status = PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ sst_dbg("in pause release \n");
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_RESUME,
+ &stream->stream_id);
+ if (0 != ret_val)
+ return ret_val;
+ stream->stream_status = RUNNING;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_pcm_prepare- internal preparation before starting a stream
+* @substream: substream for which the function is called
+* This function is called when a stream is started for internal preparation.
+*/
+static int snd_intelmad_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct mad_stream_pvt *stream = NULL;
+ int ret_val = 0;
+
+ sst_dbg("called \n");
+
+ WARN_ON(!substream);
+
+ stream = substream->runtime->private_data;
+
+ sst_dbg("format = %d \n", substream->runtime->format);
+ if (stream->stream_id == 0) {
+ ret_val = snd_intelmad_alloc_stream(substream);
+ cum_bytes = 0;
+ if (ret_val < 0)
+ return ret_val;
+ ret_val = snd_intelmad_init_stream(substream);
+ if (0 != ret_val)
+ return ret_val;
+ sst_dbg("period size = %d \n",
+ (int)substream->runtime->period_size);
+ sst_dbg("buf size = %d \n",
+ (int)substream->runtime->buffer_size);
+ memset(&stream->pcm_indirect, 0, sizeof(stream->pcm_indirect));
+ stream->pcm_indirect.hw_buffer_size =
+ snd_pcm_lib_buffer_bytes(substream);
+ stream->pcm_indirect.sw_buffer_size =
+ stream->pcm_indirect.hw_buffer_size;
+ /*return back the stream id*/
+ snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+ "%d", stream->stream_id);
+ sst_dbg("stream id to user = %s\n", substream->pcm->id);
+ }
+ return ret_val;
+}
+
+static int snd_intelmad_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int ret_val = 0;
+
+ sst_dbg("called\n");
+ ret_val = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ memset(substream->runtime->dma_area, 0, params_buffer_bytes(hw_params));
+ return ret_val;
+}
+
+static int snd_intelmad_hw_free(struct snd_pcm_substream *substream)
+{
+ sst_dbg("called\n");
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/**
+* snd_intelmad_pcm_pointer- to send the current buffer pointer processed by hw
+* @substream: substream for which the function is called
+* This function is called by ALSA framework to get the current hw buffer ptr
+* when a period is elapsed
+*/
+static snd_pcm_uframes_t snd_intelmad_pcm_pointer
+ (struct snd_pcm_substream *substream)
+{
+ /*struct snd_pcm_runtime *runtime = substream->runtime;*/
+ struct mad_stream_pvt *stream = NULL;
+ struct snd_intelmad *intelmaddata = NULL;
+ int ret_val = 0;
+ unsigned long buf_size = 0;
+
+ WARN_ON(!substream);
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ stream = substream->runtime->private_data;
+ if (stream->stream_status == INIT)
+ return 0;
+
+ stream->buf_ptr.str_id = stream->stream_id;
+
+#ifndef TIMER_TS
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_BUFFER_POINTER,
+ &stream->buf_ptr);
+ if (0 != ret_val) {
+ sst_err("error code = 0x%x \n", ret_val);
+ return ret_val;
+ }
+ sst_dbg("samples reported out 0x%lx \n", stream->buf_ptr.buffer_ptr);
+ buf_size = frames_to_bytes(substream->runtime,
+ stream->buf_ptr.buffer_ptr);
+ sst_dbg(" bytes reported out = 0x%lx\n", buf_size);
+ if (buf_size > cum_bytes)
+ sst_err("excess reported\n");
+
+ if (substream->stream == STREAM_OPS_PLAYBACK)
+ stream->period_elapsed_ptr = snd_pcm_indirect_playback_pointer(
+ substream, &stream->pcm_indirect, buf_size);
+ else
+ stream->period_elapsed_ptr = snd_pcm_indirect_capture_pointer(
+ substream, &stream->pcm_indirect, buf_size);
+
+ sst_dbg("indirect value = 0x%lx\n", stream->period_elapsed_ptr);
+
+ return stream->period_elapsed_ptr;
+#else
+ stream->pcm_buf_pos += stream->pcm_jiffie;
+ stream->pcm_buf_pos %= stream->pcm_size;
+ return snd_pcm_indirect_playback_pointer(substream,
+ &stream->pcm_indirect, stream->pcm_buf_pos);
+#endif
+
+}
+
+/**
+* snd_intelmad_close- to free parameteres when stream is stopped
+* @substream: substream for which the function is called
+* This function is called by ALSA framework when stream is stopped
+*/
+static int snd_intelmad_close(struct snd_pcm_substream *substream)
+{
+ struct snd_intelmad *intelmaddata = NULL;
+ struct mad_stream_pvt *stream = NULL;
+ int ret_val = 0;
+
+ WARN_ON(!substream);
+
+ stream = substream->runtime->private_data;
+
+ sst_dbg("called \n");
+ intelmaddata = snd_pcm_substream_chip(substream);
+ mutex_lock(&stream->mad_msg_wq.wq_lock);
+ sst_dbg("str id = %d\n", stream->stream_id);
+ /*SST API to actually stop/free the stream*/
+ ret_val = intelmaddata->sstdrv_ops->control_set(SST_SND_FREE,
+ &stream->stream_id);
+ kfree(substream->runtime->private_data);
+ mutex_unlock(&stream->mad_msg_wq.wq_lock);
+ return ret_val;
+}
+
+/**
+* snd_intelmad_open- to set runtime parameters during stream start
+* @substream: substream for which the function is called
+* This function is called by ALSA framework when stream is started
+*/
+static int snd_intelmad_open(struct snd_pcm_substream *substream)
+{
+ struct snd_intelmad *intelmaddata;
+ struct snd_pcm_runtime *runtime;
+ struct mad_stream_pvt *stream;
+
+ WARN_ON(!substream);
+
+ sst_dbg("called \n");
+
+ intelmaddata = snd_pcm_substream_chip(substream);
+ runtime = substream->runtime;
+ /*set the runtime hw parameter with our local snd_pcm_hardware struct*/
+ runtime->hw = snd_intelmad_stream;
+ /*setup the internal datastruture stream pointers based on it being
+ playback or capture stream*/
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (NULL == stream)
+ return -ENOMEM;
+ stream->stream_id = 0;
+ stream->stream_status = INIT;
+ INIT_WORK(&stream->mad_msg_wq.wq, snd_card_pcm_wq_function);
+ INIT_WORK(&stream->mad_capture_wq.wq, start_capture);
+ mutex_init(&stream->mad_msg_wq.wq_lock);
+ runtime->private_data = stream;
+ return 0;
+}
+
+static struct snd_pcm_ops snd_intelmad_playback_ops = {
+ .open = snd_intelmad_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+ .ack = snd_intelmad_pcm_ack,
+};
+
+static struct snd_pcm_ops snd_intelmad_capture_ops = {
+ .open = snd_intelmad_open,
+ .close = snd_intelmad_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_intelmad_hw_params,
+ .hw_free = snd_intelmad_hw_free,
+ .prepare = snd_intelmad_pcm_prepare,
+ .trigger = snd_intelmad_pcm_trigger,
+ .pointer = snd_intelmad_pcm_pointer,
+ .ack = snd_intelmad_pcm_ack,
+};
+
+int snd_intelmad_generate_netlink(u32 orig, enum eaudio_events event)
+{
+ struct sk_buff *skb = NULL;
+ struct nlattr *attr = NULL;
+ struct audio_genl_event *aud_event = NULL;
+ void *msg_header = NULL;
+ int size = 0, ret_val = 0;
+
+ /* allocate memory */
+ size = nla_total_size(sizeof(struct audio_genl_event)) + \
+ nla_total_size(0);
+
+ skb = genlmsg_new(size, GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ /* add the genetlink message header */
+ msg_header = genlmsg_put(skb, 0, audio_event_seqnum++,
+ &audio_event_genl_family, 0,
+ AUDIO_GENL_CMD_EVENT);
+ if (!msg_header) {
+ nlmsg_free(skb);
+ return -ENOMEM;
+ }
+
+ /* fill the data */
+ attr = nla_reserve(skb, AUDIO_GENL_ATTR_EVENT, \
+ sizeof(struct audio_genl_event));
+
+ if (!attr) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ aud_event = nla_data(attr);
+ if (!aud_event) {
+ nlmsg_free(skb);
+ return -EINVAL;
+ }
+
+ memset(aud_event, 0, sizeof(struct audio_genl_event));
+
+ aud_event->orig = orig;
+ aud_event->event = event;
+
+ /* send multicast genetlink message */
+ ret_val = genlmsg_end(skb, msg_header);
+ if (ret_val < 0) {
+ nlmsg_free(skb);
+ return ret_val;
+ }
+
+ ret_val = genlmsg_multicast(skb, 0, audio_event_mcgrp.id, GFP_ATOMIC);
+
+ if (ret_val)
+ printk(KERN_INFO "Failed to send a Genetlink message!\n");
+ return 0;
+}
+
+#ifdef REG_IRQ
+/**
+* snd_intelmad_intr_handler- interrupt handler
+*@irq : irq number of the interrupt received
+*@dev: device context
+* This function is called when an interrupt is raised at the sound card
+*/
+static irqreturn_t snd_intelmad_intr_handler(int irq, void *dev)
+{
+ struct snd_intelmad *intelmaddata =
+ (struct snd_intelmad *)dev;
+ struct sc_reg_access pmic_reg = {0,};
+ int ret_val = 0;
+ u8 intsts = 0;
+ memcpy_fromio(&intsts,
+ ((void *)(intelmaddata->int_base)),
+ sizeof(u8));
+ if (intsts != 0)
+ sst_dbg("intstatus = 0x%x\n", intsts);
+ switch (intelmaddata->sstdrv_ops->vendor_id) {
+ case SND_FS:
+ if (intsts & 0x1) {
+ /*send long push*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_LONG_PRESS);
+ }
+ if (intsts & 0x2) {
+ /*send short push*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_SHORT_PRESS);
+ }
+ if (intsts & 0x4) {
+ /*send headphone detect*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_HP_DETECT);
+ }
+ if (intsts & 0x8) {
+ /*send headset detect*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_HS_DETECT);
+ }
+ break;
+ case SND_MX:
+ if (intsts & 0x2) {
+ pmic_reg.reg_addr = 0x201;
+ ret_val = sst_sc_reg_access(&pmic_reg, PMIC_READ, 1);
+ if (0 != ret_val) {
+ /*pmic communication fails*/
+ sst_err("pmic commn failed \n");
+ return IRQ_HANDLED;
+ }
+ intsts = pmic_reg.value;
+ if (intsts & 0xc0)
+ /*send jack detect/undetect*/
+ sst_dbg("to handle jack sensing\n");
+ }
+ break;
+ case SND_NC:
+ if (intsts & 0x1) {
+ /*send headset detect/undetect*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_HS_DETECT);
+ }
+ if (intsts & 0x2) {
+ /*send headphone detect/undetect*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_HP_DETECT);
+ }
+ if (intsts & 0x4) {
+ /*send short push*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_SHORT_PRESS);
+ }
+ if (intsts & 0x8) {
+ /*send long push*/
+ ret_val = snd_intelmad_generate_netlink(1,
+ AUDIO_EVENT_LONG_PRESS);
+ }
+ break;
+ }
+ return IRQ_HANDLED;
+}
+static int __devinit snd_intelmad_register_irq(
+ struct snd_intelmad *intelmaddata)
+{
+ int ret_val = 0;
+ u32 regbase = AUDINT_BASE, regsize = 8;
+
+ /* interpret irq field */
+ sst_dbg("irq = 0x%x\n", intelmaddata->irq);
+
+ if ((intelmaddata->irq) & PMIC_SOUND_IRQ_TYPE_MASK) {
+ /* irq -> GPE_ID */
+ intelmaddata->irq = intelmaddata->irq &
+ (~PMIC_SOUND_IRQ_TYPE_MASK);
+ sst_dbg("gpe = 0x%x\n", intelmaddata->irq);
+ ret_val = request_gpe(intelmaddata->irq,
+ (gpio_function_t)snd_intelmad_intr_handler,
+ intelmaddata, 0);
+ if (ret_val) {
+ sst_dbg("cannot register gpe\n");
+ return ret_val;
+ }
+
+ } else {
+ /* irq -> system irq */
+ ret_val = request_irq(intelmaddata->irq,
+ snd_intelmad_intr_handler,
+ IRQF_SHARED, DRIVER_NAME,
+ intelmaddata);
+ if (0 != ret_val) {
+ sst_dbg("cannot register IRQ\n");
+ return ret_val;
+ }
+ }
+
+ intelmaddata->int_base = ioremap_nocache(regbase, regsize);
+ return ret_val;
+}
+static int __devinit snd_intelmad_register_netlink(void)
+{
+ int ret_val = 0;
+
+ ret_val = genl_register_family(&audio_event_genl_family);
+ if (0 != ret_val) {
+ sst_dbg("netlink registration failed\n");
+ return ret_val;
+ }
+ ret_val = genl_register_mc_group(&audio_event_genl_family,
+ &audio_event_mcgrp);
+ if (ret_val) {
+ sst_dbg("netlink group registration failed\n");
+ genl_unregister_family(&audio_event_genl_family);
+ return ret_val;
+ }
+ return ret_val;
+}
+#endif
+
+static int __devinit snd_intelmad_sst_register(
+ struct snd_intelmad *intelmaddata)
+{
+ int ret_val = 0;
+ struct sc_reg_access pmic_reg = {0,};
+
+ pmic_reg.reg_addr = 0;
+ ret_val = sst_sc_reg_access(&pmic_reg, PMIC_READ, 1);
+
+ if (0 != ret_val)
+ return ret_val;
+
+ vendor_id = pmic_reg.value & (MASK2|MASK1|MASK0);
+ sst_info("orginal reg n extrated vendor id = 0x%x %d\n",
+ pmic_reg.value, vendor_id);
+ if (vendor_id < 0 || vendor_id > 2) {
+ sst_err("vendor card not supported!!\n");
+ return -EIO;
+ }
+ intelmaddata->sstdrv_ops->module_name = SST_CARD_NAMES;
+ intelmaddata->sstdrv_ops->vendor_id = vendor_id;
+ intelmaddata->sstdrv_ops->scard_ops = &v[vendor_id];
+
+ /*registering with SST driver to get access to SST APIs to use*/
+ ret_val = register_sst_card(intelmaddata->sstdrv_ops);
+ if (0 != ret_val) {
+ sst_err("sst card registration failed\n");
+ return ret_val;
+ }
+
+ vendor_id = intelmaddata->sstdrv_ops->vendor_id;
+ intelmaddata->pmic_status = PMIC_UNINIT;
+ return ret_val;
+}
+
+/*Driver Init/exit functionalities*/
+/**
+* snd_intelmad_pcm- to setup pcm for the card
+* @card: pointer to the sound card structure
+*@intelmaddata: pointer to internal context
+* This function is called from probe function to set up pcm params and functions
+*/
+static int __devinit snd_intelmad_pcm(struct snd_card *card,
+ struct snd_intelmad *intelmaddata)
+{
+ struct snd_pcm *pcm;
+ int i, ret_val = 0;
+ char name[32] = INTEL_MAD;
+
+ WARN_ON(!card);
+ WARN_ON(!intelmaddata);
+
+ for (i = 0; i < MAX_DEVICES; i++) {
+ ret_val = snd_pcm_new(card, name, i, PLAYBACK_COUNT,
+ CAPTURE_COUNT, &pcm);
+ if (0 != ret_val)
+ break;
+ /*setup the ops for playback and capture streams*/
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_intelmad_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_intelmad_capture_ops);
+ /*setup private data which can be retrieved when required*/
+ pcm->private_data = intelmaddata;
+ pcm->info_flags = 0;
+ strncpy(pcm->name, card->shortname, strlen(card->shortname));
+ /*allocate dma pages for ALSA stream operations*/
+ snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ MIN_BUFFER, MAX_BUFFER);
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_mixer- to setup mixer settings of the card
+*@intelmaddata: pointer to internal context
+* This function is called from probe function to set up mixer controls
+*/
+static int __devinit snd_intelmad_mixer(struct snd_intelmad *intelmaddata)
+{
+ struct snd_card *card = NULL;
+ unsigned int idx;
+ int ret_val = 0;
+ char *mixername = "IntelMAD Controls";
+
+ WARN_ON(!intelmaddata);
+
+ card = intelmaddata->card;
+
+ strncpy(card->mixername, mixername, strlen(mixername));
+ /*add all widget controls and expose the same*/
+ for (idx = 0; idx < ARRAY_SIZE(snd_intelmad_controls); idx++) {
+ ret_val = snd_ctl_add(card,
+ snd_ctl_new1(&snd_intelmad_controls[idx],
+ intelmaddata));
+ sst_dbg("mixer[idx]=%d added \n", idx);
+ if (0 != ret_val)
+ break;
+ }
+ return ret_val;
+}
+
+/**
+* snd_intelmad_dev_free- to free device
+*@device: pointer to the device
+* This function is called when driver module is removed
+*/
+static int snd_intelmad_dev_free(struct snd_device *device)
+{
+ struct snd_intelmad *intelmaddata;
+
+ WARN_ON(!device);
+
+ intelmaddata = device->device_data;
+
+ sst_dbg("called\n");
+ snd_card_free(intelmaddata->card);
+ genl_unregister_family(&audio_event_genl_family);
+ unregister_sst_card(intelmaddata->sstdrv_ops);
+
+ /*free allocated memory for internal context*/
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ return 0;
+}
+
+/**
+* snd_intelmad_create- called from probe to create a snd device
+*@intelmaddata : pointer to the internal context
+*@card : pointer to the sound card
+* This function is called when driver module is started
+*/
+static int __devinit snd_intelmad_create(
+ struct snd_intelmad *intelmaddata,
+ struct snd_card *card)
+{
+ int ret_val = 0;
+ static struct snd_device_ops ops = {
+ .dev_free = snd_intelmad_dev_free,
+ };
+
+ WARN_ON(!intelmaddata);
+ WARN_ON(!card);
+ /*ALSA api to register for the device*/
+ ret_val = snd_device_new(card, SNDRV_DEV_LOWLEVEL, intelmaddata, &ops);
+ return ret_val;
+}
+
+/*********************************************************************
+ * SPI Functions
+ *********************************************************************/
+
+
+/**
+* snd_intelmad_probe- function registred for init
+*@spi : pointer to the spi device context
+* This function is called when the device is initialized
+*/
+int __devinit snd_intelmad_probe(struct spi_device *spi)
+{
+ struct snd_card *card = NULL;
+ int ret_val = 0;
+ struct snd_intelmad *intelmaddata = NULL;
+
+ sst_dbg("called \n");
+
+ /*allocate memory for saving internal context and working*/
+ intelmaddata = kzalloc(sizeof(*intelmaddata), GFP_KERNEL);
+ if (NULL == intelmaddata)
+ return -ENOMEM;
+
+ /*allocate memory for LPE API set*/
+ intelmaddata->sstdrv_ops = kzalloc(sizeof(struct intel_sst_card_ops),
+ GFP_KERNEL);
+ if (NULL == intelmaddata->sstdrv_ops) {
+ sst_err("mem alloctn fail \n");
+ kfree(intelmaddata);
+ return -ENOMEM;
+ }
+
+ /*create a card instance with ALSA framework*/
+ card = snd_card_new(card_index, card_id, THIS_MODULE, 0);
+ if (NULL == card)
+ return -ENOMEM;
+
+ intelmaddata->spi = spi;
+ intelmaddata->irq = spi->irq;
+ dev_set_drvdata(&spi->dev, intelmaddata);
+ intelmaddata->card = card;
+ intelmaddata->card_id = card_id;
+ intelmaddata->card_index = card_index;
+ strncpy(card->driver, INTEL_MAD, strlen(INTEL_MAD));
+ strncpy(card->shortname, INTEL_MAD, strlen(INTEL_MAD));
+
+#ifdef REG_IRQ
+ ret_val = snd_intelmad_register_irq(intelmaddata);
+ if (0 != ret_val) {
+ sst_err("snd_intelmad_register_irq fail\n");
+ goto free_allocs;
+ }
+ ret_val = snd_intelmad_register_netlink();
+ if (0 == ret_val) {
+ sst_dbg("...complete\n");
+ return ret_val;
+ }
+#endif
+
+ /*internal function call to register device with ALSA*/
+ ret_val = snd_intelmad_create(intelmaddata, card);
+ if (0 != ret_val) {
+ sst_err("snd_intelmad_create failed\n");
+ goto free_allocs;
+ }
+
+ ret_val = snd_intelmad_pcm(card, intelmaddata);
+ if (0 != ret_val) {
+ sst_err("snd_intelmad_pcm failed\n");
+ goto free_allocs;
+ }
+
+ ret_val = snd_intelmad_mixer(intelmaddata);
+ if (0 != ret_val) {
+ sst_err("snd_intelmad_mixer failed\n");
+ goto free_allocs;
+ }
+
+ card->private_data = &intelmaddata;
+ snd_card_set_dev(card, &spi->dev);
+ ret_val = snd_card_register(card);
+ if (0 != ret_val)
+ goto free_allocs;
+
+
+ intelmaddata->sstdrv_ops->module_name = SST_CARD_NAMES;
+ /*registering with LPE driver to get access to LPE APIsito use*/
+ ret_val = snd_intelmad_sst_register(intelmaddata);
+ if (0 == ret_val) {
+ ret_val = intelmaddata->sstdrv_ops->scard_ops->init_card();
+ intelmaddata->pmic_status = PMIC_INIT;
+ sst_dbg("...complete\n");
+ return ret_val;
+ }
+
+ sst_err("registering with LPE failed \n");
+
+free_allocs:
+ /*TODO: unregister IRQ*/
+ sst_err("probe failed\n");
+ /*snd_card_free(card);*/
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ return ret_val;
+}
+
+
+/**
+* snd_intelmad_remove- function registred for exit
+*@spi : pointer to the spi device context
+* This function is called when the device is uninitialized
+*/
+static int snd_intelmad_remove(struct spi_device *spi)
+{
+ struct snd_intelmad *intelmaddata =
+ dev_get_drvdata(&spi->dev);
+ /*
+ * TODO:: de-register interrupt handler
+ */
+
+ if (intelmaddata) {
+ snd_card_free(intelmaddata->card);
+ genl_unregister_family(&audio_event_genl_family);
+ unregister_sst_card(intelmaddata->sstdrv_ops);
+ /*free allocated memory for internal context*/
+ kfree(intelmaddata->sstdrv_ops);
+ kfree(intelmaddata);
+ }
+ return 0;
+}
+
+/*********************************************************************
+ * Driver initialization and exit
+ *********************************************************************/
+
+static struct spi_driver snd_intelmad_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = snd_intelmad_probe,
+ .remove = __devexit_p(snd_intelmad_remove),
+};
+
+/*
+* alsa_card_intelmad_init- driver init function
+* This function is called when driver module is inserted
+*/
+static int __init alsa_card_intelmad_init(void)
+{
+ sst_dbg("called\n");
+ return spi_register_driver(&snd_intelmad_driver);
+}
+
+/**
+* alsa_card_intelmad_exit- driver exit function
+* This function is called when driver module is removed
+*/
+static void __exit alsa_card_intelmad_exit(void)
+{
+ sst_dbg("called\n");
+ spi_unregister_driver(&snd_intelmad_driver);
+}
+
+module_init(alsa_card_intelmad_init)
+module_exit(alsa_card_intelmad_exit)
+
--
1.5.4.5
6
31

[alsa-devel] [PATCH 1/1] pcm_hw: Always use delay ioctl in snd_pcm_delay()
by Kai Vehmanen 11 Sep '09
by Kai Vehmanen 11 Sep '09
11 Sep '09
As the result of snd_pcm_delay() is affected not only by hw_ptr
and appl_ptr, but also by 'runtime->delay' property,
either SNDRV_PCM_IOCTL_DELAY or SNDRV_PCM_IOCTL_STATUS ioctl
must be used to get the correct result.
Previously 'runtime->delay' was ignored in case 'hw->sync_ptr'
was used.
Signed-off-by: Kai Vehmanen <kvehmanen(a)eca.cx>
---
src/pcm/pcm_hw.c | 22 ----------------------
1 files changed, 0 insertions(+), 22 deletions(-)
diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c
index c46d14f..8abb204 100644
--- a/src/pcm/pcm_hw.c
+++ b/src/pcm/pcm_hw.c
@@ -507,28 +507,6 @@ static int snd_pcm_hw_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
{
snd_pcm_hw_t *hw = pcm->private_data;
int fd = hw->fd, err;
- if (hw->sync_ptr) {
- err = sync_ptr1(hw, SNDRV_PCM_SYNC_PTR_HWSYNC);
- if (err < 0)
- return err;
- switch (FAST_PCM_STATE(hw)) {
- case SNDRV_PCM_STATE_RUNNING:
- case SNDRV_PCM_STATE_DRAINING:
- case SNDRV_PCM_STATE_PAUSED:
- case SNDRV_PCM_STATE_PREPARED:
- case SNDRV_PCM_STATE_SUSPENDED:
- break;
- case SNDRV_PCM_STATE_XRUN:
- return -EPIPE;
- default:
- return -EBADFD;
- }
- if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
- *delayp = snd_pcm_mmap_playback_hw_avail(pcm);
- else
- *delayp = snd_pcm_mmap_capture_avail(pcm);
- return 0;
- }
if (ioctl(fd, SNDRV_PCM_IOCTL_DELAY, delayp) < 0) {
err = -errno;
SYSMSG("SNDRV_PCM_IOCTL_DELAY failed");
--
1.5.6.5
1
0
Hello there,
This question is for ALSA, not ALSA SOC.
I am doing an ALSA driver for our chip (soc chip with cpu inside), inside it has I2S and DAC. The I2S can be feed to DAC, or come out of the chip to an external CODEC chip.
I wonder how should I model this in the driver, are they multiple ALSA cards, or devices, or subdevices?
Since the I2S is shared, I think the most appropriate is 'subdevice', but I do not see how this is used in linux/sound, this is only used in pci, and seems to tied together with substream number.
If the answer is 'device', is there some existing driver that are doing this?
Thanks,
Feng
2
1
Hi all,
I'm currently looking at a board which uses a Freescale i.MX31L connected to a
Wolfson WM9715L over the AC97 bus. The WM9715L will be used for both sound
and a touchscreen.
It doesn't look like there are any AC97 SoC drivers for the i.MX31. Is this
correct? If so, should I try to get the board changed to use a codec on the
I2S bus (the board is being layed out right now), or would creating the AC97
driver simple enough that we should continue on with this hardware and write
the driver when needed?
TIA,
---
Geoffrey
2
1
Hello, please find below an issue I had with loading plugins into
pulseaudio. The pulse dev has sent this mail on his list concerning
alsa's interaction with pulse, indicating that there is something buggy
with how it works. Details below. I'd be willing to post something to
the bugtracker, if I only knew what the below means...
-------- Forwarded Message --------
From: Lennart Poettering <lennart(a)poettering.net>
Reply-to: General PulseAudio Discussion
<pulseaudio-discuss(a)mail.0pointer.de>
To: pulseaudio-discuss(a)mail.0pointer.de
Subject: Re: [pulseaudio-discuss] Alsa output devices, able to use alsa
plugin?
Date: Fri, 4 Sep 2009 01:20:15 +0200
On Wed, 26.08.09 12:27, Tanu Kaskinen (tanuk(a)iki.fi) wrote:
> D: alsa-util.c: Trying test with SND_PCM_NO_AUTO_FORMAT ...
> D: alsa-util.c: Managed to open test
> D: alsa-util.c: Maximum hw buffer size is 371 ms
> I: alsa-util.c: snd_pcm_hw_params_set_periods_near() failed: Tiedostoa tai hakemistoa ei ole
> I: alsa-util.c: snd_pcm_hw_params_set_buffer_size_near() failed: Tiedostoa tai hakemistoa ei ole
> D: alsa-util.c: Trying test without SND_PCM_NO_AUTO_FORMAT ...
> D: alsa-util.c: Managed to open test
> D: alsa-util.c: Maximum hw buffer size is 371 ms
> I: alsa-util.c: snd_pcm_hw_params_set_periods_near() failed: Tiedostoa tai hakemistoa ei ole
> I: alsa-util.c: snd_pcm_hw_params_set_buffer_size_near() failed: Tiedostoa tai hakemistoa ei ole
> D: alsa-util.c: Trying plug:test with SND_PCM_NO_AUTO_FORMAT ...
> D: alsa-util.c: Managed to open plug:test
> D: alsa-util.c: Maximum hw buffer size is 371 ms
> I: alsa-util.c: snd_pcm_hw_params_set_periods_near() failed: Tiedostoa tai hakemistoa ei ole
> I: alsa-util.c: snd_pcm_hw_params_set_buffer_size_near() failed: Tiedostoa tai hakemistoa ei ole
> D: alsa-util.c: Trying plug:test without SND_PCM_NO_AUTO_FORMAT ...
> D: alsa-util.c: Managed to open plug:test
> D: alsa-util.c: Maximum hw buffer size is 371 ms
> I: alsa-util.c: snd_pcm_hw_params_set_periods_near() failed: Tiedostoa tai hakemistoa ei ole
> I: alsa-util.c: snd_pcm_hw_params_set_buffer_size_near() failed: Tiedostoa tai hakemistoa ei ole
> I: alsa-util.c: Failed to set hardware parameters on plug:test: Tiedostoa tai hakemistoa ei ole
> E: module.c: Failed to load module "module-alsa-sink" (argument: "device=test"): initialization failed.
>
> That "Tiedostoa tai hakemistoa ei ole" error means "No such file or
> directory". I tried also with device=hw:0,0 and device=plughw:0,0 and
> the first worked while the latter gave the same errors. Since the failed
> function calls pertain to buffering, I also tried with tsched=0. It
> worked. So, maybe this is a bug in the "plug" alsa plugin, ie. it
> doesn't implement the *_near() functions properly.
>
> Lennart, any insights?
I am pretty sure that ALSA is broken here. We nowadays first try to
set the number of periods, and then set the buffer size in the
hwparams struct. If this fails we print a warning message (the ones
you see above with all the vowels regarding
snd_pcm_hw_params_set_periods_near()
resp. snd_pcm_hw_params_set_buffer_size_near()), but we do not
consider this fatal.
After doing this we call snd_pcm_hw_params() to activate our
settings -- and if this fails then we consider this a fatal problem.
Now the brokeness in ALSA is that after refusing our parameters it
still fails to configure our settings, i.e. calling the two functions
that fail breaks the hwparams setup: if we wouldn't call them, no breakage
happens at all.
The two functions in question should either succeed or fail, but if
they fail they should not modify hwparams in a way that it
subsequently becomes unusable for snd_pcm_hw_params().
This issue needs to be fixed in ALSA.
Lennart
3
9
Hi there,
I was pointed here by Audacious developers, who believe I've found a
bug in the snd-cs46xx driver, which I use for my sound card (a Turtle
Beach Santa Cruz, whose PCI ID is 1013:6003).
Audacious, as of version 2.1, uses the default period time offered by
ALSA and limits itself to the "safe API" as per Lennart Poettering.
This already uncovered a bug in the Aureal Vortex driver, and I
believe, based on the information that the devs gave me, that I have
just uncovered another. (I'm also having problems with recent versions
of OpenAL. However, I'll explain this further below.)
I first started having the problem after having upgraded to Audacious
2.1, from Audacious 1.5.1. (to be more precise, the Gentoo ebuild
1.5.1-r1.) At the time, I was using the Gentoo-patched 2.6.29-series
kernel, which I use as my normal kernel. After upgrading, I could no
longer play audio directly using ALSA; instead, Audacious would
initialise to 00:00, not play anything, and report the following to
the owning console every so often:
> ERROR: ALSA: alsa-core.c:226 (alsaplug_write_buffer): (write) snd_pcm_recover: Input/output error
The thread which was doing this also seemed to be blocking, as when I
tried to do anything else in Audacious, the interface would then
freeze until the thread was done. OSS output seems to be okay,
although as I don't have OSS compiled at all into the kernel (not even
as a module), this would be going via ALSA's own OSS emulation. In
addition, ALSA output to another audio device from Audacious works
fine.
I Googled for this and came across another snd-cs46xx user having the
exact same problem in bug AUD-53 in the JIRA implementation they were
using: http://jira.atheme.org/browse/AUD-53 . Since that user had been
asked to upgrade to tip but hadn't, I did so instead (uninstalling the
Gentoo ebuilds frst, of course) and reported back the additional debug
info:
> DEBUG: ALSA: alsa-core.c:226 (alsaplug_write_buffer): snd_pcm_writei error: Input/output error
> ERROR: ALSA: alsa-core.c:230 (alsaplug_write_buffer): snd_pcm_recover error: Input/output error
I then didn't know how to continue as I wasn't sure whether the bug
would be seen as it had previously been closed as unreproducible, so I
hopped onto the IRC channel and asked about what should be done. It
was there that the probability of it being a driver bug in the
snd-cs46xx sound driver was mentioned, and the reasoning - that the
use of ALSA's safe API has been exposing driver bugs that were
previously hidden.
As mentioned previously, I'm also having issues with OpenAL. The
version I'm using now is 1.7.411; previously I was using 1.5.304,
which I believe worked fine. (I can't easily test this now as the
Gentoo ebuild for it has gone, however.) The symptoms I got were
similar - I got no sound, and messages were printed to the console
every so often (more often than with Audacious, however):
> AL lib: alsa.c:194: Wait timeout... buffer size too low?
At first I thought this was an issue with Second Life, but on seeing
the exact same symptoms using mplayer with the '-ao openal' switch,
and also seeing it occur with 'openal-info', it seems to be an OpenAL
thing. (Since I don't have many apps that I use with OpenAL, I hadn't
known this before.) It seems that this is a symptom of whatever driver
bug is causing the Audacious error.
For the purposes of making this post, I decided to compile and install
a vanilla 2.6.30.5 kernel to boot into, so that I could make sure
there were no problems with the latest stable build. (I'd rather not
run unstable versions if I can help it, or I would have done so; this
is my primary machine. However, I'm willing to apply patches for this
specific problem in order to test them.)
The results of this were that I no longer get the messages and the
threads don't block, but I get entirely new problems now - instead of
useable sound, I get what sounds a little like noise, but more
crackly, and the seek bar in Audacious seems to be going along as fast
as it can - for each wallclock second, the seek bar goes up by about
114 seconds. The same occurs when I use mplayer with the '-ao openal'
switch; I get the noise/crackles, and the time position shown on the
console zooms by.
The description of my sound card given by 'lspci -vv' (under both
kernels) is thus:
> 05:04.0 Multimedia audio controller: Cirrus Logic CS 4614/22/24/30 [CrystalClear SoundFusion Audio Accelerator] (rev 01)
> Subsystem: Voyetra Technologies Santa Cruz
> Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
> Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=slow >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
> Latency: 64 (1000ns min, 6000ns max)
> Interrupt: pin A routed to IRQ 22
> Region 0: Memory at fdafe000 (32-bit, non-prefetchable) [size=4K]
> Region 1: Memory at fd900000 (32-bit, non-prefetchable) [size=1M]
> Capabilities: [40] Power Management version 2
> Flags: PMEClk- DSI+ D1+ D2+ AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
> Status: D0 PME-Enable- DSel=0 DScale=0 PME-
> Kernel driver in use: Sound Fusion CS46xx
> Kernel modules: snd-cs46xx
Most applications that I have seem to work properly with regards to
audio, although I've heard that this is probably because ALSA's safe
API isn't very well documented and as such most applications will not
be using it, thus not exposing the bug in snd-cs46xx.
No information is output to dmesg when this happens, on either the
Gentoo-patched 2.6.29 kernel, or the vanilla 2.6.30.5 kernel.
If you need any further information, please let me know! As stated
above, I'm willing to apply patches to the vanilla sources of the
latest stable kernel in order to test them. I don't have experience of
downloading the ALSA drivers and compiling them separately but I could
also do that if it's needed.
Thank you,
- Sophie.
6
32

Re: [alsa-devel] [PATCH] To avoid the divide by zero error during the first execution, initialize the data type.
by Troy Kisky 10 Sep '09
by Troy Kisky 10 Sep '09
10 Sep '09
avm(a)ti.com wrote:
> From: Arun Mani <a0270733(a)gtcx26221.gt.design.ti.com>
>
> Signed-off-by: Arun Mani <a0270733(a)gtcx26221.gt.design.ti.com>
> ---
> sound/soc/davinci/davinci-i2s.c | 2 ++
> 1 files changed, 2 insertions(+), 0 deletions(-)
>
> diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
> index b1ea52f..61b1763 100644
> --- a/sound/soc/davinci/davinci-i2s.c
> +++ b/sound/soc/davinci/davinci-i2s.c
> @@ -104,10 +104,12 @@ enum {
>
> static struct davinci_pcm_dma_params davinci_i2s_pcm_out = {
> .name = "I2S PCM Stereo out",
> + .data_type = 2, //Initialize the data type for playback to avoid divide by zero
You should have ".acnt = 2" also for consistency.
> };
>
> static struct davinci_pcm_dma_params davinci_i2s_pcm_in = {
> .name = "I2S PCM Stereo in",
> + .data_type = 2, //Initialize the data type for playback to avoid divide by zero
> };
>
> struct davinci_mcbsp_dev {
And if the 1st stream is an 8-bit audio stream, will it initialize it incorrectly ???
Of course, we have formats = SNDRV_PCM_FMTBIT_S16_LE, currently so it's not an issue yet...
I don't see how data_type is not being set in davinci_i2s_hw_params
before being used in davinci_pcm_prepare.
Can prepare be called before hw_params ?
There is a "return -EINVAL" in davinci_i2s_hw_params with a
"printk(KERN_WARNING "davinci-i2s: unsupported PCM format\n")" before it.
Do you see this message in your log ?
Thanks
Troy
2
2

10 Sep '09
The AK4671 is a stereo CODEC with a built-in Microphone-Amplifier,
Receiver-Amplifier and Headphone-Amplifier.
The datasheet for the ak4671 can find at the following url:
http://www.asahi-kasei.co.jp/akm/en/product/ak4671/ak4671_f01e.pdf
Signed-off-by: Joonyoung Shim <jy0922.shim(a)samsung.com>
---
Change log:
v2: - use soc-cache
- remove unused pll_on variable
- add event handler for DAC and ADC
- remove unnecessary comments and PM functions
- modify error handling in ak4671_register function
v3: - use SND_SOC_DAPM_SUPPLY instead of SND_SOC_DAPM_DAC_E and
SND_SOC_DAPM_ADC_E
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ak4671.c | 825 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/ak4671.h | 156 +++++++++
4 files changed, 987 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/ak4671.c
create mode 100644 sound/soc/codecs/ak4671.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 0edca93..a2bb659 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -19,6 +19,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
select SND_SOC_AK4642 if I2C
+ select SND_SOC_AK4671 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_PCM3008
@@ -96,6 +97,9 @@ config SND_SOC_AK4535
config SND_SOC_AK4642
tristate
+config SND_SOC_AK4671
+ tristate
+
# Cirrus Logic CS4270 Codec
config SND_SOC_CS4270
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index fb4af28..13f7b4f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -6,6 +6,7 @@ snd-soc-ad73311-objs := ad73311.o
snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
snd-soc-ak4642-objs := ak4642.o
+snd-soc-ak4671-objs := ak4671.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-l3-objs := l3.o
@@ -56,6 +57,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o
+obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c
new file mode 100644
index 0000000..b61214d
--- /dev/null
+++ b/sound/soc/codecs/ak4671.c
@@ -0,0 +1,825 @@
+/*
+ * ak4671.c -- audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim(a)samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ak4671.h"
+
+static struct snd_soc_codec *ak4671_codec;
+
+/* codec private data */
+struct ak4671_priv {
+ struct snd_soc_codec codec;
+ u8 reg_cache[AK4671_CACHEREGNUM];
+};
+
+/* ak4671 register cache & default register settings */
+static const u8 ak4671_reg[AK4671_CACHEREGNUM] = {
+ 0x00, /* AK4671_AD_DA_POWER_MANAGEMENT (0x00) */
+ 0xf6, /* AK4671_PLL_MODE_SELECT0 (0x01) */
+ 0x00, /* AK4671_PLL_MODE_SELECT1 (0x02) */
+ 0x02, /* AK4671_FORMAT_SELECT (0x03) */
+ 0x00, /* AK4671_MIC_SIGNAL_SELECT (0x04) */
+ 0x55, /* AK4671_MIC_AMP_GAIN (0x05) */
+ 0x00, /* AK4671_MIXING_POWER_MANAGEMENT0 (0x06) */
+ 0x00, /* AK4671_MIXING_POWER_MANAGEMENT1 (0x07) */
+ 0xb5, /* AK4671_OUTPUT_VOLUME_CONTROL (0x08) */
+ 0x00, /* AK4671_LOUT1_SIGNAL_SELECT (0x09) */
+ 0x00, /* AK4671_ROUT1_SIGNAL_SELECT (0x0a) */
+ 0x00, /* AK4671_LOUT2_SIGNAL_SELECT (0x0b) */
+ 0x00, /* AK4671_ROUT2_SIGNAL_SELECT (0x0c) */
+ 0x00, /* AK4671_LOUT3_SIGNAL_SELECT (0x0d) */
+ 0x00, /* AK4671_ROUT3_SIGNAL_SELECT (0x0e) */
+ 0x00, /* AK4671_LOUT1_POWER_MANAGERMENT (0x0f) */
+ 0x00, /* AK4671_LOUT2_POWER_MANAGERMENT (0x10) */
+ 0x80, /* AK4671_LOUT3_POWER_MANAGERMENT (0x11) */
+ 0x91, /* AK4671_LCH_INPUT_VOLUME_CONTROL (0x12) */
+ 0x91, /* AK4671_RCH_INPUT_VOLUME_CONTROL (0x13) */
+ 0xe1, /* AK4671_ALC_REFERENCE_SELECT (0x14) */
+ 0x00, /* AK4671_DIGITAL_MIXING_CONTROL (0x15) */
+ 0x00, /* AK4671_ALC_TIMER_SELECT (0x16) */
+ 0x00, /* AK4671_ALC_MODE_CONTROL (0x17) */
+ 0x02, /* AK4671_MODE_CONTROL1 (0x18) */
+ 0x01, /* AK4671_MODE_CONTROL2 (0x19) */
+ 0x18, /* AK4671_LCH_OUTPUT_VOLUME_CONTROL (0x1a) */
+ 0x18, /* AK4671_RCH_OUTPUT_VOLUME_CONTROL (0x1b) */
+ 0x00, /* AK4671_SIDETONE_A_CONTROL (0x1c) */
+ 0x02, /* AK4671_DIGITAL_FILTER_SELECT (0x1d) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT0 (0x1e) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT1 (0x1f) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT2 (0x20) */
+ 0x00, /* AK4671_FIL3_COEFFICIENT3 (0x21) */
+ 0x00, /* AK4671_EQ_COEFFICIENT0 (0x22) */
+ 0x00, /* AK4671_EQ_COEFFICIENT1 (0x23) */
+ 0x00, /* AK4671_EQ_COEFFICIENT2 (0x24) */
+ 0x00, /* AK4671_EQ_COEFFICIENT3 (0x25) */
+ 0x00, /* AK4671_EQ_COEFFICIENT4 (0x26) */
+ 0x00, /* AK4671_EQ_COEFFICIENT5 (0x27) */
+ 0xa9, /* AK4671_FIL1_COEFFICIENT0 (0x28) */
+ 0x1f, /* AK4671_FIL1_COEFFICIENT1 (0x29) */
+ 0xad, /* AK4671_FIL1_COEFFICIENT2 (0x2a) */
+ 0x20, /* AK4671_FIL1_COEFFICIENT3 (0x2b) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT0 (0x2c) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT1 (0x2d) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT2 (0x2e) */
+ 0x00, /* AK4671_FIL2_COEFFICIENT3 (0x2f) */
+ 0x00, /* AK4671_DIGITAL_FILTER_SELECT2 (0x30) */
+ 0x00, /* this register not used */
+ 0x00, /* AK4671_E1_COEFFICIENT0 (0x32) */
+ 0x00, /* AK4671_E1_COEFFICIENT1 (0x33) */
+ 0x00, /* AK4671_E1_COEFFICIENT2 (0x34) */
+ 0x00, /* AK4671_E1_COEFFICIENT3 (0x35) */
+ 0x00, /* AK4671_E1_COEFFICIENT4 (0x36) */
+ 0x00, /* AK4671_E1_COEFFICIENT5 (0x37) */
+ 0x00, /* AK4671_E2_COEFFICIENT0 (0x38) */
+ 0x00, /* AK4671_E2_COEFFICIENT1 (0x39) */
+ 0x00, /* AK4671_E2_COEFFICIENT2 (0x3a) */
+ 0x00, /* AK4671_E2_COEFFICIENT3 (0x3b) */
+ 0x00, /* AK4671_E2_COEFFICIENT4 (0x3c) */
+ 0x00, /* AK4671_E2_COEFFICIENT5 (0x3d) */
+ 0x00, /* AK4671_E3_COEFFICIENT0 (0x3e) */
+ 0x00, /* AK4671_E3_COEFFICIENT1 (0x3f) */
+ 0x00, /* AK4671_E3_COEFFICIENT2 (0x40) */
+ 0x00, /* AK4671_E3_COEFFICIENT3 (0x41) */
+ 0x00, /* AK4671_E3_COEFFICIENT4 (0x42) */
+ 0x00, /* AK4671_E3_COEFFICIENT5 (0x43) */
+ 0x00, /* AK4671_E4_COEFFICIENT0 (0x44) */
+ 0x00, /* AK4671_E4_COEFFICIENT1 (0x45) */
+ 0x00, /* AK4671_E4_COEFFICIENT2 (0x46) */
+ 0x00, /* AK4671_E4_COEFFICIENT3 (0x47) */
+ 0x00, /* AK4671_E4_COEFFICIENT4 (0x48) */
+ 0x00, /* AK4671_E4_COEFFICIENT5 (0x49) */
+ 0x00, /* AK4671_E5_COEFFICIENT0 (0x4a) */
+ 0x00, /* AK4671_E5_COEFFICIENT1 (0x4b) */
+ 0x00, /* AK4671_E5_COEFFICIENT2 (0x4c) */
+ 0x00, /* AK4671_E5_COEFFICIENT3 (0x4d) */
+ 0x00, /* AK4671_E5_COEFFICIENT4 (0x4e) */
+ 0x00, /* AK4671_E5_COEFFICIENT5 (0x4f) */
+ 0x88, /* AK4671_EQ_CONTROL_250HZ_100HZ (0x50) */
+ 0x88, /* AK4671_EQ_CONTROL_3500HZ_1KHZ (0x51) */
+ 0x08, /* AK4671_EQ_CONTRO_10KHZ (0x52) */
+ 0x00, /* AK4671_PCM_IF_CONTROL0 (0x53) */
+ 0x00, /* AK4671_PCM_IF_CONTROL1 (0x54) */
+ 0x00, /* AK4671_PCM_IF_CONTROL2 (0x55) */
+ 0x18, /* AK4671_DIGITAL_VOLUME_B_CONTROL (0x56) */
+ 0x18, /* AK4671_DIGITAL_VOLUME_C_CONTROL (0x57) */
+ 0x00, /* AK4671_SIDETONE_VOLUME_CONTROL (0x58) */
+ 0x00, /* AK4671_DIGITAL_MIXING_CONTROL2 (0x59) */
+ 0x00, /* AK4671_SAR_ADC_CONTROL (0x5a) */
+};
+
+/*
+ * LOUT1/ROUT1 output volume control:
+ * from -24 to 6 dB in 6 dB steps (mute instead of -30 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out1_tlv, -3000, 600, 1);
+
+/*
+ * LOUT2/ROUT2 output volume control:
+ * from -33 to 6 dB in 3 dB steps (mute instead of -33 dB)
+ */
+static DECLARE_TLV_DB_SCALE(out2_tlv, -3300, 300, 1);
+
+/*
+ * LOUT3/ROUT3 output volume control:
+ * from -6 to 3 dB in 3 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(out3_tlv, -600, 300, 0);
+
+/*
+ * Mic amp gain control:
+ * from -15 to 30 dB in 3 dB steps
+ * REVISIT: The actual min value(0x01) is -12 dB and the reg value 0x00 is not
+ * available
+ */
+static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -1500, 300, 0);
+
+static const struct snd_kcontrol_new ak4671_snd_controls[] = {
+ /* Common playback gain controls */
+ SOC_SINGLE_TLV("Line Output1 Playback Volume",
+ AK4671_OUTPUT_VOLUME_CONTROL, 0, 0x6, 0, out1_tlv),
+ SOC_SINGLE_TLV("Headphone Output2 Playback Volume",
+ AK4671_OUTPUT_VOLUME_CONTROL, 4, 0xd, 0, out2_tlv),
+ SOC_SINGLE_TLV("Line Output3 Playback Volume",
+ AK4671_LOUT3_POWER_MANAGERMENT, 6, 0x3, 0, out3_tlv),
+
+ /* Common capture gain controls */
+ SOC_DOUBLE_TLV("Mic Amp Capture Volume",
+ AK4671_MIC_AMP_GAIN, 0, 4, 0xf, 0, mic_amp_tlv),
+};
+
+/* event handlers */
+static int ak4671_out2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ u8 reg;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+ reg |= AK4671_MUTEN;
+ snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT);
+ reg &= ~AK4671_MUTEN;
+ snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg);
+ break;
+ }
+
+ return 0;
+}
+
+/* Output Mixers */
+static const struct snd_kcontrol_new ak4671_lout1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACL", AK4671_LOUT1_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINL1", AK4671_LOUT1_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINL2", AK4671_LOUT1_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINL3", AK4671_LOUT1_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINL4", AK4671_LOUT1_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPL", AK4671_LOUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout1_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACR", AK4671_ROUT1_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINR1", AK4671_ROUT1_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINR2", AK4671_ROUT1_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINR3", AK4671_ROUT1_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINR4", AK4671_ROUT1_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPR", AK4671_ROUT1_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout2_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACHL", AK4671_LOUT2_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINH1", AK4671_LOUT2_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINH2", AK4671_LOUT2_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINH3", AK4671_LOUT2_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINH4", AK4671_LOUT2_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPHL", AK4671_LOUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout2_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACHR", AK4671_ROUT2_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINH1", AK4671_ROUT2_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINH2", AK4671_ROUT2_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINH3", AK4671_ROUT2_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINH4", AK4671_ROUT2_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPHR", AK4671_ROUT2_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_lout3_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACSL", AK4671_LOUT3_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("LINS1", AK4671_LOUT3_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("LINS2", AK4671_LOUT3_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("LINS3", AK4671_LOUT3_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("LINS4", AK4671_LOUT3_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPSL", AK4671_LOUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+static const struct snd_kcontrol_new ak4671_rout3_mixer_controls[] = {
+ SOC_DAPM_SINGLE("DACSR", AK4671_ROUT3_SIGNAL_SELECT, 0, 1, 0),
+ SOC_DAPM_SINGLE("RINS1", AK4671_ROUT3_SIGNAL_SELECT, 1, 1, 0),
+ SOC_DAPM_SINGLE("RINS2", AK4671_ROUT3_SIGNAL_SELECT, 2, 1, 0),
+ SOC_DAPM_SINGLE("RINS3", AK4671_ROUT3_SIGNAL_SELECT, 3, 1, 0),
+ SOC_DAPM_SINGLE("RINS4", AK4671_ROUT3_SIGNAL_SELECT, 4, 1, 0),
+ SOC_DAPM_SINGLE("LOOPSR", AK4671_ROUT3_SIGNAL_SELECT, 5, 1, 0),
+};
+
+/* Input MUXs */
+static const char *ak4671_lin_mux_texts[] =
+ {"LIN1", "LIN2", "LIN3", "LIN4"};
+static const struct soc_enum ak4671_lin_mux_enum =
+ SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 0,
+ ARRAY_SIZE(ak4671_lin_mux_texts),
+ ak4671_lin_mux_texts);
+static const struct snd_kcontrol_new ak4671_lin_mux_control =
+ SOC_DAPM_ENUM("Route", ak4671_lin_mux_enum);
+
+static const char *ak4671_rin_mux_texts[] =
+ {"RIN1", "RIN2", "RIN3", "RIN4"};
+static const struct soc_enum ak4671_rin_mux_enum =
+ SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 2,
+ ARRAY_SIZE(ak4671_rin_mux_texts),
+ ak4671_rin_mux_texts);
+static const struct snd_kcontrol_new ak4671_rin_mux_control =
+ SOC_DAPM_ENUM("Route", ak4671_rin_mux_enum);
+
+static const struct snd_soc_dapm_widget ak4671_dapm_widgets[] = {
+ /* Inputs */
+ SND_SOC_DAPM_INPUT("LIN1"),
+ SND_SOC_DAPM_INPUT("RIN1"),
+ SND_SOC_DAPM_INPUT("LIN2"),
+ SND_SOC_DAPM_INPUT("RIN2"),
+ SND_SOC_DAPM_INPUT("LIN3"),
+ SND_SOC_DAPM_INPUT("RIN3"),
+ SND_SOC_DAPM_INPUT("LIN4"),
+ SND_SOC_DAPM_INPUT("RIN4"),
+
+ /* Outputs */
+ SND_SOC_DAPM_OUTPUT("LOUT1"),
+ SND_SOC_DAPM_OUTPUT("ROUT1"),
+ SND_SOC_DAPM_OUTPUT("LOUT2"),
+ SND_SOC_DAPM_OUTPUT("ROUT2"),
+ SND_SOC_DAPM_OUTPUT("LOUT3"),
+ SND_SOC_DAPM_OUTPUT("ROUT3"),
+
+ /* DAC */
+ SND_SOC_DAPM_DAC("DAC Left", "Left HiFi Playback",
+ AK4671_AD_DA_POWER_MANAGEMENT, 6, 0),
+ SND_SOC_DAPM_DAC("DAC Right", "Right HiFi Playback",
+ AK4671_AD_DA_POWER_MANAGEMENT, 7, 0),
+
+ /* ADC */
+ SND_SOC_DAPM_ADC("ADC Left", "Left HiFi Capture",
+ AK4671_AD_DA_POWER_MANAGEMENT, 4, 0),
+ SND_SOC_DAPM_ADC("ADC Right", "Right HiFi Capture",
+ AK4671_AD_DA_POWER_MANAGEMENT, 5, 0),
+
+ /* PGA */
+ SND_SOC_DAPM_PGA("LOUT2 Mix Amp",
+ AK4671_LOUT2_POWER_MANAGERMENT, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ROUT2 Mix Amp",
+ AK4671_LOUT2_POWER_MANAGERMENT, 6, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("LIN1 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN1 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN2 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN2 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN3 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN3 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LIN4 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("RIN4 Mixing Circuit",
+ AK4671_MIXING_POWER_MANAGEMENT1, 7, 0, NULL, 0),
+
+ /* Output Mixers */
+ SND_SOC_DAPM_MIXER("LOUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 0, 0,
+ &ak4671_lout1_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout1_mixer_controls)),
+ SND_SOC_DAPM_MIXER("ROUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 1, 0,
+ &ak4671_rout1_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout1_mixer_controls)),
+ SND_SOC_DAPM_MIXER_E("LOUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+ 0, 0, &ak4671_lout2_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout2_mixer_controls),
+ ak4671_out2_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MIXER_E("ROUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT,
+ 1, 0, &ak4671_rout2_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout2_mixer_controls),
+ ak4671_out2_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_MIXER("LOUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 0, 0,
+ &ak4671_lout3_mixer_controls[0],
+ ARRAY_SIZE(ak4671_lout3_mixer_controls)),
+ SND_SOC_DAPM_MIXER("ROUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 1, 0,
+ &ak4671_rout3_mixer_controls[0],
+ ARRAY_SIZE(ak4671_rout3_mixer_controls)),
+
+ /* Input MUXs */
+ SND_SOC_DAPM_MUX("LIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 2, 0,
+ &ak4671_lin_mux_control),
+ SND_SOC_DAPM_MUX("RIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 3, 0,
+ &ak4671_rin_mux_control),
+
+ /* Mic Power */
+ SND_SOC_DAPM_MICBIAS("Mic Bias", AK4671_AD_DA_POWER_MANAGEMENT, 1, 0),
+
+ /* Supply */
+ SND_SOC_DAPM_SUPPLY("PMPLL", AK4671_PLL_MODE_SELECT1, 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ {"DAC Left", "NULL", "PMPLL"},
+ {"DAC Right", "NULL", "PMPLL"},
+ {"ADC Left", "NULL", "PMPLL"},
+ {"ADC Right", "NULL", "PMPLL"},
+
+ /* Outputs */
+ {"LOUT1", "NULL", "LOUT1 Mixer"},
+ {"ROUT1", "NULL", "ROUT1 Mixer"},
+ {"LOUT2", "NULL", "LOUT2 Mix Amp"},
+ {"ROUT2", "NULL", "ROUT2 Mix Amp"},
+ {"LOUT3", "NULL", "LOUT3 Mixer"},
+ {"ROUT3", "NULL", "ROUT3 Mixer"},
+
+ {"LOUT1 Mixer", "DACL", "DAC Left"},
+ {"ROUT1 Mixer", "DACR", "DAC Right"},
+ {"LOUT2 Mixer", "DACHL", "DAC Left"},
+ {"ROUT2 Mixer", "DACHR", "DAC Right"},
+ {"LOUT2 Mix Amp", "NULL", "LOUT2 Mixer"},
+ {"ROUT2 Mix Amp", "NULL", "ROUT2 Mixer"},
+ {"LOUT3 Mixer", "DACSL", "DAC Left"},
+ {"ROUT3 Mixer", "DACSR", "DAC Right"},
+
+ /* Inputs */
+ {"LIN MUX", "LIN1", "LIN1"},
+ {"LIN MUX", "LIN2", "LIN2"},
+ {"LIN MUX", "LIN3", "LIN3"},
+ {"LIN MUX", "LIN4", "LIN4"},
+
+ {"RIN MUX", "RIN1", "RIN1"},
+ {"RIN MUX", "RIN2", "RIN2"},
+ {"RIN MUX", "RIN3", "RIN3"},
+ {"RIN MUX", "RIN4", "RIN4"},
+
+ {"LIN1", NULL, "Mic Bias"},
+ {"RIN1", NULL, "Mic Bias"},
+ {"LIN2", NULL, "Mic Bias"},
+ {"RIN2", NULL, "Mic Bias"},
+
+ {"ADC Left", "NULL", "LIN MUX"},
+ {"ADC Right", "NULL", "RIN MUX"},
+
+ /* Analog Loops */
+ {"LIN1 Mixing Circuit", "NULL", "LIN1"},
+ {"RIN1 Mixing Circuit", "NULL", "RIN1"},
+ {"LIN2 Mixing Circuit", "NULL", "LIN2"},
+ {"RIN2 Mixing Circuit", "NULL", "RIN2"},
+ {"LIN3 Mixing Circuit", "NULL", "LIN3"},
+ {"RIN3 Mixing Circuit", "NULL", "RIN3"},
+ {"LIN4 Mixing Circuit", "NULL", "LIN4"},
+ {"RIN4 Mixing Circuit", "NULL", "RIN4"},
+
+ {"LOUT1 Mixer", "LINL1", "LIN1 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR1", "RIN1 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH1", "LIN1 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH1", "RIN1 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS1", "LIN1 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS1", "RIN1 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL2", "LIN2 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR2", "RIN2 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH2", "LIN2 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH2", "RIN2 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS2", "LIN2 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS2", "RIN2 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL3", "LIN3 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR3", "RIN3 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH3", "LIN3 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH3", "RIN3 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS3", "LIN3 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS3", "RIN3 Mixing Circuit"},
+
+ {"LOUT1 Mixer", "LINL4", "LIN4 Mixing Circuit"},
+ {"ROUT1 Mixer", "RINR4", "RIN4 Mixing Circuit"},
+ {"LOUT2 Mixer", "LINH4", "LIN4 Mixing Circuit"},
+ {"ROUT2 Mixer", "RINH4", "RIN4 Mixing Circuit"},
+ {"LOUT3 Mixer", "LINS4", "LIN4 Mixing Circuit"},
+ {"ROUT3 Mixer", "RINS4", "RIN4 Mixing Circuit"},
+};
+
+static int ak4671_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, ak4671_dapm_widgets,
+ ARRAY_SIZE(ak4671_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+static int ak4671_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 fs;
+
+ fs = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+ fs &= ~AK4671_FS;
+
+ switch (params_rate(params)) {
+ case 8000:
+ fs |= AK4671_FS_8KHZ;
+ break;
+ case 12000:
+ fs |= AK4671_FS_12KHZ;
+ break;
+ case 16000:
+ fs |= AK4671_FS_16KHZ;
+ break;
+ case 24000:
+ fs |= AK4671_FS_24KHZ;
+ break;
+ case 11025:
+ fs |= AK4671_FS_11_025KHZ;
+ break;
+ case 22050:
+ fs |= AK4671_FS_22_05KHZ;
+ break;
+ case 32000:
+ fs |= AK4671_FS_32KHZ;
+ break;
+ case 44100:
+ fs |= AK4671_FS_44_1KHZ;
+ break;
+ case 48000:
+ fs |= AK4671_FS_48KHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, fs);
+
+ return 0;
+}
+
+static int ak4671_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 pll;
+
+ pll = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0);
+ pll &= ~AK4671_PLL;
+
+ switch (freq) {
+ case 11289600:
+ pll |= AK4671_PLL_11_2896MHZ;
+ break;
+ case 12000000:
+ pll |= AK4671_PLL_12MHZ;
+ break;
+ case 12288000:
+ pll |= AK4671_PLL_12_288MHZ;
+ break;
+ case 13000000:
+ pll |= AK4671_PLL_13MHZ;
+ break;
+ case 13500000:
+ pll |= AK4671_PLL_13_5MHZ;
+ break;
+ case 19200000:
+ pll |= AK4671_PLL_19_2MHZ;
+ break;
+ case 24000000:
+ pll |= AK4671_PLL_24MHZ;
+ break;
+ case 26000000:
+ pll |= AK4671_PLL_26MHZ;
+ break;
+ case 27000000:
+ pll |= AK4671_PLL_27MHZ;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, pll);
+
+ return 0;
+}
+
+static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 mode;
+ u8 format;
+
+ /* set master/slave audio interface */
+ mode = snd_soc_read(codec, AK4671_PLL_MODE_SELECT1);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ mode |= AK4671_M_S;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ mode &= ~(AK4671_M_S);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ format = snd_soc_read(codec, AK4671_FORMAT_SELECT);
+ format &= ~AK4671_DIF;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ format |= AK4671_DIF_I2S_MODE;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ format |= AK4671_DIF_MSB_MODE;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ format |= AK4671_DIF_DSP_MODE;
+ format |= AK4671_BCKP;
+ format |= AK4671_MSBS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set mode and format */
+ snd_soc_write(codec, AK4671_PLL_MODE_SELECT1, mode);
+ snd_soc_write(codec, AK4671_FORMAT_SELECT, format);
+
+ return 0;
+}
+
+static int ak4671_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u8 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ reg = snd_soc_read(codec, AK4671_AD_DA_POWER_MANAGEMENT);
+ snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT,
+ reg | AK4671_PMVCM);
+ break;
+ case SND_SOC_BIAS_OFF:
+ snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT, 0x00);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+#define AK4671_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000)
+
+#define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE
+
+static struct snd_soc_dai_ops ak4671_dai_ops = {
+ .hw_params = ak4671_hw_params,
+ .set_sysclk = ak4671_set_dai_sysclk,
+ .set_fmt = ak4671_set_dai_fmt,
+};
+
+struct snd_soc_dai ak4671_dai = {
+ .name = "AK4671",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4671_RATES,
+ .formats = AK4671_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AK4671_RATES,
+ .formats = AK4671_FORMATS,},
+ .ops = &ak4671_dai_ops,
+};
+EXPORT_SYMBOL_GPL(ak4671_dai);
+
+static int ak4671_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ int ret = 0;
+
+ if (ak4671_codec == NULL) {
+ dev_err(&pdev->dev, "Codec device not registered\n");
+ return -ENODEV;
+ }
+
+ socdev->card->codec = ak4671_codec;
+ codec = ak4671_codec;
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms: %d\n", ret);
+ goto pcm_err;
+ }
+
+ snd_soc_add_controls(codec, ak4671_snd_controls,
+ ARRAY_SIZE(ak4671_snd_controls));
+ ak4671_add_widgets(codec);
+
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card: %d\n", ret);
+ goto card_err;
+ }
+
+ ak4671_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return ret;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+pcm_err:
+ return ret;
+}
+
+static int ak4671_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_ak4671 = {
+ .probe = ak4671_probe,
+ .remove = ak4671_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_ak4671);
+
+static int ak4671_register(struct ak4671_priv *ak4671,
+ enum snd_soc_control_type control)
+{
+ int ret;
+ struct snd_soc_codec *codec = &ak4671->codec;
+
+ if (ak4671_codec) {
+ dev_err(codec->dev, "Another AK4671 is registered\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->private_data = ak4671;
+ codec->name = "AK4671";
+ codec->owner = THIS_MODULE;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+ codec->set_bias_level = ak4671_set_bias_level;
+ codec->dai = &ak4671_dai;
+ codec->num_dai = 1;
+ codec->reg_cache_size = AK4671_CACHEREGNUM;
+ codec->reg_cache = &ak4671->reg_cache;
+
+ memcpy(codec->reg_cache, ak4671_reg, sizeof(ak4671_reg));
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, control);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ goto err;
+ }
+
+ ak4671_dai.dev = codec->dev;
+ ak4671_codec = codec;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_register_dai(&ak4671_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAI: %d\n", ret);
+ goto err_codec;
+ }
+
+ return 0;
+
+err_codec:
+ snd_soc_unregister_codec(codec);
+err:
+ kfree(ak4671);
+ return ret;
+}
+
+static void ak4671_unregister(struct ak4671_priv *ak4671)
+{
+ ak4671_set_bias_level(&ak4671->codec, SND_SOC_BIAS_OFF);
+ snd_soc_unregister_dai(&ak4671_dai);
+ snd_soc_unregister_codec(&ak4671->codec);
+ kfree(ak4671);
+ ak4671_codec = NULL;
+}
+
+static int __devinit ak4671_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ak4671_priv *ak4671;
+ struct snd_soc_codec *codec;
+
+ ak4671 = kzalloc(sizeof(struct ak4671_priv), GFP_KERNEL);
+ if (ak4671 == NULL)
+ return -ENOMEM;
+
+ codec = &ak4671->codec;
+ codec->hw_write = (hw_write_t)i2c_master_send;
+
+ i2c_set_clientdata(client, ak4671);
+ codec->control_data = client;
+
+ codec->dev = &client->dev;
+
+ return ak4671_register(ak4671, SND_SOC_I2C);
+}
+
+static __devexit int ak4671_i2c_remove(struct i2c_client *client)
+{
+ struct ak4671_priv *ak4671 = i2c_get_clientdata(client);
+
+ ak4671_unregister(ak4671);
+
+ return 0;
+}
+
+static const struct i2c_device_id ak4671_i2c_id[] = {
+ { "ak4671", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ak4671_i2c_id);
+
+static struct i2c_driver ak4671_i2c_driver = {
+ .driver = {
+ .name = "ak4671",
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4671_i2c_probe,
+ .remove = __devexit_p(ak4671_i2c_remove),
+ .id_table = ak4671_i2c_id,
+};
+
+static int __init ak4671_modinit(void)
+{
+ return i2c_add_driver(&ak4671_i2c_driver);
+}
+module_init(ak4671_modinit);
+
+static void __exit ak4671_exit(void)
+{
+ i2c_del_driver(&ak4671_i2c_driver);
+}
+module_exit(ak4671_exit);
+
+MODULE_DESCRIPTION("ASoC AK4671 codec driver");
+MODULE_AUTHOR("Joonyoung Shim <jy0922.shim(a)samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ak4671.h b/sound/soc/codecs/ak4671.h
new file mode 100644
index 0000000..e2fad96
--- /dev/null
+++ b/sound/soc/codecs/ak4671.h
@@ -0,0 +1,156 @@
+/*
+ * ak4671.h -- audio driver for AK4671
+ *
+ * Copyright (C) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim(a)samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#ifndef _AK4671_H
+#define _AK4671_H
+
+#define AK4671_AD_DA_POWER_MANAGEMENT 0x00
+#define AK4671_PLL_MODE_SELECT0 0x01
+#define AK4671_PLL_MODE_SELECT1 0x02
+#define AK4671_FORMAT_SELECT 0x03
+#define AK4671_MIC_SIGNAL_SELECT 0x04
+#define AK4671_MIC_AMP_GAIN 0x05
+#define AK4671_MIXING_POWER_MANAGEMENT0 0x06
+#define AK4671_MIXING_POWER_MANAGEMENT1 0x07
+#define AK4671_OUTPUT_VOLUME_CONTROL 0x08
+#define AK4671_LOUT1_SIGNAL_SELECT 0x09
+#define AK4671_ROUT1_SIGNAL_SELECT 0x0a
+#define AK4671_LOUT2_SIGNAL_SELECT 0x0b
+#define AK4671_ROUT2_SIGNAL_SELECT 0x0c
+#define AK4671_LOUT3_SIGNAL_SELECT 0x0d
+#define AK4671_ROUT3_SIGNAL_SELECT 0x0e
+#define AK4671_LOUT1_POWER_MANAGERMENT 0x0f
+#define AK4671_LOUT2_POWER_MANAGERMENT 0x10
+#define AK4671_LOUT3_POWER_MANAGERMENT 0x11
+#define AK4671_LCH_INPUT_VOLUME_CONTROL 0x12
+#define AK4671_RCH_INPUT_VOLUME_CONTROL 0x13
+#define AK4671_ALC_REFERENCE_SELECT 0x14
+#define AK4671_DIGITAL_MIXING_CONTROL 0x15
+#define AK4671_ALC_TIMER_SELECT 0x16
+#define AK4671_ALC_MODE_CONTROL 0x17
+#define AK4671_MODE_CONTROL1 0x18
+#define AK4671_MODE_CONTROL2 0x19
+#define AK4671_LCH_OUTPUT_VOLUME_CONTROL 0x1a
+#define AK4671_RCH_OUTPUT_VOLUME_CONTROL 0x1b
+#define AK4671_SIDETONE_A_CONTROL 0x1c
+#define AK4671_DIGITAL_FILTER_SELECT 0x1d
+#define AK4671_FIL3_COEFFICIENT0 0x1e
+#define AK4671_FIL3_COEFFICIENT1 0x1f
+#define AK4671_FIL3_COEFFICIENT2 0x20
+#define AK4671_FIL3_COEFFICIENT3 0x21
+#define AK4671_EQ_COEFFICIENT0 0x22
+#define AK4671_EQ_COEFFICIENT1 0x23
+#define AK4671_EQ_COEFFICIENT2 0x24
+#define AK4671_EQ_COEFFICIENT3 0x25
+#define AK4671_EQ_COEFFICIENT4 0x26
+#define AK4671_EQ_COEFFICIENT5 0x27
+#define AK4671_FIL1_COEFFICIENT0 0x28
+#define AK4671_FIL1_COEFFICIENT1 0x29
+#define AK4671_FIL1_COEFFICIENT2 0x2a
+#define AK4671_FIL1_COEFFICIENT3 0x2b
+#define AK4671_FIL2_COEFFICIENT0 0x2c
+#define AK4671_FIL2_COEFFICIENT1 0x2d
+#define AK4671_FIL2_COEFFICIENT2 0x2e
+#define AK4671_FIL2_COEFFICIENT3 0x2f
+#define AK4671_DIGITAL_FILTER_SELECT2 0x30
+#define AK4671_E1_COEFFICIENT0 0x32
+#define AK4671_E1_COEFFICIENT1 0x33
+#define AK4671_E1_COEFFICIENT2 0x34
+#define AK4671_E1_COEFFICIENT3 0x35
+#define AK4671_E1_COEFFICIENT4 0x36
+#define AK4671_E1_COEFFICIENT5 0x37
+#define AK4671_E2_COEFFICIENT0 0x38
+#define AK4671_E2_COEFFICIENT1 0x39
+#define AK4671_E2_COEFFICIENT2 0x3a
+#define AK4671_E2_COEFFICIENT3 0x3b
+#define AK4671_E2_COEFFICIENT4 0x3c
+#define AK4671_E2_COEFFICIENT5 0x3d
+#define AK4671_E3_COEFFICIENT0 0x3e
+#define AK4671_E3_COEFFICIENT1 0x3f
+#define AK4671_E3_COEFFICIENT2 0x40
+#define AK4671_E3_COEFFICIENT3 0x41
+#define AK4671_E3_COEFFICIENT4 0x42
+#define AK4671_E3_COEFFICIENT5 0x43
+#define AK4671_E4_COEFFICIENT0 0x44
+#define AK4671_E4_COEFFICIENT1 0x45
+#define AK4671_E4_COEFFICIENT2 0x46
+#define AK4671_E4_COEFFICIENT3 0x47
+#define AK4671_E4_COEFFICIENT4 0x48
+#define AK4671_E4_COEFFICIENT5 0x49
+#define AK4671_E5_COEFFICIENT0 0x4a
+#define AK4671_E5_COEFFICIENT1 0x4b
+#define AK4671_E5_COEFFICIENT2 0x4c
+#define AK4671_E5_COEFFICIENT3 0x4d
+#define AK4671_E5_COEFFICIENT4 0x4e
+#define AK4671_E5_COEFFICIENT5 0x4f
+#define AK4671_EQ_CONTROL_250HZ_100HZ 0x50
+#define AK4671_EQ_CONTROL_3500HZ_1KHZ 0x51
+#define AK4671_EQ_CONTRO_10KHZ 0x52
+#define AK4671_PCM_IF_CONTROL0 0x53
+#define AK4671_PCM_IF_CONTROL1 0x54
+#define AK4671_PCM_IF_CONTROL2 0x55
+#define AK4671_DIGITAL_VOLUME_B_CONTROL 0x56
+#define AK4671_DIGITAL_VOLUME_C_CONTROL 0x57
+#define AK4671_SIDETONE_VOLUME_CONTROL 0x58
+#define AK4671_DIGITAL_MIXING_CONTROL2 0x59
+#define AK4671_SAR_ADC_CONTROL 0x5a
+
+#define AK4671_CACHEREGNUM (AK4671_SAR_ADC_CONTROL + 1)
+
+/* Bitfield Definitions */
+
+/* AK4671_AD_DA_POWER_MANAGEMENT (0x00) Fields */
+#define AK4671_PMVCM 0x01
+
+/* AK4671_PLL_MODE_SELECT0 (0x01) Fields */
+#define AK4671_PLL 0x0f
+#define AK4671_PLL_11_2896MHZ (4 << 0)
+#define AK4671_PLL_12_288MHZ (5 << 0)
+#define AK4671_PLL_12MHZ (6 << 0)
+#define AK4671_PLL_24MHZ (7 << 0)
+#define AK4671_PLL_19_2MHZ (8 << 0)
+#define AK4671_PLL_13_5MHZ (12 << 0)
+#define AK4671_PLL_27MHZ (13 << 0)
+#define AK4671_PLL_13MHZ (14 << 0)
+#define AK4671_PLL_26MHZ (15 << 0)
+#define AK4671_FS 0xf0
+#define AK4671_FS_8KHZ (0 << 4)
+#define AK4671_FS_12KHZ (1 << 4)
+#define AK4671_FS_16KHZ (2 << 4)
+#define AK4671_FS_24KHZ (3 << 4)
+#define AK4671_FS_11_025KHZ (5 << 4)
+#define AK4671_FS_22_05KHZ (7 << 4)
+#define AK4671_FS_32KHZ (10 << 4)
+#define AK4671_FS_48KHZ (11 << 4)
+#define AK4671_FS_44_1KHZ (15 << 4)
+
+/* AK4671_PLL_MODE_SELECT1 (0x02) Fields */
+#define AK4671_PMPLL 0x01
+#define AK4671_M_S 0x02
+
+/* AK4671_FORMAT_SELECT (0x03) Fields */
+#define AK4671_DIF 0x03
+#define AK4671_DIF_DSP_MODE (0 << 0)
+#define AK4671_DIF_MSB_MODE (2 << 0)
+#define AK4671_DIF_I2S_MODE (3 << 0)
+#define AK4671_BCKP 0x04
+#define AK4671_MSBS 0x08
+#define AK4671_SDOD 0x10
+
+/* AK4671_LOUT2_POWER_MANAGEMENT (0x10) Fields */
+#define AK4671_MUTEN 0x04
+
+extern struct snd_soc_dai ak4671_dai;
+extern struct snd_soc_codec_device soc_codec_dev_ak4671;
+
+#endif
--
1.6.0.4
2
1

[alsa-devel] Is there a way to figure out if the clocks of two snd_pcm_t's deviate? (snd_pcm_info_get_sync?)
by Lennart Poettering 09 Sep '09
by Lennart Poettering 09 Sep '09
09 Sep '09
Heya!
I am looking for a way to find out whether the clocks of two
snd_pcm_t's are perfectly in sync and don't deviate (or only deviate
by a constant phase).
i.e. for real duplex devices that use the same crystal for input and
output this should tell me "Yes" for the playback snd_pcm_t tested
against the recording snd_pcm_t; and for all other, unrelated audio
devices it should return "No".
I found snd_pcm_info_get_sync() in the docs, but its documentation is
a bit terse, so I am wondering if this might be what I want. Is it?
Does this (untested) code snippet do what I hope it does?
<snip>
snd_pcm_info(a, info_a);
snd_pcm_info(b, info_b);
in_sync = memcmp(&snd_pcm_info_get_sync(info_a), &snd_pcm_info_get_sync(info_b)) == 0;
</snip>
Thanks,
Lennart
--
Lennart Poettering Red Hat, Inc.
lennart [at] poettering [dot] net
http://0pointer.net/lennart/ GnuPG 0x1A015CC4
4
6
Hi,
I have a question about how pts are computed using alsa while grabbing
audio.
I take an example to explain :
I do grab audio during 10 seconds, the first 2 seconds, there is no problem,
so the pts are like that (I take examples values) : 1 2 3 4 5 6 .... x <- x
is the value of the last pts (2nd second).
Then, for any reason, no capture during 3 seconds. And then no problem until
the end.
Nicolas Krieger
1
1