summaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/Kconfig2
-rw-r--r--sound/Makefile2
-rw-r--r--sound/arm/aaci.c56
-rw-r--r--sound/core/control.c46
-rw-r--r--sound/firewire/Kconfig25
-rw-r--r--sound/firewire/Makefile6
-rw-r--r--sound/firewire/amdtp.c562
-rw-r--r--sound/firewire/amdtp.h169
-rw-r--r--sound/firewire/cmp.c308
-rw-r--r--sound/firewire/cmp.h41
-rw-r--r--sound/firewire/fcp.c224
-rw-r--r--sound/firewire/fcp.h12
-rw-r--r--sound/firewire/iso-resources.c232
-rw-r--r--sound/firewire/iso-resources.h39
-rw-r--r--sound/firewire/lib.c85
-rw-r--r--sound/firewire/lib.h19
-rw-r--r--sound/firewire/packets-buffer.c74
-rw-r--r--sound/firewire/packets-buffer.h26
-rw-r--r--sound/firewire/speakers.c858
-rw-r--r--sound/pci/hda/patch_cirrus.c2
-rw-r--r--sound/soc/Kconfig2
-rw-r--r--sound/soc/Makefile2
-rw-r--r--sound/soc/codecs/Kconfig34
-rw-r--r--sound/soc/codecs/Makefile18
-rw-r--r--sound/soc/codecs/ak4104.c1
-rw-r--r--sound/soc/codecs/ak4642.c24
-rw-r--r--sound/soc/codecs/cs4270.c8
-rw-r--r--sound/soc/codecs/cs4271.c667
-rw-r--r--sound/soc/codecs/dfbmcs320.c72
-rw-r--r--sound/soc/codecs/lm4857.c276
-rw-r--r--sound/soc/codecs/max98088.c2
-rw-r--r--sound/soc/codecs/max9850.c389
-rw-r--r--sound/soc/codecs/max9850.h38
-rw-r--r--sound/soc/codecs/sgtl5000.c1513
-rw-r--r--sound/soc/codecs/sgtl5000.h400
-rw-r--r--sound/soc/codecs/sn95031.c949
-rw-r--r--sound/soc/codecs/sn95031.h132
-rw-r--r--sound/soc/codecs/tlv320aic32x4.c794
-rw-r--r--sound/soc/codecs/tlv320aic32x4.h143
-rw-r--r--sound/soc/codecs/tlv320dac33.c1
-rw-r--r--sound/soc/codecs/twl6040.c4
-rw-r--r--sound/soc/codecs/wm2000.c14
-rw-r--r--sound/soc/codecs/wm8523.c8
-rw-r--r--sound/soc/codecs/wm8741.c13
-rw-r--r--sound/soc/codecs/wm8753.c296
-rw-r--r--sound/soc/codecs/wm8804.c2
-rw-r--r--sound/soc/codecs/wm8900.c2
-rw-r--r--sound/soc/codecs/wm8903.c641
-rw-r--r--sound/soc/codecs/wm8903.h8
-rw-r--r--sound/soc/codecs/wm8904.c43
-rw-r--r--sound/soc/codecs/wm8955.c27
-rw-r--r--sound/soc/codecs/wm8961.c2
-rw-r--r--sound/soc/codecs/wm8962.c36
-rw-r--r--sound/soc/codecs/wm8978.c19
-rw-r--r--sound/soc/codecs/wm8991.c1427
-rw-r--r--sound/soc/codecs/wm8991.h833
-rw-r--r--sound/soc/codecs/wm8993.c2
-rw-r--r--sound/soc/codecs/wm8994-tables.c12
-rw-r--r--sound/soc/codecs/wm8994.c188
-rw-r--r--sound/soc/codecs/wm8994.h2
-rw-r--r--sound/soc/codecs/wm8995.c103
-rw-r--r--sound/soc/codecs/wm9081.c85
-rw-r--r--sound/soc/codecs/wm9090.c45
-rw-r--r--sound/soc/codecs/wm_hubs.c3
-rw-r--r--sound/soc/davinci/davinci-i2s.c28
-rw-r--r--sound/soc/davinci/davinci-mcasp.c29
-rw-r--r--sound/soc/ep93xx/Kconfig9
-rw-r--r--sound/soc/ep93xx/Makefile2
-rw-r--r--sound/soc/ep93xx/edb93xx.c142
-rw-r--r--sound/soc/ep93xx/ep93xx-ac97.c1
-rw-r--r--sound/soc/ep93xx/ep93xx-i2s.c31
-rw-r--r--sound/soc/ep93xx/ep93xx-pcm.c4
-rw-r--r--sound/soc/fsl/mpc8610_hpcd.c6
-rw-r--r--sound/soc/fsl/p1022_ds.c6
-rw-r--r--sound/soc/imx/Kconfig13
-rw-r--r--sound/soc/imx/Makefile2
-rw-r--r--sound/soc/imx/eukrea-tlv320.c3
-rw-r--r--sound/soc/imx/imx-ssi.c5
-rw-r--r--sound/soc/imx/mx27vis-aic32x4.c137
-rw-r--r--sound/soc/mid-x86/Kconfig14
-rw-r--r--sound/soc/mid-x86/Makefile5
-rw-r--r--sound/soc/mid-x86/mfld_machine.c452
-rw-r--r--sound/soc/mid-x86/sst_platform.c474
-rw-r--r--sound/soc/mid-x86/sst_platform.h63
-rw-r--r--sound/soc/omap/Kconfig1
-rw-r--r--sound/soc/omap/rx51.c131
-rw-r--r--sound/soc/pxa/raumfeld.c20
-rw-r--r--sound/soc/pxa/tosa.c4
-rw-r--r--sound/soc/pxa/z2.c7
-rw-r--r--sound/soc/pxa/zylonite.c9
-rw-r--r--sound/soc/samsung/Kconfig19
-rw-r--r--sound/soc/samsung/Makefile2
-rw-r--r--sound/soc/samsung/ac97.c8
-rw-r--r--sound/soc/samsung/ac97.h21
-rw-r--r--sound/soc/samsung/dma.c13
-rw-r--r--sound/soc/samsung/dma.h8
-rw-r--r--sound/soc/samsung/goni_wm8994.c10
-rw-r--r--sound/soc/samsung/h1940_uda1380.c9
-rw-r--r--sound/soc/samsung/i2s.c3
-rw-r--r--sound/soc/samsung/jive_wm8750.c11
-rw-r--r--sound/soc/samsung/lm4857.h32
-rw-r--r--sound/soc/samsung/ln2440sbc_alc650.c7
-rw-r--r--sound/soc/samsung/neo1973_gta02_wm8753.c504
-rw-r--r--sound/soc/samsung/neo1973_wm8753.c635
-rw-r--r--sound/soc/samsung/pcm.c118
-rw-r--r--sound/soc/samsung/pcm.h107
-rw-r--r--sound/soc/samsung/rx1950_uda1380.c11
-rw-r--r--sound/soc/samsung/s3c-i2s-v2.c3
-rw-r--r--sound/soc/samsung/s3c2412-i2s.c12
-rw-r--r--sound/soc/samsung/s3c24xx-i2s.c14
-rw-r--r--sound/soc/samsung/s3c24xx_simtec.c7
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_hermes.c10
-rw-r--r--sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c12
-rw-r--r--sound/soc/samsung/s3c24xx_uda134x.c9
-rw-r--r--sound/soc/samsung/smartq_wm8987.c6
-rw-r--r--sound/soc/samsung/smdk2443_wm9710.c7
-rw-r--r--sound/soc/samsung/smdk_spdif.c5
-rw-r--r--sound/soc/samsung/smdk_wm8580.c7
-rw-r--r--sound/soc/samsung/smdk_wm9713.c5
-rw-r--r--sound/soc/samsung/spdif.c3
-rw-r--r--sound/soc/sh/fsi-ak4642.c24
-rw-r--r--sound/soc/sh/fsi-da7210.c13
-rw-r--r--sound/soc/sh/fsi-hdmi.c77
-rw-r--r--sound/soc/sh/fsi.c203
-rw-r--r--sound/soc/soc-cache.c386
-rw-r--r--sound/soc/soc-core.c548
-rw-r--r--sound/soc/soc-dapm.c259
-rw-r--r--sound/soc/soc-jack.c58
-rw-r--r--sound/soc/soc-utils.c23
-rw-r--r--sound/soc/tegra/Kconfig26
-rw-r--r--sound/soc/tegra/Makefile15
-rw-r--r--sound/soc/tegra/harmony.c393
-rw-r--r--sound/soc/tegra/tegra_asoc_utils.c155
-rw-r--r--sound/soc/tegra/tegra_asoc_utils.h45
-rw-r--r--sound/soc/tegra/tegra_das.c265
-rw-r--r--sound/soc/tegra/tegra_das.h135
-rw-r--r--sound/soc/tegra/tegra_i2s.c503
-rw-r--r--sound/soc/tegra/tegra_i2s.h165
-rw-r--r--sound/soc/tegra/tegra_pcm.c404
-rw-r--r--sound/soc/tegra/tegra_pcm.h55
140 files changed, 16523 insertions, 2513 deletions
diff --git a/sound/Kconfig b/sound/Kconfig
index fcad760f569..1fef141ef8e 100644
--- a/sound/Kconfig
+++ b/sound/Kconfig
@@ -97,6 +97,8 @@ source "sound/sh/Kconfig"
# here assuming USB is defined before ALSA
source "sound/usb/Kconfig"
+source "sound/firewire/Kconfig"
+
# the following will depend on the order of config.
# here assuming PCMCIA is defined before ALSA
source "sound/pcmcia/Kconfig"
diff --git a/sound/Makefile b/sound/Makefile
index ec467decfa7..ce9132b1c39 100644
--- a/sound/Makefile
+++ b/sound/Makefile
@@ -6,7 +6,7 @@ obj-$(CONFIG_SOUND_PRIME) += sound_firmware.o
obj-$(CONFIG_SOUND_PRIME) += oss/
obj-$(CONFIG_DMASOUND) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
- sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/
+ firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/
obj-$(CONFIG_SND_AOA) += aoa/
# This one must be compilable even if sound is configured out
diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c
index 91acc9a243e..7c1fc64cb53 100644
--- a/sound/arm/aaci.c
+++ b/sound/arm/aaci.c
@@ -30,6 +30,8 @@
#define DRIVER_NAME "aaci-pl041"
+#define FRAME_PERIOD_US 21
+
/*
* PM support is not complete. Turn it off.
*/
@@ -48,7 +50,11 @@ static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97)
if (v & SLFR_1RXV)
readl(aaci->base + AACI_SL1RX);
- writel(maincr, aaci->base + AACI_MAINCR);
+ if (maincr != readl(aaci->base + AACI_MAINCR)) {
+ writel(maincr, aaci->base + AACI_MAINCR);
+ readl(aaci->base + AACI_MAINCR);
+ udelay(1);
+ }
}
/*
@@ -64,8 +70,8 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
unsigned short val)
{
struct aaci *aaci = ac97->private_data;
+ int timeout;
u32 v;
- int timeout = 5000;
if (ac97->num >= 4)
return;
@@ -81,14 +87,17 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
writel(val << 4, aaci->base + AACI_SL2TX);
writel(reg << 12, aaci->base + AACI_SL1TX);
- /*
- * Wait for the transmission of both slots to complete.
- */
+ /* Initially, wait one frame period */
+ udelay(FRAME_PERIOD_US);
+
+ /* And then wait an additional eight frame periods for it to be sent */
+ timeout = FRAME_PERIOD_US * 8;
do {
+ udelay(1);
v = readl(aaci->base + AACI_SLFR);
} while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout);
- if (!timeout)
+ if (v & (SLFR_1TXB|SLFR_2TXB))
dev_err(&aaci->dev->dev,
"timeout waiting for write to complete\n");
@@ -101,9 +110,8 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
struct aaci *aaci = ac97->private_data;
+ int timeout, retries = 10;
u32 v;
- int timeout = 5000;
- int retries = 10;
if (ac97->num >= 4)
return ~0;
@@ -117,35 +125,34 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
*/
writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX);
- /*
- * Wait for the transmission to complete.
- */
+ /* Initially, wait one frame period */
+ udelay(FRAME_PERIOD_US);
+
+ /* And then wait an additional eight frame periods for it to be sent */
+ timeout = FRAME_PERIOD_US * 8;
do {
+ udelay(1);
v = readl(aaci->base + AACI_SLFR);
} while ((v & SLFR_1TXB) && --timeout);
- if (!timeout) {
+ if (v & SLFR_1TXB) {
dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n");
v = ~0;
goto out;
}
- /*
- * Give the AC'97 codec more than enough time
- * to respond. (42us = ~2 frames at 48kHz.)
- */
- udelay(42);
+ /* Now wait for the response frame */
+ udelay(FRAME_PERIOD_US);
- /*
- * Wait for slot 2 to indicate data.
- */
- timeout = 5000;
+ /* And then wait an additional eight frame periods for data */
+ timeout = FRAME_PERIOD_US * 8;
do {
+ udelay(1);
cond_resched();
v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV);
} while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout);
- if (!timeout) {
+ if (v != (SLFR_1RXV|SLFR_2RXV)) {
dev_err(&aaci->dev->dev, "timeout on RX valid\n");
v = ~0;
goto out;
@@ -179,6 +186,7 @@ aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask)
int timeout = 5000;
do {
+ udelay(1);
val = readl(aacirun->base + AACI_SR);
} while (val & mask && timeout--);
}
@@ -874,7 +882,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci)
* Give the AC'97 codec more than enough time
* to wake up. (42us = ~2 frames at 48kHz.)
*/
- udelay(42);
+ udelay(FRAME_PERIOD_US * 2);
ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
if (ret)
@@ -989,6 +997,8 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
* disabling the channel doesn't clear the FIFO.
*/
writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
+ readl(aaci->base + AACI_MAINCR);
+ udelay(1);
writel(aaci->maincr, aaci->base + AACI_MAINCR);
/*
diff --git a/sound/core/control.c b/sound/core/control.c
index 9ce00ed20fb..db51e4e6498 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -466,6 +466,52 @@ error:
}
/**
+ * snd_ctl_activate_id - activate/inactivate the control of the given id
+ * @card: the card instance
+ * @id: the control id to activate/inactivate
+ * @active: non-zero to activate
+ *
+ * Finds the control instance with the given id, and activate or
+ * inactivate the control together with notification, if changed.
+ *
+ * Returns 0 if unchanged, 1 if changed, or a negative error code on failure.
+ */
+int snd_ctl_activate_id(struct snd_card *card, struct snd_ctl_elem_id *id,
+ int active)
+{
+ struct snd_kcontrol *kctl;
+ struct snd_kcontrol_volatile *vd;
+ unsigned int index_offset;
+ int ret;
+
+ down_write(&card->controls_rwsem);
+ kctl = snd_ctl_find_id(card, id);
+ if (kctl == NULL) {
+ ret = -ENOENT;
+ goto unlock;
+ }
+ index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
+ vd = &kctl->vd[index_offset];
+ ret = 0;
+ if (active) {
+ if (!(vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE))
+ goto unlock;
+ vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ } else {
+ if (vd->access & SNDRV_CTL_ELEM_ACCESS_INACTIVE)
+ goto unlock;
+ vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+ }
+ ret = 1;
+ unlock:
+ up_write(&card->controls_rwsem);
+ if (ret > 0)
+ snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, id);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_ctl_activate_id);
+
+/**
* snd_ctl_rename_id - replace the id of a control on the card
* @card: the card instance
* @src_id: the old id
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
new file mode 100644
index 00000000000..e486f48660f
--- /dev/null
+++ b/sound/firewire/Kconfig
@@ -0,0 +1,25 @@
+menuconfig SND_FIREWIRE
+ bool "FireWire sound devices"
+ depends on FIREWIRE
+ default y
+ help
+ Support for IEEE-1394/FireWire/iLink sound devices.
+
+if SND_FIREWIRE && FIREWIRE
+
+config SND_FIREWIRE_LIB
+ tristate
+ depends on SND_PCM
+
+config SND_FIREWIRE_SPEAKERS
+ tristate "FireWire speakers"
+ select SND_PCM
+ select SND_FIREWIRE_LIB
+ help
+ Say Y here to include support for the Griffin FireWave Surround
+ and the LaCie FireWire Speakers.
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-firewire-speakers.
+
+endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
new file mode 100644
index 00000000000..e5b1634d9ad
--- /dev/null
+++ b/sound/firewire/Makefile
@@ -0,0 +1,6 @@
+snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
+ fcp.o cmp.o amdtp.o
+snd-firewire-speakers-objs := speakers.o
+
+obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
+obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c
new file mode 100644
index 00000000000..b18140ff2b9
--- /dev/null
+++ b/sound/firewire/amdtp.c
@@ -0,0 +1,562 @@
+/*
+ * Audio and Music Data Transmission Protocol (IEC 61883-6) streams
+ * with Common Isochronous Packet (IEC 61883-1) headers
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/firewire.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include "amdtp.h"
+
+#define TICKS_PER_CYCLE 3072
+#define CYCLES_PER_SECOND 8000
+#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
+
+#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 µs */
+
+#define TAG_CIP 1
+
+#define CIP_EOH (1u << 31)
+#define CIP_FMT_AM (0x10 << 24)
+#define AMDTP_FDF_AM824 (0 << 19)
+#define AMDTP_FDF_SFC_SHIFT 16
+
+/* TODO: make these configurable */
+#define INTERRUPT_INTERVAL 16
+#define QUEUE_LENGTH 48
+
+/**
+ * amdtp_out_stream_init - initialize an AMDTP output stream structure
+ * @s: the AMDTP output stream to initialize
+ * @unit: the target of the stream
+ * @flags: the packet transmission method to use
+ */
+int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit,
+ enum cip_out_flags flags)
+{
+ if (flags != CIP_NONBLOCKING)
+ return -EINVAL;
+
+ s->unit = fw_unit_get(unit);
+ s->flags = flags;
+ s->context = ERR_PTR(-1);
+ mutex_init(&s->mutex);
+ s->packet_index = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(amdtp_out_stream_init);
+
+/**
+ * amdtp_out_stream_destroy - free stream resources
+ * @s: the AMDTP output stream to destroy
+ */
+void amdtp_out_stream_destroy(struct amdtp_out_stream *s)
+{
+ WARN_ON(!IS_ERR(s->context));
+ mutex_destroy(&s->mutex);
+ fw_unit_put(s->unit);
+}
+EXPORT_SYMBOL(amdtp_out_stream_destroy);
+
+/**
+ * amdtp_out_stream_set_rate - set the sample rate
+ * @s: the AMDTP output stream to configure
+ * @rate: the sample rate
+ *
+ * The sample rate must be set before the stream is started, and must not be
+ * changed while the stream is running.
+ */
+void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate)
+{
+ static const struct {
+ unsigned int rate;
+ unsigned int syt_interval;
+ } rate_info[] = {
+ [CIP_SFC_32000] = { 32000, 8, },
+ [CIP_SFC_44100] = { 44100, 8, },
+ [CIP_SFC_48000] = { 48000, 8, },
+ [CIP_SFC_88200] = { 88200, 16, },
+ [CIP_SFC_96000] = { 96000, 16, },
+ [CIP_SFC_176400] = { 176400, 32, },
+ [CIP_SFC_192000] = { 192000, 32, },
+ };
+ unsigned int sfc;
+
+ if (WARN_ON(!IS_ERR(s->context)))
+ return;
+
+ for (sfc = 0; sfc < ARRAY_SIZE(rate_info); ++sfc)
+ if (rate_info[sfc].rate == rate) {
+ s->sfc = sfc;
+ s->syt_interval = rate_info[sfc].syt_interval;
+ return;
+ }
+ WARN_ON(1);
+}
+EXPORT_SYMBOL(amdtp_out_stream_set_rate);
+
+/**
+ * amdtp_out_stream_get_max_payload - get the stream's packet size
+ * @s: the AMDTP output stream
+ *
+ * This function must not be called before the stream has been configured
+ * with amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and
+ * amdtp_out_stream_set_midi().
+ */
+unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s)
+{
+ static const unsigned int max_data_blocks[] = {
+ [CIP_SFC_32000] = 4,
+ [CIP_SFC_44100] = 6,
+ [CIP_SFC_48000] = 6,
+ [CIP_SFC_88200] = 12,
+ [CIP_SFC_96000] = 12,
+ [CIP_SFC_176400] = 23,
+ [CIP_SFC_192000] = 24,
+ };
+
+ s->data_block_quadlets = s->pcm_channels;
+ s->data_block_quadlets += DIV_ROUND_UP(s->midi_ports, 8);
+
+ return 8 + max_data_blocks[s->sfc] * 4 * s->data_block_quadlets;
+}
+EXPORT_SYMBOL(amdtp_out_stream_get_max_payload);
+
+static void amdtp_write_s16(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames);
+static void amdtp_write_s32(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames);
+
+/**
+ * amdtp_out_stream_set_pcm_format - set the PCM format
+ * @s: the AMDTP output stream to configure
+ * @format: the format of the ALSA PCM device
+ *
+ * The sample format must be set before the stream is started, and must not be
+ * changed while the stream is running.
+ */
+void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s,
+ snd_pcm_format_t format)
+{
+ if (WARN_ON(!IS_ERR(s->context)))
+ return;
+
+ switch (format) {
+ default:
+ WARN_ON(1);
+ /* fall through */
+ case SNDRV_PCM_FORMAT_S16:
+ s->transfer_samples = amdtp_write_s16;
+ break;
+ case SNDRV_PCM_FORMAT_S32:
+ s->transfer_samples = amdtp_write_s32;
+ break;
+ }
+}
+EXPORT_SYMBOL(amdtp_out_stream_set_pcm_format);
+
+static unsigned int calculate_data_blocks(struct amdtp_out_stream *s)
+{
+ unsigned int phase, data_blocks;
+
+ if (!cip_sfc_is_base_44100(s->sfc)) {
+ /* Sample_rate / 8000 is an integer, and precomputed. */
+ data_blocks = s->data_block_state;
+ } else {
+ phase = s->data_block_state;
+
+ /*
+ * This calculates the number of data blocks per packet so that
+ * 1) the overall rate is correct and exactly synchronized to
+ * the bus clock, and
+ * 2) packets with a rounded-up number of blocks occur as early
+ * as possible in the sequence (to prevent underruns of the
+ * device's buffer).
+ */
+ if (s->sfc == CIP_SFC_44100)
+ /* 6 6 5 6 5 6 5 ... */
+ data_blocks = 5 + ((phase & 1) ^
+ (phase == 0 || phase >= 40));
+ else
+ /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */
+ data_blocks = 11 * (s->sfc >> 1) + (phase == 0);
+ if (++phase >= (80 >> (s->sfc >> 1)))
+ phase = 0;
+ s->data_block_state = phase;
+ }
+
+ return data_blocks;
+}
+
+static unsigned int calculate_syt(struct amdtp_out_stream *s,
+ unsigned int cycle)
+{
+ unsigned int syt_offset, phase, index, syt;
+
+ if (s->last_syt_offset < TICKS_PER_CYCLE) {
+ if (!cip_sfc_is_base_44100(s->sfc))
+ syt_offset = s->last_syt_offset + s->syt_offset_state;
+ else {
+ /*
+ * The time, in ticks, of the n'th SYT_INTERVAL sample is:
+ * n * SYT_INTERVAL * 24576000 / sample_rate
+ * Modulo TICKS_PER_CYCLE, the difference between successive
+ * elements is about 1386.23. Rounding the results of this
+ * formula to the SYT precision results in a sequence of
+ * differences that begins with:
+ * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ...
+ * This code generates _exactly_ the same sequence.
+ */
+ phase = s->syt_offset_state;
+ index = phase % 13;
+ syt_offset = s->last_syt_offset;
+ syt_offset += 1386 + ((index && !(index & 3)) ||
+ phase == 146);
+ if (++phase >= 147)
+ phase = 0;
+ s->syt_offset_state = phase;
+ }
+ } else
+ syt_offset = s->last_syt_offset - TICKS_PER_CYCLE;
+ s->last_syt_offset = syt_offset;
+
+ if (syt_offset < TICKS_PER_CYCLE) {
+ syt_offset += TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE;
+ syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12;
+ syt += syt_offset % TICKS_PER_CYCLE;
+
+ return syt & 0xffff;
+ } else {
+ return 0xffff; /* no info */
+ }
+}
+
+static void amdtp_write_s32(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ unsigned int channels, remaining_frames, frame_step, i, c;
+ const u32 *src;
+
+ channels = s->pcm_channels;
+ src = (void *)runtime->dma_area +
+ s->pcm_buffer_pointer * (runtime->frame_bits / 8);
+ remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+ frame_step = s->data_block_quadlets - channels;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < channels; ++c) {
+ *buffer = cpu_to_be32((*src >> 8) | 0x40000000);
+ src++;
+ buffer++;
+ }
+ buffer += frame_step;
+ if (--remaining_frames == 0)
+ src = (void *)runtime->dma_area;
+ }
+}
+
+static void amdtp_write_s16(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ unsigned int channels, remaining_frames, frame_step, i, c;
+ const u16 *src;
+
+ channels = s->pcm_channels;
+ src = (void *)runtime->dma_area +
+ s->pcm_buffer_pointer * (runtime->frame_bits / 8);
+ remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+ frame_step = s->data_block_quadlets - channels;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < channels; ++c) {
+ *buffer = cpu_to_be32((*src << 8) | 0x40000000);
+ src++;
+ buffer++;
+ }
+ buffer += frame_step;
+ if (--remaining_frames == 0)
+ src = (void *)runtime->dma_area;
+ }
+}
+
+static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s,
+ __be32 *buffer, unsigned int frames)
+{
+ unsigned int i, c;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < s->pcm_channels; ++c)
+ buffer[c] = cpu_to_be32(0x40000000);
+ buffer += s->data_block_quadlets;
+ }
+}
+
+static void amdtp_fill_midi(struct amdtp_out_stream *s,
+ __be32 *buffer, unsigned int frames)
+{
+ unsigned int i;
+
+ for (i = 0; i < frames; ++i)
+ buffer[s->pcm_channels + i * s->data_block_quadlets] =
+ cpu_to_be32(0x80000000);
+}
+
+static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle)
+{
+ __be32 *buffer;
+ unsigned int index, data_blocks, syt, ptr;
+ struct snd_pcm_substream *pcm;
+ struct fw_iso_packet packet;
+ int err;
+
+ if (s->packet_index < 0)
+ return;
+ index = s->packet_index;
+
+ data_blocks = calculate_data_blocks(s);
+ syt = calculate_syt(s, cycle);
+
+ buffer = s->buffer.packets[index].buffer;
+ buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) |
+ (s->data_block_quadlets << 16) |
+ s->data_block_counter);
+ buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 |
+ (s->sfc << AMDTP_FDF_SFC_SHIFT) | syt);
+ buffer += 2;
+
+ pcm = ACCESS_ONCE(s->pcm);
+ if (pcm)
+ s->transfer_samples(s, pcm, buffer, data_blocks);
+ else
+ amdtp_fill_pcm_silence(s, buffer, data_blocks);
+ if (s->midi_ports)
+ amdtp_fill_midi(s, buffer, data_blocks);
+
+ s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
+
+ packet.payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
+ packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL);
+ packet.skip = 0;
+ packet.tag = TAG_CIP;
+ packet.sy = 0;
+ packet.header_length = 0;
+
+ err = fw_iso_context_queue(s->context, &packet, &s->buffer.iso_buffer,
+ s->buffer.packets[index].offset);
+ if (err < 0) {
+ dev_err(&s->unit->device, "queueing error: %d\n", err);
+ s->packet_index = -1;
+ amdtp_out_stream_pcm_abort(s);
+ return;
+ }
+
+ if (++index >= QUEUE_LENGTH)
+ index = 0;
+ s->packet_index = index;
+
+ if (pcm) {
+ ptr = s->pcm_buffer_pointer + data_blocks;
+ if (ptr >= pcm->runtime->buffer_size)
+ ptr -= pcm->runtime->buffer_size;
+ ACCESS_ONCE(s->pcm_buffer_pointer) = ptr;
+
+ s->pcm_period_pointer += data_blocks;
+ if (s->pcm_period_pointer >= pcm->runtime->period_size) {
+ s->pcm_period_pointer -= pcm->runtime->period_size;
+ snd_pcm_period_elapsed(pcm);
+ }
+ }
+}
+
+static void out_packet_callback(struct fw_iso_context *context, u32 cycle,
+ size_t header_length, void *header, void *data)
+{
+ struct amdtp_out_stream *s = data;
+ unsigned int i, packets = header_length / 4;
+
+ /*
+ * Compute the cycle of the last queued packet.
+ * (We need only the four lowest bits for the SYT, so we can ignore
+ * that bits 0-11 must wrap around at 3072.)
+ */
+ cycle += QUEUE_LENGTH - packets;
+
+ for (i = 0; i < packets; ++i)
+ queue_out_packet(s, ++cycle);
+}
+
+static int queue_initial_skip_packets(struct amdtp_out_stream *s)
+{
+ struct fw_iso_packet skip_packet = {
+ .skip = 1,
+ };
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < QUEUE_LENGTH; ++i) {
+ skip_packet.interrupt = IS_ALIGNED(s->packet_index + 1,
+ INTERRUPT_INTERVAL);
+ err = fw_iso_context_queue(s->context, &skip_packet, NULL, 0);
+ if (err < 0)
+ return err;
+ if (++s->packet_index >= QUEUE_LENGTH)
+ s->packet_index = 0;
+ }
+
+ return 0;
+}
+
+/**
+ * amdtp_out_stream_start - start sending packets
+ * @s: the AMDTP output stream to start
+ * @channel: the isochronous channel on the bus
+ * @speed: firewire speed code
+ *
+ * The stream cannot be started until it has been configured with
+ * amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and
+ * amdtp_out_stream_set_midi(); and it must be started before any
+ * PCM or MIDI device can be started.
+ */
+int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed)
+{
+ static const struct {
+ unsigned int data_block;
+ unsigned int syt_offset;
+ } initial_state[] = {
+ [CIP_SFC_32000] = { 4, 3072 },
+ [CIP_SFC_48000] = { 6, 1024 },
+ [CIP_SFC_96000] = { 12, 1024 },
+ [CIP_SFC_192000] = { 24, 1024 },
+ [CIP_SFC_44100] = { 0, 67 },
+ [CIP_SFC_88200] = { 0, 67 },
+ [CIP_SFC_176400] = { 0, 67 },
+ };
+ int err;
+
+ mutex_lock(&s->mutex);
+
+ if (WARN_ON(!IS_ERR(s->context) ||
+ (!s->pcm_channels && !s->midi_ports))) {
+ err = -EBADFD;
+ goto err_unlock;
+ }
+
+ s->data_block_state = initial_state[s->sfc].data_block;
+ s->syt_offset_state = initial_state[s->sfc].syt_offset;
+ s->last_syt_offset = TICKS_PER_CYCLE;
+
+ err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
+ amdtp_out_stream_get_max_payload(s),
+ DMA_TO_DEVICE);
+ if (err < 0)
+ goto err_unlock;
+
+ s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
+ FW_ISO_CONTEXT_TRANSMIT,
+ channel, speed, 0,
+ out_packet_callback, s);
+ if (IS_ERR(s->context)) {
+ err = PTR_ERR(s->context);
+ if (err == -EBUSY)
+ dev_err(&s->unit->device,
+ "no free output stream on this controller\n");
+ goto err_buffer;
+ }
+
+ amdtp_out_stream_update(s);
+
+ s->packet_index = 0;
+ s->data_block_counter = 0;
+ err = queue_initial_skip_packets(s);
+ if (err < 0)
+ goto err_context;
+
+ err = fw_iso_context_start(s->context, -1, 0, 0);
+ if (err < 0)
+ goto err_context;
+
+ mutex_unlock(&s->mutex);
+
+ return 0;
+
+err_context:
+ fw_iso_context_destroy(s->context);
+ s->context = ERR_PTR(-1);
+err_buffer:
+ iso_packets_buffer_destroy(&s->buffer, s->unit);
+err_unlock:
+ mutex_unlock(&s->mutex);
+
+ return err;
+}
+EXPORT_SYMBOL(amdtp_out_stream_start);
+
+/**
+ * amdtp_out_stream_update - update the stream after a bus reset
+ * @s: the AMDTP output stream
+ */
+void amdtp_out_stream_update(struct amdtp_out_stream *s)
+{
+ ACCESS_ONCE(s->source_node_id_field) =
+ (fw_parent_device(s->unit)->card->node_id & 0x3f) << 24;
+}
+EXPORT_SYMBOL(amdtp_out_stream_update);
+
+/**
+ * amdtp_out_stream_stop - stop sending packets
+ * @s: the AMDTP output stream to stop
+ *
+ * All PCM and MIDI devices of the stream must be stopped before the stream
+ * itself can be stopped.
+ */
+void amdtp_out_stream_stop(struct amdtp_out_stream *s)
+{
+ mutex_lock(&s->mutex);
+
+ if (IS_ERR(s->context)) {
+ mutex_unlock(&s->mutex);
+ return;
+ }
+
+ fw_iso_context_stop(s->context);
+ fw_iso_context_destroy(s->context);
+ s->context = ERR_PTR(-1);
+ iso_packets_buffer_destroy(&s->buffer, s->unit);
+
+ mutex_unlock(&s->mutex);
+}
+EXPORT_SYMBOL(amdtp_out_stream_stop);
+
+/**
+ * amdtp_out_stream_pcm_abort - abort the running PCM device
+ * @s: the AMDTP stream about to be stopped
+ *
+ * If the isochronous stream needs to be stopped asynchronously, call this
+ * function first to stop the PCM device.
+ */
+void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s)
+{
+ struct snd_pcm_substream *pcm;
+
+ pcm = ACCESS_ONCE(s->pcm);
+ if (pcm) {
+ snd_pcm_stream_lock_irq(pcm);
+ if (snd_pcm_running(pcm))
+ snd_pcm_stop(pcm, SNDRV_PCM_STATE_XRUN);
+ snd_pcm_stream_unlock_irq(pcm);
+ }
+}
+EXPORT_SYMBOL(amdtp_out_stream_pcm_abort);
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h
new file mode 100644
index 00000000000..537a9cb8358
--- /dev/null
+++ b/sound/firewire/amdtp.h
@@ -0,0 +1,169 @@
+#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED
+#define SOUND_FIREWIRE_AMDTP_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include "packets-buffer.h"
+
+/**
+ * enum cip_out_flags - describes details of the streaming protocol
+ * @CIP_NONBLOCKING: In non-blocking mode, each packet contains
+ * sample_rate/8000 samples, with rounding up or down to adjust
+ * for clock skew and left-over fractional samples. This should
+ * be used if supported by the device.
+ */
+enum cip_out_flags {
+ CIP_NONBLOCKING = 0,
+};
+
+/**
+ * enum cip_sfc - a stream's sample rate
+ */
+enum cip_sfc {
+ CIP_SFC_32000 = 0,
+ CIP_SFC_44100 = 1,
+ CIP_SFC_48000 = 2,
+ CIP_SFC_88200 = 3,
+ CIP_SFC_96000 = 4,
+ CIP_SFC_176400 = 5,
+ CIP_SFC_192000 = 6,
+};
+
+#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \
+ SNDRV_PCM_FMTBIT_S32)
+
+struct fw_unit;
+struct fw_iso_context;
+struct snd_pcm_substream;
+
+struct amdtp_out_stream {
+ struct fw_unit *unit;
+ enum cip_out_flags flags;
+ struct fw_iso_context *context;
+ struct mutex mutex;
+
+ enum cip_sfc sfc;
+ unsigned int data_block_quadlets;
+ unsigned int pcm_channels;
+ unsigned int midi_ports;
+ void (*transfer_samples)(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames);
+
+ unsigned int syt_interval;
+ unsigned int source_node_id_field;
+ struct iso_packets_buffer buffer;
+
+ struct snd_pcm_substream *pcm;
+
+ int packet_index;
+ unsigned int data_block_counter;
+
+ unsigned int data_block_state;
+
+ unsigned int last_syt_offset;
+ unsigned int syt_offset_state;
+
+ unsigned int pcm_buffer_pointer;
+ unsigned int pcm_period_pointer;
+};
+
+int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit,
+ enum cip_out_flags flags);
+void amdtp_out_stream_destroy(struct amdtp_out_stream *s);
+
+void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate);
+unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s);
+
+int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed);
+void amdtp_out_stream_update(struct amdtp_out_stream *s);
+void amdtp_out_stream_stop(struct amdtp_out_stream *s);
+
+void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s,
+ snd_pcm_format_t format);
+void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s);
+
+/**
+ * amdtp_out_stream_set_pcm - configure format of PCM samples
+ * @s: the AMDTP output stream to be configured
+ * @pcm_channels: the number of PCM samples in each data block, to be encoded
+ * as AM824 multi-bit linear audio
+ *
+ * This function must not be called while the stream is running.
+ */
+static inline void amdtp_out_stream_set_pcm(struct amdtp_out_stream *s,
+ unsigned int pcm_channels)
+{
+ s->pcm_channels = pcm_channels;
+}
+
+/**
+ * amdtp_out_stream_set_midi - configure format of MIDI data
+ * @s: the AMDTP output stream to be configured
+ * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels)
+ *
+ * This function must not be called while the stream is running.
+ */
+static inline void amdtp_out_stream_set_midi(struct amdtp_out_stream *s,
+ unsigned int midi_ports)
+{
+ s->midi_ports = midi_ports;
+}
+
+/**
+ * amdtp_out_streaming_error - check for streaming error
+ * @s: the AMDTP output stream
+ *
+ * If this function returns true, the stream's packet queue has stopped due to
+ * an asynchronous error.
+ */
+static inline bool amdtp_out_streaming_error(struct amdtp_out_stream *s)
+{
+ return s->packet_index < 0;
+}
+
+/**
+ * amdtp_out_stream_pcm_prepare - prepare PCM device for running
+ * @s: the AMDTP output stream
+ *
+ * This function should be called from the PCM device's .prepare callback.
+ */
+static inline void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s)
+{
+ s->pcm_buffer_pointer = 0;
+ s->pcm_period_pointer = 0;
+}
+
+/**
+ * amdtp_out_stream_pcm_trigger - start/stop playback from a PCM device
+ * @s: the AMDTP output stream
+ * @pcm: the PCM device to be started, or %NULL to stop the current device
+ *
+ * Call this function on a running isochronous stream to enable the actual
+ * transmission of PCM data. This function should be called from the PCM
+ * device's .trigger callback.
+ */
+static inline void amdtp_out_stream_pcm_trigger(struct amdtp_out_stream *s,
+ struct snd_pcm_substream *pcm)
+{
+ ACCESS_ONCE(s->pcm) = pcm;
+}
+
+/**
+ * amdtp_out_stream_pcm_pointer - get the PCM buffer position
+ * @s: the AMDTP output stream that transports the PCM data
+ *
+ * Returns the current buffer position, in frames.
+ */
+static inline unsigned long
+amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s)
+{
+ return ACCESS_ONCE(s->pcm_buffer_pointer);
+}
+
+static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
+{
+ return sfc & 1;
+}
+
+#endif
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c
new file mode 100644
index 00000000000..4a37f3a6fab
--- /dev/null
+++ b/sound/firewire/cmp.c
@@ -0,0 +1,308 @@
+/*
+ * Connection Management Procedures (IEC 61883-1) helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include "lib.h"
+#include "iso-resources.h"
+#include "cmp.h"
+
+#define IMPR_SPEED_MASK 0xc0000000
+#define IMPR_SPEED_SHIFT 30
+#define IMPR_XSPEED_MASK 0x00000060
+#define IMPR_XSPEED_SHIFT 5
+#define IMPR_PLUGS_MASK 0x0000001f
+
+#define IPCR_ONLINE 0x80000000
+#define IPCR_BCAST_CONN 0x40000000
+#define IPCR_P2P_CONN_MASK 0x3f000000
+#define IPCR_P2P_CONN_SHIFT 24
+#define IPCR_CHANNEL_MASK 0x003f0000
+#define IPCR_CHANNEL_SHIFT 16
+
+enum bus_reset_handling {
+ ABORT_ON_BUS_RESET,
+ SUCCEED_ON_BUS_RESET,
+};
+
+static __attribute__((format(printf, 2, 3)))
+void cmp_error(struct cmp_connection *c, const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ dev_err(&c->resources.unit->device, "%cPCR%u: %pV",
+ 'i', c->pcr_index, &(struct va_format){ fmt, &va });
+ va_end(va);
+}
+
+static int pcr_modify(struct cmp_connection *c,
+ __be32 (*modify)(struct cmp_connection *c, __be32 old),
+ int (*check)(struct cmp_connection *c, __be32 pcr),
+ enum bus_reset_handling bus_reset_handling)
+{
+ struct fw_device *device = fw_parent_device(c->resources.unit);
+ __be32 *buffer = c->resources.buffer;
+ int generation = c->resources.generation;
+ int rcode, errors = 0;
+ __be32 old_arg;
+ int err;
+
+ buffer[0] = c->last_pcr_value;
+ for (;;) {
+ old_arg = buffer[0];
+ buffer[1] = modify(c, buffer[0]);
+
+ rcode = fw_run_transaction(
+ device->card, TCODE_LOCK_COMPARE_SWAP,
+ device->node_id, generation, device->max_speed,
+ CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index),
+ buffer, 8);
+
+ if (rcode == RCODE_COMPLETE) {
+ if (buffer[0] == old_arg) /* success? */
+ break;
+
+ if (check) {
+ err = check(c, buffer[0]);
+ if (err < 0)
+ return err;
+ }
+ } else if (rcode == RCODE_GENERATION)
+ goto bus_reset;
+ else if (rcode_is_permanent_error(rcode) || ++errors >= 3)
+ goto io_error;
+ }
+ c->last_pcr_value = buffer[1];
+
+ return 0;
+
+io_error:
+ cmp_error(c, "transaction failed: %s\n", rcode_string(rcode));
+ return -EIO;
+
+bus_reset:
+ return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0;
+}
+
+
+/**
+ * cmp_connection_init - initializes a connection manager
+ * @c: the connection manager to initialize
+ * @unit: a unit of the target device
+ * @ipcr_index: the index of the iPCR on the target device
+ */
+int cmp_connection_init(struct cmp_connection *c,
+ struct fw_unit *unit,
+ unsigned int ipcr_index)
+{
+ __be32 impr_be;
+ u32 impr;
+ int err;
+
+ err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+ CSR_REGISTER_BASE + CSR_IMPR,
+ &impr_be, 4);
+ if (err < 0)
+ return err;
+ impr = be32_to_cpu(impr_be);
+
+ if (ipcr_index >= (impr & IMPR_PLUGS_MASK))
+ return -EINVAL;
+
+ err = fw_iso_resources_init(&c->resources, unit);
+ if (err < 0)
+ return err;
+
+ c->connected = false;
+ mutex_init(&c->mutex);
+ c->last_pcr_value = cpu_to_be32(0x80000000);
+ c->pcr_index = ipcr_index;
+ c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT;
+ if (c->max_speed == SCODE_BETA)
+ c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT;
+
+ return 0;
+}
+EXPORT_SYMBOL(cmp_connection_init);
+
+/**
+ * cmp_connection_destroy - free connection manager resources
+ * @c: the connection manager
+ */
+void cmp_connection_destroy(struct cmp_connection *c)
+{
+ WARN_ON(c->connected);
+ mutex_destroy(&c->mutex);
+ fw_iso_resources_destroy(&c->resources);
+}
+EXPORT_SYMBOL(cmp_connection_destroy);
+
+
+static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr)
+{
+ ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN |
+ IPCR_P2P_CONN_MASK |
+ IPCR_CHANNEL_MASK);
+ ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT);
+ ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT);
+
+ return ipcr;
+}
+
+static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr)
+{
+ if (ipcr & cpu_to_be32(IPCR_BCAST_CONN |
+ IPCR_P2P_CONN_MASK)) {
+ cmp_error(c, "plug is already in use\n");
+ return -EBUSY;
+ }
+ if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) {
+ cmp_error(c, "plug is not on-line\n");
+ return -ECONNREFUSED;
+ }
+
+ return 0;
+}
+
+/**
+ * cmp_connection_establish - establish a connection to the target
+ * @c: the connection manager
+ * @max_payload_bytes: the amount of data (including CIP headers) per packet
+ *
+ * This function establishes a point-to-point connection from the local
+ * computer to the target by allocating isochronous resources (channel and
+ * bandwidth) and setting the target's input plug control register. When this
+ * function succeeds, the caller is responsible for starting transmitting
+ * packets.
+ */
+int cmp_connection_establish(struct cmp_connection *c,
+ unsigned int max_payload_bytes)
+{
+ int err;
+
+ if (WARN_ON(c->connected))
+ return -EISCONN;
+
+ c->speed = min(c->max_speed,
+ fw_parent_device(c->resources.unit)->max_speed);
+
+ mutex_lock(&c->mutex);
+
+retry_after_bus_reset:
+ err = fw_iso_resources_allocate(&c->resources,
+ max_payload_bytes, c->speed);
+ if (err < 0)
+ goto err_mutex;
+
+ err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
+ ABORT_ON_BUS_RESET);
+ if (err == -EAGAIN) {
+ fw_iso_resources_free(&c->resources);
+ goto retry_after_bus_reset;
+ }
+ if (err < 0)
+ goto err_resources;
+
+ c->connected = true;
+
+ mutex_unlock(&c->mutex);
+
+ return 0;
+
+err_resources:
+ fw_iso_resources_free(&c->resources);
+err_mutex:
+ mutex_unlock(&c->mutex);
+
+ return err;
+}
+EXPORT_SYMBOL(cmp_connection_establish);
+
+/**
+ * cmp_connection_update - update the connection after a bus reset
+ * @c: the connection manager
+ *
+ * This function must be called from the driver's .update handler to reestablish
+ * any connection that might have been active.
+ *
+ * Returns zero on success, or a negative error code. On an error, the
+ * connection is broken and the caller must stop transmitting iso packets.
+ */
+int cmp_connection_update(struct cmp_connection *c)
+{
+ int err;
+
+ mutex_lock(&c->mutex);
+
+ if (!c->connected) {
+ mutex_unlock(&c->mutex);
+ return 0;
+ }
+
+ err = fw_iso_resources_update(&c->resources);
+ if (err < 0)
+ goto err_unconnect;
+
+ err = pcr_modify(c, ipcr_set_modify, ipcr_set_check,
+ SUCCEED_ON_BUS_RESET);
+ if (err < 0)
+ goto err_resources;
+
+ mutex_unlock(&c->mutex);
+
+ return 0;
+
+err_resources:
+ fw_iso_resources_free(&c->resources);
+err_unconnect:
+ c->connected = false;
+ mutex_unlock(&c->mutex);
+
+ return err;
+}
+EXPORT_SYMBOL(cmp_connection_update);
+
+
+static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr)
+{
+ return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK);
+}
+
+/**
+ * cmp_connection_break - break the connection to the target
+ * @c: the connection manager
+ *
+ * This function deactives the connection in the target's input plug control
+ * register, and frees the isochronous resources of the connection. Before
+ * calling this function, the caller should cease transmitting packets.
+ */
+void cmp_connection_break(struct cmp_connection *c)
+{
+ int err;
+
+ mutex_lock(&c->mutex);
+
+ if (!c->connected) {
+ mutex_unlock(&c->mutex);
+ return;
+ }
+
+ err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);
+ if (err < 0)
+ cmp_error(c, "plug is still connected\n");
+
+ fw_iso_resources_free(&c->resources);
+
+ c->connected = false;
+
+ mutex_unlock(&c->mutex);
+}
+EXPORT_SYMBOL(cmp_connection_break);
diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h
new file mode 100644
index 00000000000..f47de08feb1
--- /dev/null
+++ b/sound/firewire/cmp.h
@@ -0,0 +1,41 @@
+#ifndef SOUND_FIREWIRE_CMP_H_INCLUDED
+#define SOUND_FIREWIRE_CMP_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include "iso-resources.h"
+
+struct fw_unit;
+
+/**
+ * struct cmp_connection - manages an isochronous connection to a device
+ * @speed: the connection's actual speed
+ *
+ * This structure manages (using CMP) an isochronous stream from the local
+ * computer to a device's input plug (iPCR).
+ *
+ * There is no corresponding oPCR created on the local computer, so it is not
+ * possible to overlay connections on top of this one.
+ */
+struct cmp_connection {
+ int speed;
+ /* private: */
+ bool connected;
+ struct mutex mutex;
+ struct fw_iso_resources resources;
+ __be32 last_pcr_value;
+ unsigned int pcr_index;
+ unsigned int max_speed;
+};
+
+int cmp_connection_init(struct cmp_connection *connection,
+ struct fw_unit *unit,
+ unsigned int ipcr_index);
+void cmp_connection_destroy(struct cmp_connection *connection);
+
+int cmp_connection_establish(struct cmp_connection *connection,
+ unsigned int max_payload);
+int cmp_connection_update(struct cmp_connection *connection);
+void cmp_connection_break(struct cmp_connection *connection);
+
+#endif
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c
new file mode 100644
index 00000000000..ec578b5ad8d
--- /dev/null
+++ b/sound/firewire/fcp.c
@@ -0,0 +1,224 @@
+/*
+ * Function Control Protocol (IEC 61883-1) helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include "fcp.h"
+#include "lib.h"
+
+#define CTS_AVC 0x00
+
+#define ERROR_RETRIES 3
+#define ERROR_DELAY_MS 5
+#define FCP_TIMEOUT_MS 125
+
+static DEFINE_SPINLOCK(transactions_lock);
+static LIST_HEAD(transactions);
+
+enum fcp_state {
+ STATE_PENDING,
+ STATE_BUS_RESET,
+ STATE_COMPLETE,
+};
+
+struct fcp_transaction {
+ struct list_head list;
+ struct fw_unit *unit;
+ void *response_buffer;
+ unsigned int response_size;
+ unsigned int response_match_bytes;
+ enum fcp_state state;
+ wait_queue_head_t wait;
+};
+
+/**
+ * fcp_avc_transaction - send an AV/C command and wait for its response
+ * @unit: a unit on the target device
+ * @command: a buffer containing the command frame; must be DMA-able
+ * @command_size: the size of @command
+ * @response: a buffer for the response frame
+ * @response_size: the maximum size of @response
+ * @response_match_bytes: a bitmap specifying the bytes used to detect the
+ * correct response frame
+ *
+ * This function sends a FCP command frame to the target and waits for the
+ * corresponding response frame to be returned.
+ *
+ * Because it is possible for multiple FCP transactions to be active at the
+ * same time, the correct response frame is detected by the value of certain
+ * bytes. These bytes must be set in @response before calling this function,
+ * and the corresponding bits must be set in @response_match_bytes.
+ *
+ * @command and @response can point to the same buffer.
+ *
+ * Asynchronous operation (INTERIM, NOTIFY) is not supported at the moment.
+ *
+ * Returns the actual size of the response frame, or a negative error code.
+ */
+int fcp_avc_transaction(struct fw_unit *unit,
+ const void *command, unsigned int command_size,
+ void *response, unsigned int response_size,
+ unsigned int response_match_bytes)
+{
+ struct fcp_transaction t;
+ int tcode, ret, tries = 0;
+
+ t.unit = unit;
+ t.response_buffer = response;
+ t.response_size = response_size;
+ t.response_match_bytes = response_match_bytes;
+ t.state = STATE_PENDING;
+ init_waitqueue_head(&t.wait);
+
+ spin_lock_irq(&transactions_lock);
+ list_add_tail(&t.list, &transactions);
+ spin_unlock_irq(&transactions_lock);
+
+ for (;;) {
+ tcode = command_size == 4 ? TCODE_WRITE_QUADLET_REQUEST
+ : TCODE_WRITE_BLOCK_REQUEST;
+ ret = snd_fw_transaction(t.unit, tcode,
+ CSR_REGISTER_BASE + CSR_FCP_COMMAND,
+ (void *)command, command_size);
+ if (ret < 0)
+ break;
+
+ wait_event_timeout(t.wait, t.state != STATE_PENDING,
+ msecs_to_jiffies(FCP_TIMEOUT_MS));
+
+ if (t.state == STATE_COMPLETE) {
+ ret = t.response_size;
+ break;
+ } else if (t.state == STATE_BUS_RESET) {
+ msleep(ERROR_DELAY_MS);
+ } else if (++tries >= ERROR_RETRIES) {
+ dev_err(&t.unit->device, "FCP command timed out\n");
+ ret = -EIO;
+ break;
+ }
+ }
+
+ spin_lock_irq(&transactions_lock);
+ list_del(&t.list);
+ spin_unlock_irq(&transactions_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(fcp_avc_transaction);
+
+/**
+ * fcp_bus_reset - inform the target handler about a bus reset
+ * @unit: the unit that might be used by fcp_avc_transaction()
+ *
+ * This function must be called from the driver's .update handler to inform
+ * the FCP transaction handler that a bus reset has happened. Any pending FCP
+ * transactions are retried.
+ */
+void fcp_bus_reset(struct fw_unit *unit)
+{
+ struct fcp_transaction *t;
+
+ spin_lock_irq(&transactions_lock);
+ list_for_each_entry(t, &transactions, list) {
+ if (t->unit == unit &&
+ t->state == STATE_PENDING) {
+ t->state = STATE_BUS_RESET;
+ wake_up(&t->wait);
+ }
+ }
+ spin_unlock_irq(&transactions_lock);
+}
+EXPORT_SYMBOL(fcp_bus_reset);
+
+/* checks whether the response matches the masked bytes in response_buffer */
+static bool is_matching_response(struct fcp_transaction *transaction,
+ const void *response, size_t length)
+{
+ const u8 *p1, *p2;
+ unsigned int mask, i;
+
+ p1 = response;
+ p2 = transaction->response_buffer;
+ mask = transaction->response_match_bytes;
+
+ for (i = 0; ; ++i) {
+ if ((mask & 1) && p1[i] != p2[i])
+ return false;
+ mask >>= 1;
+ if (!mask)
+ return true;
+ if (--length == 0)
+ return false;
+ }
+}
+
+static void fcp_response(struct fw_card *card, struct fw_request *request,
+ int tcode, int destination, int source,
+ int generation, unsigned long long offset,
+ void *data, size_t length, void *callback_data)
+{
+ struct fcp_transaction *t;
+ unsigned long flags;
+
+ if (length < 1 || (*(const u8 *)data & 0xf0) != CTS_AVC)
+ return;
+
+ spin_lock_irqsave(&transactions_lock, flags);
+ list_for_each_entry(t, &transactions, list) {
+ struct fw_device *device = fw_parent_device(t->unit);
+ if (device->card != card ||
+ device->generation != generation)
+ continue;
+ smp_rmb(); /* node_id vs. generation */
+ if (device->node_id != source)
+ continue;
+
+ if (t->state == STATE_PENDING &&
+ is_matching_response(t, data, length)) {
+ t->state = STATE_COMPLETE;
+ t->response_size = min((unsigned int)length,
+ t->response_size);
+ memcpy(t->response_buffer, data, t->response_size);
+ wake_up(&t->wait);
+ }
+ }
+ spin_unlock_irqrestore(&transactions_lock, flags);
+}
+
+static struct fw_address_handler response_register_handler = {
+ .length = 0x200,
+ .address_callback = fcp_response,
+};
+
+static int __init fcp_module_init(void)
+{
+ static const struct fw_address_region response_register_region = {
+ .start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE,
+ .end = CSR_REGISTER_BASE + CSR_FCP_END,
+ };
+
+ fw_core_add_address_handler(&response_register_handler,
+ &response_register_region);
+
+ return 0;
+}
+
+static void __exit fcp_module_exit(void)
+{
+ WARN_ON(!list_empty(&transactions));
+ fw_core_remove_address_handler(&response_register_handler);
+}
+
+module_init(fcp_module_init);
+module_exit(fcp_module_exit);
diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h
new file mode 100644
index 00000000000..86595688bd9
--- /dev/null
+++ b/sound/firewire/fcp.h
@@ -0,0 +1,12 @@
+#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED
+#define SOUND_FIREWIRE_FCP_H_INCLUDED
+
+struct fw_unit;
+
+int fcp_avc_transaction(struct fw_unit *unit,
+ const void *command, unsigned int command_size,
+ void *response, unsigned int response_size,
+ unsigned int response_match_bytes);
+void fcp_bus_reset(struct fw_unit *unit);
+
+#endif
diff --git a/sound/firewire/iso-resources.c b/sound/firewire/iso-resources.c
new file mode 100644
index 00000000000..775dbd5f344
--- /dev/null
+++ b/sound/firewire/iso-resources.c
@@ -0,0 +1,232 @@
+/*
+ * isochronous resources helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include "iso-resources.h"
+
+/**
+ * fw_iso_resources_init - initializes a &struct fw_iso_resources
+ * @r: the resource manager to initialize
+ * @unit: the device unit for which the resources will be needed
+ *
+ * If the device does not support all channel numbers, change @r->channels_mask
+ * after calling this function.
+ */
+int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit)
+{
+ r->buffer = kmalloc(2 * 4, GFP_KERNEL);
+ if (!r->buffer)
+ return -ENOMEM;
+
+ r->channels_mask = ~0uLL;
+ r->unit = fw_unit_get(unit);
+ mutex_init(&r->mutex);
+ r->allocated = false;
+
+ return 0;
+}
+
+/**
+ * fw_iso_resources_destroy - destroy a resource manager
+ * @r: the resource manager that is no longer needed
+ */
+void fw_iso_resources_destroy(struct fw_iso_resources *r)
+{
+ WARN_ON(r->allocated);
+ kfree(r->buffer);
+ mutex_destroy(&r->mutex);
+ fw_unit_put(r->unit);
+}
+
+static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed)
+{
+ unsigned int bytes, s400_bytes;
+
+ /* iso packets have three header quadlets and quadlet-aligned payload */
+ bytes = 3 * 4 + ALIGN(max_payload_bytes, 4);
+
+ /* convert to bandwidth units (quadlets at S1600 = bytes at S400) */
+ if (speed <= SCODE_400)
+ s400_bytes = bytes * (1 << (SCODE_400 - speed));
+ else
+ s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400));
+
+ return s400_bytes;
+}
+
+static int current_bandwidth_overhead(struct fw_card *card)
+{
+ /*
+ * Under the usual pessimistic assumption (cable length 4.5 m), the
+ * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or
+ * 88.3 + N * 24.3 in bandwidth units.
+ *
+ * The calculation below tries to deduce N from the current gap count.
+ * If the gap count has been optimized by measuring the actual packet
+ * transmission time, this derived overhead should be near the actual
+ * overhead as well.
+ */
+ return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512;
+}
+
+static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card)
+{
+ for (;;) {
+ s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64();
+ if (delay <= 0)
+ return 0;
+ if (schedule_timeout_interruptible(delay) > 0)
+ return -ERESTARTSYS;
+ }
+}
+
+/**
+ * fw_iso_resources_allocate - allocate isochronous channel and bandwidth
+ * @r: the resource manager
+ * @max_payload_bytes: the amount of data (including CIP headers) per packet
+ * @speed: the speed (e.g., SCODE_400) at which the packets will be sent
+ *
+ * This function allocates one isochronous channel and enough bandwidth for the
+ * specified packet size.
+ *
+ * Returns the channel number that the caller must use for streaming, or
+ * a negative error code. Due to potentionally long delays, this function is
+ * interruptible and can return -ERESTARTSYS. On success, the caller is
+ * responsible for calling fw_iso_resources_update() on bus resets, and
+ * fw_iso_resources_free() when the resources are not longer needed.
+ */
+int fw_iso_resources_allocate(struct fw_iso_resources *r,
+ unsigned int max_payload_bytes, int speed)
+{
+ struct fw_card *card = fw_parent_device(r->unit)->card;
+ int bandwidth, channel, err;
+
+ if (WARN_ON(r->allocated))
+ return -EBADFD;
+
+ r->bandwidth = packet_bandwidth(max_payload_bytes, speed);
+
+retry_after_bus_reset:
+ spin_lock_irq(&card->lock);
+ r->generation = card->generation;
+ r->bandwidth_overhead = current_bandwidth_overhead(card);
+ spin_unlock_irq(&card->lock);
+
+ err = wait_isoch_resource_delay_after_bus_reset(card);
+ if (err < 0)
+ return err;
+
+ mutex_lock(&r->mutex);
+
+ bandwidth = r->bandwidth + r->bandwidth_overhead;
+ fw_iso_resource_manage(card, r->generation, r->channels_mask,
+ &channel, &bandwidth, true, r->buffer);
+ if (channel == -EAGAIN) {
+ mutex_unlock(&r->mutex);
+ goto retry_after_bus_reset;
+ }
+ if (channel >= 0) {
+ r->channel = channel;
+ r->allocated = true;
+ } else {
+ if (channel == -EBUSY)
+ dev_err(&r->unit->device,
+ "isochronous resources exhausted\n");
+ else
+ dev_err(&r->unit->device,
+ "isochronous resource allocation failed\n");
+ }
+
+ mutex_unlock(&r->mutex);
+
+ return channel;
+}
+
+/**
+ * fw_iso_resources_update - update resource allocations after a bus reset
+ * @r: the resource manager
+ *
+ * This function must be called from the driver's .update handler to reallocate
+ * any resources that were allocated before the bus reset. It is safe to call
+ * this function if no resources are currently allocated.
+ *
+ * Returns a negative error code on failure. If this happens, the caller must
+ * stop streaming.
+ */
+int fw_iso_resources_update(struct fw_iso_resources *r)
+{
+ struct fw_card *card = fw_parent_device(r->unit)->card;
+ int bandwidth, channel;
+
+ mutex_lock(&r->mutex);
+
+ if (!r->allocated) {
+ mutex_unlock(&r->mutex);
+ return 0;
+ }
+
+ spin_lock_irq(&card->lock);
+ r->generation = card->generation;
+ r->bandwidth_overhead = current_bandwidth_overhead(card);
+ spin_unlock_irq(&card->lock);
+
+ bandwidth = r->bandwidth + r->bandwidth_overhead;
+
+ fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
+ &channel, &bandwidth, true, r->buffer);
+ /*
+ * When another bus reset happens, pretend that the allocation
+ * succeeded; we will try again for the new generation later.
+ */
+ if (channel < 0 && channel != -EAGAIN) {
+ r->allocated = false;
+ if (channel == -EBUSY)
+ dev_err(&r->unit->device,
+ "isochronous resources exhausted\n");
+ else
+ dev_err(&r->unit->device,
+ "isochronous resource allocation failed\n");
+ }
+
+ mutex_unlock(&r->mutex);
+
+ return channel;
+}
+
+/**
+ * fw_iso_resources_free - frees allocated resources
+ * @r: the resource manager
+ *
+ * This function deallocates the channel and bandwidth, if allocated.
+ */
+void fw_iso_resources_free(struct fw_iso_resources *r)
+{
+ struct fw_card *card = fw_parent_device(r->unit)->card;
+ int bandwidth, channel;
+
+ mutex_lock(&r->mutex);
+
+ if (r->allocated) {
+ bandwidth = r->bandwidth + r->bandwidth_overhead;
+ fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
+ &channel, &bandwidth, false, r->buffer);
+ if (channel < 0)
+ dev_err(&r->unit->device,
+ "isochronous resource deallocation failed\n");
+
+ r->allocated = false;
+ }
+
+ mutex_unlock(&r->mutex);
+}
diff --git a/sound/firewire/iso-resources.h b/sound/firewire/iso-resources.h
new file mode 100644
index 00000000000..3f0730e4d84
--- /dev/null
+++ b/sound/firewire/iso-resources.h
@@ -0,0 +1,39 @@
+#ifndef SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED
+#define SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+struct fw_unit;
+
+/**
+ * struct fw_iso_resources - manages channel/bandwidth allocation
+ * @channels_mask: if the device does not support all channel numbers, set this
+ * bit mask to something else than the default (all ones)
+ *
+ * This structure manages (de)allocation of isochronous resources (channel and
+ * bandwidth) for one isochronous stream.
+ */
+struct fw_iso_resources {
+ u64 channels_mask;
+ /* private: */
+ struct fw_unit *unit;
+ struct mutex mutex;
+ unsigned int channel;
+ unsigned int bandwidth; /* in bandwidth units, without overhead */
+ unsigned int bandwidth_overhead;
+ int generation; /* in which allocation is valid */
+ bool allocated;
+ __be32 *buffer;
+};
+
+int fw_iso_resources_init(struct fw_iso_resources *r,
+ struct fw_unit *unit);
+void fw_iso_resources_destroy(struct fw_iso_resources *r);
+
+int fw_iso_resources_allocate(struct fw_iso_resources *r,
+ unsigned int max_payload_bytes, int speed);
+int fw_iso_resources_update(struct fw_iso_resources *r);
+void fw_iso_resources_free(struct fw_iso_resources *r);
+
+#endif
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c
new file mode 100644
index 00000000000..4750cea2210
--- /dev/null
+++ b/sound/firewire/lib.c
@@ -0,0 +1,85 @@
+/*
+ * miscellaneous helper functions
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/module.h>
+#include "lib.h"
+
+#define ERROR_RETRY_DELAY_MS 5
+
+/**
+ * rcode_string - convert a firewire result code to a string
+ * @rcode: the result
+ */
+const char *rcode_string(unsigned int rcode)
+{
+ static const char *const names[] = {
+ [RCODE_COMPLETE] = "complete",
+ [RCODE_CONFLICT_ERROR] = "conflict error",
+ [RCODE_DATA_ERROR] = "data error",
+ [RCODE_TYPE_ERROR] = "type error",
+ [RCODE_ADDRESS_ERROR] = "address error",
+ [RCODE_SEND_ERROR] = "send error",
+ [RCODE_CANCELLED] = "cancelled",
+ [RCODE_BUSY] = "busy",
+ [RCODE_GENERATION] = "generation",
+ [RCODE_NO_ACK] = "no ack",
+ };
+
+ if (rcode < ARRAY_SIZE(names) && names[rcode])
+ return names[rcode];
+ else
+ return "unknown";
+}
+EXPORT_SYMBOL(rcode_string);
+
+/**
+ * snd_fw_transaction - send a request and wait for its completion
+ * @unit: the driver's unit on the target device
+ * @tcode: the transaction code
+ * @offset: the address in the target's address space
+ * @buffer: input/output data
+ * @length: length of @buffer
+ *
+ * Submits an asynchronous request to the target device, and waits for the
+ * response. The node ID and the current generation are derived from @unit.
+ * On a bus reset or an error, the transaction is retried a few times.
+ * Returns zero on success, or a negative error code.
+ */
+int snd_fw_transaction(struct fw_unit *unit, int tcode,
+ u64 offset, void *buffer, size_t length)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ int generation, rcode, tries = 0;
+
+ for (;;) {
+ generation = device->generation;
+ smp_rmb(); /* node_id vs. generation */
+ rcode = fw_run_transaction(device->card, tcode,
+ device->node_id, generation,
+ device->max_speed, offset,
+ buffer, length);
+
+ if (rcode == RCODE_COMPLETE)
+ return 0;
+
+ if (rcode_is_permanent_error(rcode) || ++tries >= 3) {
+ dev_err(&unit->device, "transaction failed: %s\n",
+ rcode_string(rcode));
+ return -EIO;
+ }
+
+ msleep(ERROR_RETRY_DELAY_MS);
+ }
+}
+EXPORT_SYMBOL(snd_fw_transaction);
+
+MODULE_DESCRIPTION("FireWire audio helper functions");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h
new file mode 100644
index 00000000000..064f3fd9ab0
--- /dev/null
+++ b/sound/firewire/lib.h
@@ -0,0 +1,19 @@
+#ifndef SOUND_FIREWIRE_LIB_H_INCLUDED
+#define SOUND_FIREWIRE_LIB_H_INCLUDED
+
+#include <linux/firewire-constants.h>
+#include <linux/types.h>
+
+struct fw_unit;
+
+int snd_fw_transaction(struct fw_unit *unit, int tcode,
+ u64 offset, void *buffer, size_t length);
+const char *rcode_string(unsigned int rcode);
+
+/* returns true if retrying the transaction would not make sense */
+static inline bool rcode_is_permanent_error(int rcode)
+{
+ return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
+}
+
+#endif
diff --git a/sound/firewire/packets-buffer.c b/sound/firewire/packets-buffer.c
new file mode 100644
index 00000000000..1e20e60ba6a
--- /dev/null
+++ b/sound/firewire/packets-buffer.c
@@ -0,0 +1,74 @@
+/*
+ * helpers for managing a buffer for many packets
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/firewire.h>
+#include <linux/slab.h>
+#include "packets-buffer.h"
+
+/**
+ * iso_packets_buffer_init - allocates the memory for packets
+ * @b: the buffer structure to initialize
+ * @unit: the device at the other end of the stream
+ * @count: the number of packets
+ * @packet_size: the (maximum) size of a packet, in bytes
+ * @direction: %DMA_TO_DEVICE or %DMA_FROM_DEVICE
+ */
+int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit,
+ unsigned int count, unsigned int packet_size,
+ enum dma_data_direction direction)
+{
+ unsigned int packets_per_page, pages;
+ unsigned int i, page_index, offset_in_page;
+ void *p;
+ int err;
+
+ b->packets = kmalloc(count * sizeof(*b->packets), GFP_KERNEL);
+ if (!b->packets) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ packet_size = L1_CACHE_ALIGN(packet_size);
+ packets_per_page = PAGE_SIZE / packet_size;
+ if (WARN_ON(!packets_per_page)) {
+ err = -EINVAL;
+ goto error;
+ }
+ pages = DIV_ROUND_UP(count, packets_per_page);
+
+ err = fw_iso_buffer_init(&b->iso_buffer, fw_parent_device(unit)->card,
+ pages, direction);
+ if (err < 0)
+ goto err_packets;
+
+ for (i = 0; i < count; ++i) {
+ page_index = i / packets_per_page;
+ p = page_address(b->iso_buffer.pages[page_index]);
+ offset_in_page = (i % packets_per_page) * packet_size;
+ b->packets[i].buffer = p + offset_in_page;
+ b->packets[i].offset = page_index * PAGE_SIZE + offset_in_page;
+ }
+
+ return 0;
+
+err_packets:
+ kfree(b->packets);
+error:
+ return err;
+}
+
+/**
+ * iso_packets_buffer_destroy - frees packet buffer resources
+ * @b: the buffer structure to free
+ * @unit: the device at the other end of the stream
+ */
+void iso_packets_buffer_destroy(struct iso_packets_buffer *b,
+ struct fw_unit *unit)
+{
+ fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card);
+ kfree(b->packets);
+}
diff --git a/sound/firewire/packets-buffer.h b/sound/firewire/packets-buffer.h
new file mode 100644
index 00000000000..6513c5cb6ea
--- /dev/null
+++ b/sound/firewire/packets-buffer.h
@@ -0,0 +1,26 @@
+#ifndef SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED
+#define SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED
+
+#include <linux/dma-mapping.h>
+#include <linux/firewire.h>
+
+/**
+ * struct iso_packets_buffer - manages a buffer for many packets
+ * @iso_buffer: the memory containing the packets
+ * @packets: an array, with each element pointing to one packet
+ */
+struct iso_packets_buffer {
+ struct fw_iso_buffer iso_buffer;
+ struct {
+ void *buffer;
+ unsigned int offset;
+ } *packets;
+};
+
+int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit,
+ unsigned int count, unsigned int packet_size,
+ enum dma_data_direction direction);
+void iso_packets_buffer_destroy(struct iso_packets_buffer *b,
+ struct fw_unit *unit);
+
+#endif
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c
new file mode 100644
index 00000000000..0fce9218abb
--- /dev/null
+++ b/sound/firewire/speakers.c
@@ -0,0 +1,858 @@
+/*
+ * OXFW970-based speakers driver
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "cmp.h"
+#include "fcp.h"
+#include "amdtp.h"
+#include "lib.h"
+
+#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000)
+/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
+
+#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020)
+#define OXFORD_HARDWARE_ID_OXFW970 0x39443841
+#define OXFORD_HARDWARE_ID_OXFW971 0x39373100
+
+#define VENDOR_GRIFFIN 0x001292
+#define VENDOR_LACIE 0x00d04b
+
+#define SPECIFIER_1394TA 0x00a02d
+#define VERSION_AVC 0x010001
+
+struct device_info {
+ const char *driver_name;
+ const char *short_name;
+ const char *long_name;
+ int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
+ unsigned int mixer_channels;
+ u8 mute_fb_id;
+ u8 volume_fb_id;
+};
+
+struct fwspk {
+ struct snd_card *card;
+ struct fw_unit *unit;
+ const struct device_info *device_info;
+ struct snd_pcm_substream *pcm;
+ struct mutex mutex;
+ struct cmp_connection connection;
+ struct amdtp_out_stream stream;
+ bool stream_running;
+ bool mute;
+ s16 volume[6];
+ s16 volume_min;
+ s16 volume_max;
+};
+
+MODULE_DESCRIPTION("FireWire speakers driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+
+static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ static unsigned int stereo_rates[] = { 48000, 96000 };
+ struct snd_interval *channels =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval *rate =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+ /* two channels work only at 48/96 kHz */
+ if (snd_interval_max(channels) < 6)
+ return snd_interval_list(rate, 2, stereo_rates, 0);
+ return 0;
+}
+
+static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
+ struct snd_pcm_hw_rule *rule)
+{
+ static const struct snd_interval all_channels = { .min = 6, .max = 6 };
+ struct snd_interval *rate =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval *channels =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+
+ /* 32/44.1 kHz work only with all six channels */
+ if (snd_interval_max(rate) < 48000)
+ return snd_interval_refine(channels, &all_channels);
+ return 0;
+}
+
+static int firewave_constraints(struct snd_pcm_runtime *runtime)
+{
+ static unsigned int channels_list[] = { 2, 6 };
+ static struct snd_pcm_hw_constraint_list channels_list_constraint = {
+ .count = 2,
+ .list = channels_list,
+ };
+ int err;
+
+ runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000;
+ runtime->hw.channels_max = 6;
+
+ err = snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS,
+ &channels_list_constraint);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ firewave_rate_constraint, NULL,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ return err;
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ firewave_channels_constraint, NULL,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+{
+ runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_88200 |
+ SNDRV_PCM_RATE_96000;
+
+ return 0;
+}
+
+static int fwspk_open(struct snd_pcm_substream *substream)
+{
+ static const struct snd_pcm_hardware hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = AMDTP_OUT_PCM_FORMAT_BITS,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 4 * 1024 * 1024,
+ .period_bytes_min = 1,
+ .period_bytes_max = UINT_MAX,
+ .periods_min = 1,
+ .periods_max = UINT_MAX,
+ };
+ struct fwspk *fwspk = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int err;
+
+ runtime->hw = hardware;
+
+ err = fwspk->device_info->pcm_constraints(runtime);
+ if (err < 0)
+ return err;
+ err = snd_pcm_limit_hw_rates(runtime);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+ 5000, 8192000);
+ if (err < 0)
+ return err;
+
+ err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int fwspk_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+}
+
+static void fwspk_stop_stream(struct fwspk *fwspk)
+{
+ if (fwspk->stream_running) {
+ amdtp_out_stream_stop(&fwspk->stream);
+ cmp_connection_break(&fwspk->connection);
+ fwspk->stream_running = false;
+ }
+}
+
+static int fwspk_set_rate(struct fwspk *fwspk, unsigned int sfc)
+{
+ u8 *buf;
+ int err;
+
+ buf = kmalloc(8, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* AV/C, CONTROL */
+ buf[1] = 0xff; /* unit */
+ buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
+ buf[3] = 0x00; /* plug 0 */
+ buf[4] = 0x90; /* format: audio */
+ buf[5] = 0x00 | sfc; /* AM824, frequency */
+ buf[6] = 0xff; /* SYT (not used) */
+ buf[7] = 0xff;
+
+ err = fcp_avc_transaction(fwspk->unit, buf, 8, buf, 8,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
+ if (err < 0)
+ goto error;
+ if (err < 6 || buf[0] != 0x09 /* ACCEPTED */) {
+ dev_err(&fwspk->unit->device, "failed to set sample rate\n");
+ err = -EIO;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ kfree(buf);
+
+ return err;
+}
+
+static int fwspk_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct fwspk *fwspk = substream->private_data;
+ int err;
+
+ mutex_lock(&fwspk->mutex);
+ fwspk_stop_stream(fwspk);
+ mutex_unlock(&fwspk->mutex);
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ goto error;
+
+ amdtp_out_stream_set_rate(&fwspk->stream, params_rate(hw_params));
+ amdtp_out_stream_set_pcm(&fwspk->stream, params_channels(hw_params));
+
+ amdtp_out_stream_set_pcm_format(&fwspk->stream,
+ params_format(hw_params));
+
+ err = fwspk_set_rate(fwspk, fwspk->stream.sfc);
+ if (err < 0)
+ goto err_buffer;
+
+ return 0;
+
+err_buffer:
+ snd_pcm_lib_free_vmalloc_buffer(substream);
+error:
+ return err;
+}
+
+static int fwspk_hw_free(struct snd_pcm_substream *substream)
+{
+ struct fwspk *fwspk = substream->private_data;
+
+ mutex_lock(&fwspk->mutex);
+ fwspk_stop_stream(fwspk);
+ mutex_unlock(&fwspk->mutex);
+
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int fwspk_prepare(struct snd_pcm_substream *substream)
+{
+ struct fwspk *fwspk = substream->private_data;
+ int err;
+
+ mutex_lock(&fwspk->mutex);
+
+ if (amdtp_out_streaming_error(&fwspk->stream))
+ fwspk_stop_stream(fwspk);
+
+ if (!fwspk->stream_running) {
+ err = cmp_connection_establish(&fwspk->connection,
+ amdtp_out_stream_get_max_payload(&fwspk->stream));
+ if (err < 0)
+ goto err_mutex;
+
+ err = amdtp_out_stream_start(&fwspk->stream,
+ fwspk->connection.resources.channel,
+ fwspk->connection.speed);
+ if (err < 0)
+ goto err_connection;
+
+ fwspk->stream_running = true;
+ }
+
+ mutex_unlock(&fwspk->mutex);
+
+ amdtp_out_stream_pcm_prepare(&fwspk->stream);
+
+ return 0;
+
+err_connection:
+ cmp_connection_break(&fwspk->connection);
+err_mutex:
+ mutex_unlock(&fwspk->mutex);
+
+ return err;
+}
+
+static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct fwspk *fwspk = substream->private_data;
+ struct snd_pcm_substream *pcm;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ pcm = substream;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pcm = NULL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ amdtp_out_stream_pcm_trigger(&fwspk->stream, pcm);
+ return 0;
+}
+
+static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream)
+{
+ struct fwspk *fwspk = substream->private_data;
+
+ return amdtp_out_stream_pcm_pointer(&fwspk->stream);
+}
+
+static int fwspk_create_pcm(struct fwspk *fwspk)
+{
+ static struct snd_pcm_ops ops = {
+ .open = fwspk_open,
+ .close = fwspk_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = fwspk_hw_params,
+ .hw_free = fwspk_hw_free,
+ .prepare = fwspk_prepare,
+ .trigger = fwspk_trigger,
+ .pointer = fwspk_pointer,
+ .page = snd_pcm_lib_get_vmalloc_page,
+ .mmap = snd_pcm_lib_mmap_vmalloc,
+ };
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm);
+ if (err < 0)
+ return err;
+ pcm->private_data = fwspk;
+ strcpy(pcm->name, fwspk->device_info->short_name);
+ fwspk->pcm = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+ fwspk->pcm->ops = &ops;
+ return 0;
+}
+
+enum control_action { CTL_READ, CTL_WRITE };
+enum control_attribute {
+ CTL_MIN = 0x02,
+ CTL_MAX = 0x03,
+ CTL_CURRENT = 0x10,
+};
+
+static int fwspk_mute_command(struct fwspk *fwspk, bool *value,
+ enum control_action action)
+{
+ u8 *buf;
+ u8 response_ok;
+ int err;
+
+ buf = kmalloc(11, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (action == CTL_READ) {
+ buf[0] = 0x01; /* AV/C, STATUS */
+ response_ok = 0x0c; /* STABLE */
+ } else {
+ buf[0] = 0x00; /* AV/C, CONTROL */
+ response_ok = 0x09; /* ACCEPTED */
+ }
+ buf[1] = 0x08; /* audio unit 0 */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x81; /* function block type: feature */
+ buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */
+ buf[5] = 0x10; /* control attribute: current */
+ buf[6] = 0x02; /* selector length */
+ buf[7] = 0x00; /* audio channel number */
+ buf[8] = 0x01; /* control selector: mute */
+ buf[9] = 0x01; /* control data length */
+ if (action == CTL_READ)
+ buf[10] = 0xff;
+ else
+ buf[10] = *value ? 0x70 : 0x60;
+
+ err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe);
+ if (err < 0)
+ goto error;
+ if (err < 11) {
+ dev_err(&fwspk->unit->device, "short FCP response\n");
+ err = -EIO;
+ goto error;
+ }
+ if (buf[0] != response_ok) {
+ dev_err(&fwspk->unit->device, "mute command failed\n");
+ err = -EIO;
+ goto error;
+ }
+ if (action == CTL_READ)
+ *value = buf[10] == 0x70;
+
+ err = 0;
+
+error:
+ kfree(buf);
+
+ return err;
+}
+
+static int fwspk_volume_command(struct fwspk *fwspk, s16 *value,
+ unsigned int channel,
+ enum control_attribute attribute,
+ enum control_action action)
+{
+ u8 *buf;
+ u8 response_ok;
+ int err;
+
+ buf = kmalloc(12, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ if (action == CTL_READ) {
+ buf[0] = 0x01; /* AV/C, STATUS */
+ response_ok = 0x0c; /* STABLE */
+ } else {
+ buf[0] = 0x00; /* AV/C, CONTROL */
+ response_ok = 0x09; /* ACCEPTED */
+ }
+ buf[1] = 0x08; /* audio unit 0 */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x81; /* function block type: feature */
+ buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */
+ buf[5] = attribute; /* control attribute */
+ buf[6] = 0x02; /* selector length */
+ buf[7] = channel; /* audio channel number */
+ buf[8] = 0x02; /* control selector: volume */
+ buf[9] = 0x02; /* control data length */
+ if (action == CTL_READ) {
+ buf[10] = 0xff;
+ buf[11] = 0xff;
+ } else {
+ buf[10] = *value >> 8;
+ buf[11] = *value;
+ }
+
+ err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe);
+ if (err < 0)
+ goto error;
+ if (err < 12) {
+ dev_err(&fwspk->unit->device, "short FCP response\n");
+ err = -EIO;
+ goto error;
+ }
+ if (buf[0] != response_ok) {
+ dev_err(&fwspk->unit->device, "volume command failed\n");
+ err = -EIO;
+ goto error;
+ }
+ if (action == CTL_READ)
+ *value = (buf[10] << 8) | buf[11];
+
+ err = 0;
+
+error:
+ kfree(buf);
+
+ return err;
+}
+
+static int fwspk_mute_get(struct snd_kcontrol *control,
+ struct snd_ctl_elem_value *value)
+{
+ struct fwspk *fwspk = control->private_data;
+
+ value->value.integer.value[0] = !fwspk->mute;
+
+ return 0;
+}
+
+static int fwspk_mute_put(struct snd_kcontrol *control,
+ struct snd_ctl_elem_value *value)
+{
+ struct fwspk *fwspk = control->private_data;
+ bool mute;
+ int err;
+
+ mute = !value->value.integer.value[0];
+
+ if (mute == fwspk->mute)
+ return 0;
+
+ err = fwspk_mute_command(fwspk, &mute, CTL_WRITE);
+ if (err < 0)
+ return err;
+ fwspk->mute = mute;
+
+ return 1;
+}
+
+static int fwspk_volume_info(struct snd_kcontrol *control,
+ struct snd_ctl_elem_info *info)
+{
+ struct fwspk *fwspk = control->private_data;
+
+ info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ info->count = fwspk->device_info->mixer_channels;
+ info->value.integer.min = fwspk->volume_min;
+ info->value.integer.max = fwspk->volume_max;
+
+ return 0;
+}
+
+static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
+
+static int fwspk_volume_get(struct snd_kcontrol *control,
+ struct snd_ctl_elem_value *value)
+{
+ struct fwspk *fwspk = control->private_data;
+ unsigned int i;
+
+ for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
+ value->value.integer.value[channel_map[i]] = fwspk->volume[i];
+
+ return 0;
+}
+
+static int fwspk_volume_put(struct snd_kcontrol *control,
+ struct snd_ctl_elem_value *value)
+{
+ struct fwspk *fwspk = control->private_data;
+ unsigned int i, changed_channels;
+ bool equal_values = true;
+ s16 volume;
+ int err;
+
+ for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
+ if (value->value.integer.value[i] < fwspk->volume_min ||
+ value->value.integer.value[i] > fwspk->volume_max)
+ return -EINVAL;
+ if (value->value.integer.value[i] !=
+ value->value.integer.value[0])
+ equal_values = false;
+ }
+
+ changed_channels = 0;
+ for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
+ if (value->value.integer.value[channel_map[i]] !=
+ fwspk->volume[i])
+ changed_channels |= 1 << (i + 1);
+
+ if (equal_values && changed_channels != 0)
+ changed_channels = 1 << 0;
+
+ for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) {
+ volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
+ if (changed_channels & (1 << i)) {
+ err = fwspk_volume_command(fwspk, &volume, i,
+ CTL_CURRENT, CTL_WRITE);
+ if (err < 0)
+ return err;
+ }
+ if (i > 0)
+ fwspk->volume[i - 1] = volume;
+ }
+
+ return changed_channels != 0;
+}
+
+static int fwspk_create_mixer(struct fwspk *fwspk)
+{
+ static const struct snd_kcontrol_new controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .info = snd_ctl_boolean_mono_info,
+ .get = fwspk_mute_get,
+ .put = fwspk_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .info = fwspk_volume_info,
+ .get = fwspk_volume_get,
+ .put = fwspk_volume_put,
+ },
+ };
+ unsigned int i, first_ch;
+ int err;
+
+ err = fwspk_volume_command(fwspk, &fwspk->volume_min,
+ 0, CTL_MIN, CTL_READ);
+ if (err < 0)
+ return err;
+ err = fwspk_volume_command(fwspk, &fwspk->volume_max,
+ 0, CTL_MAX, CTL_READ);
+ if (err < 0)
+ return err;
+
+ err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ);
+ if (err < 0)
+ return err;
+
+ first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1;
+ for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
+ err = fwspk_volume_command(fwspk, &fwspk->volume[i],
+ first_ch + i, CTL_CURRENT, CTL_READ);
+ if (err < 0)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(controls); ++i) {
+ err = snd_ctl_add(fwspk->card,
+ snd_ctl_new1(&controls[i], fwspk));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static u32 fwspk_read_firmware_version(struct fw_unit *unit)
+{
+ __be32 data;
+ int err;
+
+ err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+ OXFORD_FIRMWARE_ID_ADDRESS, &data, 4);
+ return err >= 0 ? be32_to_cpu(data) : 0;
+}
+
+static void fwspk_card_free(struct snd_card *card)
+{
+ struct fwspk *fwspk = card->private_data;
+ struct fw_device *dev = fw_parent_device(fwspk->unit);
+
+ amdtp_out_stream_destroy(&fwspk->stream);
+ cmp_connection_destroy(&fwspk->connection);
+ fw_unit_put(fwspk->unit);
+ fw_device_put(dev);
+ mutex_destroy(&fwspk->mutex);
+}
+
+static const struct device_info *__devinit fwspk_detect(struct fw_device *dev)
+{
+ static const struct device_info griffin_firewave = {
+ .driver_name = "FireWave",
+ .short_name = "FireWave",
+ .long_name = "Griffin FireWave Surround",
+ .pcm_constraints = firewave_constraints,
+ .mixer_channels = 6,
+ .mute_fb_id = 0x01,
+ .volume_fb_id = 0x02,
+ };
+ static const struct device_info lacie_speakers = {
+ .driver_name = "FWSpeakers",
+ .short_name = "FireWire Speakers",
+ .long_name = "LaCie FireWire Speakers",
+ .pcm_constraints = lacie_speakers_constraints,
+ .mixer_channels = 1,
+ .mute_fb_id = 0x01,
+ .volume_fb_id = 0x01,
+ };
+ struct fw_csr_iterator i;
+ int key, value;
+
+ fw_csr_iterator_init(&i, dev->config_rom);
+ while (fw_csr_iterator_next(&i, &key, &value))
+ if (key == CSR_VENDOR)
+ switch (value) {
+ case VENDOR_GRIFFIN:
+ return &griffin_firewave;
+ case VENDOR_LACIE:
+ return &lacie_speakers;
+ }
+
+ return NULL;
+}
+
+static int __devinit fwspk_probe(struct device *unit_dev)
+{
+ struct fw_unit *unit = fw_unit(unit_dev);
+ struct fw_device *fw_dev = fw_parent_device(unit);
+ struct snd_card *card;
+ struct fwspk *fwspk;
+ u32 firmware;
+ int err;
+
+ err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card);
+ if (err < 0)
+ return err;
+ snd_card_set_dev(card, unit_dev);
+
+ fwspk = card->private_data;
+ fwspk->card = card;
+ mutex_init(&fwspk->mutex);
+ fw_device_get(fw_dev);
+ fwspk->unit = fw_unit_get(unit);
+ fwspk->device_info = fwspk_detect(fw_dev);
+ if (!fwspk->device_info) {
+ err = -ENODEV;
+ goto err_unit;
+ }
+
+ err = cmp_connection_init(&fwspk->connection, unit, 0);
+ if (err < 0)
+ goto err_unit;
+
+ err = amdtp_out_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING);
+ if (err < 0)
+ goto err_connection;
+
+ card->private_free = fwspk_card_free;
+
+ strcpy(card->driver, fwspk->device_info->driver_name);
+ strcpy(card->shortname, fwspk->device_info->short_name);
+ firmware = fwspk_read_firmware_version(unit);
+ snprintf(card->longname, sizeof(card->longname),
+ "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+ fwspk->device_info->long_name,
+ firmware >> 20, firmware & 0xffff,
+ fw_dev->config_rom[3], fw_dev->config_rom[4],
+ dev_name(&unit->device), 100 << fw_dev->max_speed);
+ strcpy(card->mixername, "OXFW970");
+
+ err = fwspk_create_pcm(fwspk);
+ if (err < 0)
+ goto error;
+
+ err = fwspk_create_mixer(fwspk);
+ if (err < 0)
+ goto error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto error;
+
+ dev_set_drvdata(unit_dev, fwspk);
+
+ return 0;
+
+err_connection:
+ cmp_connection_destroy(&fwspk->connection);
+err_unit:
+ fw_unit_put(fwspk->unit);
+ fw_device_put(fw_dev);
+ mutex_destroy(&fwspk->mutex);
+error:
+ snd_card_free(card);
+ return err;
+}
+
+static int __devexit fwspk_remove(struct device *dev)
+{
+ struct fwspk *fwspk = dev_get_drvdata(dev);
+
+ snd_card_disconnect(fwspk->card);
+
+ mutex_lock(&fwspk->mutex);
+ amdtp_out_stream_pcm_abort(&fwspk->stream);
+ fwspk_stop_stream(fwspk);
+ mutex_unlock(&fwspk->mutex);
+
+ snd_card_free_when_closed(fwspk->card);
+
+ return 0;
+}
+
+static void fwspk_bus_reset(struct fw_unit *unit)
+{
+ struct fwspk *fwspk = dev_get_drvdata(&unit->device);
+
+ fcp_bus_reset(fwspk->unit);
+
+ if (cmp_connection_update(&fwspk->connection) < 0) {
+ mutex_lock(&fwspk->mutex);
+ amdtp_out_stream_pcm_abort(&fwspk->stream);
+ fwspk_stop_stream(fwspk);
+ mutex_unlock(&fwspk->mutex);
+ return;
+ }
+
+ amdtp_out_stream_update(&fwspk->stream);
+}
+
+static const struct ieee1394_device_id fwspk_id_table[] = {
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID |
+ IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .vendor_id = VENDOR_GRIFFIN,
+ .model_id = 0x00f970,
+ .specifier_id = SPECIFIER_1394TA,
+ .version = VERSION_AVC,
+ },
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID |
+ IEEE1394_MATCH_SPECIFIER_ID |
+ IEEE1394_MATCH_VERSION,
+ .vendor_id = VENDOR_LACIE,
+ .model_id = 0x00f970,
+ .specifier_id = SPECIFIER_1394TA,
+ .version = VERSION_AVC,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table);
+
+static struct fw_driver fwspk_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .bus = &fw_bus_type,
+ .probe = fwspk_probe,
+ .remove = __devexit_p(fwspk_remove),
+ },
+ .update = fwspk_bus_reset,
+ .id_table = fwspk_id_table,
+};
+
+static int __init alsa_fwspk_init(void)
+{
+ return driver_register(&fwspk_driver.driver);
+}
+
+static void __exit alsa_fwspk_exit(void)
+{
+ driver_unregister(&fwspk_driver.driver);
+}
+
+module_init(alsa_fwspk_init);
+module_exit(alsa_fwspk_exit);
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
index a07b031090d..067982f4f18 100644
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -1039,9 +1039,11 @@ static struct hda_verb cs_errata_init_verbs[] = {
{0x11, AC_VERB_SET_PROC_COEF, 0x0008},
{0x11, AC_VERB_SET_PROC_STATE, 0x00},
+#if 0 /* Don't to set to D3 as we are in power-up sequence */
{0x07, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Rx: D3 */
{0x08, AC_VERB_SET_POWER_STATE, 0x03}, /* S/PDIF Tx: D3 */
/*{0x01, AC_VERB_SET_POWER_STATE, 0x03},*/ /* AFG: D3 This is already handled */
+#endif
{} /* terminator */
};
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index a3efc52a34d..8224db5f043 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -50,10 +50,12 @@ source "sound/soc/jz4740/Kconfig"
source "sound/soc/nuc900/Kconfig"
source "sound/soc/omap/Kconfig"
source "sound/soc/kirkwood/Kconfig"
+source "sound/soc/mid-x86/Kconfig"
source "sound/soc/pxa/Kconfig"
source "sound/soc/samsung/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
+source "sound/soc/tegra/Kconfig"
source "sound/soc/txx9/Kconfig"
# Supported codecs
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index ce913bf5213..1ed61c5df2c 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_SND_SOC) += ep93xx/
obj-$(CONFIG_SND_SOC) += fsl/
obj-$(CONFIG_SND_SOC) += imx/
obj-$(CONFIG_SND_SOC) += jz4740/
+obj-$(CONFIG_SND_SOC) += mid-x86/
obj-$(CONFIG_SND_SOC) += nuc900/
obj-$(CONFIG_SND_SOC) += omap/
obj-$(CONFIG_SND_SOC) += kirkwood/
@@ -17,4 +18,5 @@ obj-$(CONFIG_SND_SOC) += pxa/
obj-$(CONFIG_SND_SOC) += samsung/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
+obj-$(CONFIG_SND_SOC) += tegra/
obj-$(CONFIG_SND_SOC) += txx9/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c48b23c1d4f..d63c1754e05 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -26,17 +26,24 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
+ select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI
select SND_SOC_CX20442
select SND_SOC_DA7210 if I2C
+ select SND_SOC_DFBMCS320
select SND_SOC_JZ4740_CODEC if SOC_JZ4740
+ select SND_SOC_LM4857 if I2C
select SND_SOC_MAX98088 if I2C
+ select SND_SOC_MAX9850 if I2C
select SND_SOC_MAX9877 if I2C
select SND_SOC_PCM3008
+ select SND_SOC_SGTL5000 if I2C
+ select SND_SOC_SN95031 if INTEL_SCU_IPC
select SND_SOC_SPDIF
select SND_SOC_SSM2602 if I2C
select SND_SOC_STAC9766 if SND_SOC_AC97_BUS
select SND_SOC_TLV320AIC23 if I2C
select SND_SOC_TLV320AIC26 if SPI_MASTER
+ select SND_SOC_TVL320AIC32X4 if I2C
select SND_SOC_TLV320AIC3X if I2C
select SND_SOC_TPA6130A2 if I2C
select SND_SOC_TLV320DAC33 if I2C
@@ -76,6 +83,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8990 if I2C
+ select SND_SOC_WM8991 if I2C
select SND_SOC_WM8993 if I2C
select SND_SOC_WM8994 if MFD_WM8994
select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI
@@ -155,6 +163,9 @@ config SND_SOC_CS4270_VD33_ERRATA
bool
depends on SND_SOC_CS4270
+config SND_SOC_CS4271
+ tristate
+
config SND_SOC_CX20442
tristate
@@ -167,15 +178,28 @@ config SND_SOC_L3
config SND_SOC_DA7210
tristate
+config SND_SOC_DFBMCS320
+ tristate
+
config SND_SOC_DMIC
tristate
config SND_SOC_MAX98088
tristate
+config SND_SOC_MAX9850
+ tristate
+
config SND_SOC_PCM3008
tristate
+#Freescale sgtl5000 codec
+config SND_SOC_SGTL5000
+ tristate
+
+config SND_SOC_SN95031
+ tristate
+
config SND_SOC_SPDIF
tristate
@@ -192,6 +216,9 @@ config SND_SOC_TLV320AIC26
tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
depends on SPI
+config SND_SOC_TVL320AIC32X4
+ tristate
+
config SND_SOC_TLV320AIC3X
tristate
@@ -304,6 +331,9 @@ config SND_SOC_WM8988
config SND_SOC_WM8990
tristate
+config SND_SOC_WM8991
+ tristate
+
config SND_SOC_WM8993
tristate
@@ -326,6 +356,9 @@ config SND_SOC_WM9713
tristate
# Amp
+config SND_SOC_LM4857
+ tristate
+
config SND_SOC_MAX9877
tristate
@@ -337,4 +370,3 @@ config SND_SOC_WM2000
config SND_SOC_WM9090
tristate
-
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 579af9c4f12..379bc55f072 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -12,19 +12,25 @@ snd-soc-ak4671-objs := ak4671.o
snd-soc-cq93vc-objs := cq93vc.o
snd-soc-cs42l51-objs := cs42l51.o
snd-soc-cs4270-objs := cs4270.o
+snd-soc-cs4271-objs := cs4271.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
+snd-soc-dfbmcs320-objs := dfbmcs320.o
snd-soc-dmic-objs := dmic.o
snd-soc-l3-objs := l3.o
snd-soc-max98088-objs := max98088.o
+snd-soc-max9850-objs := max9850.o
snd-soc-pcm3008-objs := pcm3008.o
+snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
+snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-objs := spdif_transciever.o
snd-soc-ssm2602-objs := ssm2602.o
snd-soc-stac9766-objs := stac9766.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic26-objs := tlv320aic26.o
snd-soc-tlv320aic3x-objs := tlv320aic3x.o
+snd-soc-tlv320aic32x4-objs := tlv320aic32x4.o
snd-soc-tlv320dac33-objs := tlv320dac33.o
snd-soc-twl4030-objs := twl4030.o
snd-soc-twl6040-objs := twl6040.o
@@ -61,6 +67,7 @@ snd-soc-wm8978-objs := wm8978.o
snd-soc-wm8985-objs := wm8985.o
snd-soc-wm8988-objs := wm8988.o
snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm8991-objs := wm8991.o
snd-soc-wm8993-objs := wm8993.o
snd-soc-wm8994-objs := wm8994.o wm8994-tables.o
snd-soc-wm8995-objs := wm8995.o
@@ -72,6 +79,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-jz4740-codec-objs := jz4740.o
# Amp
+snd-soc-lm4857-objs := lm4857.o
snd-soc-max9877-objs := max9877.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
snd-soc-wm2000-objs := wm2000.o
@@ -88,23 +96,29 @@ 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_ALC5623) += snd-soc-alc5623.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
+obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
+obj-$(CONFIG_SND_SOC_DFBMCS320) += snd-soc-dfbmcs320.o
obj-$(CONFIG_SND_SOC_DMIC) += snd-soc-dmic.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o
+obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
-obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o
+obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
+obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o
obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o
obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o
obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o
+obj-$(CONFIG_SND_SOC_TVL320AIC32X4) += snd-soc-tlv320aic32x4.o
obj-$(CONFIG_SND_SOC_TLV320DAC33) += snd-soc-tlv320dac33.o
obj-$(CONFIG_SND_SOC_TWL4030) += snd-soc-twl4030.o
obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o
@@ -141,6 +155,7 @@ obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o
obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.o
obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o
obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o
obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o
obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o
@@ -151,6 +166,7 @@ obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
+obj-$(CONFIG_SND_SOC_LM4857) += snd-soc-lm4857.o
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
diff --git a/sound/soc/codecs/ak4104.c b/sound/soc/codecs/ak4104.c
index c27f8f59dc6..cbf0b6d400b 100644
--- a/sound/soc/codecs/ak4104.c
+++ b/sound/soc/codecs/ak4104.c
@@ -294,7 +294,6 @@ static struct spi_driver ak4104_spi_driver = {
static int __init ak4104_init(void)
{
- pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n");
return spi_register_driver(&ak4104_spi_driver);
}
module_init(ak4104_init);
diff --git a/sound/soc/codecs/ak4642.c b/sound/soc/codecs/ak4642.c
index f00eba313df..4be0570e3f1 100644
--- a/sound/soc/codecs/ak4642.c
+++ b/sound/soc/codecs/ak4642.c
@@ -116,6 +116,12 @@
#define BCKO_MASK (1 << 3)
#define BCKO_64 BCKO_MASK
+#define DIF_MASK (3 << 0)
+#define DSP (0 << 0)
+#define RIGHT_J (1 << 0)
+#define LEFT_J (2 << 0)
+#define I2S (3 << 0)
+
/* MD_CTL2 */
#define FS0 (1 << 0)
#define FS1 (1 << 1)
@@ -354,6 +360,24 @@ static int ak4642_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
snd_soc_update_bits(codec, PW_MGMT2, MS, data);
snd_soc_update_bits(codec, MD_CTL1, BCKO_MASK, bcko);
+ /* format type */
+ data = 0;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ data = LEFT_J;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ data = I2S;
+ break;
+ /* FIXME
+ * Please add RIGHT_J / DSP support here
+ */
+ default:
+ return -EINVAL;
+ break;
+ }
+ snd_soc_update_bits(codec, MD_CTL1, DIF_MASK, data);
+
return 0;
}
diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c
index 8b51245f231..0206a17d728 100644
--- a/sound/soc/codecs/cs4270.c
+++ b/sound/soc/codecs/cs4270.c
@@ -193,12 +193,12 @@ static struct cs4270_mode_ratios cs4270_mode_ratios[] = {
/* The number of MCLK/LRCK ratios supported by the CS4270 */
#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios)
-static int cs4270_reg_is_readable(unsigned int reg)
+static int cs4270_reg_is_readable(struct snd_soc_codec *codec, unsigned int reg)
{
return (reg >= CS4270_FIRSTREG) && (reg <= CS4270_LASTREG);
}
-static int cs4270_reg_is_volatile(unsigned int reg)
+static int cs4270_reg_is_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
/* Unreadable registers are considered volatile */
if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG))
@@ -719,7 +719,7 @@ static int cs4270_i2c_remove(struct i2c_client *i2c_client)
/*
* cs4270_id - I2C device IDs supported by this driver
*/
-static struct i2c_device_id cs4270_id[] = {
+static const struct i2c_device_id cs4270_id[] = {
{"cs4270", 0},
{}
};
@@ -743,8 +743,6 @@ static struct i2c_driver cs4270_i2c_driver = {
static int __init cs4270_init(void)
{
- pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n");
-
return i2c_add_driver(&cs4270_i2c_driver);
}
module_init(cs4270_init);
diff --git a/sound/soc/codecs/cs4271.c b/sound/soc/codecs/cs4271.c
new file mode 100644
index 00000000000..083aab96ca8
--- /dev/null
+++ b/sound/soc/codecs/cs4271.c
@@ -0,0 +1,667 @@
+/*
+ * CS4271 ASoC codec driver
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * This driver support CS4271 codec being master or slave, working
+ * in control port mode, connected either via SPI or I2C.
+ * The data format accepted is I2S or left-justified.
+ * DAPM support not implemented.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/spi/spi.h>
+#include <sound/cs4271.h>
+
+#define CS4271_PCM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+#define CS4271_PCM_RATES SNDRV_PCM_RATE_8000_192000
+
+/*
+ * CS4271 registers
+ * High byte represents SPI chip address (0x10) + write command (0)
+ * Low byte - codec register address
+ */
+#define CS4271_MODE1 0x2001 /* Mode Control 1 */
+#define CS4271_DACCTL 0x2002 /* DAC Control */
+#define CS4271_DACVOL 0x2003 /* DAC Volume & Mixing Control */
+#define CS4271_VOLA 0x2004 /* DAC Channel A Volume Control */
+#define CS4271_VOLB 0x2005 /* DAC Channel B Volume Control */
+#define CS4271_ADCCTL 0x2006 /* ADC Control */
+#define CS4271_MODE2 0x2007 /* Mode Control 2 */
+#define CS4271_CHIPID 0x2008 /* Chip ID */
+
+#define CS4271_FIRSTREG CS4271_MODE1
+#define CS4271_LASTREG CS4271_MODE2
+#define CS4271_NR_REGS ((CS4271_LASTREG & 0xFF) + 1)
+
+/* Bit masks for the CS4271 registers */
+#define CS4271_MODE1_MODE_MASK 0xC0
+#define CS4271_MODE1_MODE_1X 0x00
+#define CS4271_MODE1_MODE_2X 0x80
+#define CS4271_MODE1_MODE_4X 0xC0
+
+#define CS4271_MODE1_DIV_MASK 0x30
+#define CS4271_MODE1_DIV_1 0x00
+#define CS4271_MODE1_DIV_15 0x10
+#define CS4271_MODE1_DIV_2 0x20
+#define CS4271_MODE1_DIV_3 0x30
+
+#define CS4271_MODE1_MASTER 0x08
+
+#define CS4271_MODE1_DAC_DIF_MASK 0x07
+#define CS4271_MODE1_DAC_DIF_LJ 0x00
+#define CS4271_MODE1_DAC_DIF_I2S 0x01
+#define CS4271_MODE1_DAC_DIF_RJ16 0x02
+#define CS4271_MODE1_DAC_DIF_RJ24 0x03
+#define CS4271_MODE1_DAC_DIF_RJ20 0x04
+#define CS4271_MODE1_DAC_DIF_RJ18 0x05
+
+#define CS4271_DACCTL_AMUTE 0x80
+#define CS4271_DACCTL_IF_SLOW 0x40
+
+#define CS4271_DACCTL_DEM_MASK 0x30
+#define CS4271_DACCTL_DEM_DIS 0x00
+#define CS4271_DACCTL_DEM_441 0x10
+#define CS4271_DACCTL_DEM_48 0x20
+#define CS4271_DACCTL_DEM_32 0x30
+
+#define CS4271_DACCTL_SVRU 0x08
+#define CS4271_DACCTL_SRD 0x04
+#define CS4271_DACCTL_INVA 0x02
+#define CS4271_DACCTL_INVB 0x01
+
+#define CS4271_DACVOL_BEQUA 0x40
+#define CS4271_DACVOL_SOFT 0x20
+#define CS4271_DACVOL_ZEROC 0x10
+
+#define CS4271_DACVOL_ATAPI_MASK 0x0F
+#define CS4271_DACVOL_ATAPI_M_M 0x00
+#define CS4271_DACVOL_ATAPI_M_BR 0x01
+#define CS4271_DACVOL_ATAPI_M_BL 0x02
+#define CS4271_DACVOL_ATAPI_M_BLR2 0x03
+#define CS4271_DACVOL_ATAPI_AR_M 0x04
+#define CS4271_DACVOL_ATAPI_AR_BR 0x05
+#define CS4271_DACVOL_ATAPI_AR_BL 0x06
+#define CS4271_DACVOL_ATAPI_AR_BLR2 0x07
+#define CS4271_DACVOL_ATAPI_AL_M 0x08
+#define CS4271_DACVOL_ATAPI_AL_BR 0x09
+#define CS4271_DACVOL_ATAPI_AL_BL 0x0A
+#define CS4271_DACVOL_ATAPI_AL_BLR2 0x0B
+#define CS4271_DACVOL_ATAPI_ALR2_M 0x0C
+#define CS4271_DACVOL_ATAPI_ALR2_BR 0x0D
+#define CS4271_DACVOL_ATAPI_ALR2_BL 0x0E
+#define CS4271_DACVOL_ATAPI_ALR2_BLR2 0x0F
+
+#define CS4271_VOLA_MUTE 0x80
+#define CS4271_VOLA_VOL_MASK 0x7F
+#define CS4271_VOLB_MUTE 0x80
+#define CS4271_VOLB_VOL_MASK 0x7F
+
+#define CS4271_ADCCTL_DITHER16 0x20
+
+#define CS4271_ADCCTL_ADC_DIF_MASK 0x10
+#define CS4271_ADCCTL_ADC_DIF_LJ 0x00
+#define CS4271_ADCCTL_ADC_DIF_I2S 0x10
+
+#define CS4271_ADCCTL_MUTEA 0x08
+#define CS4271_ADCCTL_MUTEB 0x04
+#define CS4271_ADCCTL_HPFDA 0x02
+#define CS4271_ADCCTL_HPFDB 0x01
+
+#define CS4271_MODE2_LOOP 0x10
+#define CS4271_MODE2_MUTECAEQUB 0x08
+#define CS4271_MODE2_FREEZE 0x04
+#define CS4271_MODE2_CPEN 0x02
+#define CS4271_MODE2_PDN 0x01
+
+#define CS4271_CHIPID_PART_MASK 0xF0
+#define CS4271_CHIPID_REV_MASK 0x0F
+
+/*
+ * Default CS4271 power-up configuration
+ * Array contains non-existing in hw register at address 0
+ * Array do not include Chip ID, as codec driver does not use
+ * registers read operations at all
+ */
+static const u8 cs4271_dflt_reg[CS4271_NR_REGS] = {
+ 0,
+ 0,
+ CS4271_DACCTL_AMUTE,
+ CS4271_DACVOL_SOFT | CS4271_DACVOL_ATAPI_AL_BR,
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+struct cs4271_private {
+ /* SND_SOC_I2C or SND_SOC_SPI */
+ enum snd_soc_control_type bus_type;
+ void *control_data;
+ unsigned int mclk;
+ bool master;
+ bool deemph;
+ /* Current sample rate for de-emphasis control */
+ int rate;
+ /* GPIO driving Reset pin, if any */
+ int gpio_nreset;
+ /* GPIO that disable serial bus, if any */
+ int gpio_disable;
+};
+
+/*
+ * @freq is the desired MCLK rate
+ * MCLK rate should (c) be the sample rate, multiplied by one of the
+ * ratios listed in cs4271_mclk_fs_ratios table
+ */
+static int cs4271_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ cs4271->mclk = freq;
+ return 0;
+}
+
+static int cs4271_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val = 0;
+ int ret;
+
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs4271->master = 0;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs4271->master = 1;
+ val |= CS4271_MODE1_MASTER;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format\n");
+ return -EINVAL;
+ }
+
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= CS4271_MODE1_DAC_DIF_LJ;
+ ret = snd_soc_update_bits(codec, CS4271_ADCCTL,
+ CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_LJ);
+ if (ret < 0)
+ return ret;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= CS4271_MODE1_DAC_DIF_I2S;
+ ret = snd_soc_update_bits(codec, CS4271_ADCCTL,
+ CS4271_ADCCTL_ADC_DIF_MASK, CS4271_ADCCTL_ADC_DIF_I2S);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ dev_err(codec->dev, "Invalid DAI format\n");
+ return -EINVAL;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE1,
+ CS4271_MODE1_DAC_DIF_MASK | CS4271_MODE1_MASTER, val);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_deemph[] = {0, 44100, 48000, 32000};
+
+static int cs4271_set_deemph(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ int val = CS4271_DACCTL_DEM_DIS;
+
+ if (cs4271->deemph) {
+ /* Find closest de-emphasis freq */
+ val = 1;
+ for (i = 2; i < ARRAY_SIZE(cs4271_deemph); i++)
+ if (abs(cs4271_deemph[i] - cs4271->rate) <
+ abs(cs4271_deemph[val] - cs4271->rate))
+ val = i;
+ val <<= 4;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_DACCTL,
+ CS4271_DACCTL_DEM_MASK, val);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_get_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.enumerated.item[0] = cs4271->deemph;
+ return 0;
+}
+
+static int cs4271_put_deemph(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+
+ cs4271->deemph = ucontrol->value.enumerated.item[0];
+ return cs4271_set_deemph(codec);
+}
+
+struct cs4271_clk_cfg {
+ bool master; /* codec mode */
+ u8 speed_mode; /* codec speed mode: 1x, 2x, 4x */
+ unsigned short ratio; /* MCLK / sample rate */
+ u8 ratio_mask; /* ratio bit mask for Master mode */
+};
+
+static struct cs4271_clk_cfg cs4271_clk_tab[] = {
+ {1, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_3},
+ {1, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_3},
+ {1, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1},
+ {1, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_15},
+ {1, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_2},
+ {1, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_3},
+ {0, CS4271_MODE1_MODE_1X, 256, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 384, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 512, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_1X, 768, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_1X, 1024, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_2X, 128, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 192, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 256, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_2X, 384, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_2X, 512, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_4X, 64, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 96, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 128, CS4271_MODE1_DIV_1},
+ {0, CS4271_MODE1_MODE_4X, 192, CS4271_MODE1_DIV_2},
+ {0, CS4271_MODE1_MODE_4X, 256, CS4271_MODE1_DIV_2},
+};
+
+#define CS4171_NR_RATIOS ARRAY_SIZE(cs4271_clk_tab)
+
+static int cs4271_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int i, ret;
+ unsigned int ratio, val;
+
+ cs4271->rate = params_rate(params);
+
+ /* Configure DAC */
+ if (cs4271->rate < 50000)
+ val = CS4271_MODE1_MODE_1X;
+ else if (cs4271->rate < 100000)
+ val = CS4271_MODE1_MODE_2X;
+ else
+ val = CS4271_MODE1_MODE_4X;
+
+ ratio = cs4271->mclk / cs4271->rate;
+ for (i = 0; i < CS4171_NR_RATIOS; i++)
+ if ((cs4271_clk_tab[i].master == cs4271->master) &&
+ (cs4271_clk_tab[i].speed_mode == val) &&
+ (cs4271_clk_tab[i].ratio == ratio))
+ break;
+
+ if (i == CS4171_NR_RATIOS) {
+ dev_err(codec->dev, "Invalid sample rate\n");
+ return -EINVAL;
+ }
+
+ val |= cs4271_clk_tab[i].ratio_mask;
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE1,
+ CS4271_MODE1_MODE_MASK | CS4271_MODE1_DIV_MASK, val);
+ if (ret < 0)
+ return ret;
+
+ return cs4271_set_deemph(codec);
+}
+
+static int cs4271_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ int ret;
+ int val_a = 0;
+ int val_b = 0;
+
+ if (mute) {
+ val_a = CS4271_VOLA_MUTE;
+ val_b = CS4271_VOLB_MUTE;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_VOLA, CS4271_VOLA_MUTE, val_a);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_update_bits(codec, CS4271_VOLB, CS4271_VOLB_MUTE, val_b);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* CS4271 controls */
+static DECLARE_TLV_DB_SCALE(cs4271_dac_tlv, -12700, 100, 0);
+
+static const struct snd_kcontrol_new cs4271_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("Master Playback Volume", CS4271_VOLA, CS4271_VOLB,
+ 0, 0x7F, 1, cs4271_dac_tlv),
+ SOC_SINGLE("Digital Loopback Switch", CS4271_MODE2, 4, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS4271_DACVOL, 5, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS4271_DACVOL, 4, 1, 0),
+ SOC_SINGLE_BOOL_EXT("De-emphasis Switch", 0,
+ cs4271_get_deemph, cs4271_put_deemph),
+ SOC_SINGLE("Auto-Mute Switch", CS4271_DACCTL, 7, 1, 0),
+ SOC_SINGLE("Slow Roll Off Filter Switch", CS4271_DACCTL, 6, 1, 0),
+ SOC_SINGLE("Soft Volume Ramp-Up Switch", CS4271_DACCTL, 3, 1, 0),
+ SOC_SINGLE("Soft Ramp-Down Switch", CS4271_DACCTL, 2, 1, 0),
+ SOC_SINGLE("Left Channel Inversion Switch", CS4271_DACCTL, 1, 1, 0),
+ SOC_SINGLE("Right Channel Inversion Switch", CS4271_DACCTL, 0, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", CS4271_ADCCTL, 3, 2, 1, 1),
+ SOC_SINGLE("Dither 16-Bit Data Switch", CS4271_ADCCTL, 5, 1, 0),
+ SOC_DOUBLE("High Pass Filter Switch", CS4271_ADCCTL, 1, 0, 1, 1),
+ SOC_DOUBLE_R("Master Playback Switch", CS4271_VOLA, CS4271_VOLB,
+ 7, 1, 1),
+};
+
+static struct snd_soc_dai_ops cs4271_dai_ops = {
+ .hw_params = cs4271_hw_params,
+ .set_sysclk = cs4271_set_dai_sysclk,
+ .set_fmt = cs4271_set_dai_fmt,
+ .digital_mute = cs4271_digital_mute,
+};
+
+static struct snd_soc_dai_driver cs4271_dai = {
+ .name = "cs4271-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CS4271_PCM_RATES,
+ .formats = CS4271_PCM_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = CS4271_PCM_RATES,
+ .formats = CS4271_PCM_FORMATS,
+ },
+ .ops = &cs4271_dai_ops,
+ .symmetric_rates = 1,
+};
+
+#ifdef CONFIG_PM
+static int cs4271_soc_suspend(struct snd_soc_codec *codec, pm_message_t mesg)
+{
+ int ret;
+ /* Set power-down bit */
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, 0, CS4271_MODE2_PDN);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int cs4271_soc_resume(struct snd_soc_codec *codec)
+{
+ int ret;
+ /* Restore codec state */
+ ret = snd_soc_cache_sync(codec);
+ if (ret < 0)
+ return ret;
+ /* then disable the power-down bit */
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+#else
+#define cs4271_soc_suspend NULL
+#define cs4271_soc_resume NULL
+#endif /* CONFIG_PM */
+
+static int cs4271_probe(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ struct cs4271_platform_data *cs4271plat = codec->dev->platform_data;
+ int ret;
+ int gpio_nreset = -EINVAL;
+
+ codec->control_data = cs4271->control_data;
+
+ if (cs4271plat && gpio_is_valid(cs4271plat->gpio_nreset))
+ gpio_nreset = cs4271plat->gpio_nreset;
+
+ if (gpio_nreset >= 0)
+ if (gpio_request(gpio_nreset, "CS4271 Reset"))
+ gpio_nreset = -EINVAL;
+ if (gpio_nreset >= 0) {
+ /* Reset codec */
+ gpio_direction_output(gpio_nreset, 0);
+ udelay(1);
+ gpio_set_value(gpio_nreset, 1);
+ /* Give the codec time to wake up */
+ udelay(1);
+ }
+
+ cs4271->gpio_nreset = gpio_nreset;
+
+ /*
+ * In case of I2C, chip address specified in board data.
+ * So cache IO operations use 8 bit codec register address.
+ * In case of SPI, chip address and register address
+ * passed together as 16 bit value.
+ * Anyway, register address is masked with 0xFF inside
+ * soc-cache code.
+ */
+ if (cs4271->bus_type == SND_SOC_SPI)
+ ret = snd_soc_codec_set_cache_io(codec, 16, 8,
+ cs4271->bus_type);
+ else
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8,
+ cs4271->bus_type);
+ if (ret) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, 0,
+ CS4271_MODE2_PDN | CS4271_MODE2_CPEN);
+ if (ret < 0)
+ return ret;
+ ret = snd_soc_update_bits(codec, CS4271_MODE2, CS4271_MODE2_PDN, 0);
+ if (ret < 0)
+ return ret;
+ /* Power-up sequence requires 85 uS */
+ udelay(85);
+
+ return snd_soc_add_controls(codec, cs4271_snd_controls,
+ ARRAY_SIZE(cs4271_snd_controls));
+}
+
+static int cs4271_remove(struct snd_soc_codec *codec)
+{
+ struct cs4271_private *cs4271 = snd_soc_codec_get_drvdata(codec);
+ int gpio_nreset;
+
+ gpio_nreset = cs4271->gpio_nreset;
+
+ if (gpio_is_valid(gpio_nreset)) {
+ /* Set codec to the reset state */
+ gpio_set_value(gpio_nreset, 0);
+ gpio_free(gpio_nreset);
+ }
+
+ return 0;
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_cs4271 = {
+ .probe = cs4271_probe,
+ .remove = cs4271_remove,
+ .suspend = cs4271_soc_suspend,
+ .resume = cs4271_soc_resume,
+ .reg_cache_default = cs4271_dflt_reg,
+ .reg_cache_size = ARRAY_SIZE(cs4271_dflt_reg),
+ .reg_word_size = sizeof(cs4271_dflt_reg[0]),
+ .compress_type = SND_SOC_FLAT_COMPRESSION,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int __devinit cs4271_spi_probe(struct spi_device *spi)
+{
+ struct cs4271_private *cs4271;
+
+ cs4271 = devm_kzalloc(&spi->dev, sizeof(*cs4271), GFP_KERNEL);
+ if (!cs4271)
+ return -ENOMEM;
+
+ spi_set_drvdata(spi, cs4271);
+ cs4271->control_data = spi;
+ cs4271->bus_type = SND_SOC_SPI;
+
+ return snd_soc_register_codec(&spi->dev, &soc_codec_dev_cs4271,
+ &cs4271_dai, 1);
+}
+
+static int __devexit cs4271_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+ return 0;
+}
+
+static struct spi_driver cs4271_spi_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .probe = cs4271_spi_probe,
+ .remove = __devexit_p(cs4271_spi_remove),
+};
+#endif /* defined(CONFIG_SPI_MASTER) */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static const struct i2c_device_id cs4271_i2c_id[] = {
+ {"cs4271", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4271_i2c_id);
+
+static int __devinit cs4271_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct cs4271_private *cs4271;
+
+ cs4271 = devm_kzalloc(&client->dev, sizeof(*cs4271), GFP_KERNEL);
+ if (!cs4271)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, cs4271);
+ cs4271->control_data = client;
+ cs4271->bus_type = SND_SOC_I2C;
+
+ return snd_soc_register_codec(&client->dev, &soc_codec_dev_cs4271,
+ &cs4271_dai, 1);
+}
+
+static int __devexit cs4271_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ return 0;
+}
+
+static struct i2c_driver cs4271_i2c_driver = {
+ .driver = {
+ .name = "cs4271",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4271_i2c_id,
+ .probe = cs4271_i2c_probe,
+ .remove = __devexit_p(cs4271_i2c_remove),
+};
+#endif /* defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) */
+
+/*
+ * We only register our serial bus driver here without
+ * assignment to particular chip. So if any of the below
+ * fails, there is some problem with I2C or SPI subsystem.
+ * In most cases this module will be compiled with support
+ * of only one serial bus.
+ */
+static int __init cs4271_modinit(void)
+{
+ int ret;
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&cs4271_i2c_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 I2C driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&cs4271_spi_driver);
+ if (ret) {
+ pr_err("Failed to register CS4271 SPI driver: %d\n", ret);
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+module_init(cs4271_modinit);
+
+static void __exit cs4271_modexit(void)
+{
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&cs4271_spi_driver);
+#endif
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&cs4271_i2c_driver);
+#endif
+}
+module_exit(cs4271_modexit);
+
+MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>");
+MODULE_DESCRIPTION("Cirrus Logic CS4271 ALSA SoC Codec Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/dfbmcs320.c b/sound/soc/codecs/dfbmcs320.c
new file mode 100644
index 00000000000..704bbde6573
--- /dev/null
+++ b/sound/soc/codecs/dfbmcs320.c
@@ -0,0 +1,72 @@
+/*
+ * Driver for the DFBM-CS320 bluetooth module
+ * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static struct snd_soc_dai_driver dfbmcs320_dai = {
+ .name = "dfbmcs320-pcm",
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SNDRV_PCM_RATE_8000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_dfbmcs320;
+
+static int __devinit dfbmcs320_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_dfbmcs320,
+ &dfbmcs320_dai, 1);
+}
+
+static int __devexit dfbmcs320_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+
+ return 0;
+}
+
+static struct platform_driver dfmcs320_driver = {
+ .driver = {
+ .name = "dfbmcs320",
+ .owner = THIS_MODULE,
+ },
+ .probe = dfbmcs320_probe,
+ .remove = __devexit_p(dfbmcs320_remove),
+};
+
+static int __init dfbmcs320_init(void)
+{
+ return platform_driver_register(&dfmcs320_driver);
+}
+module_init(dfbmcs320_init);
+
+static void __exit dfbmcs320_exit(void)
+{
+ platform_driver_unregister(&dfmcs320_driver);
+}
+module_exit(dfbmcs320_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ASoC DFBM-CS320 bluethooth module driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/lm4857.c b/sound/soc/codecs/lm4857.c
new file mode 100644
index 00000000000..72de47e5d04
--- /dev/null
+++ b/sound/soc/codecs/lm4857.c
@@ -0,0 +1,276 @@
+/*
+ * LM4857 AMP driver
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Copyright 2011 Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+struct lm4857 {
+ struct i2c_client *i2c;
+ uint8_t mode;
+};
+
+static const uint8_t lm4857_default_regs[] = {
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/* The register offsets in the cache array */
+#define LM4857_MVOL 0
+#define LM4857_LVOL 1
+#define LM4857_RVOL 2
+#define LM4857_CTRL 3
+
+/* the shifts required to set these bits */
+#define LM4857_3D 5
+#define LM4857_WAKEUP 5
+#define LM4857_EPGAIN 4
+
+static int lm4857_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ uint8_t data;
+ int ret;
+
+ ret = snd_soc_cache_write(codec, reg, value);
+ if (ret < 0)
+ return ret;
+
+ data = (reg << 6) | value;
+ ret = i2c_master_send(codec->control_data, &data, 1);
+ if (ret != 1) {
+ dev_err(codec->dev, "Failed to write register: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static unsigned int lm4857_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ unsigned int val;
+ int ret;
+
+ ret = snd_soc_cache_read(codec, reg, &val);
+ if (ret)
+ return -1;
+
+ return val;
+}
+
+static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = lm4857->mode;
+
+ return 0;
+}
+
+static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+ uint8_t value = ucontrol->value.integer.value[0];
+
+ lm4857->mode = value;
+
+ if (codec->dapm.bias_level == SND_SOC_BIAS_ON)
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, value + 6);
+
+ return 1;
+}
+
+static int lm4857_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, lm4857->mode + 6);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ snd_soc_update_bits(codec, LM4857_CTRL, 0x0F, 0);
+ break;
+ default:
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static const char *lm4857_mode[] = {
+ "Earpiece",
+ "Loudspeaker",
+ "Loudspeaker + Headphone",
+ "Headphone",
+};
+
+static const struct soc_enum lm4857_mode_enum =
+ SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode);
+
+static const struct snd_soc_dapm_widget lm4857_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("IN"),
+
+ SND_SOC_DAPM_OUTPUT("LS"),
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_OUTPUT("EP"),
+};
+
+static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
+static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
+
+static const struct snd_kcontrol_new lm4857_controls[] = {
+ SOC_SINGLE_TLV("Left Playback Volume", LM4857_LVOL, 0, 31, 0,
+ stereo_tlv),
+ SOC_SINGLE_TLV("Right Playback Volume", LM4857_RVOL, 0, 31, 0,
+ stereo_tlv),
+ SOC_SINGLE_TLV("Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
+ mono_tlv),
+ SOC_SINGLE("Spk 3D Playback Switch", LM4857_LVOL, LM4857_3D, 1, 0),
+ SOC_SINGLE("HP 3D Playback Switch", LM4857_RVOL, LM4857_3D, 1, 0),
+ SOC_SINGLE("Fast Wakeup Playback Switch", LM4857_CTRL,
+ LM4857_WAKEUP, 1, 0),
+ SOC_SINGLE("Earpiece 6dB Playback Switch", LM4857_CTRL,
+ LM4857_EPGAIN, 1, 0),
+
+ SOC_ENUM_EXT("Mode", lm4857_mode_enum,
+ lm4857_get_mode, lm4857_set_mode),
+};
+
+/* There is a demux inbetween the the input signal and the output signals.
+ * Currently there is no easy way to model it in ASoC and since it does not make
+ * much of a difference in practice simply connect the input direclty to the
+ * outputs. */
+static const struct snd_soc_dapm_route lm4857_routes[] = {
+ {"LS", NULL, "IN"},
+ {"HP", NULL, "IN"},
+ {"EP", NULL, "IN"},
+};
+
+static int lm4857_probe(struct snd_soc_codec *codec)
+{
+ struct lm4857 *lm4857 = snd_soc_codec_get_drvdata(codec);
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ codec->control_data = lm4857->i2c;
+
+ ret = snd_soc_add_controls(codec, lm4857_controls,
+ ARRAY_SIZE(lm4857_controls));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, lm4857_dapm_widgets,
+ ARRAY_SIZE(lm4857_dapm_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, lm4857_routes,
+ ARRAY_SIZE(lm4857_routes));
+ if (ret)
+ return ret;
+
+ snd_soc_dapm_new_widgets(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_lm4857 = {
+ .write = lm4857_write,
+ .read = lm4857_read,
+ .probe = lm4857_probe,
+ .reg_cache_size = ARRAY_SIZE(lm4857_default_regs),
+ .reg_word_size = sizeof(uint8_t),
+ .reg_cache_default = lm4857_default_regs,
+ .set_bias_level = lm4857_set_bias_level,
+};
+
+static int __devinit lm4857_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct lm4857 *lm4857;
+ int ret;
+
+ lm4857 = kzalloc(sizeof(*lm4857), GFP_KERNEL);
+ if (!lm4857)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, lm4857);
+
+ lm4857->i2c = i2c;
+
+ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_lm4857, NULL, 0);
+
+ if (ret) {
+ kfree(lm4857);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devexit lm4857_i2c_remove(struct i2c_client *i2c)
+{
+ struct lm4857 *lm4857 = i2c_get_clientdata(i2c);
+
+ snd_soc_unregister_codec(&i2c->dev);
+ kfree(lm4857);
+
+ return 0;
+}
+
+static const struct i2c_device_id lm4857_i2c_id[] = {
+ { "lm4857", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm4857_i2c_id);
+
+static struct i2c_driver lm4857_i2c_driver = {
+ .driver = {
+ .name = "lm4857",
+ .owner = THIS_MODULE,
+ },
+ .probe = lm4857_i2c_probe,
+ .remove = __devexit_p(lm4857_i2c_remove),
+ .id_table = lm4857_i2c_id,
+};
+
+static int __init lm4857_init(void)
+{
+ return i2c_add_driver(&lm4857_i2c_driver);
+}
+module_init(lm4857_init);
+
+static void __exit lm4857_exit(void)
+{
+ i2c_del_driver(&lm4857_i2c_driver);
+}
+module_exit(lm4857_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("LM4857 amplifier driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
index 89498f9ad2e..bd0517cb798 100644
--- a/sound/soc/codecs/max98088.c
+++ b/sound/soc/codecs/max98088.c
@@ -608,7 +608,7 @@ static struct {
{ 0xFF, 0x00, 1 }, /* FF */
};
-static int max98088_volatile_register(unsigned int reg)
+static int max98088_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
return max98088_access[reg].vol;
}
diff --git a/sound/soc/codecs/max9850.c b/sound/soc/codecs/max9850.c
new file mode 100644
index 00000000000..208d2ee6185
--- /dev/null
+++ b/sound/soc/codecs/max9850.c
@@ -0,0 +1,389 @@
+/*
+ * max9850.c -- codec driver for max9850
+ *
+ * Copyright (C) 2011 taskit GmbH
+ *
+ * Author: Christian Glindkamp <christian.glindkamp@taskit.de>
+ *
+ * Initial development of this code was funded by
+ * MICRONIC Computer Systeme GmbH, http://www.mcsberlin.de/
+ *
+ * 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/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "max9850.h"
+
+struct max9850_priv {
+ unsigned int sysclk;
+};
+
+/* max9850 register cache */
+static const u8 max9850_reg[MAX9850_CACHEREGNUM] = {
+ 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* these registers are not used at the moment but provided for the sake of
+ * completeness */
+static int max9850_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case MAX9850_STATUSA:
+ case MAX9850_STATUSB:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static const unsigned int max9850_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0x18, 0x1f, TLV_DB_SCALE_ITEM(-7450, 400, 0),
+ 0x20, 0x33, TLV_DB_SCALE_ITEM(-4150, 200, 0),
+ 0x34, 0x37, TLV_DB_SCALE_ITEM(-150, 100, 0),
+ 0x38, 0x3f, TLV_DB_SCALE_ITEM(250, 50, 0),
+};
+
+static const struct snd_kcontrol_new max9850_controls[] = {
+SOC_SINGLE_TLV("Headphone Volume", MAX9850_VOLUME, 0, 0x3f, 1, max9850_tlv),
+SOC_SINGLE("Headphone Switch", MAX9850_VOLUME, 7, 1, 1),
+SOC_SINGLE("Mono Switch", MAX9850_GENERAL_PURPOSE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new max9850_mixer_controls[] = {
+ SOC_DAPM_SINGLE("Line In Switch", MAX9850_ENABLE, 1, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget max9850_dapm_widgets[] = {
+SND_SOC_DAPM_SUPPLY("Charge Pump 1", MAX9850_ENABLE, 4, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("Charge Pump 2", MAX9850_ENABLE, 5, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("MCLK", MAX9850_ENABLE, 6, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("SHDN", MAX9850_ENABLE, 7, 0, NULL, 0),
+SND_SOC_DAPM_MIXER_NAMED_CTL("Output Mixer", MAX9850_ENABLE, 2, 0,
+ &max9850_mixer_controls[0],
+ ARRAY_SIZE(max9850_mixer_controls)),
+SND_SOC_DAPM_PGA("Headphone Output", MAX9850_ENABLE, 3, 0, NULL, 0),
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", MAX9850_ENABLE, 0, 0),
+SND_SOC_DAPM_OUTPUT("OUTL"),
+SND_SOC_DAPM_OUTPUT("HPL"),
+SND_SOC_DAPM_OUTPUT("OUTR"),
+SND_SOC_DAPM_OUTPUT("HPR"),
+SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+SND_SOC_DAPM_INPUT("INL"),
+SND_SOC_DAPM_INPUT("INR"),
+};
+
+static const struct snd_soc_dapm_route intercon[] = {
+ /* output mixer */
+ {"Output Mixer", NULL, "DAC"},
+ {"Output Mixer", "Line In Switch", "Line Input"},
+
+ /* outputs */
+ {"Headphone Output", NULL, "Output Mixer"},
+ {"HPL", NULL, "Headphone Output"},
+ {"HPR", NULL, "Headphone Output"},
+ {"OUTL", NULL, "Output Mixer"},
+ {"OUTR", NULL, "Output Mixer"},
+
+ /* inputs */
+ {"Line Input", NULL, "INL"},
+ {"Line Input", NULL, "INR"},
+
+ /* supplies */
+ {"Output Mixer", NULL, "Charge Pump 1"},
+ {"Output Mixer", NULL, "Charge Pump 2"},
+ {"Output Mixer", NULL, "SHDN"},
+ {"DAC", NULL, "MCLK"},
+};
+
+static int max9850_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec);
+ u64 lrclk_div;
+ u8 sf, da;
+
+ if (!max9850->sysclk)
+ return -EINVAL;
+
+ /* lrclk_div = 2^22 * rate / iclk with iclk = mclk / sf */
+ sf = (snd_soc_read(codec, MAX9850_CLOCK) >> 2) + 1;
+ lrclk_div = (1 << 22);
+ lrclk_div *= params_rate(params);
+ lrclk_div *= sf;
+ do_div(lrclk_div, max9850->sysclk);
+
+ snd_soc_write(codec, MAX9850_LRCLK_MSB, (lrclk_div >> 8) & 0x7f);
+ snd_soc_write(codec, MAX9850_LRCLK_LSB, lrclk_div & 0xff);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ da = 0;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ da = 0x2;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ da = 0x3;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(codec, MAX9850_DIGITAL_AUDIO, 0x3, da);
+
+ return 0;
+}
+
+static int max9850_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max9850_priv *max9850 = snd_soc_codec_get_drvdata(codec);
+
+ /* calculate mclk -> iclk divider */
+ if (freq <= 13000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x0);
+ else if (freq <= 26000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x4);
+ else if (freq <= 40000000)
+ snd_soc_write(codec, MAX9850_CLOCK, 0x8);
+ else
+ return -EINVAL;
+
+ max9850->sysclk = freq;
+ return 0;
+}
+
+static int max9850_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u8 da = 0;
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ da |= MAX9850_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ da |= MAX9850_DLY;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ da |= MAX9850_RTJ;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ da |= MAX9850_BCINV | MAX9850_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ da |= MAX9850_BCINV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ da |= MAX9850_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set da */
+ snd_soc_write(codec, MAX9850_DIGITAL_AUDIO, da);
+
+ return 0;
+}
+
+static int max9850_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_cache_sync(codec);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define MAX9850_RATES SNDRV_PCM_RATE_8000_48000
+
+#define MAX9850_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max9850_dai_ops = {
+ .hw_params = max9850_hw_params,
+ .set_sysclk = max9850_set_dai_sysclk,
+ .set_fmt = max9850_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver max9850_dai = {
+ .name = "max9850-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX9850_RATES,
+ .formats = MAX9850_FORMATS
+ },
+ .ops = &max9850_dai_ops,
+};
+
+#ifdef CONFIG_PM
+static int max9850_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ max9850_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+static int max9850_resume(struct snd_soc_codec *codec)
+{
+ max9850_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+}
+#else
+#define max9850_suspend NULL
+#define max9850_resume NULL
+#endif
+
+static int max9850_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ /* enable zero-detect */
+ snd_soc_update_bits(codec, MAX9850_GENERAL_PURPOSE, 1, 1);
+ /* enable slew-rate control */
+ snd_soc_update_bits(codec, MAX9850_VOLUME, 0x40, 0x40);
+ /* set slew-rate 125ms */
+ snd_soc_update_bits(codec, MAX9850_CHARGE_PUMP, 0xff, 0xc0);
+
+ snd_soc_dapm_new_controls(dapm, max9850_dapm_widgets,
+ ARRAY_SIZE(max9850_dapm_widgets));
+ snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
+
+ snd_soc_add_controls(codec, max9850_controls,
+ ARRAY_SIZE(max9850_controls));
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max9850 = {
+ .probe = max9850_probe,
+ .suspend = max9850_suspend,
+ .resume = max9850_resume,
+ .set_bias_level = max9850_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(max9850_reg),
+ .reg_word_size = sizeof(u8),
+ .reg_cache_default = max9850_reg,
+ .volatile_register = max9850_volatile_register,
+};
+
+static int __devinit max9850_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max9850_priv *max9850;
+ int ret;
+
+ max9850 = kzalloc(sizeof(struct max9850_priv), GFP_KERNEL);
+ if (max9850 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max9850);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_max9850, &max9850_dai, 1);
+ if (ret < 0)
+ kfree(max9850);
+ return ret;
+}
+
+static __devexit int max9850_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id max9850_i2c_id[] = {
+ { "max9850", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max9850_i2c_id);
+
+static struct i2c_driver max9850_i2c_driver = {
+ .driver = {
+ .name = "max9850",
+ .owner = THIS_MODULE,
+ },
+ .probe = max9850_i2c_probe,
+ .remove = __devexit_p(max9850_i2c_remove),
+ .id_table = max9850_i2c_id,
+};
+
+static int __init max9850_init(void)
+{
+ return i2c_add_driver(&max9850_i2c_driver);
+}
+module_init(max9850_init);
+
+static void __exit max9850_exit(void)
+{
+ i2c_del_driver(&max9850_i2c_driver);
+}
+module_exit(max9850_exit);
+
+MODULE_AUTHOR("Christian Glindkamp <christian.glindkamp@taskit.de>");
+MODULE_DESCRIPTION("ASoC MAX9850 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max9850.h b/sound/soc/codecs/max9850.h
new file mode 100644
index 00000000000..72b1ddb04b0
--- /dev/null
+++ b/sound/soc/codecs/max9850.h
@@ -0,0 +1,38 @@
+/*
+ * max9850.h -- codec driver for max9850
+ *
+ * Copyright (C) 2011 taskit GmbH
+ * Author: Christian Glindkamp <christian.glindkamp@taskit.de>
+ *
+ * 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 _MAX9850_H
+#define _MAX9850_H
+
+#define MAX9850_STATUSA 0x00
+#define MAX9850_STATUSB 0x01
+#define MAX9850_VOLUME 0x02
+#define MAX9850_GENERAL_PURPOSE 0x03
+#define MAX9850_INTERRUPT 0x04
+#define MAX9850_ENABLE 0x05
+#define MAX9850_CLOCK 0x06
+#define MAX9850_CHARGE_PUMP 0x07
+#define MAX9850_LRCLK_MSB 0x08
+#define MAX9850_LRCLK_LSB 0x09
+#define MAX9850_DIGITAL_AUDIO 0x0a
+
+#define MAX9850_CACHEREGNUM 11
+
+/* MAX9850_DIGITAL_AUDIO */
+#define MAX9850_MASTER (1<<7)
+#define MAX9850_INV (1<<6)
+#define MAX9850_BCINV (1<<5)
+#define MAX9850_DLY (1<<3)
+#define MAX9850_RTJ (1<<2)
+
+#endif
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
new file mode 100644
index 00000000000..1f7217f703e
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.c
@@ -0,0 +1,1513 @@
+/*
+ * sgtl5000.c -- SGTL5000 ALSA SoC Audio driver
+ *
+ * Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/consumer.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "sgtl5000.h"
+
+#define SGTL5000_DAP_REG_OFFSET 0x0100
+#define SGTL5000_MAX_REG_OFFSET 0x013A
+
+/* default value of sgtl5000 registers except DAP */
+static const u16 sgtl5000_regs[SGTL5000_MAX_REG_OFFSET >> 1] = {
+ 0xa011, /* 0x0000, CHIP_ID. 11 stand for revison 17 */
+ 0x0000, /* 0x0002, CHIP_DIG_POWER. */
+ 0x0008, /* 0x0004, CHIP_CKL_CTRL */
+ 0x0010, /* 0x0006, CHIP_I2S_CTRL */
+ 0x0000, /* 0x0008, reserved */
+ 0x0008, /* 0x000A, CHIP_SSS_CTRL */
+ 0x0000, /* 0x000C, reserved */
+ 0x020c, /* 0x000E, CHIP_ADCDAC_CTRL */
+ 0x3c3c, /* 0x0010, CHIP_DAC_VOL */
+ 0x0000, /* 0x0012, reserved */
+ 0x015f, /* 0x0014, CHIP_PAD_STRENGTH */
+ 0x0000, /* 0x0016, reserved */
+ 0x0000, /* 0x0018, reserved */
+ 0x0000, /* 0x001A, reserved */
+ 0x0000, /* 0x001E, reserved */
+ 0x0000, /* 0x0020, CHIP_ANA_ADC_CTRL */
+ 0x1818, /* 0x0022, CHIP_ANA_HP_CTRL */
+ 0x0111, /* 0x0024, CHIP_ANN_CTRL */
+ 0x0000, /* 0x0026, CHIP_LINREG_CTRL */
+ 0x0000, /* 0x0028, CHIP_REF_CTRL */
+ 0x0000, /* 0x002A, CHIP_MIC_CTRL */
+ 0x0000, /* 0x002C, CHIP_LINE_OUT_CTRL */
+ 0x0404, /* 0x002E, CHIP_LINE_OUT_VOL */
+ 0x7060, /* 0x0030, CHIP_ANA_POWER */
+ 0x5000, /* 0x0032, CHIP_PLL_CTRL */
+ 0x0000, /* 0x0034, CHIP_CLK_TOP_CTRL */
+ 0x0000, /* 0x0036, CHIP_ANA_STATUS */
+ 0x0000, /* 0x0038, reserved */
+ 0x0000, /* 0x003A, CHIP_ANA_TEST2 */
+ 0x0000, /* 0x003C, CHIP_SHORT_CTRL */
+ 0x0000, /* reserved */
+};
+
+/* default value of dap registers */
+static const u16 sgtl5000_dap_regs[] = {
+ 0x0000, /* 0x0100, DAP_CONTROL */
+ 0x0000, /* 0x0102, DAP_PEQ */
+ 0x0040, /* 0x0104, DAP_BASS_ENHANCE */
+ 0x051f, /* 0x0106, DAP_BASS_ENHANCE_CTRL */
+ 0x0000, /* 0x0108, DAP_AUDIO_EQ */
+ 0x0040, /* 0x010A, DAP_SGTL_SURROUND */
+ 0x0000, /* 0x010C, DAP_FILTER_COEF_ACCESS */
+ 0x0000, /* 0x010E, DAP_COEF_WR_B0_MSB */
+ 0x0000, /* 0x0110, DAP_COEF_WR_B0_LSB */
+ 0x0000, /* 0x0112, reserved */
+ 0x0000, /* 0x0114, reserved */
+ 0x002f, /* 0x0116, DAP_AUDIO_EQ_BASS_BAND0 */
+ 0x002f, /* 0x0118, DAP_AUDIO_EQ_BAND0 */
+ 0x002f, /* 0x011A, DAP_AUDIO_EQ_BAND2 */
+ 0x002f, /* 0x011C, DAP_AUDIO_EQ_BAND3 */
+ 0x002f, /* 0x011E, DAP_AUDIO_EQ_TREBLE_BAND4 */
+ 0x8000, /* 0x0120, DAP_MAIN_CHAN */
+ 0x0000, /* 0x0122, DAP_MIX_CHAN */
+ 0x0510, /* 0x0124, DAP_AVC_CTRL */
+ 0x1473, /* 0x0126, DAP_AVC_THRESHOLD */
+ 0x0028, /* 0x0128, DAP_AVC_ATTACK */
+ 0x0050, /* 0x012A, DAP_AVC_DECAY */
+ 0x0000, /* 0x012C, DAP_COEF_WR_B1_MSB */
+ 0x0000, /* 0x012E, DAP_COEF_WR_B1_LSB */
+ 0x0000, /* 0x0130, DAP_COEF_WR_B2_MSB */
+ 0x0000, /* 0x0132, DAP_COEF_WR_B2_LSB */
+ 0x0000, /* 0x0134, DAP_COEF_WR_A1_MSB */
+ 0x0000, /* 0x0136, DAP_COEF_WR_A1_LSB */
+ 0x0000, /* 0x0138, DAP_COEF_WR_A2_MSB */
+ 0x0000, /* 0x013A, DAP_COEF_WR_A2_LSB */
+};
+
+/* regulator supplies for sgtl5000, VDDD is an optional external supply */
+enum sgtl5000_regulator_supplies {
+ VDDA,
+ VDDIO,
+ VDDD,
+ SGTL5000_SUPPLY_NUM
+};
+
+/* vddd is optional supply */
+static const char *supply_names[SGTL5000_SUPPLY_NUM] = {
+ "VDDA",
+ "VDDIO",
+ "VDDD"
+};
+
+#define LDO_CONSUMER_NAME "VDDD_LDO"
+#define LDO_VOLTAGE 1200000
+
+static struct regulator_consumer_supply ldo_consumer[] = {
+ REGULATOR_SUPPLY(LDO_CONSUMER_NAME, NULL),
+};
+
+static struct regulator_init_data ldo_init_data = {
+ .constraints = {
+ .min_uV = 850000,
+ .max_uV = 1600000,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &ldo_consumer[0],
+};
+
+/*
+ * sgtl5000 internal ldo regulator,
+ * enabled when VDDD not provided
+ */
+struct ldo_regulator {
+ struct regulator_desc desc;
+ struct regulator_dev *dev;
+ int voltage;
+ void *codec_data;
+ bool enabled;
+};
+
+/* sgtl5000 private structure in codec */
+struct sgtl5000_priv {
+ int sysclk; /* sysclk rate */
+ int master; /* i2s master or not */
+ int fmt; /* i2s data format */
+ struct regulator_bulk_data supplies[SGTL5000_SUPPLY_NUM];
+ struct ldo_regulator *ldo;
+};
+
+/*
+ * mic_bias power on/off share the same register bits with
+ * output impedance of mic bias, when power on mic bias, we
+ * need reclaim it to impedance value.
+ * 0x0 = Powered off
+ * 0x1 = 2Kohm
+ * 0x2 = 4Kohm
+ * 0x3 = 8Kohm
+ */
+static int mic_bias_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* change mic bias resistor to 4Kohm */
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_BIAS_R_4k, SGTL5000_BIAS_R_4k);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ /*
+ * SGTL5000_BIAS_R_8k as mask to clean the two bits
+ * of mic bias and output impedance
+ */
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_MIC_CTRL,
+ SGTL5000_BIAS_R_8k, 0);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * using codec assist to small pop, hp_powerup or lineout_powerup
+ * should stay setting until vag_powerup is fully ramped down,
+ * vag fully ramped down require 400ms.
+ */
+static int small_pop_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(w->codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VAG_POWERUP, 0);
+ msleep(400);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* input sources for ADC */
+static const char *adc_mux_text[] = {
+ "MIC_IN", "LINE_IN"
+};
+
+static const struct soc_enum adc_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 2, 2, adc_mux_text);
+
+static const struct snd_kcontrol_new adc_mux =
+SOC_DAPM_ENUM("Capture Mux", adc_enum);
+
+/* input sources for DAC */
+static const char *dac_mux_text[] = {
+ "DAC", "LINE_IN"
+};
+
+static const struct soc_enum dac_enum =
+SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text);
+
+static const struct snd_kcontrol_new dac_mux =
+SOC_DAPM_ENUM("Headphone Mux", dac_enum);
+
+static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("LINE_IN"),
+ SND_SOC_DAPM_INPUT("MIC_IN"),
+
+ SND_SOC_DAPM_OUTPUT("HP_OUT"),
+ SND_SOC_DAPM_OUTPUT("LINE_OUT"),
+
+ SND_SOC_DAPM_MICBIAS_E("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0,
+ mic_bias_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_PGA_E("HP", SGTL5000_CHIP_ANA_POWER, 4, 0, NULL, 0,
+ small_pop_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("LO", SGTL5000_CHIP_ANA_POWER, 0, 0, NULL, 0,
+ small_pop_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux),
+ SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &dac_mux),
+
+ /* aif for i2s input */
+ SND_SOC_DAPM_AIF_IN("AIFIN", "Playback",
+ 0, SGTL5000_CHIP_DIG_POWER,
+ 0, 0),
+
+ /* aif for i2s output */
+ SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture",
+ 0, SGTL5000_CHIP_DIG_POWER,
+ 1, 0),
+
+ SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0),
+
+ SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0),
+};
+
+/* routes for sgtl5000 */
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Capture Mux", "LINE_IN", "LINE_IN"}, /* line_in --> adc_mux */
+ {"Capture Mux", "MIC_IN", "MIC_IN"}, /* mic_in --> adc_mux */
+
+ {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */
+ {"AIFOUT", NULL, "ADC"}, /* adc --> i2s_out */
+
+ {"DAC", NULL, "AIFIN"}, /* i2s-->dac,skip audio mux */
+ {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */
+ {"LO", NULL, "DAC"}, /* dac --> line_out */
+
+ {"Headphone Mux", "LINE_IN", "LINE_IN"},/* line_in --> hp_mux */
+ {"HP", NULL, "Headphone Mux"}, /* hp_mux --> hp */
+
+ {"LINE_OUT", NULL, "LO"},
+ {"HP_OUT", NULL, "HP"},
+};
+
+/* custom function to fetch info of PCM playback volume */
+static int dac_info_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xfc - 0x3c;
+ return 0;
+}
+
+/*
+ * custom function to get of PCM playback volume
+ *
+ * dac volume register
+ * 15-------------8-7--------------0
+ * | R channel vol | L channel vol |
+ * -------------------------------
+ *
+ * PCM volume with 0.5017 dB steps from 0 to -90 dB
+ *
+ * register values map to dB
+ * 0x3B and less = Reserved
+ * 0x3C = 0 dB
+ * 0x3D = -0.5 dB
+ * 0xF0 = -90 dB
+ * 0xFC and greater = Muted
+ *
+ * register value map to userspace value
+ *
+ * register value 0x3c(0dB) 0xf0(-90dB)0xfc
+ * ------------------------------
+ * userspace value 0xc0 0
+ */
+static int dac_get_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg;
+ int l;
+ int r;
+
+ reg = snd_soc_read(codec, SGTL5000_CHIP_DAC_VOL);
+
+ /* get left channel volume */
+ l = (reg & SGTL5000_DAC_VOL_LEFT_MASK) >> SGTL5000_DAC_VOL_LEFT_SHIFT;
+
+ /* get right channel volume */
+ r = (reg & SGTL5000_DAC_VOL_RIGHT_MASK) >> SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ /* make sure value fall in (0x3c,0xfc) */
+ l = clamp(l, 0x3c, 0xfc);
+ r = clamp(r, 0x3c, 0xfc);
+
+ /* invert it and map to userspace value */
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ ucontrol->value.integer.value[0] = l;
+ ucontrol->value.integer.value[1] = r;
+
+ return 0;
+}
+
+/*
+ * custom function to put of PCM playback volume
+ *
+ * dac volume register
+ * 15-------------8-7--------------0
+ * | R channel vol | L channel vol |
+ * -------------------------------
+ *
+ * PCM volume with 0.5017 dB steps from 0 to -90 dB
+ *
+ * register values map to dB
+ * 0x3B and less = Reserved
+ * 0x3C = 0 dB
+ * 0x3D = -0.5 dB
+ * 0xF0 = -90 dB
+ * 0xFC and greater = Muted
+ *
+ * userspace value map to register value
+ *
+ * userspace value 0xc0 0
+ * ------------------------------
+ * register value 0x3c(0dB) 0xf0(-90dB)0xfc
+ */
+static int dac_put_volsw(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg;
+ int l;
+ int r;
+
+ l = ucontrol->value.integer.value[0];
+ r = ucontrol->value.integer.value[1];
+
+ /* make sure userspace volume fall in (0, 0xfc-0x3c) */
+ l = clamp(l, 0, 0xfc - 0x3c);
+ r = clamp(r, 0, 0xfc - 0x3c);
+
+ /* invert it, get the value can be set to register */
+ l = 0xfc - l;
+ r = 0xfc - r;
+
+ /* shift to get the register value */
+ reg = l << SGTL5000_DAC_VOL_LEFT_SHIFT |
+ r << SGTL5000_DAC_VOL_RIGHT_SHIFT;
+
+ snd_soc_write(codec, SGTL5000_CHIP_DAC_VOL, reg);
+
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0);
+
+/* tlv for mic gain, 0db 20db 30db 40db */
+static const unsigned int mic_gain_tlv[] = {
+ TLV_DB_RANGE_HEAD(4),
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
+ 1, 3, TLV_DB_SCALE_ITEM(2000, 1000, 0),
+};
+
+/* tlv for hp volume, -51.5db to 12.0db, step .5db */
+static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0);
+
+static const struct snd_kcontrol_new sgtl5000_snd_controls[] = {
+ /* SOC_DOUBLE_S8_TLV with invert */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = dac_info_volsw,
+ .get = dac_get_volsw,
+ .put = dac_put_volsw,
+ },
+
+ SOC_DOUBLE("Capture Volume", SGTL5000_CHIP_ANA_ADC_CTRL, 0, 4, 0xf, 0),
+ SOC_SINGLE_TLV("Capture Attenuate Switch (-6dB)",
+ SGTL5000_CHIP_ANA_ADC_CTRL,
+ 8, 2, 0, capture_6db_attenuate),
+ SOC_SINGLE("Capture ZC Switch", SGTL5000_CHIP_ANA_CTRL, 1, 1, 0),
+
+ SOC_DOUBLE_TLV("Headphone Playback Volume",
+ SGTL5000_CHIP_ANA_HP_CTRL,
+ 0, 8,
+ 0x7f, 1,
+ headphone_volume),
+ SOC_SINGLE("Headphone Playback ZC Switch", SGTL5000_CHIP_ANA_CTRL,
+ 5, 1, 0),
+
+ SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL,
+ 0, 4, 0, mic_gain_tlv),
+};
+
+/* mute the codec used by alsa core */
+static int sgtl5000_digital_mute(struct snd_soc_dai *codec_dai, int mute)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 adcdac_ctrl = SGTL5000_DAC_MUTE_LEFT | SGTL5000_DAC_MUTE_RIGHT;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ADCDAC_CTRL,
+ adcdac_ctrl, mute ? adcdac_ctrl : 0);
+
+ return 0;
+}
+
+/* set codec format */
+static int sgtl5000_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ u16 i2sctl = 0;
+
+ sgtl5000->master = 0;
+ /*
+ * i2s clock and frame master setting.
+ * ONLY support:
+ * - clock and frame slave,
+ * - clock and frame master
+ */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ i2sctl |= SGTL5000_I2S_MASTER;
+ sgtl5000->master = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* setting i2s data format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2sctl |= SGTL5000_I2S_MODE_PCM;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2sctl |= SGTL5000_I2S_MODE_RJ;
+ i2sctl |= SGTL5000_I2S_LRPOL;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2sctl |= SGTL5000_I2S_MODE_I2S_LJ;
+ i2sctl |= SGTL5000_I2S_LRALIGN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ sgtl5000->fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+ /* Clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ i2sctl |= SGTL5000_I2S_SCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, SGTL5000_CHIP_I2S_CTRL, i2sctl);
+
+ return 0;
+}
+
+/* set codec sysclk */
+static int sgtl5000_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ switch (clk_id) {
+ case SGTL5000_SYSCLK:
+ sgtl5000->sysclk = freq;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * set clock according to i2s frame clock,
+ * sgtl5000 provide 2 clock sources.
+ * 1. sys_mclk. sample freq can only configure to
+ * 1/256, 1/384, 1/512 of sys_mclk.
+ * 2. pll. can derive any audio clocks.
+ *
+ * clock setting rules:
+ * 1. in slave mode, only sys_mclk can use.
+ * 2. as constraint by sys_mclk, sample freq should
+ * set to 32k, 44.1k and above.
+ * 3. using sys_mclk prefer to pll to save power.
+ */
+static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int clk_ctl = 0;
+ int sys_fs; /* sample freq */
+
+ /*
+ * sample freq should be divided by frame clock,
+ * if frame clock lower than 44.1khz, sample feq should set to
+ * 32khz or 44.1khz.
+ */
+ switch (frame_rate) {
+ case 8000:
+ case 16000:
+ sys_fs = 32000;
+ break;
+ case 11025:
+ case 22050:
+ sys_fs = 44100;
+ break;
+ default:
+ sys_fs = frame_rate;
+ break;
+ }
+
+ /* set divided factor of frame clock */
+ switch (sys_fs / frame_rate) {
+ case 4:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 2:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ case 1:
+ clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set the sys_fs according to frame rate */
+ switch (sys_fs) {
+ case 32000:
+ clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 44100:
+ clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 48000:
+ clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ case 96000:
+ clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT;
+ break;
+ default:
+ dev_err(codec->dev, "frame rate %d not supported\n",
+ frame_rate);
+ return -EINVAL;
+ }
+
+ /*
+ * calculate the divider of mclk/sample_freq,
+ * factor of freq =96k can only be 256, since mclk in range (12m,27m)
+ */
+ switch (sgtl5000->sysclk / sys_fs) {
+ case 256:
+ clk_ctl |= SGTL5000_MCLK_FREQ_256FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ case 384:
+ clk_ctl |= SGTL5000_MCLK_FREQ_384FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ case 512:
+ clk_ctl |= SGTL5000_MCLK_FREQ_512FS <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ break;
+ default:
+ /* if mclk not satisify the divider, use pll */
+ if (sgtl5000->master) {
+ clk_ctl |= SGTL5000_MCLK_FREQ_PLL <<
+ SGTL5000_MCLK_FREQ_SHIFT;
+ } else {
+ dev_err(codec->dev,
+ "PLL not supported in slave mode\n");
+ return -EINVAL;
+ }
+ }
+
+ /* if using pll, please check manual 6.4.2 for detail */
+ if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
+ u64 out, t;
+ int div2;
+ int pll_ctl;
+ unsigned int in, int_div, frac_div;
+
+ if (sgtl5000->sysclk > 17000000) {
+ div2 = 1;
+ in = sgtl5000->sysclk / 2;
+ } else {
+ div2 = 0;
+ in = sgtl5000->sysclk;
+ }
+ if (sys_fs == 44100)
+ out = 180633600;
+ else
+ out = 196608000;
+ t = do_div(out, in);
+ int_div = out;
+ t *= 2048;
+ do_div(t, in);
+ frac_div = t;
+ pll_ctl = int_div << SGTL5000_PLL_INT_DIV_SHIFT |
+ frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT;
+
+ snd_soc_write(codec, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
+ if (div2)
+ snd_soc_update_bits(codec,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INPUT_FREQ_DIV2,
+ SGTL5000_INPUT_FREQ_DIV2);
+ else
+ snd_soc_update_bits(codec,
+ SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INPUT_FREQ_DIV2,
+ 0);
+
+ /* power up pll */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
+ } else {
+ /* power down pll */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP,
+ 0);
+ }
+
+ /* if using pll, clk_ctrl must be set after pll power up */
+ snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ * input: params_rate, params_fmt
+ */
+static int sgtl5000_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ int channels = params_channels(params);
+ int i2s_ctl = 0;
+ int stereo;
+ int ret;
+
+ /* sysclk should already set */
+ if (!sgtl5000->sysclk) {
+ dev_err(codec->dev, "%s: set sysclk first!\n", __func__);
+ return -EFAULT;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stereo = SGTL5000_DAC_STEREO;
+ else
+ stereo = SGTL5000_ADC_STEREO;
+
+ /* set mono to save power */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, stereo,
+ channels == 1 ? 0 : stereo);
+
+ /* set codec clock base on lrclk */
+ ret = sgtl5000_set_clock(codec, params_rate(params));
+ if (ret)
+ return ret;
+
+ /* set i2s data format */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_16 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_32FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_20 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s_ctl |= SGTL5000_I2S_DLEN_24 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ if (sgtl5000->fmt == SND_SOC_DAIFMT_RIGHT_J)
+ return -EINVAL;
+ i2s_ctl |= SGTL5000_I2S_DLEN_32 << SGTL5000_I2S_DLEN_SHIFT;
+ i2s_ctl |= SGTL5000_I2S_SCLKFREQ_64FS <<
+ SGTL5000_I2S_SCLKFREQ_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_I2S_CTRL, i2s_ctl, i2s_ctl);
+
+ return 0;
+}
+
+static int ldo_regulator_is_enabled(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+
+ return ldo->enabled;
+}
+
+static int ldo_regulator_enable(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+ struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
+ int reg;
+
+ if (ldo_regulator_is_enabled(dev))
+ return 0;
+
+ /* set regulator value firstly */
+ reg = (1600 - ldo->voltage / 1000) / 50;
+ reg = clamp(reg, 0x0, 0xf);
+
+ /* amend the voltage value, unit: uV */
+ ldo->voltage = (1600 - reg * 50) * 1000;
+
+ /* set voltage to register */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
+ (0x1 << 4) - 1, reg);
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINEREG_D_POWERUP,
+ SGTL5000_LINEREG_D_POWERUP);
+
+ /* when internal ldo enabled, simple digital power can be disabled */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINREG_SIMPLE_POWERUP,
+ 0);
+
+ ldo->enabled = 1;
+ return 0;
+}
+
+static int ldo_regulator_disable(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+ struct snd_soc_codec *codec = (struct snd_soc_codec *)ldo->codec_data;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINEREG_D_POWERUP,
+ 0);
+
+ /* clear voltage info */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
+ (0x1 << 4) - 1, 0);
+
+ ldo->enabled = 0;
+
+ return 0;
+}
+
+static int ldo_regulator_get_voltage(struct regulator_dev *dev)
+{
+ struct ldo_regulator *ldo = rdev_get_drvdata(dev);
+
+ return ldo->voltage;
+}
+
+static struct regulator_ops ldo_regulator_ops = {
+ .is_enabled = ldo_regulator_is_enabled,
+ .enable = ldo_regulator_enable,
+ .disable = ldo_regulator_disable,
+ .get_voltage = ldo_regulator_get_voltage,
+};
+
+static int ldo_regulator_register(struct snd_soc_codec *codec,
+ struct regulator_init_data *init_data,
+ int voltage)
+{
+ struct ldo_regulator *ldo;
+
+ ldo = kzalloc(sizeof(struct ldo_regulator), GFP_KERNEL);
+
+ if (!ldo) {
+ dev_err(codec->dev, "failed to allocate ldo_regulator\n");
+ return -ENOMEM;
+ }
+
+ ldo->desc.name = kstrdup(dev_name(codec->dev), GFP_KERNEL);
+ if (!ldo->desc.name) {
+ kfree(ldo);
+ dev_err(codec->dev, "failed to allocate decs name memory\n");
+ return -ENOMEM;
+ }
+
+ ldo->desc.type = REGULATOR_VOLTAGE;
+ ldo->desc.owner = THIS_MODULE;
+ ldo->desc.ops = &ldo_regulator_ops;
+ ldo->desc.n_voltages = 1;
+
+ ldo->codec_data = codec;
+ ldo->voltage = voltage;
+
+ ldo->dev = regulator_register(&ldo->desc, codec->dev,
+ init_data, ldo);
+ if (IS_ERR(ldo->dev)) {
+ int ret = PTR_ERR(ldo->dev);
+
+ dev_err(codec->dev, "failed to register regulator\n");
+ kfree(ldo->desc.name);
+ kfree(ldo);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ldo_regulator_remove(struct snd_soc_codec *codec)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+ struct ldo_regulator *ldo = sgtl5000->ldo;
+
+ if (!ldo)
+ return 0;
+
+ regulator_unregister(ldo->dev);
+ kfree(ldo->desc.name);
+ kfree(ldo);
+
+ return 0;
+}
+
+/*
+ * set dac bias
+ * common state changes:
+ * startup:
+ * off --> standby --> prepare --> on
+ * standby --> prepare --> on
+ *
+ * stop:
+ * on --> prepare --> standby
+ */
+static int sgtl5000_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ int ret;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ return ret;
+ udelay(10);
+ }
+
+ break;
+ case SND_SOC_BIAS_OFF:
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define SGTL5000_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE |\
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops sgtl5000_ops = {
+ .hw_params = sgtl5000_pcm_hw_params,
+ .digital_mute = sgtl5000_digital_mute,
+ .set_fmt = sgtl5000_set_dai_fmt,
+ .set_sysclk = sgtl5000_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver sgtl5000_dai = {
+ .name = "sgtl5000",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ /*
+ * only support 8~48K + 96K,
+ * TODO modify hw_param to support more
+ */
+ .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
+ .formats = SGTL5000_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
+ .formats = SGTL5000_FORMATS,
+ },
+ .ops = &sgtl5000_ops,
+ .symmetric_rates = 1,
+};
+
+static int sgtl5000_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ switch (reg) {
+ case SGTL5000_CHIP_ID:
+ case SGTL5000_CHIP_ADCDAC_CTRL:
+ case SGTL5000_CHIP_ANA_STATUS:
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_SUSPEND
+static int sgtl5000_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+/*
+ * restore all sgtl5000 registers,
+ * since a big hole between dap and regular registers,
+ * we will restore them respectively.
+ */
+static int sgtl5000_restore_regs(struct snd_soc_codec *codec)
+{
+ u16 *cache = codec->reg_cache;
+ int i;
+ int regular_regs = SGTL5000_CHIP_SHORT_CTRL >> 1;
+
+ /* restore regular registers */
+ for (i = 0; i < regular_regs; i++) {
+ int reg = i << 1;
+
+ /* this regs depends on the others */
+ if (reg == SGTL5000_CHIP_ANA_POWER ||
+ reg == SGTL5000_CHIP_CLK_CTRL ||
+ reg == SGTL5000_CHIP_LINREG_CTRL ||
+ reg == SGTL5000_CHIP_LINE_OUT_CTRL ||
+ reg == SGTL5000_CHIP_CLK_CTRL)
+ continue;
+
+ snd_soc_write(codec, reg, cache[i]);
+ }
+
+ /* restore dap registers */
+ for (i = SGTL5000_DAP_REG_OFFSET >> 1;
+ i < SGTL5000_MAX_REG_OFFSET >> 1; i++) {
+ int reg = i << 1;
+
+ snd_soc_write(codec, reg, cache[i]);
+ }
+
+ /*
+ * restore power and other regs according
+ * to set_power() and set_clock()
+ */
+ snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL,
+ cache[SGTL5000_CHIP_LINREG_CTRL >> 1]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER,
+ cache[SGTL5000_CHIP_ANA_POWER >> 1]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_CLK_CTRL,
+ cache[SGTL5000_CHIP_CLK_CTRL >> 1]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_REF_CTRL,
+ cache[SGTL5000_CHIP_REF_CTRL >> 1]);
+
+ snd_soc_write(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
+ cache[SGTL5000_CHIP_LINE_OUT_CTRL >> 1]);
+ return 0;
+}
+
+static int sgtl5000_resume(struct snd_soc_codec *codec)
+{
+ /* Bring the codec back up to standby to enable regulators */
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* Restore registers by cached in memory */
+ sgtl5000_restore_regs(codec);
+ return 0;
+}
+#else
+#define sgtl5000_suspend NULL
+#define sgtl5000_resume NULL
+#endif /* CONFIG_SUSPEND */
+
+/*
+ * sgtl5000 has 3 internal power supplies:
+ * 1. VAG, normally set to vdda/2
+ * 2. chargepump, set to different value
+ * according to voltage of vdda and vddio
+ * 3. line out VAG, normally set to vddio/2
+ *
+ * and should be set according to:
+ * 1. vddd provided by external or not
+ * 2. vdda and vddio voltage value. > 3.1v or not
+ * 3. chip revision >=0x11 or not. If >=0x11, not use external vddd.
+ */
+static int sgtl5000_set_power_regs(struct snd_soc_codec *codec)
+{
+ int vddd;
+ int vdda;
+ int vddio;
+ u16 ana_pwr;
+ u16 lreg_ctrl;
+ int vag;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ vdda = regulator_get_voltage(sgtl5000->supplies[VDDA].consumer);
+ vddio = regulator_get_voltage(sgtl5000->supplies[VDDIO].consumer);
+ vddd = regulator_get_voltage(sgtl5000->supplies[VDDD].consumer);
+
+ vdda = vdda / 1000;
+ vddio = vddio / 1000;
+ vddd = vddd / 1000;
+
+ if (vdda <= 0 || vddio <= 0 || vddd < 0) {
+ dev_err(codec->dev, "regulator voltage not set correctly\n");
+
+ return -EINVAL;
+ }
+
+ /* according to datasheet, maximum voltage of supplies */
+ if (vdda > 3600 || vddio > 3600 || vddd > 1980) {
+ dev_err(codec->dev,
+ "exceed max voltage vdda %dmv vddio %dma vddd %dma\n",
+ vdda, vddio, vddd);
+
+ return -EINVAL;
+ }
+
+ /* reset value */
+ ana_pwr = snd_soc_read(codec, SGTL5000_CHIP_ANA_POWER);
+ ana_pwr |= SGTL5000_DAC_STEREO |
+ SGTL5000_ADC_STEREO |
+ SGTL5000_REFTOP_POWERUP;
+ lreg_ctrl = snd_soc_read(codec, SGTL5000_CHIP_LINREG_CTRL);
+
+ if (vddio < 3100 && vdda < 3100) {
+ /* enable internal oscillator used for charge pump */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_CLK_TOP_CTRL,
+ SGTL5000_INT_OSC_EN,
+ SGTL5000_INT_OSC_EN);
+ /* Enable VDDC charge pump */
+ ana_pwr |= SGTL5000_VDDC_CHRGPMP_POWERUP;
+ } else if (vddio >= 3100 && vdda >= 3100) {
+ /*
+ * if vddio and vddd > 3.1v,
+ * charge pump should be clean before set ana_pwr
+ */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_VDDC_CHRGPMP_POWERUP, 0);
+
+ /* VDDC use VDDIO rail */
+ lreg_ctrl |= SGTL5000_VDDC_ASSN_OVRD;
+ lreg_ctrl |= SGTL5000_VDDC_MAN_ASSN_VDDIO <<
+ SGTL5000_VDDC_MAN_ASSN_SHIFT;
+ }
+
+ snd_soc_write(codec, SGTL5000_CHIP_LINREG_CTRL, lreg_ctrl);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_POWER, ana_pwr);
+
+ /* set voltage to register */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINREG_CTRL,
+ (0x1 << 4) - 1, 0x8);
+
+ /*
+ * if vddd linear reg has been enabled,
+ * simple digital supply should be clear to get
+ * proper VDDD voltage.
+ */
+ if (ana_pwr & SGTL5000_LINEREG_D_POWERUP)
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINREG_SIMPLE_POWERUP,
+ 0);
+ else
+ snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER,
+ SGTL5000_LINREG_SIMPLE_POWERUP |
+ SGTL5000_STARTUP_POWERUP,
+ 0);
+
+ /*
+ * set ADC/DAC VAG to vdda / 2,
+ * should stay in range (0.8v, 1.575v)
+ */
+ vag = vdda / 2;
+ if (vag <= SGTL5000_ANA_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_ANA_GND_BASE + SGTL5000_ANA_GND_STP *
+ (SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT))
+ vag = SGTL5000_ANA_GND_MASK >> SGTL5000_ANA_GND_SHIFT;
+ else
+ vag = (vag - SGTL5000_ANA_GND_BASE) / SGTL5000_ANA_GND_STP;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
+ vag << SGTL5000_ANA_GND_SHIFT,
+ vag << SGTL5000_ANA_GND_SHIFT);
+
+ /* set line out VAG to vddio / 2, in range (0.8v, 1.675v) */
+ vag = vddio / 2;
+ if (vag <= SGTL5000_LINE_OUT_GND_BASE)
+ vag = 0;
+ else if (vag >= SGTL5000_LINE_OUT_GND_BASE +
+ SGTL5000_LINE_OUT_GND_STP * SGTL5000_LINE_OUT_GND_MAX)
+ vag = SGTL5000_LINE_OUT_GND_MAX;
+ else
+ vag = (vag - SGTL5000_LINE_OUT_GND_BASE) /
+ SGTL5000_LINE_OUT_GND_STP;
+
+ snd_soc_update_bits(codec, SGTL5000_CHIP_LINE_OUT_CTRL,
+ vag << SGTL5000_LINE_OUT_GND_SHIFT |
+ SGTL5000_LINE_OUT_CURRENT_360u <<
+ SGTL5000_LINE_OUT_CURRENT_SHIFT,
+ vag << SGTL5000_LINE_OUT_GND_SHIFT |
+ SGTL5000_LINE_OUT_CURRENT_360u <<
+ SGTL5000_LINE_OUT_CURRENT_SHIFT);
+
+ return 0;
+}
+
+static int sgtl5000_enable_regulators(struct snd_soc_codec *codec)
+{
+ u16 reg;
+ int ret;
+ int rev;
+ int i;
+ int external_vddd = 0;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ for (i = 0; i < ARRAY_SIZE(sgtl5000->supplies); i++)
+ sgtl5000->supplies[i].supply = supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (!ret)
+ external_vddd = 1;
+ else {
+ /* set internal ldo to 1.2v */
+ int voltage = LDO_VOLTAGE;
+
+ ret = ldo_regulator_register(codec, &ldo_init_data, voltage);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to register vddd internal supplies: %d\n",
+ ret);
+ return ret;
+ }
+
+ sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
+
+ ret = regulator_bulk_get(codec->dev,
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+
+ if (ret) {
+ ldo_regulator_remove(codec);
+ dev_err(codec->dev,
+ "Failed to request supplies: %d\n", ret);
+
+ return ret;
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ goto err_regulator_free;
+
+ /* wait for all power rails bring up */
+ udelay(10);
+
+ /* read chip information */
+ reg = snd_soc_read(codec, SGTL5000_CHIP_ID);
+ if (((reg & SGTL5000_PARTID_MASK) >> SGTL5000_PARTID_SHIFT) !=
+ SGTL5000_PARTID_PART_ID) {
+ dev_err(codec->dev,
+ "Device with ID register %x is not a sgtl5000\n", reg);
+ ret = -ENODEV;
+ goto err_regulator_disable;
+ }
+
+ rev = (reg & SGTL5000_REVID_MASK) >> SGTL5000_REVID_SHIFT;
+ dev_info(codec->dev, "sgtl5000 revision %d\n", rev);
+
+ /*
+ * workaround for revision 0x11 and later,
+ * roll back to use internal LDO
+ */
+ if (external_vddd && rev >= 0x11) {
+ int voltage = LDO_VOLTAGE;
+ /* disable all regulator first */
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ /* free VDDD regulator */
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+
+ ret = ldo_regulator_register(codec, &ldo_init_data, voltage);
+ if (ret)
+ return ret;
+
+ sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME;
+
+ ret = regulator_bulk_get(codec->dev,
+ ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret) {
+ ldo_regulator_remove(codec);
+ dev_err(codec->dev,
+ "Failed to request supplies: %d\n", ret);
+
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (ret)
+ goto err_regulator_free;
+
+ /* wait for all power rails bring up */
+ udelay(10);
+ }
+
+ return 0;
+
+err_regulator_disable:
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+err_regulator_free:
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ if (external_vddd)
+ ldo_regulator_remove(codec);
+ return ret;
+
+}
+
+static int sgtl5000_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ /* setup i2c data ops */
+ ret = snd_soc_codec_set_cache_io(codec, 16, 16, SND_SOC_I2C);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ ret = sgtl5000_enable_regulators(codec);
+ if (ret)
+ return ret;
+
+ /* power up sgtl5000 */
+ ret = sgtl5000_set_power_regs(codec);
+ if (ret)
+ goto err;
+
+ /* enable small pop, introduce 400ms delay in turning off */
+ snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
+ SGTL5000_SMALL_POP,
+ SGTL5000_SMALL_POP);
+
+ /* disable short cut detector */
+ snd_soc_write(codec, SGTL5000_CHIP_SHORT_CTRL, 0);
+
+ /*
+ * set i2s as default input of sound switch
+ * TODO: add sound switch to control and dapm widge.
+ */
+ snd_soc_write(codec, SGTL5000_CHIP_SSS_CTRL,
+ SGTL5000_DAC_SEL_I2S_IN << SGTL5000_DAC_SEL_SHIFT);
+ snd_soc_write(codec, SGTL5000_CHIP_DIG_POWER,
+ SGTL5000_ADC_EN | SGTL5000_DAC_EN);
+
+ /* enable dac volume ramp by default */
+ snd_soc_write(codec, SGTL5000_CHIP_ADCDAC_CTRL,
+ SGTL5000_DAC_VOL_RAMP_EN |
+ SGTL5000_DAC_MUTE_RIGHT |
+ SGTL5000_DAC_MUTE_LEFT);
+
+ snd_soc_write(codec, SGTL5000_CHIP_PAD_STRENGTH, 0x015f);
+
+ snd_soc_write(codec, SGTL5000_CHIP_ANA_CTRL,
+ SGTL5000_HP_ZCD_EN |
+ SGTL5000_ADC_ZCD_EN);
+
+ snd_soc_write(codec, SGTL5000_CHIP_MIC_CTRL, 0);
+
+ /*
+ * disable DAP
+ * TODO:
+ * Enable DAP in kcontrol and dapm.
+ */
+ snd_soc_write(codec, SGTL5000_DAP_CTRL, 0);
+
+ /* leading to standby state */
+ ret = sgtl5000_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ if (ret)
+ goto err;
+
+ snd_soc_add_controls(codec, sgtl5000_snd_controls,
+ ARRAY_SIZE(sgtl5000_snd_controls));
+
+ snd_soc_dapm_new_controls(&codec->dapm, sgtl5000_dapm_widgets,
+ ARRAY_SIZE(sgtl5000_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+
+ return 0;
+
+err:
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ ldo_regulator_remove(codec);
+
+ return ret;
+}
+
+static int sgtl5000_remove(struct snd_soc_codec *codec)
+{
+ struct sgtl5000_priv *sgtl5000 = snd_soc_codec_get_drvdata(codec);
+
+ sgtl5000_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ regulator_bulk_disable(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
+ sgtl5000->supplies);
+ ldo_regulator_remove(codec);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver sgtl5000_driver = {
+ .probe = sgtl5000_probe,
+ .remove = sgtl5000_remove,
+ .suspend = sgtl5000_suspend,
+ .resume = sgtl5000_resume,
+ .set_bias_level = sgtl5000_set_bias_level,
+ .reg_cache_size = ARRAY_SIZE(sgtl5000_regs),
+ .reg_word_size = sizeof(u16),
+ .reg_cache_step = 2,
+ .reg_cache_default = sgtl5000_regs,
+ .volatile_register = sgtl5000_volatile_register,
+};
+
+static __devinit int sgtl5000_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sgtl5000_priv *sgtl5000;
+ int ret;
+
+ sgtl5000 = kzalloc(sizeof(struct sgtl5000_priv), GFP_KERNEL);
+ if (!sgtl5000)
+ return -ENOMEM;
+
+ /*
+ * copy DAP default values to default value array.
+ * sgtl5000 register space has a big hole, merge it
+ * at init phase makes life easy.
+ * FIXME: should we drop 'const' of sgtl5000_regs?
+ */
+ memcpy((void *)(&sgtl5000_regs[0] + (SGTL5000_DAP_REG_OFFSET >> 1)),
+ sgtl5000_dap_regs,
+ SGTL5000_MAX_REG_OFFSET - SGTL5000_DAP_REG_OFFSET);
+
+ i2c_set_clientdata(client, sgtl5000);
+
+ ret = snd_soc_register_codec(&client->dev,
+ &sgtl5000_driver, &sgtl5000_dai, 1);
+ if (ret) {
+ dev_err(&client->dev, "Failed to register codec: %d\n", ret);
+ kfree(sgtl5000);
+ return ret;
+ }
+
+ return 0;
+}
+
+static __devexit int sgtl5000_i2c_remove(struct i2c_client *client)
+{
+ struct sgtl5000_priv *sgtl5000 = i2c_get_clientdata(client);
+
+ snd_soc_unregister_codec(&client->dev);
+
+ kfree(sgtl5000);
+ return 0;
+}
+
+static const struct i2c_device_id sgtl5000_id[] = {
+ {"sgtl5000", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, sgtl5000_id);
+
+static struct i2c_driver sgtl5000_i2c_driver = {
+ .driver = {
+ .name = "sgtl5000",
+ .owner = THIS_MODULE,
+ },
+ .probe = sgtl5000_i2c_probe,
+ .remove = __devexit_p(sgtl5000_i2c_remove),
+ .id_table = sgtl5000_id,
+};
+
+static int __init sgtl5000_modinit(void)
+{
+ return i2c_add_driver(&sgtl5000_i2c_driver);
+}
+module_init(sgtl5000_modinit);
+
+static void __exit sgtl5000_exit(void)
+{
+ i2c_del_driver(&sgtl5000_i2c_driver);
+}
+module_exit(sgtl5000_exit);
+
+MODULE_DESCRIPTION("Freescale SGTL5000 ALSA SoC Codec Driver");
+MODULE_AUTHOR("Zeng Zhaoming <zhaoming.zeng@freescale.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/sgtl5000.h b/sound/soc/codecs/sgtl5000.h
new file mode 100644
index 00000000000..eec3ab368f3
--- /dev/null
+++ b/sound/soc/codecs/sgtl5000.h
@@ -0,0 +1,400 @@
+/*
+ * sgtl5000.h - SGTL5000 audio codec interface
+ *
+ * Copyright 2010-2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _SGTL5000_H
+#define _SGTL5000_H
+
+/*
+ * Register values.
+ */
+#define SGTL5000_CHIP_ID 0x0000
+#define SGTL5000_CHIP_DIG_POWER 0x0002
+#define SGTL5000_CHIP_CLK_CTRL 0x0004
+#define SGTL5000_CHIP_I2S_CTRL 0x0006
+#define SGTL5000_CHIP_SSS_CTRL 0x000a
+#define SGTL5000_CHIP_ADCDAC_CTRL 0x000e
+#define SGTL5000_CHIP_DAC_VOL 0x0010
+#define SGTL5000_CHIP_PAD_STRENGTH 0x0014
+#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020
+#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022
+#define SGTL5000_CHIP_ANA_CTRL 0x0024
+#define SGTL5000_CHIP_LINREG_CTRL 0x0026
+#define SGTL5000_CHIP_REF_CTRL 0x0028
+#define SGTL5000_CHIP_MIC_CTRL 0x002a
+#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002c
+#define SGTL5000_CHIP_LINE_OUT_VOL 0x002e
+#define SGTL5000_CHIP_ANA_POWER 0x0030
+#define SGTL5000_CHIP_PLL_CTRL 0x0032
+#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034
+#define SGTL5000_CHIP_ANA_STATUS 0x0036
+#define SGTL5000_CHIP_SHORT_CTRL 0x003c
+#define SGTL5000_CHIP_ANA_TEST2 0x003a
+#define SGTL5000_DAP_CTRL 0x0100
+#define SGTL5000_DAP_PEQ 0x0102
+#define SGTL5000_DAP_BASS_ENHANCE 0x0104
+#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106
+#define SGTL5000_DAP_AUDIO_EQ 0x0108
+#define SGTL5000_DAP_SURROUND 0x010a
+#define SGTL5000_DAP_FLT_COEF_ACCESS 0x010c
+#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010e
+#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110
+#define SGTL5000_DAP_EQ_BASS_BAND0 0x0116
+#define SGTL5000_DAP_EQ_BASS_BAND1 0x0118
+#define SGTL5000_DAP_EQ_BASS_BAND2 0x011a
+#define SGTL5000_DAP_EQ_BASS_BAND3 0x011c
+#define SGTL5000_DAP_EQ_BASS_BAND4 0x011e
+#define SGTL5000_DAP_MAIN_CHAN 0x0120
+#define SGTL5000_DAP_MIX_CHAN 0x0122
+#define SGTL5000_DAP_AVC_CTRL 0x0124
+#define SGTL5000_DAP_AVC_THRESHOLD 0x0126
+#define SGTL5000_DAP_AVC_ATTACK 0x0128
+#define SGTL5000_DAP_AVC_DECAY 0x012a
+#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012c
+#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012e
+#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130
+#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132
+#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134
+#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136
+#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138
+#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013a
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * SGTL5000_CHIP_ID
+ */
+#define SGTL5000_PARTID_MASK 0xff00
+#define SGTL5000_PARTID_SHIFT 8
+#define SGTL5000_PARTID_WIDTH 8
+#define SGTL5000_PARTID_PART_ID 0xa0
+#define SGTL5000_REVID_MASK 0x00ff
+#define SGTL5000_REVID_SHIFT 0
+#define SGTL5000_REVID_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_DIG_POWER
+ */
+#define SGTL5000_ADC_EN 0x0040
+#define SGTL5000_DAC_EN 0x0020
+#define SGTL5000_DAP_POWERUP 0x0010
+#define SGTL5000_I2S_OUT_POWERUP 0x0002
+#define SGTL5000_I2S_IN_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_CLK_CTRL
+ */
+#define SGTL5000_RATE_MODE_MASK 0x0030
+#define SGTL5000_RATE_MODE_SHIFT 4
+#define SGTL5000_RATE_MODE_WIDTH 2
+#define SGTL5000_RATE_MODE_DIV_1 0
+#define SGTL5000_RATE_MODE_DIV_2 1
+#define SGTL5000_RATE_MODE_DIV_4 2
+#define SGTL5000_RATE_MODE_DIV_6 3
+#define SGTL5000_SYS_FS_MASK 0x000c
+#define SGTL5000_SYS_FS_SHIFT 2
+#define SGTL5000_SYS_FS_WIDTH 2
+#define SGTL5000_SYS_FS_32k 0x0
+#define SGTL5000_SYS_FS_44_1k 0x1
+#define SGTL5000_SYS_FS_48k 0x2
+#define SGTL5000_SYS_FS_96k 0x3
+#define SGTL5000_MCLK_FREQ_MASK 0x0003
+#define SGTL5000_MCLK_FREQ_SHIFT 0
+#define SGTL5000_MCLK_FREQ_WIDTH 2
+#define SGTL5000_MCLK_FREQ_256FS 0x0
+#define SGTL5000_MCLK_FREQ_384FS 0x1
+#define SGTL5000_MCLK_FREQ_512FS 0x2
+#define SGTL5000_MCLK_FREQ_PLL 0x3
+
+/*
+ * SGTL5000_CHIP_I2S_CTRL
+ */
+#define SGTL5000_I2S_SCLKFREQ_MASK 0x0100
+#define SGTL5000_I2S_SCLKFREQ_SHIFT 8
+#define SGTL5000_I2S_SCLKFREQ_WIDTH 1
+#define SGTL5000_I2S_SCLKFREQ_64FS 0x0
+#define SGTL5000_I2S_SCLKFREQ_32FS 0x1 /* Not for RJ mode */
+#define SGTL5000_I2S_MASTER 0x0080
+#define SGTL5000_I2S_SCLK_INV 0x0040
+#define SGTL5000_I2S_DLEN_MASK 0x0030
+#define SGTL5000_I2S_DLEN_SHIFT 4
+#define SGTL5000_I2S_DLEN_WIDTH 2
+#define SGTL5000_I2S_DLEN_32 0x0
+#define SGTL5000_I2S_DLEN_24 0x1
+#define SGTL5000_I2S_DLEN_20 0x2
+#define SGTL5000_I2S_DLEN_16 0x3
+#define SGTL5000_I2S_MODE_MASK 0x000c
+#define SGTL5000_I2S_MODE_SHIFT 2
+#define SGTL5000_I2S_MODE_WIDTH 2
+#define SGTL5000_I2S_MODE_I2S_LJ 0x0
+#define SGTL5000_I2S_MODE_RJ 0x1
+#define SGTL5000_I2S_MODE_PCM 0x2
+#define SGTL5000_I2S_LRALIGN 0x0002
+#define SGTL5000_I2S_LRPOL 0x0001 /* set for which mode */
+
+/*
+ * SGTL5000_CHIP_SSS_CTRL
+ */
+#define SGTL5000_DAP_MIX_LRSWAP 0x4000
+#define SGTL5000_DAP_LRSWAP 0x2000
+#define SGTL5000_DAC_LRSWAP 0x1000
+#define SGTL5000_I2S_OUT_LRSWAP 0x0400
+#define SGTL5000_DAP_MIX_SEL_MASK 0x0300
+#define SGTL5000_DAP_MIX_SEL_SHIFT 8
+#define SGTL5000_DAP_MIX_SEL_WIDTH 2
+#define SGTL5000_DAP_MIX_SEL_ADC 0x0
+#define SGTL5000_DAP_MIX_SEL_I2S_IN 0x1
+#define SGTL5000_DAP_SEL_MASK 0x00c0
+#define SGTL5000_DAP_SEL_SHIFT 6
+#define SGTL5000_DAP_SEL_WIDTH 2
+#define SGTL5000_DAP_SEL_ADC 0x0
+#define SGTL5000_DAP_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_MASK 0x0030
+#define SGTL5000_DAC_SEL_SHIFT 4
+#define SGTL5000_DAC_SEL_WIDTH 2
+#define SGTL5000_DAC_SEL_ADC 0x0
+#define SGTL5000_DAC_SEL_I2S_IN 0x1
+#define SGTL5000_DAC_SEL_DAP 0x3
+#define SGTL5000_I2S_OUT_SEL_MASK 0x0003
+#define SGTL5000_I2S_OUT_SEL_SHIFT 0
+#define SGTL5000_I2S_OUT_SEL_WIDTH 2
+#define SGTL5000_I2S_OUT_SEL_ADC 0x0
+#define SGTL5000_I2S_OUT_SEL_I2S_IN 0x1
+#define SGTL5000_I2S_OUT_SEL_DAP 0x3
+
+/*
+ * SGTL5000_CHIP_ADCDAC_CTRL
+ */
+#define SGTL5000_VOL_BUSY_DAC_RIGHT 0x2000
+#define SGTL5000_VOL_BUSY_DAC_LEFT 0x1000
+#define SGTL5000_DAC_VOL_RAMP_EN 0x0200
+#define SGTL5000_DAC_VOL_RAMP_EXPO 0x0100
+#define SGTL5000_DAC_MUTE_RIGHT 0x0008
+#define SGTL5000_DAC_MUTE_LEFT 0x0004
+#define SGTL5000_ADC_HPF_FREEZE 0x0002
+#define SGTL5000_ADC_HPF_BYPASS 0x0001
+
+/*
+ * SGTL5000_CHIP_DAC_VOL
+ */
+#define SGTL5000_DAC_VOL_RIGHT_MASK 0xff00
+#define SGTL5000_DAC_VOL_RIGHT_SHIFT 8
+#define SGTL5000_DAC_VOL_RIGHT_WIDTH 8
+#define SGTL5000_DAC_VOL_LEFT_MASK 0x00ff
+#define SGTL5000_DAC_VOL_LEFT_SHIFT 0
+#define SGTL5000_DAC_VOL_LEFT_WIDTH 8
+
+/*
+ * SGTL5000_CHIP_PAD_STRENGTH
+ */
+#define SGTL5000_PAD_I2S_LRCLK_MASK 0x0300
+#define SGTL5000_PAD_I2S_LRCLK_SHIFT 8
+#define SGTL5000_PAD_I2S_LRCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_SCLK_MASK 0x00c0
+#define SGTL5000_PAD_I2S_SCLK_SHIFT 6
+#define SGTL5000_PAD_I2S_SCLK_WIDTH 2
+#define SGTL5000_PAD_I2S_DOUT_MASK 0x0030
+#define SGTL5000_PAD_I2S_DOUT_SHIFT 4
+#define SGTL5000_PAD_I2S_DOUT_WIDTH 2
+#define SGTL5000_PAD_I2C_SDA_MASK 0x000c
+#define SGTL5000_PAD_I2C_SDA_SHIFT 2
+#define SGTL5000_PAD_I2C_SDA_WIDTH 2
+#define SGTL5000_PAD_I2C_SCL_MASK 0x0003
+#define SGTL5000_PAD_I2C_SCL_SHIFT 0
+#define SGTL5000_PAD_I2C_SCL_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_ANA_ADC_CTRL
+ */
+#define SGTL5000_ADC_VOL_M6DB 0x0100
+#define SGTL5000_ADC_VOL_RIGHT_MASK 0x00f0
+#define SGTL5000_ADC_VOL_RIGHT_SHIFT 4
+#define SGTL5000_ADC_VOL_RIGHT_WIDTH 4
+#define SGTL5000_ADC_VOL_LEFT_MASK 0x000f
+#define SGTL5000_ADC_VOL_LEFT_SHIFT 0
+#define SGTL5000_ADC_VOL_LEFT_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_ANA_HP_CTRL
+ */
+#define SGTL5000_HP_VOL_RIGHT_MASK 0x7f00
+#define SGTL5000_HP_VOL_RIGHT_SHIFT 8
+#define SGTL5000_HP_VOL_RIGHT_WIDTH 7
+#define SGTL5000_HP_VOL_LEFT_MASK 0x007f
+#define SGTL5000_HP_VOL_LEFT_SHIFT 0
+#define SGTL5000_HP_VOL_LEFT_WIDTH 7
+
+/*
+ * SGTL5000_CHIP_ANA_CTRL
+ */
+#define SGTL5000_LINE_OUT_MUTE 0x0100
+#define SGTL5000_HP_SEL_MASK 0x0040
+#define SGTL5000_HP_SEL_SHIFT 6
+#define SGTL5000_HP_SEL_WIDTH 1
+#define SGTL5000_HP_SEL_DAC 0x0
+#define SGTL5000_HP_SEL_LINE_IN 0x1
+#define SGTL5000_HP_ZCD_EN 0x0020
+#define SGTL5000_HP_MUTE 0x0010
+#define SGTL5000_ADC_SEL_MASK 0x0004
+#define SGTL5000_ADC_SEL_SHIFT 2
+#define SGTL5000_ADC_SEL_WIDTH 1
+#define SGTL5000_ADC_SEL_MIC 0x0
+#define SGTL5000_ADC_SEL_LINE_IN 0x1
+#define SGTL5000_ADC_ZCD_EN 0x0002
+#define SGTL5000_ADC_MUTE 0x0001
+
+/*
+ * SGTL5000_CHIP_LINREG_CTRL
+ */
+#define SGTL5000_VDDC_MAN_ASSN_MASK 0x0040
+#define SGTL5000_VDDC_MAN_ASSN_SHIFT 6
+#define SGTL5000_VDDC_MAN_ASSN_WIDTH 1
+#define SGTL5000_VDDC_MAN_ASSN_VDDA 0x0
+#define SGTL5000_VDDC_MAN_ASSN_VDDIO 0x1
+#define SGTL5000_VDDC_ASSN_OVRD 0x0020
+#define SGTL5000_LINREG_VDDD_MASK 0x000f
+#define SGTL5000_LINREG_VDDD_SHIFT 0
+#define SGTL5000_LINREG_VDDD_WIDTH 4
+
+/*
+ * SGTL5000_CHIP_REF_CTRL
+ */
+#define SGTL5000_ANA_GND_MASK 0x01f0
+#define SGTL5000_ANA_GND_SHIFT 4
+#define SGTL5000_ANA_GND_WIDTH 5
+#define SGTL5000_ANA_GND_BASE 800 /* mv */
+#define SGTL5000_ANA_GND_STP 25 /*mv */
+#define SGTL5000_BIAS_CTRL_MASK 0x000e
+#define SGTL5000_BIAS_CTRL_SHIFT 1
+#define SGTL5000_BIAS_CTRL_WIDTH 3
+#define SGTL5000_SMALL_POP 0x0001
+
+/*
+ * SGTL5000_CHIP_MIC_CTRL
+ */
+#define SGTL5000_BIAS_R_MASK 0x0200
+#define SGTL5000_BIAS_R_SHIFT 8
+#define SGTL5000_BIAS_R_WIDTH 2
+#define SGTL5000_BIAS_R_off 0x0
+#define SGTL5000_BIAS_R_2K 0x1
+#define SGTL5000_BIAS_R_4k 0x2
+#define SGTL5000_BIAS_R_8k 0x3
+#define SGTL5000_BIAS_VOLT_MASK 0x0070
+#define SGTL5000_BIAS_VOLT_SHIFT 4
+#define SGTL5000_BIAS_VOLT_WIDTH 3
+#define SGTL5000_MIC_GAIN_MASK 0x0003
+#define SGTL5000_MIC_GAIN_SHIFT 0
+#define SGTL5000_MIC_GAIN_WIDTH 2
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_CTRL
+ */
+#define SGTL5000_LINE_OUT_CURRENT_MASK 0x0f00
+#define SGTL5000_LINE_OUT_CURRENT_SHIFT 8
+#define SGTL5000_LINE_OUT_CURRENT_WIDTH 4
+#define SGTL5000_LINE_OUT_CURRENT_180u 0x0
+#define SGTL5000_LINE_OUT_CURRENT_270u 0x1
+#define SGTL5000_LINE_OUT_CURRENT_360u 0x3
+#define SGTL5000_LINE_OUT_CURRENT_450u 0x7
+#define SGTL5000_LINE_OUT_CURRENT_540u 0xf
+#define SGTL5000_LINE_OUT_GND_MASK 0x003f
+#define SGTL5000_LINE_OUT_GND_SHIFT 0
+#define SGTL5000_LINE_OUT_GND_WIDTH 6
+#define SGTL5000_LINE_OUT_GND_BASE 800 /* mv */
+#define SGTL5000_LINE_OUT_GND_STP 25
+#define SGTL5000_LINE_OUT_GND_MAX 0x23
+
+/*
+ * SGTL5000_CHIP_LINE_OUT_VOL
+ */
+#define SGTL5000_LINE_OUT_VOL_RIGHT_MASK 0x1f00
+#define SGTL5000_LINE_OUT_VOL_RIGHT_SHIFT 8
+#define SGTL5000_LINE_OUT_VOL_RIGHT_WIDTH 5
+#define SGTL5000_LINE_OUT_VOL_LEFT_MASK 0x001f
+#define SGTL5000_LINE_OUT_VOL_LEFT_SHIFT 0
+#define SGTL5000_LINE_OUT_VOL_LEFT_WIDTH 5
+
+/*
+ * SGTL5000_CHIP_ANA_POWER
+ */
+#define SGTL5000_DAC_STEREO 0x4000
+#define SGTL5000_LINREG_SIMPLE_POWERUP 0x2000
+#define SGTL5000_STARTUP_POWERUP 0x1000
+#define SGTL5000_VDDC_CHRGPMP_POWERUP 0x0800
+#define SGTL5000_PLL_POWERUP 0x0400
+#define SGTL5000_LINEREG_D_POWERUP 0x0200
+#define SGTL5000_VCOAMP_POWERUP 0x0100
+#define SGTL5000_VAG_POWERUP 0x0080
+#define SGTL5000_ADC_STEREO 0x0040
+#define SGTL5000_REFTOP_POWERUP 0x0020
+#define SGTL5000_HP_POWERUP 0x0010
+#define SGTL5000_DAC_POWERUP 0x0008
+#define SGTL5000_CAPLESS_HP_POWERUP 0x0004
+#define SGTL5000_ADC_POWERUP 0x0002
+#define SGTL5000_LINE_OUT_POWERUP 0x0001
+
+/*
+ * SGTL5000_CHIP_PLL_CTRL
+ */
+#define SGTL5000_PLL_INT_DIV_MASK 0xf800
+#define SGTL5000_PLL_INT_DIV_SHIFT 11
+#define SGTL5000_PLL_INT_DIV_WIDTH 5
+#define SGTL5000_PLL_FRAC_DIV_MASK 0x0700
+#define SGTL5000_PLL_FRAC_DIV_SHIFT 0
+#define SGTL5000_PLL_FRAC_DIV_WIDTH 11
+
+/*
+ * SGTL5000_CHIP_CLK_TOP_CTRL
+ */
+#define SGTL5000_INT_OSC_EN 0x0800
+#define SGTL5000_INPUT_FREQ_DIV2 0x0008
+
+/*
+ * SGTL5000_CHIP_ANA_STATUS
+ */
+#define SGTL5000_HP_LRSHORT 0x0200
+#define SGTL5000_CAPLESS_SHORT 0x0100
+#define SGTL5000_PLL_LOCKED 0x0010
+
+/*
+ * SGTL5000_CHIP_SHORT_CTRL
+ */
+#define SGTL5000_LVLADJR_MASK 0x7000
+#define SGTL5000_LVLADJR_SHIFT 12
+#define SGTL5000_LVLADJR_WIDTH 3
+#define SGTL5000_LVLADJL_MASK 0x0700
+#define SGTL5000_LVLADJL_SHIFT 8
+#define SGTL5000_LVLADJL_WIDTH 3
+#define SGTL5000_LVLADJC_MASK 0x0070
+#define SGTL5000_LVLADJC_SHIFT 4
+#define SGTL5000_LVLADJC_WIDTH 3
+#define SGTL5000_LR_SHORT_MOD_MASK 0x000c
+#define SGTL5000_LR_SHORT_MOD_SHIFT 2
+#define SGTL5000_LR_SHORT_MOD_WIDTH 2
+#define SGTL5000_CM_SHORT_MOD_MASK 0x0003
+#define SGTL5000_CM_SHORT_MOD_SHIFT 0
+#define SGTL5000_CM_SHORT_MOD_WIDTH 2
+
+/*
+ *SGTL5000_CHIP_ANA_TEST2
+ */
+#define SGTL5000_MONO_DAC 0x1000
+
+/*
+ * SGTL5000_DAP_CTRL
+ */
+#define SGTL5000_DAP_MIX_EN 0x0010
+#define SGTL5000_DAP_EN 0x0001
+
+#define SGTL5000_SYSCLK 0x00
+#define SGTL5000_LRCLK 0x01
+
+#endif
diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c
new file mode 100644
index 00000000000..2a30eae1881
--- /dev/null
+++ b/sound/soc/codecs/sn95031.c
@@ -0,0 +1,949 @@
+/*
+ * sn95031.c - TI sn95031 Codec driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <asm/intel_scu_ipc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/jack.h>
+#include "sn95031.h"
+
+#define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100)
+#define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+
+/* adc helper functions */
+
+/* enables mic bias voltage */
+static void sn95031_enable_mic_bias(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_VAUD, BIT(2)|BIT(1)|BIT(0));
+ snd_soc_update_bits(codec, SN95031_MICBIAS, BIT(2), BIT(2));
+}
+
+/* Enable/Disable the ADC depending on the argument */
+static void configure_adc(struct snd_soc_codec *sn95031_codec, int val)
+{
+ int value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1);
+
+ if (val) {
+ /* Enable and start the ADC */
+ value |= (SN95031_ADC_ENBL | SN95031_ADC_START);
+ value &= (~SN95031_ADC_NO_LOOP);
+ } else {
+ /* Just stop the ADC */
+ value &= (~SN95031_ADC_START);
+ }
+ snd_soc_write(sn95031_codec, SN95031_ADC1CNTL1, value);
+}
+
+/*
+ * finds an empty channel for conversion
+ * If the ADC is not enabled then start using 0th channel
+ * itself. Otherwise find an empty channel by looking for a
+ * channel in which the stopbit is set to 1. returns the index
+ * of the first free channel if succeeds or an error code.
+ *
+ * Context: can sleep
+ *
+ */
+static int find_free_channel(struct snd_soc_codec *sn95031_codec)
+{
+ int ret = 0, i, value;
+
+ /* check whether ADC is enabled */
+ value = snd_soc_read(sn95031_codec, SN95031_ADC1CNTL1);
+
+ if ((value & SN95031_ADC_ENBL) == 0)
+ return 0;
+
+ /* ADC is already enabled; Looking for an empty channel */
+ for (i = 0; i < SN95031_ADC_CHANLS_MAX; i++) {
+ value = snd_soc_read(sn95031_codec,
+ SN95031_ADC_CHNL_START_ADDR + i);
+ if (value & SN95031_STOPBIT_MASK) {
+ ret = i;
+ break;
+ }
+ }
+ return (ret > SN95031_ADC_LOOP_MAX) ? (-EINVAL) : ret;
+}
+
+/* Initialize the ADC for reading micbias values. Can sleep. */
+static int sn95031_initialize_adc(struct snd_soc_codec *sn95031_codec)
+{
+ int base_addr, chnl_addr;
+ int value;
+ static int channel_index;
+
+ /* Index of the first channel in which the stop bit is set */
+ channel_index = find_free_channel(sn95031_codec);
+ if (channel_index < 0) {
+ pr_err("No free ADC channels");
+ return channel_index;
+ }
+
+ base_addr = SN95031_ADC_CHNL_START_ADDR + channel_index;
+
+ if (!(channel_index == 0 || channel_index == SN95031_ADC_LOOP_MAX)) {
+ /* Reset stop bit for channels other than 0 and 12 */
+ value = snd_soc_read(sn95031_codec, base_addr);
+ /* Set the stop bit to zero */
+ snd_soc_write(sn95031_codec, base_addr, value & 0xEF);
+ /* Index of the first free channel */
+ base_addr++;
+ channel_index++;
+ }
+
+ /* Since this is the last channel, set the stop bit
+ to 1 by ORing the DIE_SENSOR_CODE with 0x10 */
+ snd_soc_write(sn95031_codec, base_addr,
+ SN95031_AUDIO_DETECT_CODE | 0x10);
+
+ chnl_addr = SN95031_ADC_DATA_START_ADDR + 2 * channel_index;
+ pr_debug("mid_initialize : %x", chnl_addr);
+ configure_adc(sn95031_codec, 1);
+ return chnl_addr;
+}
+
+
+/* reads the ADC registers and gets the mic bias value in mV. */
+static unsigned int sn95031_get_mic_bias(struct snd_soc_codec *codec)
+{
+ u16 adc_adr = sn95031_initialize_adc(codec);
+ u16 adc_val1, adc_val2;
+ unsigned int mic_bias;
+
+ sn95031_enable_mic_bias(codec);
+
+ /* Enable the sound card for conversion before reading */
+ snd_soc_write(codec, SN95031_ADC1CNTL3, 0x05);
+ /* Re-toggle the RRDATARD bit */
+ snd_soc_write(codec, SN95031_ADC1CNTL3, 0x04);
+
+ /* Read the higher bits of data */
+ msleep(1000);
+ adc_val1 = snd_soc_read(codec, adc_adr);
+ adc_adr++;
+ adc_val2 = snd_soc_read(codec, adc_adr);
+
+ /* Adding lower two bits to the higher bits */
+ mic_bias = (adc_val1 << 2) + (adc_val2 & 3);
+ mic_bias = (mic_bias * SN95031_ADC_ONE_LSB_MULTIPLIER) / 1000;
+ pr_debug("mic bias = %dmV\n", mic_bias);
+ return mic_bias;
+}
+EXPORT_SYMBOL_GPL(sn95031_get_mic_bias);
+/*end - adc helper functions */
+
+static inline unsigned int sn95031_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 value = 0;
+ int ret;
+
+ ret = intel_scu_ipc_ioread8(reg, &value);
+ if (ret)
+ pr_err("read of %x failed, err %d\n", reg, ret);
+ return value;
+
+}
+
+static inline int sn95031_write(struct snd_soc_codec *codec,
+ unsigned int reg, unsigned int value)
+{
+ int ret;
+
+ ret = intel_scu_ipc_iowrite8(reg, value);
+ if (ret)
+ pr_err("write of %x failed, err %d\n", reg, ret);
+ return ret;
+}
+
+static int sn95031_set_vaud_bias(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
+ pr_debug("vaud_bias powering up pll\n");
+ /* power up the pll */
+ snd_soc_write(codec, SN95031_AUDPLLCTRL, BIT(5));
+ /* enable pcm 2 */
+ snd_soc_update_bits(codec, SN95031_PCM2C2,
+ BIT(0), BIT(0));
+ }
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ pr_debug("vaud_bias power up rail\n");
+ /* power up the rail */
+ snd_soc_write(codec, SN95031_VAUD,
+ BIT(2)|BIT(1)|BIT(0));
+ msleep(1);
+ } else if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) {
+ /* turn off pcm */
+ pr_debug("vaud_bias power dn pcm\n");
+ snd_soc_update_bits(codec, SN95031_PCM2C2, BIT(0), 0);
+ snd_soc_write(codec, SN95031_AUDPLLCTRL, 0);
+ }
+ break;
+
+
+ case SND_SOC_BIAS_OFF:
+ pr_debug("vaud_bias _OFF doing rail shutdown\n");
+ snd_soc_write(codec, SN95031_VAUD, BIT(3));
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int sn95031_vhs_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ pr_debug("VHS SND_SOC_DAPM_EVENT_ON doing rail startup now\n");
+ /* power up the rail */
+ snd_soc_write(w->codec, SN95031_VHSP, 0x3D);
+ snd_soc_write(w->codec, SN95031_VHSN, 0x3F);
+ msleep(1);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ pr_debug("VHS SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n");
+ snd_soc_write(w->codec, SN95031_VHSP, 0xC4);
+ snd_soc_write(w->codec, SN95031_VHSN, 0x04);
+ }
+ return 0;
+}
+
+static int sn95031_vihf_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ pr_debug("VIHF SND_SOC_DAPM_EVENT_ON doing rail startup now\n");
+ /* power up the rail */
+ snd_soc_write(w->codec, SN95031_VIHF, 0x27);
+ msleep(1);
+ } else if (SND_SOC_DAPM_EVENT_OFF(event)) {
+ pr_debug("VIHF SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n");
+ snd_soc_write(w->codec, SN95031_VIHF, 0x24);
+ }
+ return 0;
+}
+
+static int sn95031_dmic12_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0, clk_dir = 0, data_dir = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ldo = BIT(5)|BIT(4);
+ clk_dir = BIT(0);
+ data_dir = BIT(7);
+ }
+ /* program DMIC LDO, clock and set clock */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(0), clk_dir);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(7), data_dir);
+ return 0;
+}
+
+static int sn95031_dmic34_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0, clk_dir = 0, data_dir = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ ldo = BIT(5)|BIT(4);
+ clk_dir = BIT(2);
+ data_dir = BIT(1);
+ }
+ /* program DMIC LDO, clock and set clock */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(5)|BIT(4), ldo);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF0123, BIT(2), clk_dir);
+ snd_soc_update_bits(w->codec, SN95031_DMICBUF45, BIT(1), data_dir);
+ return 0;
+}
+
+static int sn95031_dmic56_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ unsigned int ldo = 0;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ ldo = BIT(7)|BIT(6);
+
+ /* program DMIC LDO */
+ snd_soc_update_bits(w->codec, SN95031_MICBIAS, BIT(7)|BIT(6), ldo);
+ return 0;
+}
+
+/* mux controls */
+static const char *sn95031_mic_texts[] = { "AMIC", "LineIn" };
+
+static const struct soc_enum sn95031_micl_enum =
+ SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 1, 2, sn95031_mic_texts);
+
+static const struct snd_kcontrol_new sn95031_micl_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_micl_enum);
+
+static const struct soc_enum sn95031_micr_enum =
+ SOC_ENUM_SINGLE(SN95031_ADCCONFIG, 3, 2, sn95031_mic_texts);
+
+static const struct snd_kcontrol_new sn95031_micr_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_micr_enum);
+
+static const char *sn95031_input_texts[] = { "DMIC1", "DMIC2", "DMIC3",
+ "DMIC4", "DMIC5", "DMIC6",
+ "ADC Left", "ADC Right" };
+
+static const struct soc_enum sn95031_input1_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 0, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input1_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input1_enum);
+
+static const struct soc_enum sn95031_input2_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX12, 4, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input2_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input2_enum);
+
+static const struct soc_enum sn95031_input3_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 0, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input3_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input3_enum);
+
+static const struct soc_enum sn95031_input4_enum =
+ SOC_ENUM_SINGLE(SN95031_AUDIOMUX34, 4, 8, sn95031_input_texts);
+
+static const struct snd_kcontrol_new sn95031_input4_mux_control =
+ SOC_DAPM_ENUM("Route", sn95031_input4_enum);
+
+/* capture path controls */
+
+static const char *sn95031_micmode_text[] = {"Single Ended", "Differential"};
+
+/* 0dB to 30dB in 10dB steps */
+static const DECLARE_TLV_DB_SCALE(mic_tlv, 0, 10, 0);
+
+static const struct soc_enum sn95031_micmode1_enum =
+ SOC_ENUM_SINGLE(SN95031_MICAMP1, 1, 2, sn95031_micmode_text);
+static const struct soc_enum sn95031_micmode2_enum =
+ SOC_ENUM_SINGLE(SN95031_MICAMP2, 1, 2, sn95031_micmode_text);
+
+static const char *sn95031_dmic_cfg_text[] = {"GPO", "DMIC"};
+
+static const struct soc_enum sn95031_dmic12_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 0, 2, sn95031_dmic_cfg_text);
+static const struct soc_enum sn95031_dmic34_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 1, 2, sn95031_dmic_cfg_text);
+static const struct soc_enum sn95031_dmic56_cfg_enum =
+ SOC_ENUM_SINGLE(SN95031_DMICMUX, 2, 2, sn95031_dmic_cfg_text);
+
+static const struct snd_kcontrol_new sn95031_snd_controls[] = {
+ SOC_ENUM("Mic1Mode Capture Route", sn95031_micmode1_enum),
+ SOC_ENUM("Mic2Mode Capture Route", sn95031_micmode2_enum),
+ SOC_ENUM("DMIC12 Capture Route", sn95031_dmic12_cfg_enum),
+ SOC_ENUM("DMIC34 Capture Route", sn95031_dmic34_cfg_enum),
+ SOC_ENUM("DMIC56 Capture Route", sn95031_dmic56_cfg_enum),
+ SOC_SINGLE_TLV("Mic1 Capture Volume", SN95031_MICAMP1,
+ 2, 4, 0, mic_tlv),
+ SOC_SINGLE_TLV("Mic2 Capture Volume", SN95031_MICAMP2,
+ 2, 4, 0, mic_tlv),
+};
+
+/* DAPM widgets */
+static const struct snd_soc_dapm_widget sn95031_dapm_widgets[] = {
+
+ /* all end points mic, hs etc */
+ SND_SOC_DAPM_OUTPUT("HPOUTL"),
+ SND_SOC_DAPM_OUTPUT("HPOUTR"),
+ SND_SOC_DAPM_OUTPUT("EPOUT"),
+ SND_SOC_DAPM_OUTPUT("IHFOUTL"),
+ SND_SOC_DAPM_OUTPUT("IHFOUTR"),
+ SND_SOC_DAPM_OUTPUT("LINEOUTL"),
+ SND_SOC_DAPM_OUTPUT("LINEOUTR"),
+ SND_SOC_DAPM_OUTPUT("VIB1OUT"),
+ SND_SOC_DAPM_OUTPUT("VIB2OUT"),
+
+ SND_SOC_DAPM_INPUT("AMIC1"), /* headset mic */
+ SND_SOC_DAPM_INPUT("AMIC2"),
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+ SND_SOC_DAPM_INPUT("DMIC3"),
+ SND_SOC_DAPM_INPUT("DMIC4"),
+ SND_SOC_DAPM_INPUT("DMIC5"),
+ SND_SOC_DAPM_INPUT("DMIC6"),
+ SND_SOC_DAPM_INPUT("LINEINL"),
+ SND_SOC_DAPM_INPUT("LINEINR"),
+
+ SND_SOC_DAPM_MICBIAS("AMIC1Bias", SN95031_MICBIAS, 2, 0),
+ SND_SOC_DAPM_MICBIAS("AMIC2Bias", SN95031_MICBIAS, 3, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC12Bias", SN95031_DMICMUX, 3, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC34Bias", SN95031_DMICMUX, 4, 0),
+ SND_SOC_DAPM_MICBIAS("DMIC56Bias", SN95031_DMICMUX, 5, 0),
+
+ SND_SOC_DAPM_SUPPLY("DMIC12supply", SN95031_DMICLK, 0, 0,
+ sn95031_dmic12_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("DMIC34supply", SN95031_DMICLK, 1, 0,
+ sn95031_dmic34_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("DMIC56supply", SN95031_DMICLK, 2, 0,
+ sn95031_dmic56_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_AIF_OUT("PCM_Out", "Capture", 0,
+ SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_SUPPLY("Headset Rail", SND_SOC_NOPM, 0, 0,
+ sn95031_vhs_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_SUPPLY("Speaker Rail", SND_SOC_NOPM, 0, 0,
+ sn95031_vihf_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* playback path driver enables */
+ SND_SOC_DAPM_PGA("Headset Left Playback",
+ SN95031_DRIVEREN, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Headset Right Playback",
+ SN95031_DRIVEREN, 1, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Left Playback",
+ SN95031_DRIVEREN, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Right Playback",
+ SN95031_DRIVEREN, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Vibra1 Playback",
+ SN95031_DRIVEREN, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Vibra2 Playback",
+ SN95031_DRIVEREN, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Earpiece Playback",
+ SN95031_DRIVEREN, 6, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lineout Left Playback",
+ SN95031_LOCTL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Lineout Right Playback",
+ SN95031_LOCTL, 4, 0, NULL, 0),
+
+ /* playback path filter enable */
+ SND_SOC_DAPM_PGA("Headset Left Filter",
+ SN95031_HSEPRXCTRL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Headset Right Filter",
+ SN95031_HSEPRXCTRL, 5, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Left Filter",
+ SN95031_IHFRXCTRL, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Speaker Right Filter",
+ SN95031_IHFRXCTRL, 1, 0, NULL, 0),
+
+ /* DACs */
+ SND_SOC_DAPM_DAC("HSDAC Left", "Headset",
+ SN95031_DACCONFIG, 0, 0),
+ SND_SOC_DAPM_DAC("HSDAC Right", "Headset",
+ SN95031_DACCONFIG, 1, 0),
+ SND_SOC_DAPM_DAC("IHFDAC Left", "Speaker",
+ SN95031_DACCONFIG, 2, 0),
+ SND_SOC_DAPM_DAC("IHFDAC Right", "Speaker",
+ SN95031_DACCONFIG, 3, 0),
+ SND_SOC_DAPM_DAC("Vibra1 DAC", "Vibra1",
+ SN95031_VIB1C5, 1, 0),
+ SND_SOC_DAPM_DAC("Vibra2 DAC", "Vibra2",
+ SN95031_VIB2C5, 1, 0),
+
+ /* capture widgets */
+ SND_SOC_DAPM_PGA("LineIn Enable Left", SN95031_MICAMP1,
+ 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("LineIn Enable Right", SN95031_MICAMP2,
+ 7, 0, NULL, 0),
+
+ SND_SOC_DAPM_PGA("MIC1 Enable", SN95031_MICAMP1, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("MIC2 Enable", SN95031_MICAMP2, 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX1 Enable", SN95031_AUDIOTXEN, 2, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX2 Enable", SN95031_AUDIOTXEN, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX3 Enable", SN95031_AUDIOTXEN, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("TX4 Enable", SN95031_AUDIOTXEN, 5, 0, NULL, 0),
+
+ /* ADC have null stream as they will be turned ON by TX path */
+ SND_SOC_DAPM_ADC("ADC Left", NULL,
+ SN95031_ADCCONFIG, 0, 0),
+ SND_SOC_DAPM_ADC("ADC Right", NULL,
+ SN95031_ADCCONFIG, 2, 0),
+
+ SND_SOC_DAPM_MUX("Mic_InputL Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_micl_mux_control),
+ SND_SOC_DAPM_MUX("Mic_InputR Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_micr_mux_control),
+
+ SND_SOC_DAPM_MUX("Txpath1 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input1_mux_control),
+ SND_SOC_DAPM_MUX("Txpath2 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input2_mux_control),
+ SND_SOC_DAPM_MUX("Txpath3 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input3_mux_control),
+ SND_SOC_DAPM_MUX("Txpath4 Capture Route",
+ SND_SOC_NOPM, 0, 0, &sn95031_input4_mux_control),
+
+};
+
+static const struct snd_soc_dapm_route sn95031_audio_map[] = {
+ /* headset and earpiece map */
+ { "HPOUTL", NULL, "Headset Rail"},
+ { "HPOUTR", NULL, "Headset Rail"},
+ { "HPOUTL", NULL, "Headset Left Playback" },
+ { "HPOUTR", NULL, "Headset Right Playback" },
+ { "EPOUT", NULL, "Earpiece Playback" },
+ { "Headset Left Playback", NULL, "Headset Left Filter"},
+ { "Headset Right Playback", NULL, "Headset Right Filter"},
+ { "Earpiece Playback", NULL, "Headset Left Filter"},
+ { "Headset Left Filter", NULL, "HSDAC Left"},
+ { "Headset Right Filter", NULL, "HSDAC Right"},
+
+ /* speaker map */
+ { "IHFOUTL", NULL, "Speaker Rail"},
+ { "IHFOUTR", NULL, "Speaker Rail"},
+ { "IHFOUTL", "NULL", "Speaker Left Playback"},
+ { "IHFOUTR", "NULL", "Speaker Right Playback"},
+ { "Speaker Left Playback", NULL, "Speaker Left Filter"},
+ { "Speaker Right Playback", NULL, "Speaker Right Filter"},
+ { "Speaker Left Filter", NULL, "IHFDAC Left"},
+ { "Speaker Right Filter", NULL, "IHFDAC Right"},
+
+ /* vibra map */
+ { "VIB1OUT", NULL, "Vibra1 Playback"},
+ { "Vibra1 Playback", NULL, "Vibra1 DAC"},
+
+ { "VIB2OUT", NULL, "Vibra2 Playback"},
+ { "Vibra2 Playback", NULL, "Vibra2 DAC"},
+
+ /* lineout */
+ { "LINEOUTL", NULL, "Lineout Left Playback"},
+ { "LINEOUTR", NULL, "Lineout Right Playback"},
+ { "Lineout Left Playback", NULL, "Headset Left Filter"},
+ { "Lineout Left Playback", NULL, "Speaker Left Filter"},
+ { "Lineout Left Playback", NULL, "Vibra1 DAC"},
+ { "Lineout Right Playback", NULL, "Headset Right Filter"},
+ { "Lineout Right Playback", NULL, "Speaker Right Filter"},
+ { "Lineout Right Playback", NULL, "Vibra2 DAC"},
+
+ /* Headset (AMIC1) mic */
+ { "AMIC1Bias", NULL, "AMIC1"},
+ { "MIC1 Enable", NULL, "AMIC1Bias"},
+ { "Mic_InputL Capture Route", "AMIC", "MIC1 Enable"},
+
+ /* AMIC2 */
+ { "AMIC2Bias", NULL, "AMIC2"},
+ { "MIC2 Enable", NULL, "AMIC2Bias"},
+ { "Mic_InputR Capture Route", "AMIC", "MIC2 Enable"},
+
+
+ /* Linein */
+ { "LineIn Enable Left", NULL, "LINEINL"},
+ { "LineIn Enable Right", NULL, "LINEINR"},
+ { "Mic_InputL Capture Route", "LineIn", "LineIn Enable Left"},
+ { "Mic_InputR Capture Route", "LineIn", "LineIn Enable Right"},
+
+ /* ADC connection */
+ { "ADC Left", NULL, "Mic_InputL Capture Route"},
+ { "ADC Right", NULL, "Mic_InputR Capture Route"},
+
+ /*DMIC connections */
+ { "DMIC1", NULL, "DMIC12supply"},
+ { "DMIC2", NULL, "DMIC12supply"},
+ { "DMIC3", NULL, "DMIC34supply"},
+ { "DMIC4", NULL, "DMIC34supply"},
+ { "DMIC5", NULL, "DMIC56supply"},
+ { "DMIC6", NULL, "DMIC56supply"},
+
+ { "DMIC12Bias", NULL, "DMIC1"},
+ { "DMIC12Bias", NULL, "DMIC2"},
+ { "DMIC34Bias", NULL, "DMIC3"},
+ { "DMIC34Bias", NULL, "DMIC4"},
+ { "DMIC56Bias", NULL, "DMIC5"},
+ { "DMIC56Bias", NULL, "DMIC6"},
+
+ /*TX path inputs*/
+ { "Txpath1 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath2 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath3 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath4 Capture Route", "ADC Left", "ADC Left"},
+ { "Txpath1 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath2 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath3 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath4 Capture Route", "ADC Right", "ADC Right"},
+ { "Txpath1 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath2 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath3 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath4 Capture Route", "DMIC1", "DMIC1"},
+ { "Txpath1 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath2 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath3 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath4 Capture Route", "DMIC2", "DMIC2"},
+ { "Txpath1 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath2 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath3 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath4 Capture Route", "DMIC3", "DMIC3"},
+ { "Txpath1 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath2 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath3 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath4 Capture Route", "DMIC4", "DMIC4"},
+ { "Txpath1 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath2 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath3 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath4 Capture Route", "DMIC5", "DMIC5"},
+ { "Txpath1 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath2 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath3 Capture Route", "DMIC6", "DMIC6"},
+ { "Txpath4 Capture Route", "DMIC6", "DMIC6"},
+
+ /* tx path */
+ { "TX1 Enable", NULL, "Txpath1 Capture Route"},
+ { "TX2 Enable", NULL, "Txpath2 Capture Route"},
+ { "TX3 Enable", NULL, "Txpath3 Capture Route"},
+ { "TX4 Enable", NULL, "Txpath4 Capture Route"},
+ { "PCM_Out", NULL, "TX1 Enable"},
+ { "PCM_Out", NULL, "TX2 Enable"},
+ { "PCM_Out", NULL, "TX3 Enable"},
+ { "PCM_Out", NULL, "TX4 Enable"},
+
+};
+
+/* speaker and headset mutes, for audio pops and clicks */
+static int sn95031_pcm_hs_mute(struct snd_soc_dai *dai, int mute)
+{
+ snd_soc_update_bits(dai->codec,
+ SN95031_HSLVOLCTRL, BIT(7), (!mute << 7));
+ snd_soc_update_bits(dai->codec,
+ SN95031_HSRVOLCTRL, BIT(7), (!mute << 7));
+ return 0;
+}
+
+static int sn95031_pcm_spkr_mute(struct snd_soc_dai *dai, int mute)
+{
+ snd_soc_update_bits(dai->codec,
+ SN95031_IHFLVOLCTRL, BIT(7), (!mute << 7));
+ snd_soc_update_bits(dai->codec,
+ SN95031_IHFRVOLCTRL, BIT(7), (!mute << 7));
+ return 0;
+}
+
+int sn95031_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ unsigned int format, rate;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ format = BIT(4)|BIT(5);
+ break;
+
+ case SNDRV_PCM_FORMAT_S24_LE:
+ format = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_update_bits(dai->codec, SN95031_PCM2C2,
+ BIT(4)|BIT(5), format);
+
+ switch (params_rate(params)) {
+ case 48000:
+ pr_debug("RATE_48000\n");
+ rate = 0;
+ break;
+
+ case 44100:
+ pr_debug("RATE_44100\n");
+ rate = BIT(7);
+ break;
+
+ default:
+ pr_err("ERR rate %d\n", params_rate(params));
+ return -EINVAL;
+ }
+ snd_soc_update_bits(dai->codec, SN95031_PCM1C1, BIT(7), rate);
+
+ return 0;
+}
+
+/* Codec DAI section */
+static struct snd_soc_dai_ops sn95031_headset_dai_ops = {
+ .digital_mute = sn95031_pcm_hs_mute,
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_speaker_dai_ops = {
+ .digital_mute = sn95031_pcm_spkr_mute,
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_vib1_dai_ops = {
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+static struct snd_soc_dai_ops sn95031_vib2_dai_ops = {
+ .hw_params = sn95031_pcm_hw_params,
+};
+
+struct snd_soc_dai_driver sn95031_dais[] = {
+{
+ .name = "SN95031 Headset",
+ .playback = {
+ .stream_name = "Headset",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 5,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_headset_dai_ops,
+},
+{ .name = "SN95031 Speaker",
+ .playback = {
+ .stream_name = "Speaker",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_speaker_dai_ops,
+},
+{ .name = "SN95031 Vibra1",
+ .playback = {
+ .stream_name = "Vibra1",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_vib1_dai_ops,
+},
+{ .name = "SN95031 Vibra2",
+ .playback = {
+ .stream_name = "Vibra2",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = SN95031_RATES,
+ .formats = SN95031_FORMATS,
+ },
+ .ops = &sn95031_vib2_dai_ops,
+},
+};
+
+static inline void sn95031_disable_jack_btn(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_BTNCTRL2, 0x00);
+}
+
+static inline void sn95031_enable_jack_btn(struct snd_soc_codec *codec)
+{
+ snd_soc_write(codec, SN95031_BTNCTRL1, 0x77);
+ snd_soc_write(codec, SN95031_BTNCTRL2, 0x01);
+}
+
+static int sn95031_get_headset_state(struct snd_soc_jack *mfld_jack)
+{
+ int micbias = sn95031_get_mic_bias(mfld_jack->codec);
+
+ int jack_type = snd_soc_jack_get_type(mfld_jack, micbias);
+
+ pr_debug("jack type detected = %d\n", jack_type);
+ if (jack_type == SND_JACK_HEADSET)
+ sn95031_enable_jack_btn(mfld_jack->codec);
+ return jack_type;
+}
+
+void sn95031_jack_detection(struct mfld_jack_data *jack_data)
+{
+ unsigned int status;
+ unsigned int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_HEADSET;
+
+ pr_debug("interrupt id read in sram = 0x%x\n", jack_data->intr_id);
+ if (jack_data->intr_id & 0x1) {
+ pr_debug("short_push detected\n");
+ status = SND_JACK_HEADSET | SND_JACK_BTN_0;
+ } else if (jack_data->intr_id & 0x2) {
+ pr_debug("long_push detected\n");
+ status = SND_JACK_HEADSET | SND_JACK_BTN_1;
+ } else if (jack_data->intr_id & 0x4) {
+ pr_debug("headset or headphones inserted\n");
+ status = sn95031_get_headset_state(jack_data->mfld_jack);
+ } else if (jack_data->intr_id & 0x8) {
+ pr_debug("headset or headphones removed\n");
+ status = 0;
+ sn95031_disable_jack_btn(jack_data->mfld_jack->codec);
+ } else {
+ pr_err("unidentified interrupt\n");
+ return;
+ }
+
+ snd_soc_jack_report(jack_data->mfld_jack, status, mask);
+ /*button pressed and released so we send explicit button release */
+ if ((status & SND_JACK_BTN_0) | (status & SND_JACK_BTN_1))
+ snd_soc_jack_report(jack_data->mfld_jack,
+ SND_JACK_HEADSET, mask);
+}
+EXPORT_SYMBOL_GPL(sn95031_jack_detection);
+
+/* codec registration */
+static int sn95031_codec_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+
+ pr_debug("codec_probe called\n");
+
+ codec->dapm.bias_level = SND_SOC_BIAS_OFF;
+ codec->dapm.idle_bias_off = 1;
+
+ /* PCM interface config
+ * This sets the pcm rx slot conguration to max 6 slots
+ * for max 4 dais (2 stereo and 2 mono)
+ */
+ snd_soc_write(codec, SN95031_PCM2RXSLOT01, 0x10);
+ snd_soc_write(codec, SN95031_PCM2RXSLOT23, 0x32);
+ snd_soc_write(codec, SN95031_PCM2RXSLOT45, 0x54);
+ snd_soc_write(codec, SN95031_PCM2TXSLOT01, 0x10);
+ snd_soc_write(codec, SN95031_PCM2TXSLOT23, 0x32);
+ /* pcm port setting
+ * This sets the pcm port to slave and clock at 19.2Mhz which
+ * can support 6slots, sampling rate set per stream in hw-params
+ */
+ snd_soc_write(codec, SN95031_PCM1C1, 0x00);
+ snd_soc_write(codec, SN95031_PCM2C1, 0x01);
+ snd_soc_write(codec, SN95031_PCM2C2, 0x0A);
+ snd_soc_write(codec, SN95031_HSMIXER, BIT(0)|BIT(4));
+ /* vendor vibra workround, the vibras are muted by
+ * custom register so unmute them
+ */
+ snd_soc_write(codec, SN95031_SSR5, 0x80);
+ snd_soc_write(codec, SN95031_SSR6, 0x80);
+ snd_soc_write(codec, SN95031_VIB1C5, 0x00);
+ snd_soc_write(codec, SN95031_VIB2C5, 0x00);
+ /* configure vibras for pcm port */
+ snd_soc_write(codec, SN95031_VIB1C3, 0x00);
+ snd_soc_write(codec, SN95031_VIB2C3, 0x00);
+
+ /* soft mute ramp time */
+ snd_soc_write(codec, SN95031_SOFTMUTE, 0x3);
+ /* fix the initial volume at 1dB,
+ * default in +9dB,
+ * 1dB give optimal swing on DAC, amps
+ */
+ snd_soc_write(codec, SN95031_HSLVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_HSRVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_IHFLVOLCTRL, 0x08);
+ snd_soc_write(codec, SN95031_IHFRVOLCTRL, 0x08);
+ /* dac mode and lineout workaround */
+ snd_soc_write(codec, SN95031_SSR2, 0x10);
+ snd_soc_write(codec, SN95031_SSR3, 0x40);
+
+ snd_soc_add_controls(codec, sn95031_snd_controls,
+ ARRAY_SIZE(sn95031_snd_controls));
+
+ ret = snd_soc_dapm_new_controls(&codec->dapm, sn95031_dapm_widgets,
+ ARRAY_SIZE(sn95031_dapm_widgets));
+ if (ret)
+ pr_err("soc_dapm_new_control failed %d", ret);
+ ret = snd_soc_dapm_add_routes(&codec->dapm, sn95031_audio_map,
+ ARRAY_SIZE(sn95031_audio_map));
+ if (ret)
+ pr_err("soc_dapm_add_routes failed %d", ret);
+
+ return ret;
+}
+
+static int sn95031_codec_remove(struct snd_soc_codec *codec)
+{
+ pr_debug("codec_remove called\n");
+ sn95031_set_vaud_bias(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+struct snd_soc_codec_driver sn95031_codec = {
+ .probe = sn95031_codec_probe,
+ .remove = sn95031_codec_remove,
+ .read = sn95031_read,
+ .write = sn95031_write,
+ .set_bias_level = sn95031_set_vaud_bias,
+};
+
+static int __devinit sn95031_device_probe(struct platform_device *pdev)
+{
+ pr_debug("codec device probe called for %s\n", dev_name(&pdev->dev));
+ return snd_soc_register_codec(&pdev->dev, &sn95031_codec,
+ sn95031_dais, ARRAY_SIZE(sn95031_dais));
+}
+
+static int __devexit sn95031_device_remove(struct platform_device *pdev)
+{
+ pr_debug("codec device remove called\n");
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver sn95031_codec_driver = {
+ .driver = {
+ .name = "sn95031",
+ .owner = THIS_MODULE,
+ },
+ .probe = sn95031_device_probe,
+ .remove = sn95031_device_remove,
+};
+
+static int __init sn95031_init(void)
+{
+ pr_debug("driver init called\n");
+ return platform_driver_register(&sn95031_codec_driver);
+}
+module_init(sn95031_init);
+
+static void __exit sn95031_exit(void)
+{
+ pr_debug("driver exit called\n");
+ platform_driver_unregister(&sn95031_codec_driver);
+}
+module_exit(sn95031_exit);
+
+MODULE_DESCRIPTION("ASoC TI SN95031 codec driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sn95031");
diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h
new file mode 100644
index 00000000000..20376d234fb
--- /dev/null
+++ b/sound/soc/codecs/sn95031.h
@@ -0,0 +1,132 @@
+/*
+ * sn95031.h - TI sn95031 Codec driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#ifndef _SN95031_H
+#define _SN95031_H
+
+/*register map*/
+#define SN95031_VAUD 0xDB
+#define SN95031_VHSP 0xDC
+#define SN95031_VHSN 0xDD
+#define SN95031_VIHF 0xC9
+
+#define SN95031_AUDPLLCTRL 0x240
+#define SN95031_DMICBUF0123 0x241
+#define SN95031_DMICBUF45 0x242
+#define SN95031_DMICGPO 0x244
+#define SN95031_DMICMUX 0x245
+#define SN95031_DMICLK 0x246
+#define SN95031_MICBIAS 0x247
+#define SN95031_ADCCONFIG 0x248
+#define SN95031_MICAMP1 0x249
+#define SN95031_MICAMP2 0x24A
+#define SN95031_NOISEMUX 0x24B
+#define SN95031_AUDIOMUX12 0x24C
+#define SN95031_AUDIOMUX34 0x24D
+#define SN95031_AUDIOSINC 0x24E
+#define SN95031_AUDIOTXEN 0x24F
+#define SN95031_HSEPRXCTRL 0x250
+#define SN95031_IHFRXCTRL 0x251
+#define SN95031_HSMIXER 0x256
+#define SN95031_DACCONFIG 0x257
+#define SN95031_SOFTMUTE 0x258
+#define SN95031_HSLVOLCTRL 0x259
+#define SN95031_HSRVOLCTRL 0x25A
+#define SN95031_IHFLVOLCTRL 0x25B
+#define SN95031_IHFRVOLCTRL 0x25C
+#define SN95031_DRIVEREN 0x25D
+#define SN95031_LOCTL 0x25E
+#define SN95031_VIB1C1 0x25F
+#define SN95031_VIB1C2 0x260
+#define SN95031_VIB1C3 0x261
+#define SN95031_VIB1SPIPCM1 0x262
+#define SN95031_VIB1SPIPCM2 0x263
+#define SN95031_VIB1C5 0x264
+#define SN95031_VIB2C1 0x265
+#define SN95031_VIB2C2 0x266
+#define SN95031_VIB2C3 0x267
+#define SN95031_VIB2SPIPCM1 0x268
+#define SN95031_VIB2SPIPCM2 0x269
+#define SN95031_VIB2C5 0x26A
+#define SN95031_BTNCTRL1 0x26B
+#define SN95031_BTNCTRL2 0x26C
+#define SN95031_PCM1TXSLOT01 0x26D
+#define SN95031_PCM1TXSLOT23 0x26E
+#define SN95031_PCM1TXSLOT45 0x26F
+#define SN95031_PCM1RXSLOT0_3 0x270
+#define SN95031_PCM1RXSLOT45 0x271
+#define SN95031_PCM2TXSLOT01 0x272
+#define SN95031_PCM2TXSLOT23 0x273
+#define SN95031_PCM2TXSLOT45 0x274
+#define SN95031_PCM2RXSLOT01 0x275
+#define SN95031_PCM2RXSLOT23 0x276
+#define SN95031_PCM2RXSLOT45 0x277
+#define SN95031_PCM1C1 0x278
+#define SN95031_PCM1C2 0x279
+#define SN95031_PCM1C3 0x27A
+#define SN95031_PCM2C1 0x27B
+#define SN95031_PCM2C2 0x27C
+/*end codec register defn*/
+
+/*vendor defn these are not part of avp*/
+#define SN95031_SSR2 0x381
+#define SN95031_SSR3 0x382
+#define SN95031_SSR5 0x384
+#define SN95031_SSR6 0x385
+
+/* ADC registers */
+
+#define SN95031_ADC1CNTL1 0x1C0
+#define SN95031_ADC_ENBL 0x10
+#define SN95031_ADC_START 0x08
+#define SN95031_ADC1CNTL3 0x1C2
+#define SN95031_ADCTHERM_ENBL 0x04
+#define SN95031_ADCRRDATA_ENBL 0x05
+#define SN95031_STOPBIT_MASK 16
+#define SN95031_ADCTHERM_MASK 4
+#define SN95031_ADC_CHANLS_MAX 15 /* Number of ADC channels */
+#define SN95031_ADC_LOOP_MAX (SN95031_ADC_CHANLS_MAX - 1)
+#define SN95031_ADC_NO_LOOP 0x07
+#define SN95031_AUDIO_GPIO_CTRL 0x070
+
+/* ADC channel code values */
+#define SN95031_AUDIO_DETECT_CODE 0x06
+
+/* ADC base addresses */
+#define SN95031_ADC_CHNL_START_ADDR 0x1C5 /* increments by 1 */
+#define SN95031_ADC_DATA_START_ADDR 0x1D4 /* increments by 2 */
+/* multipier to convert to mV */
+#define SN95031_ADC_ONE_LSB_MULTIPLIER 2346
+
+
+struct mfld_jack_data {
+ int intr_id;
+ int micbias_vol;
+ struct snd_soc_jack *mfld_jack;
+};
+
+extern void sn95031_jack_detection(struct mfld_jack_data *jack_data);
+
+#endif
diff --git a/sound/soc/codecs/tlv320aic32x4.c b/sound/soc/codecs/tlv320aic32x4.c
new file mode 100644
index 00000000000..e93b9d1ae1d
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic32x4.c
@@ -0,0 +1,794 @@
+/*
+ * linux/sound/soc/codecs/tlv320aic32x4.c
+ *
+ * Copyright 2011 Vista Silicon S.L.
+ *
+ * Author: Javier Martin <javier.martin@vista-silicon.com>
+ *
+ * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
+ *
+ * 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.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/cdev.h>
+#include <linux/slab.h>
+
+#include <sound/tlv320aic32x4.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "tlv320aic32x4.h"
+
+struct aic32x4_rate_divs {
+ u32 mclk;
+ u32 rate;
+ u8 p_val;
+ u8 pll_j;
+ u16 pll_d;
+ u16 dosr;
+ u8 ndac;
+ u8 mdac;
+ u8 aosr;
+ u8 nadc;
+ u8 madc;
+ u8 blck_N;
+};
+
+struct aic32x4_priv {
+ u32 sysclk;
+ s32 master;
+ u8 page_no;
+ void *control_data;
+ u32 power_cfg;
+ u32 micpga_routing;
+ bool swapdacs;
+};
+
+/* 0dB min, 1dB steps */
+static DECLARE_TLV_DB_SCALE(tlv_step_1, 0, 100, 0);
+/* 0dB min, 0.5dB steps */
+static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0);
+
+static const struct snd_kcontrol_new aic32x4_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("PCM Playback Volume", AIC32X4_LDACVOL,
+ AIC32X4_RDACVOL, 0, 0x30, 0, tlv_step_0_5),
+ SOC_DOUBLE_R_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN,
+ AIC32X4_HPRGAIN, 0, 0x1D, 0, tlv_step_1),
+ SOC_DOUBLE_R_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN,
+ AIC32X4_LORGAIN, 0, 0x1D, 0, tlv_step_1),
+ SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN,
+ AIC32X4_HPRGAIN, 6, 0x01, 1),
+ SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN,
+ AIC32X4_LORGAIN, 6, 0x01, 1),
+ SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL,
+ AIC32X4_RMICPGAVOL, 7, 0x01, 1),
+
+ SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0),
+ SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0),
+
+ SOC_DOUBLE_R_TLV("ADC Level Volume", AIC32X4_LADCVOL,
+ AIC32X4_RADCVOL, 0, 0x28, 0, tlv_step_0_5),
+ SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL,
+ AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5),
+
+ SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0),
+
+ SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0),
+ SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0),
+ SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1,
+ 4, 0x07, 0),
+ SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1,
+ 0, 0x03, 0),
+ SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2,
+ 6, 0x03, 0),
+ SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2,
+ 1, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3,
+ 0, 0x7F, 0),
+ SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4,
+ 3, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5,
+ 3, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6,
+ 0, 0x1F, 0),
+ SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7,
+ 0, 0x0F, 0),
+};
+
+static const struct aic32x4_rate_divs aic32x4_divs[] = {
+ /* 8k rate */
+ {AIC32X4_FREQ_12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24},
+ {AIC32X4_FREQ_24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24},
+ {AIC32X4_FREQ_25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24},
+ /* 11.025k rate */
+ {AIC32X4_FREQ_12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16},
+ {AIC32X4_FREQ_24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16},
+ /* 16k rate */
+ {AIC32X4_FREQ_12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12},
+ {AIC32X4_FREQ_24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12},
+ {AIC32X4_FREQ_25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12},
+ /* 22.05k rate */
+ {AIC32X4_FREQ_12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8},
+ {AIC32X4_FREQ_24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8},
+ {AIC32X4_FREQ_25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8},
+ /* 32k rate */
+ {AIC32X4_FREQ_12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6},
+ {AIC32X4_FREQ_24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6},
+ /* 44.1k rate */
+ {AIC32X4_FREQ_12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4},
+ {AIC32X4_FREQ_24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4},
+ {AIC32X4_FREQ_25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4},
+ /* 48k rate */
+ {AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4},
+ {AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4},
+ {AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}
+};
+
+static const struct snd_kcontrol_new hpl_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new hpr_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0),
+ SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new lol_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new lor_output_mixer_controls[] = {
+ SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0),
+};
+
+static const struct snd_kcontrol_new left_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0),
+};
+
+static const struct snd_kcontrol_new right_input_mixer_controls[] = {
+ SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0),
+ SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0),
+ SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0),
+ SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0,
+ &hpl_output_mixer_controls[0],
+ ARRAY_SIZE(hpl_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0,
+ &lol_output_mixer_controls[0],
+ ARRAY_SIZE(lol_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0),
+ SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0,
+ &hpr_output_mixer_controls[0],
+ ARRAY_SIZE(hpr_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0,
+ &lor_output_mixer_controls[0],
+ ARRAY_SIZE(lor_output_mixer_controls)),
+ SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
+ &left_input_mixer_controls[0],
+ ARRAY_SIZE(left_input_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
+ &right_input_mixer_controls[0],
+ ARRAY_SIZE(right_input_mixer_controls)),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0),
+ SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("LOL"),
+ SND_SOC_DAPM_OUTPUT("LOR"),
+ SND_SOC_DAPM_INPUT("IN1_L"),
+ SND_SOC_DAPM_INPUT("IN1_R"),
+ SND_SOC_DAPM_INPUT("IN2_L"),
+ SND_SOC_DAPM_INPUT("IN2_R"),
+ SND_SOC_DAPM_INPUT("IN3_L"),
+ SND_SOC_DAPM_INPUT("IN3_R"),
+};
+
+static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
+ /* Left Output */
+ {"HPL Output Mixer", "L_DAC Switch", "Left DAC"},
+ {"HPL Output Mixer", "IN1_L Switch", "IN1_L"},
+
+ {"HPL Power", NULL, "HPL Output Mixer"},
+ {"HPL", NULL, "HPL Power"},
+
+ {"LOL Output Mixer", "L_DAC Switch", "Left DAC"},
+
+ {"LOL Power", NULL, "LOL Output Mixer"},
+ {"LOL", NULL, "LOL Power"},
+
+ /* Right Output */
+ {"HPR Output Mixer", "R_DAC Switch", "Right DAC"},
+ {"HPR Output Mixer", "IN1_R Switch", "IN1_R"},
+
+ {"HPR Power", NULL, "HPR Output Mixer"},
+ {"HPR", NULL, "HPR Power"},
+
+ {"LOR Output Mixer", "R_DAC Switch", "Right DAC"},
+
+ {"LOR Power", NULL, "LOR Output Mixer"},
+ {"LOR", NULL, "LOR Power"},
+
+ /* Left input */
+ {"Left Input Mixer", "IN1_L P Switch", "IN1_L"},
+ {"Left Input Mixer", "IN2_L P Switch", "IN2_L"},
+ {"Left Input Mixer", "IN3_L P Switch", "IN3_L"},
+
+ {"Left ADC", NULL, "Left Input Mixer"},
+
+ /* Right Input */
+ {"Right Input Mixer", "IN1_R P Switch", "IN1_R"},
+ {"Right Input Mixer", "IN2_R P Switch", "IN2_R"},
+ {"Right Input Mixer", "IN3_R P Switch", "IN3_R"},
+
+ {"Right ADC", NULL, "Right Input Mixer"},
+};
+
+static inline int aic32x4_change_page(struct snd_soc_codec *codec,
+ unsigned int new_page)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 data[2];
+ int ret;
+
+ data[0] = 0x00;
+ data[1] = new_page & 0xff;
+
+ ret = codec->hw_write(codec->control_data, data, 2);
+ if (ret == 2) {
+ aic32x4->page_no = new_page;
+ return 0;
+ } else {
+ return ret;
+ }
+}
+
+static int aic32x4_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ unsigned int page = reg / 128;
+ unsigned int fixed_reg = reg % 128;
+ u8 data[2];
+ int ret;
+
+ /* A write to AIC32X4_PSEL is really a non-explicit page change */
+ if (reg == AIC32X4_PSEL)
+ return aic32x4_change_page(codec, val);
+
+ if (aic32x4->page_no != page) {
+ ret = aic32x4_change_page(codec, page);
+ if (ret != 0)
+ return ret;
+ }
+
+ data[0] = fixed_reg & 0xff;
+ data[1] = val & 0xff;
+
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+static unsigned int aic32x4_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ unsigned int page = reg / 128;
+ unsigned int fixed_reg = reg % 128;
+ int ret;
+
+ if (aic32x4->page_no != page) {
+ ret = aic32x4_change_page(codec, page);
+ if (ret != 0)
+ return ret;
+ }
+ return i2c_smbus_read_byte_data(codec->control_data, fixed_reg & 0xff);
+}
+
+static inline int aic32x4_get_divs(int mclk, int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) {
+ if ((aic32x4_divs[i].rate == rate)
+ && (aic32x4_divs[i].mclk == mclk)) {
+ return i;
+ }
+ }
+ printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n");
+ return -EINVAL;
+}
+
+static int aic32x4_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(&codec->dapm, aic32x4_dapm_widgets,
+ ARRAY_SIZE(aic32x4_dapm_widgets));
+
+ snd_soc_dapm_add_routes(&codec->dapm, aic32x4_dapm_routes,
+ ARRAY_SIZE(aic32x4_dapm_routes));
+
+ snd_soc_dapm_new_widgets(&codec->dapm);
+ return 0;
+}
+
+static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+
+ switch (freq) {
+ case AIC32X4_FREQ_12000000:
+ case AIC32X4_FREQ_24000000:
+ case AIC32X4_FREQ_25000000:
+ aic32x4->sysclk = freq;
+ return 0;
+ }
+ printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n");
+ return -EINVAL;
+}
+
+static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 iface_reg_1;
+ u8 iface_reg_2;
+ u8 iface_reg_3;
+
+ iface_reg_1 = snd_soc_read(codec, AIC32X4_IFACE1);
+ iface_reg_1 = iface_reg_1 & ~(3 << 6 | 3 << 2);
+ iface_reg_2 = snd_soc_read(codec, AIC32X4_IFACE2);
+ iface_reg_2 = 0;
+ iface_reg_3 = snd_soc_read(codec, AIC32X4_IFACE3);
+ iface_reg_3 = iface_reg_3 & ~(1 << 3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ aic32x4->master = 1;
+ iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ aic32x4->master = 0;
+ break;
+ default:
+ printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n");
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
+ iface_reg_3 |= (1 << 3); /* invert bit clock */
+ iface_reg_2 = 0x01; /* add offset 1 */
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
+ iface_reg_3 |= (1 << 3); /* invert bit clock */
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ iface_reg_1 |=
+ (AIC32X4_RIGHT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface_reg_1 |=
+ (AIC32X4_LEFT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
+ break;
+ default:
+ printk(KERN_ERR "aic32x4: invalid DAI interface format\n");
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, AIC32X4_IFACE1, iface_reg_1);
+ snd_soc_write(codec, AIC32X4_IFACE2, iface_reg_2);
+ snd_soc_write(codec, AIC32X4_IFACE3, iface_reg_3);
+ return 0;
+}
+
+static int aic32x4_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 data;
+ int i;
+
+ i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params));
+ if (i < 0) {
+ printk(KERN_ERR "aic32x4: sampling rate not supported\n");
+ return i;
+ }
+
+ /* Use PLL as CODEC_CLKIN and DAC_MOD_CLK as BDIV_CLKIN */
+ snd_soc_write(codec, AIC32X4_CLKMUX, AIC32X4_PLLCLKIN);
+ snd_soc_write(codec, AIC32X4_IFACE3, AIC32X4_DACMOD2BCLK);
+
+ /* We will fix R value to 1 and will make P & J=K.D as varialble */
+ data = snd_soc_read(codec, AIC32X4_PLLPR);
+ data &= ~(7 << 4);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (data | (aic32x4_divs[i].p_val << 4) | 0x01));
+
+ snd_soc_write(codec, AIC32X4_PLLJ, aic32x4_divs[i].pll_j);
+
+ snd_soc_write(codec, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8));
+ snd_soc_write(codec, AIC32X4_PLLDLSB,
+ (aic32x4_divs[i].pll_d & 0xff));
+
+ /* NDAC divider value */
+ data = snd_soc_read(codec, AIC32X4_NDAC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_NDAC, data | aic32x4_divs[i].ndac);
+
+ /* MDAC divider value */
+ data = snd_soc_read(codec, AIC32X4_MDAC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_MDAC, data | aic32x4_divs[i].mdac);
+
+ /* DOSR MSB & LSB values */
+ snd_soc_write(codec, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8);
+ snd_soc_write(codec, AIC32X4_DOSRLSB,
+ (aic32x4_divs[i].dosr & 0xff));
+
+ /* NADC divider value */
+ data = snd_soc_read(codec, AIC32X4_NADC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_NADC, data | aic32x4_divs[i].nadc);
+
+ /* MADC divider value */
+ data = snd_soc_read(codec, AIC32X4_MADC);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_MADC, data | aic32x4_divs[i].madc);
+
+ /* AOSR value */
+ snd_soc_write(codec, AIC32X4_AOSR, aic32x4_divs[i].aosr);
+
+ /* BCLK N divider */
+ data = snd_soc_read(codec, AIC32X4_BCLKN);
+ data &= ~(0x7f);
+ snd_soc_write(codec, AIC32X4_BCLKN, data | aic32x4_divs[i].blck_N);
+
+ data = snd_soc_read(codec, AIC32X4_IFACE1);
+ data = data & ~(3 << 4);
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ data |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ data |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_DOSRMSB_SHIFT);
+ break;
+ }
+ snd_soc_write(codec, AIC32X4_IFACE1, data);
+
+ return 0;
+}
+
+static int aic32x4_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u8 dac_reg;
+
+ dac_reg = snd_soc_read(codec, AIC32X4_DACMUTE) & ~AIC32X4_MUTEON;
+ if (mute)
+ snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg | AIC32X4_MUTEON);
+ else
+ snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg);
+ return 0;
+}
+
+static int aic32x4_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u8 value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ if (aic32x4->master) {
+ /* Switch on PLL */
+ value = snd_soc_read(codec, AIC32X4_PLLPR);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (value | AIC32X4_PLLEN));
+
+ /* Switch on NDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_NDAC);
+ snd_soc_write(codec, AIC32X4_NDAC,
+ value | AIC32X4_NDACEN);
+
+ /* Switch on MDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_MDAC);
+ snd_soc_write(codec, AIC32X4_MDAC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on NADC Divider */
+ value = snd_soc_read(codec, AIC32X4_NADC);
+ snd_soc_write(codec, AIC32X4_NADC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on MADC Divider */
+ value = snd_soc_read(codec, AIC32X4_MADC);
+ snd_soc_write(codec, AIC32X4_MADC,
+ value | AIC32X4_MDACEN);
+
+ /* Switch on BCLK_N Divider */
+ value = snd_soc_read(codec, AIC32X4_BCLKN);
+ snd_soc_write(codec, AIC32X4_BCLKN,
+ value | AIC32X4_BCLKEN);
+ }
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (aic32x4->master) {
+ /* Switch off PLL */
+ value = snd_soc_read(codec, AIC32X4_PLLPR);
+ snd_soc_write(codec, AIC32X4_PLLPR,
+ (value & ~AIC32X4_PLLEN));
+
+ /* Switch off NDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_NDAC);
+ snd_soc_write(codec, AIC32X4_NDAC,
+ value & ~AIC32X4_NDACEN);
+
+ /* Switch off MDAC Divider */
+ value = snd_soc_read(codec, AIC32X4_MDAC);
+ snd_soc_write(codec, AIC32X4_MDAC,
+ value & ~AIC32X4_MDACEN);
+
+ /* Switch off NADC Divider */
+ value = snd_soc_read(codec, AIC32X4_NADC);
+ snd_soc_write(codec, AIC32X4_NADC,
+ value & ~AIC32X4_NDACEN);
+
+ /* Switch off MADC Divider */
+ value = snd_soc_read(codec, AIC32X4_MADC);
+ snd_soc_write(codec, AIC32X4_MADC,
+ value & ~AIC32X4_MDACEN);
+ value = snd_soc_read(codec, AIC32X4_BCLKN);
+
+ /* Switch off BCLK_N Divider */
+ snd_soc_write(codec, AIC32X4_BCLKN,
+ value & ~AIC32X4_BCLKEN);
+ }
+ break;
+ case SND_SOC_BIAS_OFF:
+ break;
+ }
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+#define AIC32X4_RATES SNDRV_PCM_RATE_8000_48000
+#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
+ | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_ops aic32x4_ops = {
+ .hw_params = aic32x4_hw_params,
+ .digital_mute = aic32x4_mute,
+ .set_fmt = aic32x4_set_dai_fmt,
+ .set_sysclk = aic32x4_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver aic32x4_dai = {
+ .name = "tlv320aic32x4-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC32X4_RATES,
+ .formats = AIC32X4_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = AIC32X4_RATES,
+ .formats = AIC32X4_FORMATS,},
+ .ops = &aic32x4_ops,
+ .symmetric_rates = 1,
+};
+
+static int aic32x4_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int aic32x4_resume(struct snd_soc_codec *codec)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+static int aic32x4_probe(struct snd_soc_codec *codec)
+{
+ struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
+ u32 tmp_reg;
+
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->control_data = aic32x4->control_data;
+
+ snd_soc_write(codec, AIC32X4_RESET, 0x01);
+
+ /* Power platform configuration */
+ if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) {
+ snd_soc_write(codec, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN |
+ AIC32X4_MICBIAS_2075V);
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) {
+ snd_soc_write(codec, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE);
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) {
+ snd_soc_write(codec, AIC32X4_LDOCTL, AIC32X4_LDOCTLEN);
+ }
+ tmp_reg = snd_soc_read(codec, AIC32X4_CMMODE);
+ if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) {
+ tmp_reg |= AIC32X4_LDOIN_18_36;
+ }
+ if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) {
+ tmp_reg |= AIC32X4_LDOIN2HP;
+ }
+ snd_soc_write(codec, AIC32X4_CMMODE, tmp_reg);
+
+ /* Do DACs need to be swapped? */
+ if (aic32x4->swapdacs) {
+ snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2RCHN | AIC32X4_RDAC2LCHN);
+ } else {
+ snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN);
+ }
+
+ /* Mic PGA routing */
+ if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) {
+ snd_soc_write(codec, AIC32X4_LMICPGANIN, AIC32X4_LMICPGANIN_IN2R_10K);
+ }
+ if (aic32x4->micpga_routing | AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) {
+ snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_IN1L_10K);
+ }
+
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ snd_soc_add_controls(codec, aic32x4_snd_controls,
+ ARRAY_SIZE(aic32x4_snd_controls));
+ aic32x4_add_widgets(codec);
+
+ return 0;
+}
+
+static int aic32x4_remove(struct snd_soc_codec *codec)
+{
+ aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = {
+ .read = aic32x4_read,
+ .write = aic32x4_write,
+ .probe = aic32x4_probe,
+ .remove = aic32x4_remove,
+ .suspend = aic32x4_suspend,
+ .resume = aic32x4_resume,
+ .set_bias_level = aic32x4_set_bias_level,
+};
+
+static __devinit int aic32x4_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct aic32x4_pdata *pdata = i2c->dev.platform_data;
+ struct aic32x4_priv *aic32x4;
+ int ret;
+
+ aic32x4 = kzalloc(sizeof(struct aic32x4_priv), GFP_KERNEL);
+ if (aic32x4 == NULL)
+ return -ENOMEM;
+
+ aic32x4->control_data = i2c;
+ i2c_set_clientdata(i2c, aic32x4);
+
+ if (pdata) {
+ aic32x4->power_cfg = pdata->power_cfg;
+ aic32x4->swapdacs = pdata->swapdacs;
+ aic32x4->micpga_routing = pdata->micpga_routing;
+ } else {
+ aic32x4->power_cfg = 0;
+ aic32x4->swapdacs = false;
+ aic32x4->micpga_routing = 0;
+ }
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_aic32x4, &aic32x4_dai, 1);
+ if (ret < 0)
+ kfree(aic32x4);
+ return ret;
+}
+
+static __devexit int aic32x4_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id aic32x4_i2c_id[] = {
+ { "tlv320aic32x4", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
+
+static struct i2c_driver aic32x4_i2c_driver = {
+ .driver = {
+ .name = "tlv320aic32x4",
+ .owner = THIS_MODULE,
+ },
+ .probe = aic32x4_i2c_probe,
+ .remove = __devexit_p(aic32x4_i2c_remove),
+ .id_table = aic32x4_i2c_id,
+};
+
+static int __init aic32x4_modinit(void)
+{
+ int ret = 0;
+
+ ret = i2c_add_driver(&aic32x4_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register aic32x4 I2C driver: %d\n",
+ ret);
+ }
+ return ret;
+}
+module_init(aic32x4_modinit);
+
+static void __exit aic32x4_exit(void)
+{
+ i2c_del_driver(&aic32x4_i2c_driver);
+}
+module_exit(aic32x4_exit);
+
+MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver");
+MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tlv320aic32x4.h b/sound/soc/codecs/tlv320aic32x4.h
new file mode 100644
index 00000000000..aae2b244039
--- /dev/null
+++ b/sound/soc/codecs/tlv320aic32x4.h
@@ -0,0 +1,143 @@
+/*
+ * tlv320aic32x4.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+
+#ifndef _TLV320AIC32X4_H
+#define _TLV320AIC32X4_H
+
+/* tlv320aic32x4 register space (in decimal to match datasheet) */
+
+#define AIC32X4_PAGE1 128
+
+#define AIC32X4_PSEL 0
+#define AIC32X4_RESET 1
+#define AIC32X4_CLKMUX 4
+#define AIC32X4_PLLPR 5
+#define AIC32X4_PLLJ 6
+#define AIC32X4_PLLDMSB 7
+#define AIC32X4_PLLDLSB 8
+#define AIC32X4_NDAC 11
+#define AIC32X4_MDAC 12
+#define AIC32X4_DOSRMSB 13
+#define AIC32X4_DOSRLSB 14
+#define AIC32X4_NADC 18
+#define AIC32X4_MADC 19
+#define AIC32X4_AOSR 20
+#define AIC32X4_CLKMUX2 25
+#define AIC32X4_CLKOUTM 26
+#define AIC32X4_IFACE1 27
+#define AIC32X4_IFACE2 28
+#define AIC32X4_IFACE3 29
+#define AIC32X4_BCLKN 30
+#define AIC32X4_IFACE4 31
+#define AIC32X4_IFACE5 32
+#define AIC32X4_IFACE6 33
+#define AIC32X4_DOUTCTL 53
+#define AIC32X4_DINCTL 54
+#define AIC32X4_DACSPB 60
+#define AIC32X4_ADCSPB 61
+#define AIC32X4_DACSETUP 63
+#define AIC32X4_DACMUTE 64
+#define AIC32X4_LDACVOL 65
+#define AIC32X4_RDACVOL 66
+#define AIC32X4_ADCSETUP 81
+#define AIC32X4_ADCFGA 82
+#define AIC32X4_LADCVOL 83
+#define AIC32X4_RADCVOL 84
+#define AIC32X4_LAGC1 86
+#define AIC32X4_LAGC2 87
+#define AIC32X4_LAGC3 88
+#define AIC32X4_LAGC4 89
+#define AIC32X4_LAGC5 90
+#define AIC32X4_LAGC6 91
+#define AIC32X4_LAGC7 92
+#define AIC32X4_RAGC1 94
+#define AIC32X4_RAGC2 95
+#define AIC32X4_RAGC3 96
+#define AIC32X4_RAGC4 97
+#define AIC32X4_RAGC5 98
+#define AIC32X4_RAGC6 99
+#define AIC32X4_RAGC7 100
+#define AIC32X4_PWRCFG (AIC32X4_PAGE1 + 1)
+#define AIC32X4_LDOCTL (AIC32X4_PAGE1 + 2)
+#define AIC32X4_OUTPWRCTL (AIC32X4_PAGE1 + 9)
+#define AIC32X4_CMMODE (AIC32X4_PAGE1 + 10)
+#define AIC32X4_HPLROUTE (AIC32X4_PAGE1 + 12)
+#define AIC32X4_HPRROUTE (AIC32X4_PAGE1 + 13)
+#define AIC32X4_LOLROUTE (AIC32X4_PAGE1 + 14)
+#define AIC32X4_LORROUTE (AIC32X4_PAGE1 + 15)
+#define AIC32X4_HPLGAIN (AIC32X4_PAGE1 + 16)
+#define AIC32X4_HPRGAIN (AIC32X4_PAGE1 + 17)
+#define AIC32X4_LOLGAIN (AIC32X4_PAGE1 + 18)
+#define AIC32X4_LORGAIN (AIC32X4_PAGE1 + 19)
+#define AIC32X4_HEADSTART (AIC32X4_PAGE1 + 20)
+#define AIC32X4_MICBIAS (AIC32X4_PAGE1 + 51)
+#define AIC32X4_LMICPGAPIN (AIC32X4_PAGE1 + 52)
+#define AIC32X4_LMICPGANIN (AIC32X4_PAGE1 + 54)
+#define AIC32X4_RMICPGAPIN (AIC32X4_PAGE1 + 55)
+#define AIC32X4_RMICPGANIN (AIC32X4_PAGE1 + 57)
+#define AIC32X4_FLOATINGINPUT (AIC32X4_PAGE1 + 58)
+#define AIC32X4_LMICPGAVOL (AIC32X4_PAGE1 + 59)
+#define AIC32X4_RMICPGAVOL (AIC32X4_PAGE1 + 60)
+
+#define AIC32X4_FREQ_12000000 12000000
+#define AIC32X4_FREQ_24000000 24000000
+#define AIC32X4_FREQ_25000000 25000000
+
+#define AIC32X4_WORD_LEN_16BITS 0x00
+#define AIC32X4_WORD_LEN_20BITS 0x01
+#define AIC32X4_WORD_LEN_24BITS 0x02
+#define AIC32X4_WORD_LEN_32BITS 0x03
+
+#define AIC32X4_I2S_MODE 0x00
+#define AIC32X4_DSP_MODE 0x01
+#define AIC32X4_RIGHT_JUSTIFIED_MODE 0x02
+#define AIC32X4_LEFT_JUSTIFIED_MODE 0x03
+
+#define AIC32X4_AVDDWEAKDISABLE 0x08
+#define AIC32X4_LDOCTLEN 0x01
+
+#define AIC32X4_LDOIN_18_36 0x01
+#define AIC32X4_LDOIN2HP 0x02
+
+#define AIC32X4_DACSPBLOCK_MASK 0x1f
+#define AIC32X4_ADCSPBLOCK_MASK 0x1f
+
+#define AIC32X4_PLLJ_SHIFT 6
+#define AIC32X4_DOSRMSB_SHIFT 4
+
+#define AIC32X4_PLLCLKIN 0x03
+
+#define AIC32X4_MICBIAS_LDOIN 0x08
+#define AIC32X4_MICBIAS_2075V 0x60
+
+#define AIC32X4_LMICPGANIN_IN2R_10K 0x10
+#define AIC32X4_RMICPGANIN_IN1L_10K 0x10
+
+#define AIC32X4_LMICPGAVOL_NOGAIN 0x80
+#define AIC32X4_RMICPGAVOL_NOGAIN 0x80
+
+#define AIC32X4_BCLKMASTER 0x08
+#define AIC32X4_WCLKMASTER 0x04
+#define AIC32X4_PLLEN (0x01 << 7)
+#define AIC32X4_NDACEN (0x01 << 7)
+#define AIC32X4_MDACEN (0x01 << 7)
+#define AIC32X4_NADCEN (0x01 << 7)
+#define AIC32X4_MADCEN (0x01 << 7)
+#define AIC32X4_BCLKEN (0x01 << 7)
+#define AIC32X4_DACEN (0x03 << 6)
+#define AIC32X4_RDAC2LCHN (0x02 << 2)
+#define AIC32X4_LDAC2RCHN (0x02 << 4)
+#define AIC32X4_LDAC2LCHN (0x01 << 4)
+#define AIC32X4_RDAC2RCHN (0x01 << 2)
+
+#define AIC32X4_SSTEP2WCLK 0x01
+#define AIC32X4_MUTEON 0x0C
+#define AIC32X4_DACMOD2BCLK 0x01
+
+#endif /* _TLV320AIC32X4_H */
diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c
index 71d7be8ac48..00b6d87e7bd 100644
--- a/sound/soc/codecs/tlv320dac33.c
+++ b/sound/soc/codecs/tlv320dac33.c
@@ -1615,6 +1615,7 @@ static const struct i2c_device_id tlv320dac33_i2c_id[] = {
},
{ },
};
+MODULE_DEVICE_TABLE(i2c, tlv320dac33_i2c_id);
static struct i2c_driver tlv320dac33_i2c_driver = {
.driver = {
diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c
index 4bbf1b15a49..482fcdb59bf 100644
--- a/sound/soc/codecs/twl6040.c
+++ b/sound/soc/codecs/twl6040.c
@@ -724,8 +724,8 @@ static int twl6040_power_mode_event(struct snd_soc_dapm_widget *w,
return 0;
}
-void twl6040_hs_jack_report(struct snd_soc_codec *codec,
- struct snd_soc_jack *jack, int report)
+static void twl6040_hs_jack_report(struct snd_soc_codec *codec,
+ struct snd_soc_jack *jack, int report)
{
struct twl6040_data *priv = snd_soc_codec_get_drvdata(codec);
int status;
diff --git a/sound/soc/codecs/wm2000.c b/sound/soc/codecs/wm2000.c
index 80ddf4fd23d..a3b9cbb20ee 100644
--- a/sound/soc/codecs/wm2000.c
+++ b/sound/soc/codecs/wm2000.c
@@ -836,24 +836,25 @@ static void wm2000_i2c_shutdown(struct i2c_client *i2c)
}
#ifdef CONFIG_PM
-static int wm2000_i2c_suspend(struct i2c_client *i2c, pm_message_t mesg)
+static int wm2000_i2c_suspend(struct device *dev)
{
+ struct i2c_client *i2c = to_i2c_client(dev);
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
return wm2000_anc_transition(wm2000, ANC_OFF);
}
-static int wm2000_i2c_resume(struct i2c_client *i2c)
+static int wm2000_i2c_resume(struct device *dev)
{
+ struct i2c_client *i2c = to_i2c_client(dev);
struct wm2000_priv *wm2000 = dev_get_drvdata(&i2c->dev);
return wm2000_anc_set_mode(wm2000);
}
-#else
-#define wm2000_i2c_suspend NULL
-#define wm2000_i2c_resume NULL
#endif
+static SIMPLE_DEV_PM_OPS(wm2000_pm, wm2000_i2c_suspend, wm2000_i2c_resume);
+
static const struct i2c_device_id wm2000_i2c_id[] = {
{ "wm2000", 0 },
{ }
@@ -864,11 +865,10 @@ static struct i2c_driver wm2000_i2c_driver = {
.driver = {
.name = "wm2000",
.owner = THIS_MODULE,
+ .pm = &wm2000_pm,
},
.probe = wm2000_i2c_probe,
.remove = __devexit_p(wm2000_i2c_remove),
- .suspend = wm2000_i2c_suspend,
- .resume = wm2000_i2c_resume,
.shutdown = wm2000_i2c_shutdown,
.id_table = wm2000_i2c_id,
};
diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c
index 5eb2f501ce3..4fd4d8dca0f 100644
--- a/sound/soc/codecs/wm8523.c
+++ b/sound/soc/codecs/wm8523.c
@@ -58,7 +58,7 @@ static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = {
0x0000, /* R8 - ZERO_DETECT */
};
-static int wm8523_volatile_register(unsigned int reg)
+static int wm8523_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8523_DEVICE_ID:
@@ -414,7 +414,6 @@ static int wm8523_resume(struct snd_soc_codec *codec)
static int wm8523_probe(struct snd_soc_codec *codec)
{
struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec);
- u16 *reg_cache = codec->reg_cache;
int ret, i;
codec->hw_write = (hw_write_t)i2c_master_send;
@@ -471,8 +470,9 @@ static int wm8523_probe(struct snd_soc_codec *codec)
}
/* Change some default settings - latch VU and enable ZC */
- reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU;
- reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC;
+ snd_soc_update_bits(codec, WM8523_DAC_GAINR,
+ WM8523_DACR_VU, WM8523_DACR_VU);
+ snd_soc_update_bits(codec, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC);
wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c
index 494f2d31d75..25af901fe81 100644
--- a/sound/soc/codecs/wm8741.c
+++ b/sound/soc/codecs/wm8741.c
@@ -421,7 +421,6 @@ static int wm8741_resume(struct snd_soc_codec *codec)
static int wm8741_probe(struct snd_soc_codec *codec)
{
struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec);
- u16 *reg_cache = codec->reg_cache;
int ret = 0;
ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type);
@@ -437,10 +436,14 @@ static int wm8741_probe(struct snd_soc_codec *codec)
}
/* Change some default settings - latch VU */
- reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL;
- reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM;
- reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL;
- reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM;
+ snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION,
+ WM8741_UPDATELL, WM8741_UPDATELL);
+ snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION,
+ WM8741_UPDATELM, WM8741_UPDATELM);
+ snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
+ WM8741_UPDATERL, WM8741_UPDATERL);
+ snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION,
+ WM8741_UPDATERM, WM8741_UPDATERM);
snd_soc_add_controls(codec, wm8741_snd_controls,
ARRAY_SIZE(wm8741_snd_controls));
diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c
index 79b02ae125c..3f09deea8d9 100644
--- a/sound/soc/codecs/wm8753.c
+++ b/sound/soc/codecs/wm8753.c
@@ -55,8 +55,10 @@ static int caps_charge = 2000;
module_param(caps_charge, int, 0);
MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");
-static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
- struct snd_soc_dai *dai, unsigned int hifi);
+static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt);
+static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt);
/*
* wm8753 register cache
@@ -87,6 +89,10 @@ struct wm8753_priv {
enum snd_soc_control_type control_type;
unsigned int sysclk;
unsigned int pcmclk;
+
+ unsigned int voice_fmt;
+ unsigned int hifi_fmt;
+
int dai_func;
};
@@ -170,9 +176,9 @@ static int wm8753_get_dai(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- int mode = snd_soc_read(codec, WM8753_IOCTL);
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
- ucontrol->value.integer.value[0] = (mode & 0xc) >> 2;
+ ucontrol->value.integer.value[0] = wm8753->dai_func;
return 0;
}
@@ -180,16 +186,26 @@ static int wm8753_set_dai(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- int mode = snd_soc_read(codec, WM8753_IOCTL);
struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ u16 ioctl;
+
+ if (codec->active)
+ return -EBUSY;
+
+ ioctl = snd_soc_read(codec, WM8753_IOCTL);
+
+ wm8753->dai_func = ucontrol->value.integer.value[0];
+
+ if (((ioctl >> 2) & 0x3) == wm8753->dai_func)
+ return 1;
+
+ ioctl = (ioctl & 0x1f3) | (wm8753->dai_func << 2);
+ snd_soc_write(codec, WM8753_IOCTL, ioctl);
- if (((mode & 0xc) >> 2) == ucontrol->value.integer.value[0])
- return 0;
- mode &= 0xfff3;
- mode |= (ucontrol->value.integer.value[0] << 2);
+ wm8753_hifi_write_dai_fmt(codec, wm8753->hifi_fmt);
+ wm8753_voice_write_dai_fmt(codec, wm8753->voice_fmt);
- wm8753->dai_func = ucontrol->value.integer.value[0];
return 1;
}
@@ -828,10 +844,9 @@ static int wm8753_set_dai_sysclk(struct snd_soc_dai *codec_dai,
/*
* Set's ADC and Voice DAC format.
*/
-static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 voice = snd_soc_read(codec, WM8753_PCM) & 0x01ec;
/* interface format */
@@ -858,13 +873,6 @@ static int wm8753_vdac_adc_set_dai_fmt(struct snd_soc_dai *codec_dai,
return 0;
}
-static int wm8753_pcm_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- wm8753_set_dai_mode(dai->codec, dai, 0);
- return 0;
-}
-
/*
* Set PCM DAI bit size and sample rate.
*/
@@ -905,10 +913,9 @@ static int wm8753_pcm_hw_params(struct snd_pcm_substream *substream,
/*
* Set's PCM dai fmt and BCLK.
*/
-static int wm8753_pcm_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_pcm_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 voice, ioctl;
voice = snd_soc_read(codec, WM8753_PCM) & 0x011f;
@@ -999,10 +1006,9 @@ static int wm8753_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
/*
* Set's HiFi DAC format.
*/
-static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_hdac_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 hifi = snd_soc_read(codec, WM8753_HIFI) & 0x01e0;
/* interface format */
@@ -1032,10 +1038,9 @@ static int wm8753_hdac_set_dai_fmt(struct snd_soc_dai *codec_dai,
/*
* Set's I2S DAI format.
*/
-static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_i2s_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 ioctl, hifi;
hifi = snd_soc_read(codec, WM8753_HIFI) & 0x011f;
@@ -1098,13 +1103,6 @@ static int wm8753_i2s_set_dai_fmt(struct snd_soc_dai *codec_dai,
return 0;
}
-static int wm8753_i2s_startup(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
-{
- wm8753_set_dai_mode(dai->codec, dai, 1);
- return 0;
-}
-
/*
* Set PCM DAI bit size and sample rate.
*/
@@ -1147,61 +1145,117 @@ static int wm8753_i2s_hw_params(struct snd_pcm_substream *substream,
return 0;
}
-static int wm8753_mode1v_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_mode1v_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 clock;
/* set clk source as pcmclk */
clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
snd_soc_write(codec, WM8753_CLOCK, clock);
- if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
- return -EINVAL;
- return wm8753_pcm_set_dai_fmt(codec_dai, fmt);
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
}
-static int wm8753_mode1h_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_mode1h_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
- return -EINVAL;
- return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+ return wm8753_hdac_set_dai_fmt(codec, fmt);
}
-static int wm8753_mode2_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_mode2_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 clock;
/* set clk source as pcmclk */
clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
snd_soc_write(codec, WM8753_CLOCK, clock);
- if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
- return -EINVAL;
- return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
}
-static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int wm8753_mode3_4_set_dai_fmt(struct snd_soc_codec *codec,
unsigned int fmt)
{
- struct snd_soc_codec *codec = codec_dai->codec;
u16 clock;
/* set clk source as mclk */
clock = snd_soc_read(codec, WM8753_CLOCK) & 0xfffb;
snd_soc_write(codec, WM8753_CLOCK, clock | 0x4);
- if (wm8753_hdac_set_dai_fmt(codec_dai, fmt) < 0)
+ if (wm8753_hdac_set_dai_fmt(codec, fmt) < 0)
return -EINVAL;
- if (wm8753_vdac_adc_set_dai_fmt(codec_dai, fmt) < 0)
- return -EINVAL;
- return wm8753_i2s_set_dai_fmt(codec_dai, fmt);
+ return wm8753_vdac_adc_set_dai_fmt(codec, fmt);
}
+static int wm8753_hifi_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (wm8753->dai_func) {
+ case 0:
+ ret = wm8753_mode1h_set_dai_fmt(codec, fmt);
+ break;
+ case 1:
+ ret = wm8753_mode2_set_dai_fmt(codec, fmt);
+ break;
+ case 2:
+ case 3:
+ ret = wm8753_mode3_4_set_dai_fmt(codec, fmt);
+ break;
+ default:
+ break;
+ }
+ if (ret)
+ return ret;
+
+ return wm8753_i2s_set_dai_fmt(codec, fmt);
+}
+
+static int wm8753_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ wm8753->hifi_fmt = fmt;
+
+ return wm8753_hifi_write_dai_fmt(codec, fmt);
+};
+
+static int wm8753_voice_write_dai_fmt(struct snd_soc_codec *codec,
+ unsigned int fmt)
+{
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ if (wm8753->dai_func != 0)
+ return 0;
+
+ ret = wm8753_mode1v_set_dai_fmt(codec, fmt);
+ if (ret)
+ return ret;
+ ret = wm8753_pcm_set_dai_fmt(codec, fmt);
+ if (ret)
+ return ret;
+
+ return 0;
+};
+
+static int wm8753_voice_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
+
+ wm8753->voice_fmt = fmt;
+
+ return wm8753_voice_write_dai_fmt(codec, fmt);
+};
+
static int wm8753_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
@@ -1268,57 +1322,25 @@ static int wm8753_set_bias_level(struct snd_soc_codec *codec,
* 3. Voice disabled - HIFI over HIFI
* 4. Voice disabled - HIFI over HIFI, uses voice DAI LRC for capture
*/
-static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode1 = {
- .startup = wm8753_i2s_startup,
+static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode = {
.hw_params = wm8753_i2s_hw_params,
.digital_mute = wm8753_mute,
- .set_fmt = wm8753_mode1h_set_dai_fmt,
- .set_clkdiv = wm8753_set_dai_clkdiv,
- .set_pll = wm8753_set_dai_pll,
- .set_sysclk = wm8753_set_dai_sysclk,
-};
-
-static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode1 = {
- .startup = wm8753_pcm_startup,
- .hw_params = wm8753_pcm_hw_params,
- .digital_mute = wm8753_mute,
- .set_fmt = wm8753_mode1v_set_dai_fmt,
+ .set_fmt = wm8753_hifi_set_dai_fmt,
.set_clkdiv = wm8753_set_dai_clkdiv,
.set_pll = wm8753_set_dai_pll,
.set_sysclk = wm8753_set_dai_sysclk,
};
-static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode2 = {
- .startup = wm8753_pcm_startup,
+static struct snd_soc_dai_ops wm8753_dai_ops_voice_mode = {
.hw_params = wm8753_pcm_hw_params,
.digital_mute = wm8753_mute,
- .set_fmt = wm8753_mode2_set_dai_fmt,
- .set_clkdiv = wm8753_set_dai_clkdiv,
- .set_pll = wm8753_set_dai_pll,
- .set_sysclk = wm8753_set_dai_sysclk,
-};
-
-static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode3 = {
- .startup = wm8753_i2s_startup,
- .hw_params = wm8753_i2s_hw_params,
- .digital_mute = wm8753_mute,
- .set_fmt = wm8753_mode3_4_set_dai_fmt,
- .set_clkdiv = wm8753_set_dai_clkdiv,
- .set_pll = wm8753_set_dai_pll,
- .set_sysclk = wm8753_set_dai_sysclk,
-};
-
-static struct snd_soc_dai_ops wm8753_dai_ops_hifi_mode4 = {
- .startup = wm8753_i2s_startup,
- .hw_params = wm8753_i2s_hw_params,
- .digital_mute = wm8753_mute,
- .set_fmt = wm8753_mode3_4_set_dai_fmt,
+ .set_fmt = wm8753_voice_set_dai_fmt,
.set_clkdiv = wm8753_set_dai_clkdiv,
.set_pll = wm8753_set_dai_pll,
.set_sysclk = wm8753_set_dai_sysclk,
};
-static struct snd_soc_dai_driver wm8753_all_dai[] = {
+static struct snd_soc_dai_driver wm8753_dai[] = {
/* DAI HiFi mode 1 */
{ .name = "wm8753-hifi",
.playback = {
@@ -1326,14 +1348,16 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = {
.channels_min = 1,
.channels_max = 2,
.rates = WM8753_RATES,
- .formats = WM8753_FORMATS},
+ .formats = WM8753_FORMATS
+ },
.capture = { /* dummy for fast DAI switching */
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8753_RATES,
- .formats = WM8753_FORMATS},
- .ops = &wm8753_dai_ops_hifi_mode1,
+ .formats = WM8753_FORMATS
+ },
+ .ops = &wm8753_dai_ops_hifi_mode,
},
/* DAI Voice mode 1 */
{ .name = "wm8753-voice",
@@ -1342,97 +1366,19 @@ static struct snd_soc_dai_driver wm8753_all_dai[] = {
.channels_min = 1,
.channels_max = 1,
.rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .ops = &wm8753_dai_ops_voice_mode1,
-},
-/* DAI HiFi mode 2 - dummy */
-{ .name = "wm8753-hifi",
-},
-/* DAI Voice mode 2 */
-{ .name = "wm8753-voice",
- .playback = {
- .stream_name = "Voice Playback",
- .channels_min = 1,
- .channels_max = 1,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .ops = &wm8753_dai_ops_voice_mode2,
-},
-/* DAI HiFi mode 3 */
-{ .name = "wm8753-hifi",
- .playback = {
- .stream_name = "HiFi Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .ops = &wm8753_dai_ops_hifi_mode3,
-},
-/* DAI Voice mode 3 - dummy */
-{ .name = "wm8753-voice",
-},
-/* DAI HiFi mode 4 */
-{ .name = "wm8753-hifi",
- .playback = {
- .stream_name = "HiFi Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
+ .formats = WM8753_FORMATS,
+ },
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = WM8753_RATES,
- .formats = WM8753_FORMATS,},
- .ops = &wm8753_dai_ops_hifi_mode4,
-},
-/* DAI Voice mode 4 - dummy */
-{ .name = "wm8753-voice",
-},
-};
-
-static struct snd_soc_dai_driver wm8753_dai[] = {
- {
- .name = "wm8753-aif0",
- },
- {
- .name = "wm8753-aif1",
+ .formats = WM8753_FORMATS,
},
+ .ops = &wm8753_dai_ops_voice_mode,
+},
};
-static void wm8753_set_dai_mode(struct snd_soc_codec *codec,
- struct snd_soc_dai *dai, unsigned int hifi)
-{
- struct wm8753_priv *wm8753 = snd_soc_codec_get_drvdata(codec);
-
- if (wm8753->dai_func < 4) {
- if (hifi)
- dai->driver = &wm8753_all_dai[wm8753->dai_func << 1];
- else
- dai->driver = &wm8753_all_dai[(wm8753->dai_func << 1) + 1];
- }
- snd_soc_write(codec, WM8753_IOCTL, wm8753->dai_func);
-}
-
static void wm8753_work(struct work_struct *work)
{
struct snd_soc_dapm_context *dapm =
diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c
index 6dae1b40c9f..6785688f880 100644
--- a/sound/soc/codecs/wm8804.c
+++ b/sound/soc/codecs/wm8804.c
@@ -175,7 +175,7 @@ static int txsrc_put(struct snd_kcontrol *kcontrol,
return 0;
}
-static int wm8804_volatile(unsigned int reg)
+static int wm8804_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8804_RST_DEVID1:
diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c
index cd0959926d1..449ea09a193 100644
--- a/sound/soc/codecs/wm8900.c
+++ b/sound/soc/codecs/wm8900.c
@@ -180,7 +180,7 @@ static const u16 wm8900_reg_defaults[WM8900_MAXREG] = {
/* Remaining registers all zero */
};
-static int wm8900_volatile_register(unsigned int reg)
+static int wm8900_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8900_REG_ID:
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
index 017d99ceb42..ae1cadfae84 100644
--- a/sound/soc/codecs/wm8903.c
+++ b/sound/soc/codecs/wm8903.c
@@ -2,6 +2,7 @@
* wm8903.c -- WM8903 ALSA SoC Audio driver
*
* Copyright 2008 Wolfson Microelectronics
+ * Copyright 2011 NVIDIA, Inc.
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
@@ -19,6 +20,7 @@
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/gpio.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
@@ -213,6 +215,7 @@ static u16 wm8903_reg_defaults[] = {
};
struct wm8903_priv {
+ struct snd_soc_codec *codec;
int sysclk;
int irq;
@@ -220,25 +223,36 @@ struct wm8903_priv {
int fs;
int deemph;
+ int dcs_pending;
+ int dcs_cache[4];
+
/* Reference count */
int class_w_users;
- struct completion wseq;
-
struct snd_soc_jack *mic_jack;
int mic_det;
int mic_short;
int mic_last_report;
int mic_delay;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
};
-static int wm8903_volatile_register(unsigned int reg)
+static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8903_SW_RESET_AND_ID:
case WM8903_REVISION_NUMBER:
case WM8903_INTERRUPT_STATUS_1:
case WM8903_WRITE_SEQUENCER_4:
+ case WM8903_POWER_MANAGEMENT_3:
+ case WM8903_POWER_MANAGEMENT_2:
+ case WM8903_DC_SERVO_READBACK_1:
+ case WM8903_DC_SERVO_READBACK_2:
+ case WM8903_DC_SERVO_READBACK_3:
+ case WM8903_DC_SERVO_READBACK_4:
return 1;
default:
@@ -246,50 +260,6 @@ static int wm8903_volatile_register(unsigned int reg)
}
}
-static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start)
-{
- u16 reg[5];
- struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
-
- BUG_ON(start > 48);
-
- /* Enable the sequencer if it's not already on */
- reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0);
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0,
- reg[0] | WM8903_WSEQ_ENA);
-
- dev_dbg(codec->dev, "Starting sequence at %d\n", start);
-
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3,
- start | WM8903_WSEQ_START);
-
- /* Wait for it to complete. If we have the interrupt wired up then
- * that will break us out of the poll early.
- */
- do {
- wait_for_completion_timeout(&wm8903->wseq,
- msecs_to_jiffies(10));
-
- reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4);
- } while (reg[4] & WM8903_WSEQ_BUSY);
-
- dev_dbg(codec->dev, "Sequence complete\n");
-
- /* Disable the sequencer again if we enabled it */
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]);
-
- return 0;
-}
-
-static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache)
-{
- int i;
-
- /* There really ought to be something better we can do here :/ */
- for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++)
- cache[i] = codec->hw_read(codec, i);
-}
-
static void wm8903_reset(struct snd_soc_codec *codec)
{
snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0);
@@ -297,11 +267,6 @@ static void wm8903_reset(struct snd_soc_codec *codec)
sizeof(wm8903_reg_defaults));
}
-#define WM8903_OUTPUT_SHORT 0x8
-#define WM8903_OUTPUT_OUT 0x4
-#define WM8903_OUTPUT_INT 0x2
-#define WM8903_OUTPUT_IN 0x1
-
static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -311,97 +276,101 @@ static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
return 0;
}
-/*
- * Event for headphone and line out amplifier power changes. Special
- * power up/down sequences are required in order to maximise pop/click
- * performance.
- */
-static int wm8903_output_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
+static int wm8903_dcs_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
- u16 val;
- u16 reg;
- u16 dcs_reg;
- u16 dcs_bit;
- int shift;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
- switch (w->reg) {
- case WM8903_POWER_MANAGEMENT_2:
- reg = WM8903_ANALOGUE_HP_0;
- dcs_bit = 0 + w->shift;
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ wm8903->dcs_pending |= 1 << w->shift;
break;
- case WM8903_POWER_MANAGEMENT_3:
- reg = WM8903_ANALOGUE_LINEOUT_0;
- dcs_bit = 2 + w->shift;
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ 1 << w->shift, 0);
break;
- default:
- BUG();
- return -EINVAL; /* Spurious warning from some compilers */
}
- switch (w->shift) {
- case 0:
- shift = 0;
- break;
- case 1:
- shift = 4;
- break;
- default:
- BUG();
- return -EINVAL; /* Spurious warning from some compilers */
- }
+ return 0;
+}
- if (event & SND_SOC_DAPM_PRE_PMU) {
- val = snd_soc_read(codec, reg);
+#define WM8903_DCS_MODE_WRITE_STOP 0
+#define WM8903_DCS_MODE_START_STOP 2
- /* Short the output */
- val &= ~(WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
- }
+static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm,
+ enum snd_soc_dapm_type event, int subseq)
+{
+ struct snd_soc_codec *codec = container_of(dapm,
+ struct snd_soc_codec, dapm);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int dcs_mode = WM8903_DCS_MODE_WRITE_STOP;
+ int i, val;
- if (event & SND_SOC_DAPM_POST_PMU) {
- val = snd_soc_read(codec, reg);
+ /* Complete any pending DC servo starts */
+ if (wm8903->dcs_pending) {
+ dev_dbg(codec->dev, "Starting DC servo for %x\n",
+ wm8903->dcs_pending);
- val |= (WM8903_OUTPUT_IN << shift);
- snd_soc_write(codec, reg, val);
+ /* If we've no cached values then we need to do startup */
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
- val |= (WM8903_OUTPUT_INT << shift);
- snd_soc_write(codec, reg, val);
+ if (wm8903->dcs_cache[i]) {
+ dev_dbg(codec->dev,
+ "Restore DC servo %d value %x\n",
+ 3 - i, wm8903->dcs_cache[i]);
+
+ snd_soc_write(codec, WM8903_DC_SERVO_4 + i,
+ wm8903->dcs_cache[i] & 0xff);
+ } else {
+ dev_dbg(codec->dev,
+ "Calibrate DC servo %d\n", 3 - i);
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
+ }
+ }
- /* Turn on the output ENA_OUTP */
- val |= (WM8903_OUTPUT_OUT << shift);
- snd_soc_write(codec, reg, val);
+ /* Don't trust the cache for analogue */
+ if (wm8903->class_w_users)
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
- /* Enable the DC servo */
- dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
- dcs_reg |= dcs_bit;
- snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_2,
+ WM8903_DCS_MODE_MASK, dcs_mode);
- /* Remove the short */
- val |= (WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
- }
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ WM8903_DCS_ENA_MASK, wm8903->dcs_pending);
- if (event & SND_SOC_DAPM_PRE_PMD) {
- val = snd_soc_read(codec, reg);
+ switch (dcs_mode) {
+ case WM8903_DCS_MODE_WRITE_STOP:
+ break;
- /* Short the output */
- val &= ~(WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
+ case WM8903_DCS_MODE_START_STOP:
+ msleep(270);
- /* Disable the DC servo */
- dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
- dcs_reg &= ~dcs_bit;
- snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+ /* Cache the measured offsets for digital */
+ if (wm8903->class_w_users)
+ break;
- /* Then disable the intermediate and output stages */
- val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
- WM8903_OUTPUT_IN) << shift);
- snd_soc_write(codec, reg, val);
- }
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
- return 0;
+ val = snd_soc_read(codec,
+ WM8903_DC_SERVO_READBACK_1 + i);
+ dev_dbg(codec->dev, "DC servo %d: %x\n",
+ 3 - i, val);
+ wm8903->dcs_cache[i] = val;
+ }
+ break;
+
+ default:
+ pr_warn("DCS mode %d delay not set\n", dcs_mode);
+ break;
+ }
+
+ wm8903->dcs_pending = 0;
+ }
}
/*
@@ -667,6 +636,22 @@ static const struct soc_enum lsidetone_enum =
static const struct soc_enum rsidetone_enum =
SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum lcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct soc_enum rcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct soc_enum lplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct soc_enum rplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
static const struct snd_kcontrol_new wm8903_snd_controls[] = {
/* Input PGAs - No TLV since the scale depends on PGA mode */
@@ -784,6 +769,18 @@ static const struct snd_kcontrol_new lsidetone_mux =
static const struct snd_kcontrol_new rsidetone_mux =
SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum);
+static const struct snd_kcontrol_new lcapture_mux =
+ SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum);
+
+static const struct snd_kcontrol_new rcapture_mux =
+ SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum);
+
+static const struct snd_kcontrol_new lplay_mux =
+ SOC_DAPM_ENUM("Left Playback Mux", lplay_enum);
+
+static const struct snd_kcontrol_new rplay_mux =
+ SOC_DAPM_ENUM("Right Playback Mux", rplay_enum);
+
static const struct snd_kcontrol_new left_output_mixer[] = {
SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
@@ -847,14 +844,26 @@ SND_SOC_DAPM_MUX("Right Input Mode Mux", SND_SOC_NOPM, 0, 0, &rinput_mode_mux),
SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
-SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
-SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
-SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
-SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
+SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux),
+SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0),
SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
left_output_mixer, ARRAY_SIZE(left_output_mixer)),
@@ -866,23 +875,45 @@ SND_SOC_DAPM_MIXER("Left Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 1, 0,
SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
-SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
- 1, 0, NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
- 0, 0, NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-
-SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
- NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
- NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0,
+ 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0,
+ 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 4, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 5, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
NULL, 0),
@@ -892,10 +923,18 @@ SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0,
SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0),
};
static const struct snd_soc_dapm_route intercon[] = {
+ { "CLK_DSP", NULL, "CLK_SYS" },
+ { "Mic Bias", NULL, "CLK_SYS" },
+ { "HPL_DCS", NULL, "CLK_SYS" },
+ { "HPR_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTL_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTR_DCS", NULL, "CLK_SYS" },
+
{ "Left Input Mux", "IN1L", "IN1L" },
{ "Left Input Mux", "IN2L", "IN2L" },
{ "Left Input Mux", "IN3L", "IN3L" },
@@ -936,18 +975,36 @@ static const struct snd_soc_dapm_route intercon[] = {
{ "Left Input PGA", NULL, "Left Input Mode Mux" },
{ "Right Input PGA", NULL, "Right Input Mode Mux" },
+ { "Left Capture Mux", "Left", "ADCL" },
+ { "Left Capture Mux", "Right", "ADCR" },
+
+ { "Right Capture Mux", "Left", "ADCL" },
+ { "Right Capture Mux", "Right", "ADCR" },
+
+ { "AIFTXL", NULL, "Left Capture Mux" },
+ { "AIFTXR", NULL, "Right Capture Mux" },
+
{ "ADCL", NULL, "Left Input PGA" },
{ "ADCL", NULL, "CLK_DSP" },
{ "ADCR", NULL, "Right Input PGA" },
{ "ADCR", NULL, "CLK_DSP" },
+ { "Left Playback Mux", "Left", "AIFRXL" },
+ { "Left Playback Mux", "Right", "AIFRXR" },
+
+ { "Right Playback Mux", "Left", "AIFRXL" },
+ { "Right Playback Mux", "Right", "AIFRXR" },
+
{ "DACL Sidetone", "Left", "ADCL" },
{ "DACL Sidetone", "Right", "ADCR" },
{ "DACR Sidetone", "Left", "ADCL" },
{ "DACR Sidetone", "Right", "ADCR" },
+ { "DACL", NULL, "Left Playback Mux" },
{ "DACL", NULL, "DACL Sidetone" },
{ "DACL", NULL, "CLK_DSP" },
+
+ { "DACR", NULL, "Right Playback Mux" },
{ "DACR", NULL, "DACR Sidetone" },
{ "DACR", NULL, "CLK_DSP" },
@@ -980,11 +1037,35 @@ static const struct snd_soc_dapm_route intercon[] = {
{ "Left Speaker PGA", NULL, "Left Speaker Mixer" },
{ "Right Speaker PGA", NULL, "Right Speaker Mixer" },
- { "HPOUTL", NULL, "Left Headphone Output PGA" },
- { "HPOUTR", NULL, "Right Headphone Output PGA" },
+ { "HPL_ENA_DLY", NULL, "Left Headphone Output PGA" },
+ { "HPR_ENA_DLY", NULL, "Right Headphone Output PGA" },
+ { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" },
+ { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" },
+
+ { "HPL_DCS", NULL, "DCS Master" },
+ { "HPR_DCS", NULL, "DCS Master" },
+ { "LINEOUTL_DCS", NULL, "DCS Master" },
+ { "LINEOUTR_DCS", NULL, "DCS Master" },
+
+ { "HPL_DCS", NULL, "HPL_ENA_DLY" },
+ { "HPR_DCS", NULL, "HPR_ENA_DLY" },
+ { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" },
+ { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" },
- { "LINEOUTL", NULL, "Left Line Output PGA" },
- { "LINEOUTR", NULL, "Right Line Output PGA" },
+ { "HPL_ENA_OUTP", NULL, "HPL_DCS" },
+ { "HPR_ENA_OUTP", NULL, "HPR_DCS" },
+ { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" },
+ { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" },
+
+ { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" },
+ { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" },
+ { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" },
+ { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" },
+
+ { "HPOUTL", NULL, "HPL_RMV_SHORT" },
+ { "HPOUTR", NULL, "HPR_RMV_SHORT" },
+ { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" },
+ { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" },
{ "LOP", NULL, "Left Speaker PGA" },
{ "LON", NULL, "Left Speaker PGA" },
@@ -1012,29 +1093,71 @@ static int wm8903_add_widgets(struct snd_soc_codec *codec)
static int wm8903_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
- u16 reg;
-
switch (level) {
case SND_SOC_BIAS_ON:
+ break;
+
case SND_SOC_BIAS_PREPARE:
- reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
- reg &= ~(WM8903_VMID_RES_MASK);
- reg |= WM8903_VMID_RES_50K;
- snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_50K);
break;
case SND_SOC_BIAS_STANDBY:
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
- snd_soc_write(codec, WM8903_CLOCK_RATES_2,
- WM8903_CLK_SYS_ENA);
-
- /* Change DC servo dither level in startup sequence */
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11);
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257);
- snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2);
-
- wm8903_run_sequence(codec, 0);
- wm8903_sync_reg_cache(codec, codec->reg_cache);
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_POBCTRL | WM8903_ISEL_MASK |
+ WM8903_STARTUP_BIAS_ENA |
+ WM8903_BIAS_ENA,
+ WM8903_POBCTRL |
+ (2 << WM8903_ISEL_SHIFT) |
+ WM8903_STARTUP_BIAS_ENA);
+
+ snd_soc_update_bits(codec,
+ WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0,
+ WM8903_SPK_DISCHARGE,
+ WM8903_SPK_DISCHARGE);
+
+ msleep(33);
+
+ snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA);
+
+ snd_soc_update_bits(codec,
+ WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0,
+ WM8903_SPK_DISCHARGE, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_TIE_ENA |
+ WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA |
+ WM8903_VMID_SOFT_MASK |
+ WM8903_VMID_RES_MASK |
+ WM8903_VMID_BUF_ENA,
+ WM8903_VMID_TIE_ENA |
+ WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA |
+ (2 << WM8903_VMID_SOFT_SHIFT) |
+ WM8903_VMID_RES_250K |
+ WM8903_VMID_BUF_ENA);
+
+ msleep(129);
+
+ snd_soc_update_bits(codec, WM8903_POWER_MANAGEMENT_5,
+ WM8903_SPKL_ENA | WM8903_SPKR_ENA,
+ 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_SOFT_MASK, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_50K);
+
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_BIAS_ENA | WM8903_POBCTRL,
+ WM8903_BIAS_ENA);
/* By default no bypass paths are enabled so
* enable Class W support.
@@ -1047,17 +1170,32 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec,
WM8903_CP_DYN_V);
}
- reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
- reg &= ~(WM8903_VMID_RES_MASK);
- reg |= WM8903_VMID_RES_250K;
- snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg);
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_RES_MASK,
+ WM8903_VMID_RES_250K);
break;
case SND_SOC_BIAS_OFF:
- wm8903_run_sequence(codec, 32);
- reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2);
- reg &= ~WM8903_CLK_SYS_ENA;
- snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg);
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_BIAS_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_SOFT_MASK,
+ 2 << WM8903_VMID_SOFT_SHIFT);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_BUF_ENA, 0);
+
+ msleep(290);
+
+ snd_soc_update_bits(codec, WM8903_VMID_CONTROL_0,
+ WM8903_VMID_TIE_ENA | WM8903_BUFIO_ENA |
+ WM8903_VMID_IO_ENA | WM8903_VMID_RES_MASK |
+ WM8903_VMID_SOFT_MASK |
+ WM8903_VMID_BUF_ENA, 0);
+
+ snd_soc_update_bits(codec, WM8903_BIAS_CONTROL_0,
+ WM8903_STARTUP_BIAS_ENA, 0);
break;
}
@@ -1510,8 +1648,7 @@ static irqreturn_t wm8903_irq(int irq, void *data)
int_val = snd_soc_read(codec, WM8903_INTERRUPT_STATUS_1) & mask;
if (int_val & WM8903_WSEQ_BUSY_EINT) {
- dev_dbg(codec->dev, "Write sequencer done\n");
- complete(&wm8903->wseq);
+ dev_warn(codec->dev, "Write sequencer done\n");
}
/*
@@ -1635,6 +1772,120 @@ static int wm8903_resume(struct snd_soc_codec *codec)
return 0;
}
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm8903_priv, gpio_chip);
+}
+
+static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ if (offset >= WM8903_NUM_GPIO)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK;
+ val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) |
+ WM8903_GP1_DIR;
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ int reg;
+
+ reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset);
+
+ return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT;
+}
+
+static int wm8903_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK;
+ val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) |
+ (value << WM8903_GP2_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+
+ snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ WM8903_GP1_LVL_MASK,
+ !!value << WM8903_GP1_LVL_SHIFT);
+}
+
+static struct gpio_chip wm8903_template_chip = {
+ .label = "wm8903",
+ .owner = THIS_MODULE,
+ .request = wm8903_gpio_request,
+ .direction_input = wm8903_gpio_direction_in,
+ .get = wm8903_gpio_get,
+ .direction_output = wm8903_gpio_direction_out,
+ .set = wm8903_gpio_set,
+ .can_sleep = 1,
+};
+
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
+ int ret;
+
+ wm8903->gpio_chip = wm8903_template_chip;
+ wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO;
+ wm8903->gpio_chip.dev = codec->dev;
+
+ if (pdata && pdata->gpio_base)
+ wm8903->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8903->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
static int wm8903_probe(struct snd_soc_codec *codec)
{
struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
@@ -1643,7 +1894,7 @@ static int wm8903_probe(struct snd_soc_codec *codec)
int trigger, irq_pol;
u16 val;
- init_completion(&wm8903->wseq);
+ wm8903->codec = codec;
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
if (ret != 0) {
@@ -1659,19 +1910,33 @@ static int wm8903_probe(struct snd_soc_codec *codec)
}
val = snd_soc_read(codec, WM8903_REVISION_NUMBER);
- dev_info(codec->dev, "WM8903 revision %d\n",
- val & WM8903_CHIP_REV_MASK);
+ dev_info(codec->dev, "WM8903 revision %c\n",
+ (val & WM8903_CHIP_REV_MASK) + 'A');
wm8903_reset(codec);
/* Set up GPIOs and microphone detection */
if (pdata) {
+ bool mic_gpio = false;
+
for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) {
- if (!pdata->gpio_cfg[i])
+ if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG)
continue;
snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i,
pdata->gpio_cfg[i] & 0xffff);
+
+ val = (pdata->gpio_cfg[i] & WM8903_GP1_FN_MASK)
+ >> WM8903_GP1_FN_SHIFT;
+
+ switch (val) {
+ case WM8903_GPn_FN_MICBIAS_CURRENT_DETECT:
+ case WM8903_GPn_FN_MICBIAS_SHORT_DETECT:
+ mic_gpio = true;
+ break;
+ default:
+ break;
+ }
}
snd_soc_write(codec, WM8903_MIC_BIAS_CONTROL_0,
@@ -1682,6 +1947,14 @@ static int wm8903_probe(struct snd_soc_codec *codec)
snd_soc_update_bits(codec, WM8903_WRITE_SEQUENCER_0,
WM8903_WSEQ_ENA, WM8903_WSEQ_ENA);
+ /* If microphone detection is enabled by pdata but
+ * detected via IRQ then interrupts can be lost before
+ * the machine driver has set up microphone detection
+ * IRQs as the IRQs are clear on read. The detection
+ * will be enabled when the machine driver configures.
+ */
+ WARN_ON(!mic_gpio && (pdata->micdet_cfg & WM8903_MICDET_ENA));
+
wm8903->mic_delay = pdata->micdet_delay;
}
@@ -1741,20 +2014,23 @@ static int wm8903_probe(struct snd_soc_codec *codec)
snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
/* Enable DAC soft mute by default */
- val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
- val |= WM8903_DAC_MUTEMODE;
- snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val);
+ snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE);
snd_soc_add_controls(codec, wm8903_snd_controls,
ARRAY_SIZE(wm8903_snd_controls));
wm8903_add_widgets(codec);
+ wm8903_init_gpio(codec);
+
return ret;
}
/* power down chip */
static int wm8903_remove(struct snd_soc_codec *codec)
{
+ wm8903_free_gpio(codec);
wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
@@ -1769,6 +2045,7 @@ static struct snd_soc_codec_driver soc_codec_dev_wm8903 = {
.reg_word_size = sizeof(u16),
.reg_cache_default = wm8903_reg_defaults,
.volatile_register = wm8903_volatile_register,
+ .seq_notifier = wm8903_seq_notifier,
};
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
@@ -1807,7 +2084,7 @@ MODULE_DEVICE_TABLE(i2c, wm8903_i2c_id);
static struct i2c_driver wm8903_i2c_driver = {
.driver = {
- .name = "wm8903-codec",
+ .name = "wm8903",
.owner = THIS_MODULE,
},
.probe = wm8903_i2c_probe,
diff --git a/sound/soc/codecs/wm8903.h b/sound/soc/codecs/wm8903.h
index e3ec2433b21..db949311c0f 100644
--- a/sound/soc/codecs/wm8903.h
+++ b/sound/soc/codecs/wm8903.h
@@ -75,6 +75,14 @@ extern int wm8903_mic_detect(struct snd_soc_codec *codec,
#define WM8903_ANALOGUE_SPK_OUTPUT_CONTROL_0 0x41
#define WM8903_DC_SERVO_0 0x43
#define WM8903_DC_SERVO_2 0x45
+#define WM8903_DC_SERVO_4 0x47
+#define WM8903_DC_SERVO_5 0x48
+#define WM8903_DC_SERVO_6 0x49
+#define WM8903_DC_SERVO_7 0x4A
+#define WM8903_DC_SERVO_READBACK_1 0x51
+#define WM8903_DC_SERVO_READBACK_2 0x52
+#define WM8903_DC_SERVO_READBACK_3 0x53
+#define WM8903_DC_SERVO_READBACK_4 0x54
#define WM8903_ANALOGUE_HP_0 0x5A
#define WM8903_ANALOGUE_LINEOUT_0 0x5E
#define WM8903_CHARGE_PUMP_0 0x62
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
index 9de44a4c05c..443ae580445 100644
--- a/sound/soc/codecs/wm8904.c
+++ b/sound/soc/codecs/wm8904.c
@@ -596,7 +596,7 @@ static struct {
{ 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */
};
-static int wm8904_volatile_register(unsigned int reg)
+static int wm8904_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
return wm8904_access[reg].vol;
}
@@ -2436,19 +2436,28 @@ static int wm8904_probe(struct snd_soc_codec *codec)
}
/* Change some default settings - latch VU and enable ZC */
- reg_cache[WM8904_ADC_DIGITAL_VOLUME_LEFT] |= WM8904_ADC_VU;
- reg_cache[WM8904_ADC_DIGITAL_VOLUME_RIGHT] |= WM8904_ADC_VU;
- reg_cache[WM8904_DAC_DIGITAL_VOLUME_LEFT] |= WM8904_DAC_VU;
- reg_cache[WM8904_DAC_DIGITAL_VOLUME_RIGHT] |= WM8904_DAC_VU;
- reg_cache[WM8904_ANALOGUE_OUT1_LEFT] |= WM8904_HPOUT_VU |
- WM8904_HPOUTLZC;
- reg_cache[WM8904_ANALOGUE_OUT1_RIGHT] |= WM8904_HPOUT_VU |
- WM8904_HPOUTRZC;
- reg_cache[WM8904_ANALOGUE_OUT2_LEFT] |= WM8904_LINEOUT_VU |
- WM8904_LINEOUTLZC;
- reg_cache[WM8904_ANALOGUE_OUT2_RIGHT] |= WM8904_LINEOUT_VU |
- WM8904_LINEOUTRZC;
- reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE;
+ snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_LEFT,
+ WM8904_ADC_VU, WM8904_ADC_VU);
+ snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_RIGHT,
+ WM8904_ADC_VU, WM8904_ADC_VU);
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_LEFT,
+ WM8904_DAC_VU, WM8904_DAC_VU);
+ snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_RIGHT,
+ WM8904_DAC_VU, WM8904_DAC_VU);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_LEFT,
+ WM8904_HPOUT_VU | WM8904_HPOUTLZC,
+ WM8904_HPOUT_VU | WM8904_HPOUTLZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_RIGHT,
+ WM8904_HPOUT_VU | WM8904_HPOUTRZC,
+ WM8904_HPOUT_VU | WM8904_HPOUTRZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_LEFT,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTLZC,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTLZC);
+ snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_RIGHT,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTRZC,
+ WM8904_LINEOUT_VU | WM8904_LINEOUTRZC);
+ snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0,
+ WM8904_SR_MODE, 0);
/* Apply configuration from the platform data. */
if (wm8904->pdata) {
@@ -2469,10 +2478,12 @@ static int wm8904_probe(struct snd_soc_codec *codec)
/* Set Class W by default - this will be managed by the Class
* G widget at runtime where bypass paths are available.
*/
- reg_cache[WM8904_CLASS_W_0] |= WM8904_CP_DYN_PWR;
+ snd_soc_update_bits(codec, WM8904_CLASS_W_0,
+ WM8904_CP_DYN_PWR, WM8904_CP_DYN_PWR);
/* Use normal bias source */
- reg_cache[WM8904_BIAS_CONTROL_0] &= ~WM8904_POBCTRL;
+ snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0,
+ WM8904_POBCTRL, 0);
wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c
index 7167dfc96aa..5e0214d6293 100644
--- a/sound/soc/codecs/wm8955.c
+++ b/sound/soc/codecs/wm8955.c
@@ -934,16 +934,27 @@ static int wm8955_probe(struct snd_soc_codec *codec)
}
/* Change some default settings - latch VU and enable ZC */
- reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU;
- reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU;
- reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC;
- reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC;
- reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC;
- reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC;
- reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC;
+ snd_soc_update_bits(codec, WM8955_LEFT_DAC_VOLUME,
+ WM8955_LDVU, WM8955_LDVU);
+ snd_soc_update_bits(codec, WM8955_RIGHT_DAC_VOLUME,
+ WM8955_RDVU, WM8955_RDVU);
+ snd_soc_update_bits(codec, WM8955_LOUT1_VOLUME,
+ WM8955_LO1VU | WM8955_LO1ZC,
+ WM8955_LO1VU | WM8955_LO1ZC);
+ snd_soc_update_bits(codec, WM8955_ROUT1_VOLUME,
+ WM8955_RO1VU | WM8955_RO1ZC,
+ WM8955_RO1VU | WM8955_RO1ZC);
+ snd_soc_update_bits(codec, WM8955_LOUT2_VOLUME,
+ WM8955_LO2VU | WM8955_LO2ZC,
+ WM8955_LO2VU | WM8955_LO2ZC);
+ snd_soc_update_bits(codec, WM8955_ROUT2_VOLUME,
+ WM8955_RO2VU | WM8955_RO2ZC,
+ WM8955_RO2VU | WM8955_RO2ZC);
+ snd_soc_update_bits(codec, WM8955_MONOOUT_VOLUME,
+ WM8955_MOZC, WM8955_MOZC);
/* Also enable adaptive bass boost by default */
- reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB;
+ snd_soc_update_bits(codec, WM8955_BASS_CONTROL, WM8955_BB, WM8955_BB);
/* Set platform data values */
if (pdata) {
diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c
index 55252e7d02c..cdee8103d09 100644
--- a/sound/soc/codecs/wm8961.c
+++ b/sound/soc/codecs/wm8961.c
@@ -291,7 +291,7 @@ struct wm8961_priv {
int sysclk;
};
-static int wm8961_volatile_register(unsigned int reg)
+static int wm8961_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8961_SOFTWARE_RESET:
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c
index b9cb1fcf8c9..3b71dd65c96 100644
--- a/sound/soc/codecs/wm8962.c
+++ b/sound/soc/codecs/wm8962.c
@@ -1938,7 +1938,7 @@ static const struct wm8962_reg_access {
[21139] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21139 - VSS_XTS32_0 */
};
-static int wm8962_volatile_register(unsigned int reg)
+static int wm8962_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
if (wm8962_reg_access[reg].vol)
return 1;
@@ -1946,7 +1946,7 @@ static int wm8962_volatile_register(unsigned int reg)
return 0;
}
-static int wm8962_readable_register(unsigned int reg)
+static int wm8962_readable_register(struct snd_soc_codec *codec, unsigned int reg)
{
if (wm8962_reg_access[reg].read)
return 1;
@@ -3635,7 +3635,7 @@ static void wm8962_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
struct snd_soc_codec *codec = wm8962->codec;
snd_soc_update_bits(codec, WM8962_GPIO_BASE + offset,
- WM8962_GP2_LVL, value << WM8962_GP2_LVL_SHIFT);
+ WM8962_GP2_LVL, !!value << WM8962_GP2_LVL_SHIFT);
}
static int wm8962_gpio_direction_out(struct gpio_chip *chip,
@@ -3822,16 +3822,26 @@ static int wm8962_probe(struct snd_soc_codec *codec)
}
/* Latch volume update bits */
- reg_cache[WM8962_LEFT_INPUT_VOLUME] |= WM8962_IN_VU;
- reg_cache[WM8962_RIGHT_INPUT_VOLUME] |= WM8962_IN_VU;
- reg_cache[WM8962_LEFT_ADC_VOLUME] |= WM8962_ADC_VU;
- reg_cache[WM8962_RIGHT_ADC_VOLUME] |= WM8962_ADC_VU;
- reg_cache[WM8962_LEFT_DAC_VOLUME] |= WM8962_DAC_VU;
- reg_cache[WM8962_RIGHT_DAC_VOLUME] |= WM8962_DAC_VU;
- reg_cache[WM8962_SPKOUTL_VOLUME] |= WM8962_SPKOUT_VU;
- reg_cache[WM8962_SPKOUTR_VOLUME] |= WM8962_SPKOUT_VU;
- reg_cache[WM8962_HPOUTL_VOLUME] |= WM8962_HPOUT_VU;
- reg_cache[WM8962_HPOUTR_VOLUME] |= WM8962_HPOUT_VU;
+ snd_soc_update_bits(codec, WM8962_LEFT_INPUT_VOLUME,
+ WM8962_IN_VU, WM8962_IN_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_VOLUME,
+ WM8962_IN_VU, WM8962_IN_VU);
+ snd_soc_update_bits(codec, WM8962_LEFT_ADC_VOLUME,
+ WM8962_ADC_VU, WM8962_ADC_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_ADC_VOLUME,
+ WM8962_ADC_VU, WM8962_ADC_VU);
+ snd_soc_update_bits(codec, WM8962_LEFT_DAC_VOLUME,
+ WM8962_DAC_VU, WM8962_DAC_VU);
+ snd_soc_update_bits(codec, WM8962_RIGHT_DAC_VOLUME,
+ WM8962_DAC_VU, WM8962_DAC_VU);
+ snd_soc_update_bits(codec, WM8962_SPKOUTL_VOLUME,
+ WM8962_SPKOUT_VU, WM8962_SPKOUT_VU);
+ snd_soc_update_bits(codec, WM8962_SPKOUTR_VOLUME,
+ WM8962_SPKOUT_VU, WM8962_SPKOUT_VU);
+ snd_soc_update_bits(codec, WM8962_HPOUTL_VOLUME,
+ WM8962_HPOUT_VU, WM8962_HPOUT_VU);
+ snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME,
+ WM8962_HPOUT_VU, WM8962_HPOUT_VU);
wm8962_add_widgets(codec);
diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c
index 4bbc3442703..85e3e630e76 100644
--- a/sound/soc/codecs/wm8978.c
+++ b/sound/soc/codecs/wm8978.c
@@ -93,6 +93,7 @@ static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1200, 75, 0);
static const DECLARE_TLV_DB_SCALE(spk_tlv, -5700, 100, 0);
static const DECLARE_TLV_DB_SCALE(boost_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(limiter_tlv, 0, 100, 0);
static const struct snd_kcontrol_new wm8978_snd_controls[] = {
@@ -144,19 +145,19 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = {
SOC_SINGLE("DAC Playback Limiter Threshold",
WM8978_DAC_LIMITER_2, 4, 7, 0),
- SOC_SINGLE("DAC Playback Limiter Boost",
- WM8978_DAC_LIMITER_2, 0, 15, 0),
+ SOC_SINGLE_TLV("DAC Playback Limiter Volume",
+ WM8978_DAC_LIMITER_2, 0, 12, 0, limiter_tlv),
SOC_ENUM("ALC Enable Switch", alc1),
SOC_SINGLE("ALC Capture Min Gain", WM8978_ALC_CONTROL_1, 0, 7, 0),
SOC_SINGLE("ALC Capture Max Gain", WM8978_ALC_CONTROL_1, 3, 7, 0),
- SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 7, 0),
+ SOC_SINGLE("ALC Capture Hold", WM8978_ALC_CONTROL_2, 4, 10, 0),
SOC_SINGLE("ALC Capture Target", WM8978_ALC_CONTROL_2, 0, 15, 0),
SOC_ENUM("ALC Capture Mode", alc3),
- SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 15, 0),
- SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 15, 0),
+ SOC_SINGLE("ALC Capture Decay", WM8978_ALC_CONTROL_3, 4, 10, 0),
+ SOC_SINGLE("ALC Capture Attack", WM8978_ALC_CONTROL_3, 0, 10, 0),
SOC_SINGLE("ALC Capture Noise Gate Switch", WM8978_NOISE_GATE, 3, 1, 0),
SOC_SINGLE("ALC Capture Noise Gate Threshold",
@@ -211,8 +212,10 @@ static const struct snd_kcontrol_new wm8978_snd_controls[] = {
WM8978_LOUT2_SPK_CONTROL, WM8978_ROUT2_SPK_CONTROL, 6, 1, 1),
/* DAC / ADC oversampling */
- SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL, 8, 1, 0),
- SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL, 8, 1, 0),
+ SOC_SINGLE("DAC 128x Oversampling Switch", WM8978_DAC_CONTROL,
+ 5, 1, 0),
+ SOC_SINGLE("ADC 128x Oversampling Switch", WM8978_ADC_CONTROL,
+ 5, 1, 0),
};
/* Mixer #1: Output (OUT1, OUT2) Mixer: mix AUX, Input mixer output and DAC */
@@ -965,7 +968,7 @@ static int wm8978_probe(struct snd_soc_codec *codec)
* written.
*/
for (i = 0; i < ARRAY_SIZE(update_reg); i++)
- ((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100;
+ snd_soc_update_bits(codec, update_reg[i], 0x100, 0x100);
/* Reset the codec */
ret = snd_soc_write(codec, WM8978_RESET, 0);
diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c
new file mode 100644
index 00000000000..28fdfd66661
--- /dev/null
+++ b/sound/soc/codecs/wm8991.c
@@ -0,0 +1,1427 @@
+/*
+ * wm8991.c -- WM8991 ALSA Soc Audio driver
+ *
+ * Copyright 2007-2010 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * linux@wolfsonmicro.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/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <asm/div64.h>
+
+#include "wm8991.h"
+
+struct wm8991_priv {
+ enum snd_soc_control_type control_type;
+ unsigned int pcmclk;
+};
+
+static const u16 wm8991_reg_defs[] = {
+ 0x8991, /* R0 - Reset */
+ 0x0000, /* R1 - Power Management (1) */
+ 0x6000, /* R2 - Power Management (2) */
+ 0x0000, /* R3 - Power Management (3) */
+ 0x4050, /* R4 - Audio Interface (1) */
+ 0x4000, /* R5 - Audio Interface (2) */
+ 0x01C8, /* R6 - Clocking (1) */
+ 0x0000, /* R7 - Clocking (2) */
+ 0x0040, /* R8 - Audio Interface (3) */
+ 0x0040, /* R9 - Audio Interface (4) */
+ 0x0004, /* R10 - DAC CTRL */
+ 0x00C0, /* R11 - Left DAC Digital Volume */
+ 0x00C0, /* R12 - Right DAC Digital Volume */
+ 0x0000, /* R13 - Digital Side Tone */
+ 0x0100, /* R14 - ADC CTRL */
+ 0x00C0, /* R15 - Left ADC Digital Volume */
+ 0x00C0, /* R16 - Right ADC Digital Volume */
+ 0x0000, /* R17 */
+ 0x0000, /* R18 - GPIO CTRL 1 */
+ 0x1000, /* R19 - GPIO1 & GPIO2 */
+ 0x1010, /* R20 - GPIO3 & GPIO4 */
+ 0x1010, /* R21 - GPIO5 & GPIO6 */
+ 0x8000, /* R22 - GPIOCTRL 2 */
+ 0x0800, /* R23 - GPIO_POL */
+ 0x008B, /* R24 - Left Line Input 1&2 Volume */
+ 0x008B, /* R25 - Left Line Input 3&4 Volume */
+ 0x008B, /* R26 - Right Line Input 1&2 Volume */
+ 0x008B, /* R27 - Right Line Input 3&4 Volume */
+ 0x0000, /* R28 - Left Output Volume */
+ 0x0000, /* R29 - Right Output Volume */
+ 0x0066, /* R30 - Line Outputs Volume */
+ 0x0022, /* R31 - Out3/4 Volume */
+ 0x0079, /* R32 - Left OPGA Volume */
+ 0x0079, /* R33 - Right OPGA Volume */
+ 0x0003, /* R34 - Speaker Volume */
+ 0x0003, /* R35 - ClassD1 */
+ 0x0000, /* R36 */
+ 0x0100, /* R37 - ClassD3 */
+ 0x0000, /* R38 */
+ 0x0000, /* R39 - Input Mixer1 */
+ 0x0000, /* R40 - Input Mixer2 */
+ 0x0000, /* R41 - Input Mixer3 */
+ 0x0000, /* R42 - Input Mixer4 */
+ 0x0000, /* R43 - Input Mixer5 */
+ 0x0000, /* R44 - Input Mixer6 */
+ 0x0000, /* R45 - Output Mixer1 */
+ 0x0000, /* R46 - Output Mixer2 */
+ 0x0000, /* R47 - Output Mixer3 */
+ 0x0000, /* R48 - Output Mixer4 */
+ 0x0000, /* R49 - Output Mixer5 */
+ 0x0000, /* R50 - Output Mixer6 */
+ 0x0180, /* R51 - Out3/4 Mixer */
+ 0x0000, /* R52 - Line Mixer1 */
+ 0x0000, /* R53 - Line Mixer2 */
+ 0x0000, /* R54 - Speaker Mixer */
+ 0x0000, /* R55 - Additional Control */
+ 0x0000, /* R56 - AntiPOP1 */
+ 0x0000, /* R57 - AntiPOP2 */
+ 0x0000, /* R58 - MICBIAS */
+ 0x0000, /* R59 */
+ 0x0008, /* R60 - PLL1 */
+ 0x0031, /* R61 - PLL2 */
+ 0x0026, /* R62 - PLL3 */
+};
+
+#define wm8991_reset(c) snd_soc_write(c, WM8991_RESET, 0)
+
+static const unsigned int rec_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1500, 600),
+};
+
+static const unsigned int in_pga_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 0x1F, TLV_DB_LINEAR_ITEM(-1650, 3000),
+};
+
+static const unsigned int out_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(0, -2100),
+};
+
+static const unsigned int out_pga_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 127, TLV_DB_LINEAR_ITEM(-7300, 600),
+};
+
+static const unsigned int out_omix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-600, 0),
+};
+
+static const unsigned int out_dac_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 255, TLV_DB_LINEAR_ITEM(-7163, 0),
+};
+
+static const unsigned int in_adc_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 255, TLV_DB_LINEAR_ITEM(-7163, 1763),
+};
+
+static const unsigned int out_sidetone_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 31, TLV_DB_LINEAR_ITEM(-3600, 0),
+};
+
+static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ int reg = kcontrol->private_value & 0xff;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = snd_soc_read(codec, reg);
+ return snd_soc_write(codec, reg, val | 0x0100);
+}
+
+static const char *wm8991_digital_sidetone[] =
+{"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8991_left_digital_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADC_TO_DACL_SHIFT,
+ WM8991_ADC_TO_DACL_MASK,
+ wm8991_digital_sidetone);
+
+static const struct soc_enum wm8991_right_digital_sidetone_enum =
+ SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADC_TO_DACR_SHIFT,
+ WM8991_ADC_TO_DACR_MASK,
+ wm8991_digital_sidetone);
+
+static const char *wm8991_adcmode[] =
+{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8991_right_adcmode_enum =
+ SOC_ENUM_SINGLE(WM8991_ADC_CTRL,
+ WM8991_ADC_HPF_CUT_SHIFT,
+ WM8991_ADC_HPF_CUT_MASK,
+ wm8991_adcmode);
+
+static const struct snd_kcontrol_new wm8991_snd_controls[] = {
+ /* INMIXL */
+ SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0),
+ SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0),
+ /* INMIXR */
+ SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0),
+ SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0),
+
+ /* LOMIX */
+ SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LLI3LOVOL_SHIFT, WM8991_LLI3LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LR12LOVOL_SHIFT, WM8991_LR12LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3,
+ WM8991_LL12LOVOL_SHIFT, WM8991_LL12LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRI3LOVOL_SHIFT, WM8991_LRI3LOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5,
+ WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv),
+
+ /* ROMIX */
+ SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RRI3ROVOL_SHIFT, WM8991_RRI3ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RL12ROVOL_SHIFT, WM8991_RL12ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4,
+ WM8991_RR12ROVOL_SHIFT, WM8991_RR12ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RLI3ROVOL_SHIFT, WM8991_RLI3ROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RLBROVOL_SHIFT, WM8991_RLBROVOL_MASK, 1, out_mix_tlv),
+ SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER6,
+ WM8991_RRBROVOL_SHIFT, WM8991_RRBROVOL_MASK, 1, out_mix_tlv),
+
+ /* LOUT */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8991_LEFT_OUTPUT_VOLUME,
+ WM8991_LOUTVOL_SHIFT, WM8991_LOUTVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("LOUT ZC", WM8991_LEFT_OUTPUT_VOLUME, WM8991_LOZC_BIT, 1, 0),
+
+ /* ROUT */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8991_RIGHT_OUTPUT_VOLUME,
+ WM8991_ROUTVOL_SHIFT, WM8991_ROUTVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("ROUT ZC", WM8991_RIGHT_OUTPUT_VOLUME, WM8991_ROZC_BIT, 1, 0),
+
+ /* LOPGA */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8991_LEFT_OPGA_VOLUME,
+ WM8991_LOPGAVOL_SHIFT, WM8991_LOPGAVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("LOPGA ZC Switch", WM8991_LEFT_OPGA_VOLUME,
+ WM8991_LOPGAZC_BIT, 1, 0),
+
+ /* ROPGA */
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8991_RIGHT_OPGA_VOLUME,
+ WM8991_ROPGAVOL_SHIFT, WM8991_ROPGAVOL_MASK, 0, out_pga_tlv),
+ SOC_SINGLE("ROPGA ZC Switch", WM8991_RIGHT_OPGA_VOLUME,
+ WM8991_ROPGAZC_BIT, 1, 0),
+
+ SOC_SINGLE("LON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LONMUTE_BIT, 1, 0),
+ SOC_SINGLE("LOP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LOPMUTE_BIT, 1, 0),
+ SOC_SINGLE("LOP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_LOATTN_BIT, 1, 0),
+ SOC_SINGLE("RON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_RONMUTE_BIT, 1, 0),
+ SOC_SINGLE("ROP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_ROPMUTE_BIT, 1, 0),
+ SOC_SINGLE("ROP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME,
+ WM8991_ROATTN_BIT, 1, 0),
+
+ SOC_SINGLE("OUT3 Mute Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT3MUTE_BIT, 1, 0),
+ SOC_SINGLE("OUT3 Attenuation Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT3ATTN_BIT, 1, 0),
+
+ SOC_SINGLE("OUT4 Mute Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT4MUTE_BIT, 1, 0),
+ SOC_SINGLE("OUT4 Attenuation Switch", WM8991_OUT3_4_VOLUME,
+ WM8991_OUT4ATTN_BIT, 1, 0),
+
+ SOC_SINGLE("Speaker Mode Switch", WM8991_CLASSD1,
+ WM8991_CDMODE_BIT, 1, 0),
+
+ SOC_SINGLE("Speaker Output Attenuation Volume", WM8991_SPEAKER_VOLUME,
+ WM8991_SPKVOL_SHIFT, WM8991_SPKVOL_MASK, 0),
+ SOC_SINGLE("Speaker DC Boost Volume", WM8991_CLASSD3,
+ WM8991_DCGAIN_SHIFT, WM8991_DCGAIN_MASK, 0),
+ SOC_SINGLE("Speaker AC Boost Volume", WM8991_CLASSD3,
+ WM8991_ACGAIN_SHIFT, WM8991_ACGAIN_MASK, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8991_LEFT_DAC_DIGITAL_VOLUME,
+ WM8991_DACL_VOL_SHIFT,
+ WM8991_DACL_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8991_RIGHT_DAC_DIGITAL_VOLUME,
+ WM8991_DACR_VOL_SHIFT,
+ WM8991_DACR_VOL_MASK,
+ 0,
+ out_dac_tlv),
+
+ SOC_ENUM("Left Digital Sidetone", wm8991_left_digital_sidetone_enum),
+ SOC_ENUM("Right Digital Sidetone", wm8991_right_digital_sidetone_enum),
+
+ SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADCL_DAC_SVOL_SHIFT, WM8991_ADCL_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+ SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE,
+ WM8991_ADCR_DAC_SVOL_SHIFT, WM8991_ADCR_DAC_SVOL_MASK, 0,
+ out_sidetone_tlv),
+
+ SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8991_ADC_CTRL,
+ WM8991_ADC_HPF_ENA_BIT, 1, 0),
+
+ SOC_ENUM("ADC HPF Mode", wm8991_right_adcmode_enum),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8991_LEFT_ADC_DIGITAL_VOLUME,
+ WM8991_ADCL_VOL_SHIFT,
+ WM8991_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8991_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8991_ADCR_VOL_SHIFT,
+ WM8991_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LIN12VOL_SHIFT,
+ WM8991_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("LIN12 ZC Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LI12ZC_BIT, 1, 0),
+
+ SOC_SINGLE("LIN12 Mute Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8991_LI12MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LIN34VOL_SHIFT,
+ WM8991_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("LIN34 ZC Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LI34ZC_BIT, 1, 0),
+
+ SOC_SINGLE("LIN34 Mute Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8991_LI34MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RIN12VOL_SHIFT,
+ WM8991_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("RIN12 ZC Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RI12ZC_BIT, 1, 0),
+
+ SOC_SINGLE("RIN12 Mute Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8991_RI12MUTE_BIT, 1, 0),
+
+ SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RIN34VOL_SHIFT,
+ WM8991_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+ SOC_SINGLE("RIN34 ZC Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RI34ZC_BIT, 1, 0),
+
+ SOC_SINGLE("RIN34 Mute Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8991_RI34MUTE_BIT, 1, 0),
+};
+
+/*
+ * _DAPM_ Controls
+ */
+static int inmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = snd_soc_read(w->codec, WM8991_POWER_MANAGEMENT_2);
+ fakepower = snd_soc_read(w->codec, WM8991_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8991_INMIXL_PWR_BIT) |
+ (1 << WM8991_AINLMUX_PWR_BIT)))
+ reg |= WM8991_AINL_ENA;
+ else
+ reg &= ~WM8991_AINL_ENA;
+
+ if (fakepower & ((1 << WM8991_INMIXR_PWR_BIT) |
+ (1 << WM8991_AINRMUX_PWR_BIT)))
+ reg |= WM8991_AINR_ENA;
+ else
+ reg &= ~WM8991_AINL_ENA;
+
+ snd_soc_write(w->codec, WM8991_POWER_MANAGEMENT_2, reg);
+ return 0;
+}
+
+static int outmixer_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u32 reg_shift = kcontrol->private_value & 0xfff;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8991_SPEAKER_MIXER | (WM8991_LDSPK_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER1);
+ if (reg & WM8991_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_SPEAKER_MIXER | (WM8991_RDSPK_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER2);
+ if (reg & WM8991_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_OUTPUT_MIXER1 | (WM8991_LDLO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER);
+ if (reg & WM8991_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+
+ case WM8991_OUTPUT_MIXER2 | (WM8991_RDRO_BIT << 8):
+ reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER);
+ if (reg & WM8991_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8991_dapm_lin12_pga_controls[] = {
+ SOC_DAPM_SINGLE("LIN1 Switch", WM8991_INPUT_MIXER2, WM8991_LMN1_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LIN2 Switch", WM8991_INPUT_MIXER2, WM8991_LMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8991_dapm_lin34_pga_controls[] = {
+ SOC_DAPM_SINGLE("LIN3 Switch", WM8991_INPUT_MIXER2, WM8991_LMN3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LIN4 Switch", WM8991_INPUT_MIXER2, WM8991_LMP4_BIT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8991_dapm_rin12_pga_controls[] = {
+ SOC_DAPM_SINGLE("RIN1 Switch", WM8991_INPUT_MIXER2, WM8991_RMN1_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RIN2 Switch", WM8991_INPUT_MIXER2, WM8991_RMP2_BIT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8991_dapm_rin34_pga_controls[] = {
+ SOC_DAPM_SINGLE("RIN3 Switch", WM8991_INPUT_MIXER2, WM8991_RMN3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RIN4 Switch", WM8991_INPUT_MIXER2, WM8991_RMP4_BIT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8991_dapm_inmixl_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8991_INPUT_MIXER3,
+ WM8991_LDBVOL_SHIFT, WM8991_LDBVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8991_INPUT_MIXER5, WM8991_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE("LINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT,
+ 1, 0),
+ SOC_DAPM_SINGLE("LINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = {
+ SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8991_INPUT_MIXER4,
+ WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT,
+ 1, 0),
+ SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8991_ainlmux[] =
+{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8991_ainlmux_enum =
+ SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8991_ainlmux), wm8991_ainlmux);
+
+static const struct snd_kcontrol_new wm8991_dapm_ainlmux_controls =
+ SOC_DAPM_ENUM("Route", wm8991_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8991_ainrmux[] =
+{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8991_ainrmux_enum =
+ SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8991_ainrmux), wm8991_ainrmux);
+
+static const struct snd_kcontrol_new wm8991_dapm_ainrmux_controls =
+ SOC_DAPM_ENUM("Route", wm8991_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8991_dapm_rxvoice_controls[] = {
+ SOC_DAPM_SINGLE_TLV("LIN4RXN", WM8991_INPUT_MIXER5, WM8991_LR4BVOL_SHIFT,
+ WM8991_LR4BVOL_MASK, 0, in_mix_tlv),
+ SOC_DAPM_SINGLE_TLV("RIN4RXP", WM8991_INPUT_MIXER6, WM8991_RL4BVOL_SHIFT,
+ WM8991_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lomix_controls[] = {
+ SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LRBLO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LLBLO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LRI3LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LLI3LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LR12LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LL12LO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8991_OUTPUT_MIXER1,
+ WM8991_LDLO_BIT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8991_dapm_romix_controls[] = {
+ SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RLBRO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RRBRO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RLI3RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RRI3RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RL12RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RR12RO_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8991_OUTPUT_MIXER2,
+ WM8991_RDRO_BIT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lonmix_controls[] = {
+ SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LLOPGALON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LROPGALON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8991_LINE_MIXER1,
+ WM8991_LOPLON_BIT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8991_dapm_lopmix_controls[] = {
+ SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER1,
+ WM8991_LR12LOP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER1,
+ WM8991_LL12LOP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1,
+ WM8991_LLOPGALOP_BIT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8991_dapm_ronmix_controls[] = {
+ SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RROPGARON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RLOPGARON_BIT, 1, 0),
+ SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8991_LINE_MIXER2,
+ WM8991_ROPRON_BIT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8991_dapm_ropmix_controls[] = {
+ SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER2,
+ WM8991_RL12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER2,
+ WM8991_RR12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2,
+ WM8991_RROPGAROP_BIT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8991_dapm_out3mix_controls[] = {
+ SOC_DAPM_SINGLE("OUT3MIX LIN4RXN Bypass Switch", WM8991_OUT3_4_MIXER,
+ WM8991_LI4O3_BIT, 1, 0),
+ SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8991_OUT3_4_MIXER,
+ WM8991_LPGAO3_BIT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8991_dapm_out4mix_controls[] = {
+ SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8991_OUT3_4_MIXER,
+ WM8991_RPGAO4_BIT, 1, 0),
+ SOC_DAPM_SINGLE("OUT4MIX RIN4RXP Bypass Switch", WM8991_OUT3_4_MIXER,
+ WM8991_RI4O4_BIT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8991_dapm_spkmix_controls[] = {
+ SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LI2SPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LB2SPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LOPGASPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8991_SPEAKER_MIXER,
+ WM8991_LDSPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RDSPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8991_SPEAKER_MIXER,
+ WM8991_ROPGASPK_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RL12ROP_BIT, 1, 0),
+ SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8991_SPEAKER_MIXER,
+ WM8991_RI2SPK_BIT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = {
+ /* Input Side */
+ /* Input Lines */
+ SND_SOC_DAPM_INPUT("LIN1"),
+ SND_SOC_DAPM_INPUT("LIN2"),
+ SND_SOC_DAPM_INPUT("LIN3"),
+ SND_SOC_DAPM_INPUT("LIN4RXN"),
+ SND_SOC_DAPM_INPUT("RIN3"),
+ SND_SOC_DAPM_INPUT("RIN4RXP"),
+ SND_SOC_DAPM_INPUT("RIN1"),
+ SND_SOC_DAPM_INPUT("RIN2"),
+ SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+ /* DACs */
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2,
+ WM8991_ADCL_ENA_BIT, 0),
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8991_POWER_MANAGEMENT_2,
+ WM8991_ADCR_ENA_BIT, 0),
+
+ /* Input PGAs */
+ SND_SOC_DAPM_MIXER("LIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN12_ENA_BIT,
+ 0, &wm8991_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lin12_pga_controls)),
+ SND_SOC_DAPM_MIXER("LIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN34_ENA_BIT,
+ 0, &wm8991_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lin34_pga_controls)),
+ SND_SOC_DAPM_MIXER("RIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN12_ENA_BIT,
+ 0, &wm8991_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_rin12_pga_controls)),
+ SND_SOC_DAPM_MIXER("RIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN34_ENA_BIT,
+ 0, &wm8991_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)),
+
+ /* INMIXL */
+ SND_SOC_DAPM_MIXER_E("INMIXL", WM8991_INTDRIVBITS, WM8991_INMIXL_PWR_BIT, 0,
+ &wm8991_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8991_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* AINLMUX */
+ SND_SOC_DAPM_MUX_E("AINLMUX", WM8991_INTDRIVBITS, WM8991_AINLMUX_PWR_BIT, 0,
+ &wm8991_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* INMIXR */
+ SND_SOC_DAPM_MIXER_E("INMIXR", WM8991_INTDRIVBITS, WM8991_INMIXR_PWR_BIT, 0,
+ &wm8991_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8991_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* AINRMUX */
+ SND_SOC_DAPM_MUX_E("AINRMUX", WM8991_INTDRIVBITS, WM8991_AINRMUX_PWR_BIT, 0,
+ &wm8991_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ /* Output Side */
+ /* DACs */
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8991_POWER_MANAGEMENT_3,
+ WM8991_DACL_ENA_BIT, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8991_POWER_MANAGEMENT_3,
+ WM8991_DACR_ENA_BIT, 0),
+
+ /* LOMIX */
+ SND_SOC_DAPM_MIXER_E("LOMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOMIX_ENA_BIT,
+ 0, &wm8991_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+ /* LONMIX */
+ SND_SOC_DAPM_MIXER("LONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LON_ENA_BIT, 0,
+ &wm8991_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lonmix_controls)),
+
+ /* LOPMIX */
+ SND_SOC_DAPM_MIXER("LOPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOP_ENA_BIT, 0,
+ &wm8991_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_lopmix_controls)),
+
+ /* OUT3MIX */
+ SND_SOC_DAPM_MIXER("OUT3MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT3_ENA_BIT, 0,
+ &wm8991_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_out3mix_controls)),
+
+ /* SPKMIX */
+ SND_SOC_DAPM_MIXER_E("SPKMIX", WM8991_POWER_MANAGEMENT_1, WM8991_SPK_ENA_BIT, 0,
+ &wm8991_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+ /* OUT4MIX */
+ SND_SOC_DAPM_MIXER("OUT4MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT4_ENA_BIT, 0,
+ &wm8991_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_out4mix_controls)),
+
+ /* ROPMIX */
+ SND_SOC_DAPM_MIXER("ROPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROP_ENA_BIT, 0,
+ &wm8991_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_ropmix_controls)),
+
+ /* RONMIX */
+ SND_SOC_DAPM_MIXER("RONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_RON_ENA_BIT, 0,
+ &wm8991_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_ronmix_controls)),
+
+ /* ROMIX */
+ SND_SOC_DAPM_MIXER_E("ROMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROMIX_ENA_BIT,
+ 0, &wm8991_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8991_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+ /* LOUT PGA */
+ SND_SOC_DAPM_PGA("LOUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_LOUT_ENA_BIT, 0,
+ NULL, 0),
+
+ /* ROUT PGA */
+ SND_SOC_DAPM_PGA("ROUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_ROUT_ENA_BIT, 0,
+ NULL, 0),
+
+ /* LOPGA */
+ SND_SOC_DAPM_PGA("LOPGA", WM8991_POWER_MANAGEMENT_3, WM8991_LOPGA_ENA_BIT, 0,
+ NULL, 0),
+
+ /* ROPGA */
+ SND_SOC_DAPM_PGA("ROPGA", WM8991_POWER_MANAGEMENT_3, WM8991_ROPGA_ENA_BIT, 0,
+ NULL, 0),
+
+ /* MICBIAS */
+ SND_SOC_DAPM_MICBIAS("MICBIAS", WM8991_POWER_MANAGEMENT_1,
+ WM8991_MICBIAS_ENA_BIT, 0),
+
+ SND_SOC_DAPM_OUTPUT("LON"),
+ SND_SOC_DAPM_OUTPUT("LOP"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("SPKN"),
+ SND_SOC_DAPM_OUTPUT("SPKP"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+ SND_SOC_DAPM_OUTPUT("OUT4"),
+ SND_SOC_DAPM_OUTPUT("ROP"),
+ SND_SOC_DAPM_OUTPUT("RON"),
+ SND_SOC_DAPM_OUTPUT("OUT"),
+
+ SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Make DACs turn on when playing even if not mixed into any outputs */
+ {"Internal DAC Sink", NULL, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4RXN"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AINLMUX */
+ {"AINLMUX", "INMIXL Mix", "INMIXL"},
+ {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"},
+ {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"},
+ {"AINLMUX", "RXVOICE Mix", "LIN4RXN"},
+ {"AINLMUX", "RXVOICE Mix", "RIN4RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AINLMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4RXP"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AINRMUX */
+ {"AINRMUX", "INMIXR Mix", "INMIXR"},
+ {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"},
+ {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"},
+ {"AINRMUX", "RXVOICE Mix", "LIN4RXN"},
+ {"AINRMUX", "RXVOICE Mix", "RIN4RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AINRMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4RXN Bypass Switch", "LIN4RXN"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4RXP Bypass Switch", "RIN4RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+/* PLL divisors */
+struct _pll_div {
+ u32 div2;
+ u32 n;
+ u32 k;
+};
+
+/* The size in bits of the pll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_PLL_SIZE ((1 << 16) * 10)
+
+static void pll_factors(struct _pll_div *pll_div, unsigned int target,
+ unsigned int source)
+{
+ u64 Kpart;
+ unsigned int K, Ndiv, Nmod;
+
+
+ Ndiv = target / source;
+ if (Ndiv < 6) {
+ source >>= 1;
+ pll_div->div2 = 1;
+ Ndiv = target / source;
+ } else
+ pll_div->div2 = 0;
+
+ if ((Ndiv < 6) || (Ndiv > 12))
+ printk(KERN_WARNING
+ "WM8991 N value outwith recommended range! N = %d\n", Ndiv);
+
+ pll_div->n = Ndiv;
+ Nmod = target % source;
+ Kpart = FIXED_PLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, source);
+
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+
+ pll_div->k = K;
+}
+
+static int wm8991_set_dai_pll(struct snd_soc_dai *codec_dai,
+ int pll_id, int src, unsigned int freq_in, unsigned int freq_out)
+{
+ u16 reg;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct _pll_div pll_div;
+
+ if (freq_in && freq_out) {
+ pll_factors(&pll_div, freq_out * 4, freq_in);
+
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ reg |= WM8991_PLL_ENA;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg);
+
+ /* sysclk comes from PLL */
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2);
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | WM8991_SYSCLK_SRC);
+
+ /* set up N , fractional mode and pre-divisor if neccessary */
+ snd_soc_write(codec, WM8991_PLL1, pll_div.n | WM8991_SDM |
+ (pll_div.div2 ? WM8991_PRESCALE : 0));
+ snd_soc_write(codec, WM8991_PLL2, (u8)(pll_div.k>>8));
+ snd_soc_write(codec, WM8991_PLL3, (u8)(pll_div.k & 0xFF));
+ } else {
+ /* Turn on PLL */
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ reg &= ~WM8991_PLL_ENA;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg);
+ }
+ return 0;
+}
+
+/*
+ * Set's ADC and Voice DAC format.
+ */
+static int wm8991_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1);
+ audio3 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8991_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8991_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8991_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8991_AIF_TMF_I2S;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8991_AIF_TMF_RIGHTJ;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8991_AIF_TMF_LEFTJ;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8991_AIF_TMF_DSP;
+ audio1 &= ~WM8991_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8991_AIF_TMF_DSP | WM8991_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1);
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8991_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8991_MCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_MCLK_DIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_DACCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_DAC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_ADCCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_2) &
+ ~WM8991_ADC_CLKDIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_2, reg | div);
+ break;
+ case WM8991_BCLK_DIV:
+ reg = snd_soc_read(codec, WM8991_CLOCKING_1) &
+ ~WM8991_BCLK_DIV_MASK;
+ snd_soc_write(codec, WM8991_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8991_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;
+ u16 audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8991_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8991_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8991_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8991_AIF_WL_32BITS;
+ break;
+ }
+
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8991_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val;
+
+ val = snd_soc_read(codec, WM8991_DAC_CTRL) & ~WM8991_DAC_MUTE;
+ if (mute)
+ snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE);
+ else
+ snd_soc_write(codec, WM8991_DAC_CTRL, val);
+ return 0;
+}
+
+static int wm8991_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ u16 val;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*50k */
+ val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) &
+ ~WM8991_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ snd_soc_cache_sync(codec);
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE |
+ WM8991_DIS_RLINE | WM8991_DIS_OUT3 |
+ WM8991_DIS_OUT4 | WM8991_DIS_LOUT |
+ WM8991_DIS_ROUT);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_VMIDTOG);
+
+ /* Delay to allow output caps to discharge */
+ msleep(300);
+
+ /* Disable VMIDTOG */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL);
+
+ /* disable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, 0);
+
+ /* Enable outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1b00);
+
+ msleep(50);
+
+ /* Enable VMID at 2x50k */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f02);
+
+ msleep(100);
+
+ /* Enable VREF */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03);
+
+ msleep(600);
+
+ /* Enable BUFIOEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_BUFIOEN);
+
+ /* Disable outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x3);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_BUFIOEN);
+ }
+
+ /* VMID=2*250k */
+ val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) &
+ ~WM8991_VMID_MODE_MASK;
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x4);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_POBCTRL | WM8991_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST |
+ WM8991_BUFDCOPEN | WM8991_POBCTRL |
+ WM8991_BUFIOEN);
+
+ /* mute DAC */
+ val = snd_soc_read(codec, WM8991_DAC_CTRL);
+ snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03);
+
+ /* Disable VMID */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f01);
+
+ msleep(300);
+
+ /* Enable all output discharge bits */
+ snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE |
+ WM8991_DIS_RLINE | WM8991_DIS_OUT3 |
+ WM8991_DIS_OUT4 | WM8991_DIS_LOUT |
+ WM8991_DIS_ROUT);
+
+ /* Disable VREF */
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x0);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ snd_soc_write(codec, WM8991_ANTIPOP2, 0x0);
+ codec->cache_sync = 1;
+ break;
+ }
+
+ codec->dapm.bias_level = level;
+ return 0;
+}
+
+static int wm8991_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8991_resume(struct snd_soc_codec *codec)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+ return 0;
+}
+
+/* power down chip */
+static int wm8991_remove(struct snd_soc_codec *codec)
+{
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int wm8991_probe(struct snd_soc_codec *codec)
+{
+ struct wm8991_priv *wm8991;
+ int ret;
+ unsigned int reg;
+
+ wm8991 = snd_soc_codec_get_drvdata(codec);
+
+ ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8991->control_type);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm8991_reset(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to issue reset\n");
+ return ret;
+ }
+
+ wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ reg = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_4);
+ snd_soc_write(codec, WM8991_AUDIO_INTERFACE_4, reg | WM8991_ALRCGPIO1);
+
+ reg = snd_soc_read(codec, WM8991_GPIO1_GPIO2) &
+ ~WM8991_GPIO1_SEL_MASK;
+ snd_soc_write(codec, WM8991_GPIO1_GPIO2, reg | 1);
+
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1);
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, reg | WM8991_VREF_ENA|
+ WM8991_VMID_MODE_MASK);
+
+ reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2);
+ snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg | WM8991_OPCLK_ENA);
+
+ snd_soc_write(codec, WM8991_DAC_CTRL, 0);
+ snd_soc_write(codec, WM8991_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8));
+ snd_soc_write(codec, WM8991_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8));
+
+ snd_soc_add_controls(codec, wm8991_snd_controls,
+ ARRAY_SIZE(wm8991_snd_controls));
+
+ snd_soc_dapm_new_controls(&codec->dapm, wm8991_dapm_widgets,
+ ARRAY_SIZE(wm8991_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+ return 0;
+}
+
+#define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops wm8991_ops = {
+ .hw_params = wm8991_hw_params,
+ .digital_mute = wm8991_mute,
+ .set_fmt = wm8991_set_dai_fmt,
+ .set_clkdiv = wm8991_set_dai_clkdiv,
+ .set_pll = wm8991_set_dai_pll
+};
+
+/*
+ * The WM8991 supports 2 different and mutually exclusive DAI
+ * configurations.
+ *
+ * 1. ADC/DAC on Primary Interface
+ * 2. ADC on Primary Interface/DAC on secondary
+ */
+static struct snd_soc_dai_driver wm8991_dai = {
+ /* ADC/DAC on primary */
+ .name = "wm8991",
+ .id = 1,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8991_FORMATS
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = WM8991_FORMATS
+ },
+ .ops = &wm8991_ops
+};
+
+static struct snd_soc_codec_driver soc_codec_dev_wm8991 = {
+ .probe = wm8991_probe,
+ .remove = wm8991_remove,
+ .suspend = wm8991_suspend,
+ .resume = wm8991_resume,
+ .set_bias_level = wm8991_set_bias_level,
+ .reg_cache_size = WM8991_MAX_REGISTER + 1,
+ .reg_word_size = sizeof(u16),
+ .reg_cache_default = wm8991_reg_defs
+};
+
+static __devinit int wm8991_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm8991_priv *wm8991;
+ int ret;
+
+ wm8991 = kzalloc(sizeof *wm8991, GFP_KERNEL);
+ if (!wm8991)
+ return -ENOMEM;
+
+ wm8991->control_type = SND_SOC_I2C;
+ i2c_set_clientdata(i2c, wm8991);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_wm8991, &wm8991_dai, 1);
+ if (ret < 0)
+ kfree(wm8991);
+ return ret;
+}
+
+static __devexit int wm8991_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ kfree(i2c_get_clientdata(client));
+ return 0;
+}
+
+static const struct i2c_device_id wm8991_i2c_id[] = {
+ { "wm8991", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm8991_i2c_id);
+
+static struct i2c_driver wm8991_i2c_driver = {
+ .driver = {
+ .name = "wm8991",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm8991_i2c_probe,
+ .remove = __devexit_p(wm8991_i2c_remove),
+ .id_table = wm8991_i2c_id,
+};
+
+static int __init wm8991_modinit(void)
+{
+ int ret;
+ ret = i2c_add_driver(&wm8991_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register WM8991 I2C driver: %d\n",
+ ret);
+ }
+ return 0;
+}
+module_init(wm8991_modinit);
+
+static void __exit wm8991_exit(void)
+{
+ i2c_del_driver(&wm8991_i2c_driver);
+}
+module_exit(wm8991_exit);
+
+MODULE_DESCRIPTION("ASoC WM8991 driver");
+MODULE_AUTHOR("Graeme Gregory");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h
new file mode 100644
index 00000000000..8a942efd18a
--- /dev/null
+++ b/sound/soc/codecs/wm8991.h
@@ -0,0 +1,833 @@
+/*
+ * wm8991.h -- audio driver for WM8991
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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 _WM8991_H
+#define _WM8991_H
+
+/*
+ * Register values.
+ */
+#define WM8991_RESET 0x00
+#define WM8991_POWER_MANAGEMENT_1 0x01
+#define WM8991_POWER_MANAGEMENT_2 0x02
+#define WM8991_POWER_MANAGEMENT_3 0x03
+#define WM8991_AUDIO_INTERFACE_1 0x04
+#define WM8991_AUDIO_INTERFACE_2 0x05
+#define WM8991_CLOCKING_1 0x06
+#define WM8991_CLOCKING_2 0x07
+#define WM8991_AUDIO_INTERFACE_3 0x08
+#define WM8991_AUDIO_INTERFACE_4 0x09
+#define WM8991_DAC_CTRL 0x0A
+#define WM8991_LEFT_DAC_DIGITAL_VOLUME 0x0B
+#define WM8991_RIGHT_DAC_DIGITAL_VOLUME 0x0C
+#define WM8991_DIGITAL_SIDE_TONE 0x0D
+#define WM8991_ADC_CTRL 0x0E
+#define WM8991_LEFT_ADC_DIGITAL_VOLUME 0x0F
+#define WM8991_RIGHT_ADC_DIGITAL_VOLUME 0x10
+#define WM8991_GPIO_CTRL_1 0x12
+#define WM8991_GPIO1_GPIO2 0x13
+#define WM8991_GPIO3_GPIO4 0x14
+#define WM8991_GPIO5_GPIO6 0x15
+#define WM8991_GPIOCTRL_2 0x16
+#define WM8991_GPIO_POL 0x17
+#define WM8991_LEFT_LINE_INPUT_1_2_VOLUME 0x18
+#define WM8991_LEFT_LINE_INPUT_3_4_VOLUME 0x19
+#define WM8991_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A
+#define WM8991_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B
+#define WM8991_LEFT_OUTPUT_VOLUME 0x1C
+#define WM8991_RIGHT_OUTPUT_VOLUME 0x1D
+#define WM8991_LINE_OUTPUTS_VOLUME 0x1E
+#define WM8991_OUT3_4_VOLUME 0x1F
+#define WM8991_LEFT_OPGA_VOLUME 0x20
+#define WM8991_RIGHT_OPGA_VOLUME 0x21
+#define WM8991_SPEAKER_VOLUME 0x22
+#define WM8991_CLASSD1 0x23
+#define WM8991_CLASSD3 0x25
+#define WM8991_INPUT_MIXER1 0x27
+#define WM8991_INPUT_MIXER2 0x28
+#define WM8991_INPUT_MIXER3 0x29
+#define WM8991_INPUT_MIXER4 0x2A
+#define WM8991_INPUT_MIXER5 0x2B
+#define WM8991_INPUT_MIXER6 0x2C
+#define WM8991_OUTPUT_MIXER1 0x2D
+#define WM8991_OUTPUT_MIXER2 0x2E
+#define WM8991_OUTPUT_MIXER3 0x2F
+#define WM8991_OUTPUT_MIXER4 0x30
+#define WM8991_OUTPUT_MIXER5 0x31
+#define WM8991_OUTPUT_MIXER6 0x32
+#define WM8991_OUT3_4_MIXER 0x33
+#define WM8991_LINE_MIXER1 0x34
+#define WM8991_LINE_MIXER2 0x35
+#define WM8991_SPEAKER_MIXER 0x36
+#define WM8991_ADDITIONAL_CONTROL 0x37
+#define WM8991_ANTIPOP1 0x38
+#define WM8991_ANTIPOP2 0x39
+#define WM8991_MICBIAS 0x3A
+#define WM8991_PLL1 0x3C
+#define WM8991_PLL2 0x3D
+#define WM8991_PLL3 0x3E
+#define WM8991_INTDRIVBITS 0x3F
+
+#define WM8991_REGISTER_COUNT 60
+#define WM8991_MAX_REGISTER 0x3F
+
+/*
+ * Field Definitions.
+ */
+
+/*
+ * R0 (0x00) - Reset
+ */
+#define WM8991_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID - [15:0] */
+
+/*
+ * R1 (0x01) - Power Management (1)
+ */
+#define WM8991_SPK_ENA 0x1000 /* SPK_ENA */
+#define WM8991_SPK_ENA_BIT 12
+#define WM8991_OUT3_ENA 0x0800 /* OUT3_ENA */
+#define WM8991_OUT3_ENA_BIT 11
+#define WM8991_OUT4_ENA 0x0400 /* OUT4_ENA */
+#define WM8991_OUT4_ENA_BIT 10
+#define WM8991_LOUT_ENA 0x0200 /* LOUT_ENA */
+#define WM8991_LOUT_ENA_BIT 9
+#define WM8991_ROUT_ENA 0x0100 /* ROUT_ENA */
+#define WM8991_ROUT_ENA_BIT 8
+#define WM8991_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */
+#define WM8991_MICBIAS_ENA_BIT 4
+#define WM8991_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */
+#define WM8991_VREF_ENA 0x0001 /* VREF_ENA */
+#define WM8991_VREF_ENA_BIT 0
+
+/*
+ * R2 (0x02) - Power Management (2)
+ */
+#define WM8991_PLL_ENA 0x8000 /* PLL_ENA */
+#define WM8991_PLL_ENA_BIT 15
+#define WM8991_TSHUT_ENA 0x4000 /* TSHUT_ENA */
+#define WM8991_TSHUT_ENA_BIT 14
+#define WM8991_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */
+#define WM8991_TSHUT_OPDIS_BIT 13
+#define WM8991_OPCLK_ENA 0x0800 /* OPCLK_ENA */
+#define WM8991_OPCLK_ENA_BIT 11
+#define WM8991_AINL_ENA 0x0200 /* AINL_ENA */
+#define WM8991_AINL_ENA_BIT 9
+#define WM8991_AINR_ENA 0x0100 /* AINR_ENA */
+#define WM8991_AINR_ENA_BIT 8
+#define WM8991_LIN34_ENA 0x0080 /* LIN34_ENA */
+#define WM8991_LIN34_ENA_BIT 7
+#define WM8991_LIN12_ENA 0x0040 /* LIN12_ENA */
+#define WM8991_LIN12_ENA_BIT 6
+#define WM8991_RIN34_ENA 0x0020 /* RIN34_ENA */
+#define WM8991_RIN34_ENA_BIT 5
+#define WM8991_RIN12_ENA 0x0010 /* RIN12_ENA */
+#define WM8991_RIN12_ENA_BIT 4
+#define WM8991_ADCL_ENA 0x0002 /* ADCL_ENA */
+#define WM8991_ADCL_ENA_BIT 1
+#define WM8991_ADCR_ENA 0x0001 /* ADCR_ENA */
+#define WM8991_ADCR_ENA_BIT 0
+
+/*
+ * R3 (0x03) - Power Management (3)
+ */
+#define WM8991_LON_ENA 0x2000 /* LON_ENA */
+#define WM8991_LON_ENA_BIT 13
+#define WM8991_LOP_ENA 0x1000 /* LOP_ENA */
+#define WM8991_LOP_ENA_BIT 12
+#define WM8991_RON_ENA 0x0800 /* RON_ENA */
+#define WM8991_RON_ENA_BIT 11
+#define WM8991_ROP_ENA 0x0400 /* ROP_ENA */
+#define WM8991_ROP_ENA_BIT 10
+#define WM8991_LOPGA_ENA 0x0080 /* LOPGA_ENA */
+#define WM8991_LOPGA_ENA_BIT 7
+#define WM8991_ROPGA_ENA 0x0040 /* ROPGA_ENA */
+#define WM8991_ROPGA_ENA_BIT 6
+#define WM8991_LOMIX_ENA 0x0020 /* LOMIX_ENA */
+#define WM8991_LOMIX_ENA_BIT 5
+#define WM8991_ROMIX_ENA 0x0010 /* ROMIX_ENA */
+#define WM8991_ROMIX_ENA_BIT 4
+#define WM8991_DACL_ENA 0x0002 /* DACL_ENA */
+#define WM8991_DACL_ENA_BIT 1
+#define WM8991_DACR_ENA 0x0001 /* DACR_ENA */
+#define WM8991_DACR_ENA_BIT 0
+
+/*
+ * R4 (0x04) - Audio Interface (1)
+ */
+#define WM8991_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */
+#define WM8991_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */
+#define WM8991_AIFADC_TDM 0x2000 /* AIFADC_TDM */
+#define WM8991_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */
+#define WM8991_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */
+#define WM8991_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */
+#define WM8991_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */
+#define WM8991_AIF_WL_16BITS (0 << 5)
+#define WM8991_AIF_WL_20BITS (1 << 5)
+#define WM8991_AIF_WL_24BITS (2 << 5)
+#define WM8991_AIF_WL_32BITS (3 << 5)
+#define WM8991_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */
+#define WM8991_AIF_TMF_RIGHTJ (0 << 3)
+#define WM8991_AIF_TMF_LEFTJ (1 << 3)
+#define WM8991_AIF_TMF_I2S (2 << 3)
+#define WM8991_AIF_TMF_DSP (3 << 3)
+
+/*
+ * R5 (0x05) - Audio Interface (2)
+ */
+#define WM8991_DACL_SRC 0x8000 /* DACL_SRC */
+#define WM8991_DACR_SRC 0x4000 /* DACR_SRC */
+#define WM8991_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */
+#define WM8991_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */
+#define WM8991_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */
+#define WM8991_DAC_COMP 0x0010 /* DAC_COMP */
+#define WM8991_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */
+#define WM8991_ADC_COMP 0x0004 /* ADC_COMP */
+#define WM8991_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */
+#define WM8991_LOOPBACK 0x0001 /* LOOPBACK */
+
+/*
+ * R6 (0x06) - Clocking (1)
+ */
+#define WM8991_TOCLK_RATE 0x8000 /* TOCLK_RATE */
+#define WM8991_TOCLK_ENA 0x4000 /* TOCLK_ENA */
+#define WM8991_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */
+#define WM8991_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */
+#define WM8991_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */
+#define WM8991_BCLK_DIV_1 (0x0 << 1)
+#define WM8991_BCLK_DIV_1_5 (0x1 << 1)
+#define WM8991_BCLK_DIV_2 (0x2 << 1)
+#define WM8991_BCLK_DIV_3 (0x3 << 1)
+#define WM8991_BCLK_DIV_4 (0x4 << 1)
+#define WM8991_BCLK_DIV_5_5 (0x5 << 1)
+#define WM8991_BCLK_DIV_6 (0x6 << 1)
+#define WM8991_BCLK_DIV_8 (0x7 << 1)
+#define WM8991_BCLK_DIV_11 (0x8 << 1)
+#define WM8991_BCLK_DIV_12 (0x9 << 1)
+#define WM8991_BCLK_DIV_16 (0xA << 1)
+#define WM8991_BCLK_DIV_22 (0xB << 1)
+#define WM8991_BCLK_DIV_24 (0xC << 1)
+#define WM8991_BCLK_DIV_32 (0xD << 1)
+#define WM8991_BCLK_DIV_44 (0xE << 1)
+#define WM8991_BCLK_DIV_48 (0xF << 1)
+
+/*
+ * R7 (0x07) - Clocking (2)
+ */
+#define WM8991_MCLK_SRC 0x8000 /* MCLK_SRC */
+#define WM8991_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */
+#define WM8991_CLK_FORCE 0x2000 /* CLK_FORCE */
+#define WM8991_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */
+#define WM8991_MCLK_DIV_1 (0 << 11)
+#define WM8991_MCLK_DIV_2 ( 2 << 11)
+#define WM8991_MCLK_INV 0x0400 /* MCLK_INV */
+#define WM8991_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV - [7:5] */
+#define WM8991_ADC_CLKDIV_1 (0 << 5)
+#define WM8991_ADC_CLKDIV_1_5 (1 << 5)
+#define WM8991_ADC_CLKDIV_2 (2 << 5)
+#define WM8991_ADC_CLKDIV_3 (3 << 5)
+#define WM8991_ADC_CLKDIV_4 (4 << 5)
+#define WM8991_ADC_CLKDIV_5_5 (5 << 5)
+#define WM8991_ADC_CLKDIV_6 (6 << 5)
+#define WM8991_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */
+#define WM8991_DAC_CLKDIV_1 (0 << 2)
+#define WM8991_DAC_CLKDIV_1_5 (1 << 2)
+#define WM8991_DAC_CLKDIV_2 (2 << 2)
+#define WM8991_DAC_CLKDIV_3 (3 << 2)
+#define WM8991_DAC_CLKDIV_4 (4 << 2)
+#define WM8991_DAC_CLKDIV_5_5 (5 << 2)
+#define WM8991_DAC_CLKDIV_6 (6 << 2)
+
+/*
+ * R8 (0x08) - Audio Interface (3)
+ */
+#define WM8991_AIF_MSTR1 0x8000 /* AIF_MSTR1 */
+#define WM8991_AIF_MSTR2 0x4000 /* AIF_MSTR2 */
+#define WM8991_AIF_SEL 0x2000 /* AIF_SEL */
+#define WM8991_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */
+#define WM8991_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE - [10:0] */
+
+/*
+ * R9 (0x09) - Audio Interface (4)
+ */
+#define WM8991_ALRCGPIO1 0x8000 /* ALRCGPIO1 */
+#define WM8991_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */
+#define WM8991_AIF_TRIS 0x2000 /* AIF_TRIS */
+#define WM8991_DACLRC_DIR 0x0800 /* DACLRC_DIR */
+#define WM8991_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE - [10:0] */
+
+/*
+ * R10 (0x0A) - DAC CTRL
+ */
+#define WM8991_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */
+#define WM8991_DAC_MONO 0x0200 /* DAC_MONO */
+#define WM8991_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */
+#define WM8991_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */
+#define WM8991_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */
+#define WM8991_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */
+#define WM8991_DAC_MUTE 0x0004 /* DAC_MUTE */
+#define WM8991_DACL_DATINV 0x0002 /* DACL_DATINV */
+#define WM8991_DACR_DATINV 0x0001 /* DACR_DATINV */
+
+/*
+ * R11 (0x0B) - Left DAC Digital Volume
+ */
+#define WM8991_DAC_VU 0x0100 /* DAC_VU */
+#define WM8991_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */
+#define WM8991_DACL_VOL_SHIFT 0
+/*
+ * R12 (0x0C) - Right DAC Digital Volume
+ */
+#define WM8991_DAC_VU 0x0100 /* DAC_VU */
+#define WM8991_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */
+#define WM8991_DACR_VOL_SHIFT 0
+/*
+ * R13 (0x0D) - Digital Side Tone
+ */
+#define WM8991_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL - [12:9] */
+#define WM8991_ADCL_DAC_SVOL_SHIFT 9
+#define WM8991_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL - [8:5] */
+#define WM8991_ADCR_DAC_SVOL_SHIFT 5
+#define WM8991_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */
+#define WM8991_ADC_TO_DACL_SHIFT 2
+#define WM8991_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */
+#define WM8991_ADC_TO_DACR_SHIFT 0
+
+/*
+ * R14 (0x0E) - ADC CTRL
+ */
+#define WM8991_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */
+#define WM8991_ADC_HPF_ENA_BIT 8
+#define WM8991_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */
+#define WM8991_ADC_HPF_CUT_SHIFT 5
+#define WM8991_ADCL_DATINV 0x0002 /* ADCL_DATINV */
+#define WM8991_ADCL_DATINV_BIT 1
+#define WM8991_ADCR_DATINV 0x0001 /* ADCR_DATINV */
+#define WM8991_ADCR_DATINV_BIT 0
+
+/*
+ * R15 (0x0F) - Left ADC Digital Volume
+ */
+#define WM8991_ADC_VU 0x0100 /* ADC_VU */
+#define WM8991_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */
+#define WM8991_ADCL_VOL_SHIFT 0
+
+/*
+ * R16 (0x10) - Right ADC Digital Volume
+ */
+#define WM8991_ADC_VU 0x0100 /* ADC_VU */
+#define WM8991_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */
+#define WM8991_ADCR_VOL_SHIFT 0
+
+/*
+ * R18 (0x12) - GPIO CTRL 1
+ */
+#define WM8991_IRQ 0x1000 /* IRQ */
+#define WM8991_TEMPOK 0x0800 /* TEMPOK */
+#define WM8991_MICSHRT 0x0400 /* MICSHRT */
+#define WM8991_MICDET 0x0200 /* MICDET */
+#define WM8991_PLL_LCK 0x0100 /* PLL_LCK */
+#define WM8991_GPI8_STATUS 0x0080 /* GPI8_STATUS */
+#define WM8991_GPI7_STATUS 0x0040 /* GPI7_STATUS */
+#define WM8991_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */
+#define WM8991_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */
+#define WM8991_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */
+#define WM8991_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */
+#define WM8991_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */
+#define WM8991_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */
+
+/*
+ * R19 (0x13) - GPIO1 & GPIO2
+ */
+#define WM8991_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */
+#define WM8991_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */
+#define WM8991_GPIO2_PU 0x2000 /* GPIO2_PU */
+#define WM8991_GPIO2_PD 0x1000 /* GPIO2_PD */
+#define WM8991_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */
+#define WM8991_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */
+#define WM8991_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */
+#define WM8991_GPIO1_PU 0x0020 /* GPIO1_PU */
+#define WM8991_GPIO1_PD 0x0010 /* GPIO1_PD */
+#define WM8991_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */
+
+/*
+ * R20 (0x14) - GPIO3 & GPIO4
+ */
+#define WM8991_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */
+#define WM8991_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */
+#define WM8991_GPIO4_PU 0x2000 /* GPIO4_PU */
+#define WM8991_GPIO4_PD 0x1000 /* GPIO4_PD */
+#define WM8991_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */
+#define WM8991_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */
+#define WM8991_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */
+#define WM8991_GPIO3_PU 0x0020 /* GPIO3_PU */
+#define WM8991_GPIO3_PD 0x0010 /* GPIO3_PD */
+#define WM8991_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */
+
+/*
+ * R21 (0x15) - GPIO5 & GPIO6
+ */
+#define WM8991_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */
+#define WM8991_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */
+#define WM8991_GPIO6_PU 0x2000 /* GPIO6_PU */
+#define WM8991_GPIO6_PD 0x1000 /* GPIO6_PD */
+#define WM8991_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */
+#define WM8991_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */
+#define WM8991_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */
+#define WM8991_GPIO5_PU 0x0020 /* GPIO5_PU */
+#define WM8991_GPIO5_PD 0x0010 /* GPIO5_PD */
+#define WM8991_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */
+
+/*
+ * R22 (0x16) - GPIOCTRL 2
+ */
+#define WM8991_RD_3W_ENA 0x8000 /* RD_3W_ENA */
+#define WM8991_MODE_3W4W 0x4000 /* MODE_3W4W */
+#define WM8991_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */
+#define WM8991_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */
+#define WM8991_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */
+#define WM8991_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */
+#define WM8991_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */
+#define WM8991_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */
+#define WM8991_GPI8_ENA 0x0010 /* GPI8_ENA */
+#define WM8991_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */
+#define WM8991_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */
+#define WM8991_GPI7_ENA 0x0001 /* GPI7_ENA */
+
+/*
+ * R23 (0x17) - GPIO_POL
+ */
+#define WM8991_IRQ_INV 0x1000 /* IRQ_INV */
+#define WM8991_TEMPOK_POL 0x0800 /* TEMPOK_POL */
+#define WM8991_MICSHRT_POL 0x0400 /* MICSHRT_POL */
+#define WM8991_MICDET_POL 0x0200 /* MICDET_POL */
+#define WM8991_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */
+#define WM8991_GPI8_POL 0x0080 /* GPI8_POL */
+#define WM8991_GPI7_POL 0x0040 /* GPI7_POL */
+#define WM8991_GPIO6_POL 0x0020 /* GPIO6_POL */
+#define WM8991_GPIO5_POL 0x0010 /* GPIO5_POL */
+#define WM8991_GPIO4_POL 0x0008 /* GPIO4_POL */
+#define WM8991_GPIO3_POL 0x0004 /* GPIO3_POL */
+#define WM8991_GPIO2_POL 0x0002 /* GPIO2_POL */
+#define WM8991_GPIO1_POL 0x0001 /* GPIO1_POL */
+
+/*
+ * R24 (0x18) - Left Line Input 1&2 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_LI12MUTE 0x0080 /* LI12MUTE */
+#define WM8991_LI12MUTE_BIT 7
+#define WM8991_LI12ZC 0x0040 /* LI12ZC */
+#define WM8991_LI12ZC_BIT 6
+#define WM8991_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */
+#define WM8991_LIN12VOL_SHIFT 0
+/*
+ * R25 (0x19) - Left Line Input 3&4 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_LI34MUTE 0x0080 /* LI34MUTE */
+#define WM8991_LI34MUTE_BIT 7
+#define WM8991_LI34ZC 0x0040 /* LI34ZC */
+#define WM8991_LI34ZC_BIT 6
+#define WM8991_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */
+#define WM8991_LIN34VOL_SHIFT 0
+
+/*
+ * R26 (0x1A) - Right Line Input 1&2 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_RI12MUTE 0x0080 /* RI12MUTE */
+#define WM8991_RI12MUTE_BIT 7
+#define WM8991_RI12ZC 0x0040 /* RI12ZC */
+#define WM8991_RI12ZC_BIT 6
+#define WM8991_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */
+#define WM8991_RIN12VOL_SHIFT 0
+
+/*
+ * R27 (0x1B) - Right Line Input 3&4 Volume
+ */
+#define WM8991_IPVU 0x0100 /* IPVU */
+#define WM8991_RI34MUTE 0x0080 /* RI34MUTE */
+#define WM8991_RI34MUTE_BIT 7
+#define WM8991_RI34ZC 0x0040 /* RI34ZC */
+#define WM8991_RI34ZC_BIT 6
+#define WM8991_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */
+#define WM8991_RIN34VOL_SHIFT 0
+
+/*
+ * R28 (0x1C) - Left Output Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_LOZC 0x0080 /* LOZC */
+#define WM8991_LOZC_BIT 7
+#define WM8991_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */
+#define WM8991_LOUTVOL_SHIFT 0
+/*
+ * R29 (0x1D) - Right Output Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_ROZC 0x0080 /* ROZC */
+#define WM8991_ROZC_BIT 7
+#define WM8991_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */
+#define WM8991_ROUTVOL_SHIFT 0
+/*
+ * R30 (0x1E) - Line Outputs Volume
+ */
+#define WM8991_LONMUTE 0x0040 /* LONMUTE */
+#define WM8991_LONMUTE_BIT 6
+#define WM8991_LOPMUTE 0x0020 /* LOPMUTE */
+#define WM8991_LOPMUTE_BIT 5
+#define WM8991_LOATTN 0x0010 /* LOATTN */
+#define WM8991_LOATTN_BIT 4
+#define WM8991_RONMUTE 0x0004 /* RONMUTE */
+#define WM8991_RONMUTE_BIT 2
+#define WM8991_ROPMUTE 0x0002 /* ROPMUTE */
+#define WM8991_ROPMUTE_BIT 1
+#define WM8991_ROATTN 0x0001 /* ROATTN */
+#define WM8991_ROATTN_BIT 0
+
+/*
+ * R31 (0x1F) - Out3/4 Volume
+ */
+#define WM8991_OUT3MUTE 0x0020 /* OUT3MUTE */
+#define WM8991_OUT3MUTE_BIT 5
+#define WM8991_OUT3ATTN 0x0010 /* OUT3ATTN */
+#define WM8991_OUT3ATTN_BIT 4
+#define WM8991_OUT4MUTE 0x0002 /* OUT4MUTE */
+#define WM8991_OUT4MUTE_BIT 1
+#define WM8991_OUT4ATTN 0x0001 /* OUT4ATTN */
+#define WM8991_OUT4ATTN_BIT 0
+
+/*
+ * R32 (0x20) - Left OPGA Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_LOPGAZC 0x0080 /* LOPGAZC */
+#define WM8991_LOPGAZC_BIT 7
+#define WM8991_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */
+#define WM8991_LOPGAVOL_SHIFT 0
+
+/*
+ * R33 (0x21) - Right OPGA Volume
+ */
+#define WM8991_OPVU 0x0100 /* OPVU */
+#define WM8991_ROPGAZC 0x0080 /* ROPGAZC */
+#define WM8991_ROPGAZC_BIT 7
+#define WM8991_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */
+#define WM8991_ROPGAVOL_SHIFT 0
+/*
+ * R34 (0x22) - Speaker Volume
+ */
+#define WM8991_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */
+#define WM8991_SPKVOL_SHIFT 0
+
+/*
+ * R35 (0x23) - ClassD1
+ */
+#define WM8991_CDMODE 0x0100 /* CDMODE */
+#define WM8991_CDMODE_BIT 8
+
+/*
+ * R37 (0x25) - ClassD3
+ */
+#define WM8991_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */
+#define WM8991_DCGAIN_SHIFT 3
+#define WM8991_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */
+#define WM8991_ACGAIN_SHIFT 0
+/*
+ * R39 (0x27) - Input Mixer1
+ */
+#define WM8991_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */
+#define WM8991_AINLMODE_SHIFT 2
+#define WM8991_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */
+#define WM8991_AINRMODE_SHIFT 0
+
+/*
+ * R40 (0x28) - Input Mixer2
+ */
+#define WM8991_LMP4 0x0080 /* LMP4 */
+#define WM8991_LMP4_BIT 7 /* LMP4 */
+#define WM8991_LMN3 0x0040 /* LMN3 */
+#define WM8991_LMN3_BIT 6 /* LMN3 */
+#define WM8991_LMP2 0x0020 /* LMP2 */
+#define WM8991_LMP2_BIT 5 /* LMP2 */
+#define WM8991_LMN1 0x0010 /* LMN1 */
+#define WM8991_LMN1_BIT 4 /* LMN1 */
+#define WM8991_RMP4 0x0008 /* RMP4 */
+#define WM8991_RMP4_BIT 3 /* RMP4 */
+#define WM8991_RMN3 0x0004 /* RMN3 */
+#define WM8991_RMN3_BIT 2 /* RMN3 */
+#define WM8991_RMP2 0x0002 /* RMP2 */
+#define WM8991_RMP2_BIT 1 /* RMP2 */
+#define WM8991_RMN1 0x0001 /* RMN1 */
+#define WM8991_RMN1_BIT 0 /* RMN1 */
+
+/*
+ * R41 (0x29) - Input Mixer3
+ */
+#define WM8991_L34MNB 0x0100 /* L34MNB */
+#define WM8991_L34MNB_BIT 8
+#define WM8991_L34MNBST 0x0080 /* L34MNBST */
+#define WM8991_L34MNBST_BIT 7
+#define WM8991_L12MNB 0x0020 /* L12MNB */
+#define WM8991_L12MNB_BIT 5
+#define WM8991_L12MNBST 0x0010 /* L12MNBST */
+#define WM8991_L12MNBST_BIT 4
+#define WM8991_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */
+#define WM8991_LDBVOL_SHIFT 0
+
+/*
+ * R42 (0x2A) - Input Mixer4
+ */
+#define WM8991_R34MNB 0x0100 /* R34MNB */
+#define WM8991_R34MNB_BIT 8
+#define WM8991_R34MNBST 0x0080 /* R34MNBST */
+#define WM8991_R34MNBST_BIT 7
+#define WM8991_R12MNB 0x0020 /* R12MNB */
+#define WM8991_R12MNB_BIT 5
+#define WM8991_R12MNBST 0x0010 /* R12MNBST */
+#define WM8991_R12MNBST_BIT 4
+#define WM8991_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */
+#define WM8991_RDBVOL_SHIFT 0
+
+/*
+ * R43 (0x2B) - Input Mixer5
+ */
+#define WM8991_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */
+#define WM8991_LI2BVOL_SHIFT 6
+#define WM8991_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */
+#define WM8991_LR4BVOL_SHIFT 3
+#define WM8991_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */
+#define WM8991_LL4BVOL_SHIFT 0
+
+/*
+ * R44 (0x2C) - Input Mixer6
+ */
+#define WM8991_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */
+#define WM8991_RI2BVOL_SHIFT 6
+#define WM8991_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */
+#define WM8991_RL4BVOL_SHIFT 3
+#define WM8991_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */
+#define WM8991_RR4BVOL_SHIFT 0
+
+/*
+ * R45 (0x2D) - Output Mixer1
+ */
+#define WM8991_LRBLO 0x0080 /* LRBLO */
+#define WM8991_LRBLO_BIT 7
+#define WM8991_LLBLO 0x0040 /* LLBLO */
+#define WM8991_LLBLO_BIT 6
+#define WM8991_LRI3LO 0x0020 /* LRI3LO */
+#define WM8991_LRI3LO_BIT 5
+#define WM8991_LLI3LO 0x0010 /* LLI3LO */
+#define WM8991_LLI3LO_BIT 4
+#define WM8991_LR12LO 0x0008 /* LR12LO */
+#define WM8991_LR12LO_BIT 3
+#define WM8991_LL12LO 0x0004 /* LL12LO */
+#define WM8991_LL12LO_BIT 2
+#define WM8991_LDLO 0x0001 /* LDLO */
+#define WM8991_LDLO_BIT 0
+
+/*
+ * R46 (0x2E) - Output Mixer2
+ */
+#define WM8991_RLBRO 0x0080 /* RLBRO */
+#define WM8991_RLBRO_BIT 7
+#define WM8991_RRBRO 0x0040 /* RRBRO */
+#define WM8991_RRBRO_BIT 6
+#define WM8991_RLI3RO 0x0020 /* RLI3RO */
+#define WM8991_RLI3RO_BIT 5
+#define WM8991_RRI3RO 0x0010 /* RRI3RO */
+#define WM8991_RRI3RO_BIT 4
+#define WM8991_RL12RO 0x0008 /* RL12RO */
+#define WM8991_RL12RO_BIT 3
+#define WM8991_RR12RO 0x0004 /* RR12RO */
+#define WM8991_RR12RO_BIT 2
+#define WM8991_RDRO 0x0001 /* RDRO */
+#define WM8991_RDRO_BIT 0
+
+/*
+ * R47 (0x2F) - Output Mixer3
+ */
+#define WM8991_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */
+#define WM8991_LLI3LOVOL_SHIFT 6
+#define WM8991_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */
+#define WM8991_LR12LOVOL_SHIFT 3
+#define WM8991_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */
+#define WM8991_LL12LOVOL_SHIFT 0
+
+/*
+ * R48 (0x30) - Output Mixer4
+ */
+#define WM8991_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */
+#define WM8991_RRI3ROVOL_SHIFT 6
+#define WM8991_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */
+#define WM8991_RL12ROVOL_SHIFT 3
+#define WM8991_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */
+#define WM8991_RR12ROVOL_SHIFT 0
+
+/*
+ * R49 (0x31) - Output Mixer5
+ */
+#define WM8991_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */
+#define WM8991_LRI3LOVOL_SHIFT 6
+#define WM8991_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */
+#define WM8991_LRBLOVOL_SHIFT 3
+#define WM8991_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */
+#define WM8991_LLBLOVOL_SHIFT 0
+
+/*
+ * R50 (0x32) - Output Mixer6
+ */
+#define WM8991_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */
+#define WM8991_RLI3ROVOL_SHIFT 6
+#define WM8991_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */
+#define WM8991_RLBROVOL_SHIFT 3
+#define WM8991_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */
+#define WM8991_RRBROVOL_SHIFT 0
+
+/*
+ * R51 (0x33) - Out3/4 Mixer
+ */
+#define WM8991_VSEL_MASK 0x0180 /* VSEL - [8:7] */
+#define WM8991_LI4O3 0x0020 /* LI4O3 */
+#define WM8991_LI4O3_BIT 5
+#define WM8991_LPGAO3 0x0010 /* LPGAO3 */
+#define WM8991_LPGAO3_BIT 4
+#define WM8991_RI4O4 0x0002 /* RI4O4 */
+#define WM8991_RI4O4_BIT 1
+#define WM8991_RPGAO4 0x0001 /* RPGAO4 */
+#define WM8991_RPGAO4_BIT 0
+/*
+ * R52 (0x34) - Line Mixer1
+ */
+#define WM8991_LLOPGALON 0x0040 /* LLOPGALON */
+#define WM8991_LLOPGALON_BIT 6
+#define WM8991_LROPGALON 0x0020 /* LROPGALON */
+#define WM8991_LROPGALON_BIT 5
+#define WM8991_LOPLON 0x0010 /* LOPLON */
+#define WM8991_LOPLON_BIT 4
+#define WM8991_LR12LOP 0x0004 /* LR12LOP */
+#define WM8991_LR12LOP_BIT 2
+#define WM8991_LL12LOP 0x0002 /* LL12LOP */
+#define WM8991_LL12LOP_BIT 1
+#define WM8991_LLOPGALOP 0x0001 /* LLOPGALOP */
+#define WM8991_LLOPGALOP_BIT 0
+/*
+ * R53 (0x35) - Line Mixer2
+ */
+#define WM8991_RROPGARON 0x0040 /* RROPGARON */
+#define WM8991_RROPGARON_BIT 6
+#define WM8991_RLOPGARON 0x0020 /* RLOPGARON */
+#define WM8991_RLOPGARON_BIT 5
+#define WM8991_ROPRON 0x0010 /* ROPRON */
+#define WM8991_ROPRON_BIT 4
+#define WM8991_RL12ROP 0x0004 /* RL12ROP */
+#define WM8991_RL12ROP_BIT 2
+#define WM8991_RR12ROP 0x0002 /* RR12ROP */
+#define WM8991_RR12ROP_BIT 1
+#define WM8991_RROPGAROP 0x0001 /* RROPGAROP */
+#define WM8991_RROPGAROP_BIT 0
+
+/*
+ * R54 (0x36) - Speaker Mixer
+ */
+#define WM8991_LB2SPK 0x0080 /* LB2SPK */
+#define WM8991_LB2SPK_BIT 7
+#define WM8991_RB2SPK 0x0040 /* RB2SPK */
+#define WM8991_RB2SPK_BIT 6
+#define WM8991_LI2SPK 0x0020 /* LI2SPK */
+#define WM8991_LI2SPK_BIT 5
+#define WM8991_RI2SPK 0x0010 /* RI2SPK */
+#define WM8991_RI2SPK_BIT 4
+#define WM8991_LOPGASPK 0x0008 /* LOPGASPK */
+#define WM8991_LOPGASPK_BIT 3
+#define WM8991_ROPGASPK 0x0004 /* ROPGASPK */
+#define WM8991_ROPGASPK_BIT 2
+#define WM8991_LDSPK 0x0002 /* LDSPK */
+#define WM8991_LDSPK_BIT 1
+#define WM8991_RDSPK 0x0001 /* RDSPK */
+#define WM8991_RDSPK_BIT 0
+
+/*
+ * R55 (0x37) - Additional Control
+ */
+#define WM8991_VROI 0x0001 /* VROI */
+
+/*
+ * R56 (0x38) - AntiPOP1
+ */
+#define WM8991_DIS_LLINE 0x0020 /* DIS_LLINE */
+#define WM8991_DIS_RLINE 0x0010 /* DIS_RLINE */
+#define WM8991_DIS_OUT3 0x0008 /* DIS_OUT3 */
+#define WM8991_DIS_OUT4 0x0004 /* DIS_OUT4 */
+#define WM8991_DIS_LOUT 0x0002 /* DIS_LOUT */
+#define WM8991_DIS_ROUT 0x0001 /* DIS_ROUT */
+
+/*
+ * R57 (0x39) - AntiPOP2
+ */
+#define WM8991_SOFTST 0x0040 /* SOFTST */
+#define WM8991_BUFIOEN 0x0008 /* BUFIOEN */
+#define WM8991_BUFDCOPEN 0x0004 /* BUFDCOPEN */
+#define WM8991_POBCTRL 0x0002 /* POBCTRL */
+#define WM8991_VMIDTOG 0x0001 /* VMIDTOG */
+
+/*
+ * R58 (0x3A) - MICBIAS
+ */
+#define WM8991_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */
+#define WM8991_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */
+#define WM8991_MCD 0x0004 /* MCD */
+#define WM8991_MBSEL 0x0001 /* MBSEL */
+
+/*
+ * R60 (0x3C) - PLL1
+ */
+#define WM8991_SDM 0x0080 /* SDM */
+#define WM8991_PRESCALE 0x0040 /* PRESCALE */
+#define WM8991_PLLN_MASK 0x000F /* PLLN - [3:0] */
+
+/*
+ * R61 (0x3D) - PLL2
+ */
+#define WM8991_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */
+
+/*
+ * R62 (0x3E) - PLL3
+ */
+#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
+
+/*
+ * R63 (0x3F) - Internal Driver Bits
+ */
+#define WM8991_INMIXL_PWR_BIT 0
+#define WM8991_AINLMUX_PWR_BIT 1
+#define WM8991_INMIXR_PWR_BIT 2
+#define WM8991_AINRMUX_PWR_BIT 3
+
+#define WM8991_MCLK_DIV 0
+#define WM8991_DACCLK_DIV 1
+#define WM8991_ADCCLK_DIV 2
+#define WM8991_BCLK_DIV 3
+
+#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\
+ tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+#endif /* _WM8991_H */
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index 18c0d9ce7c3..379fa22c5b6 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -242,7 +242,7 @@ struct wm8993_priv {
int fll_src;
};
-static int wm8993_volatile(unsigned int reg)
+static int wm8993_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8993_SOFTWARE_RESET:
diff --git a/sound/soc/codecs/wm8994-tables.c b/sound/soc/codecs/wm8994-tables.c
index 68e9b024dd4..a87adbd05ee 100644
--- a/sound/soc/codecs/wm8994-tables.c
+++ b/sound/soc/codecs/wm8994-tables.c
@@ -62,8 +62,8 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {
{ 0x00FF, 0x00FF }, /* R58 - MICBIAS */
{ 0x000F, 0x000F }, /* R59 - LDO 1 */
{ 0x0007, 0x0007 }, /* R60 - LDO 2 */
- { 0x0000, 0x0000 }, /* R61 */
- { 0x0000, 0x0000 }, /* R62 */
+ { 0xFFFF, 0xFFFF }, /* R61 */
+ { 0xFFFF, 0xFFFF }, /* R62 */
{ 0x0000, 0x0000 }, /* R63 */
{ 0x0000, 0x0000 }, /* R64 */
{ 0x0000, 0x0000 }, /* R65 */
@@ -209,9 +209,9 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {
{ 0x0000, 0x0000 }, /* R205 */
{ 0x0000, 0x0000 }, /* R206 */
{ 0x0000, 0x0000 }, /* R207 */
- { 0x0000, 0x0000 }, /* R208 */
- { 0x0000, 0x0000 }, /* R209 */
- { 0x0000, 0x0000 }, /* R210 */
+ { 0xFFFF, 0xFFFF }, /* R208 */
+ { 0xFFFF, 0xFFFF }, /* R209 */
+ { 0xFFFF, 0xFFFF }, /* R210 */
{ 0x0000, 0x0000 }, /* R211 */
{ 0x0000, 0x0000 }, /* R212 */
{ 0x0000, 0x0000 }, /* R213 */
@@ -1573,7 +1573,7 @@ const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE] = {
{ 0x03C3, 0x03C3 }, /* R1569 - Sidetone */
};
-const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {
+const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE] = {
0x8994, /* R0 - Software Reset */
0x0000, /* R1 - Power Management (1) */
0x6000, /* R2 - Power Management (2) */
diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c
index ebaee5ca743..3dc64c8b6a5 100644
--- a/sound/soc/codecs/wm8994.c
+++ b/sound/soc/codecs/wm8994.c
@@ -102,17 +102,19 @@ struct wm8994_priv {
wm8958_micdet_cb jack_cb;
void *jack_cb_data;
- bool jack_is_mic;
- bool jack_is_video;
+ int micdet_irq;
int revision;
struct wm8994_pdata *pdata;
unsigned int aif1clk_enable:1;
unsigned int aif2clk_enable:1;
+
+ unsigned int aif1clk_disable:1;
+ unsigned int aif2clk_disable:1;
};
-static int wm8994_readable(unsigned int reg)
+static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8994_GPIO_1:
@@ -139,7 +141,7 @@ static int wm8994_readable(unsigned int reg)
return wm8994_access_masks[reg].readable != 0;
}
-static int wm8994_volatile(unsigned int reg)
+static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
if (reg >= WM8994_CACHE_SIZE)
return 1;
@@ -167,7 +169,7 @@ static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg,
BUG_ON(reg > WM8994_MAX_REGISTER);
- if (!wm8994_volatile(reg)) {
+ if (!wm8994_volatile(codec, reg)) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret != 0)
dev_err(codec->dev, "Cache write to %x failed: %d\n",
@@ -185,7 +187,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec,
BUG_ON(reg > WM8994_MAX_REGISTER);
- if (!wm8994_volatile(reg) && wm8994_readable(reg) &&
+ if (!wm8994_volatile(codec, reg) && wm8994_readable(codec, reg) &&
reg < codec->driver->reg_cache_size) {
ret = snd_soc_cache_read(codec, reg, &val);
if (ret >= 0)
@@ -526,7 +528,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
- struct wm8994_priv *wm8994 =snd_soc_codec_get_drvdata(codec);
+ struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block];
@@ -1015,14 +1017,18 @@ static int late_enable_ev(struct snd_soc_dapm_widget *w,
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
- if (wm8994->aif1clk_enable)
+ if (wm8994->aif1clk_enable) {
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
WM8994_AIF1CLK_ENA_MASK,
WM8994_AIF1CLK_ENA);
- if (wm8994->aif2clk_enable)
+ wm8994->aif1clk_enable = 0;
+ }
+ if (wm8994->aif2clk_enable) {
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
WM8994_AIF2CLK_ENA_MASK,
WM8994_AIF2CLK_ENA);
+ wm8994->aif2clk_enable = 0;
+ }
break;
}
@@ -1037,15 +1043,15 @@ static int late_disable_ev(struct snd_soc_dapm_widget *w,
switch (event) {
case SND_SOC_DAPM_POST_PMD:
- if (wm8994->aif1clk_enable) {
+ if (wm8994->aif1clk_disable) {
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
WM8994_AIF1CLK_ENA_MASK, 0);
- wm8994->aif1clk_enable = 0;
+ wm8994->aif1clk_disable = 0;
}
- if (wm8994->aif2clk_enable) {
+ if (wm8994->aif2clk_disable) {
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
WM8994_AIF2CLK_ENA_MASK, 0);
- wm8994->aif2clk_enable = 0;
+ wm8994->aif2clk_disable = 0;
}
break;
}
@@ -1063,6 +1069,9 @@ static int aif1clk_ev(struct snd_soc_dapm_widget *w,
case SND_SOC_DAPM_PRE_PMU:
wm8994->aif1clk_enable = 1;
break;
+ case SND_SOC_DAPM_POST_PMD:
+ wm8994->aif1clk_disable = 1;
+ break;
}
return 0;
@@ -1078,11 +1087,28 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w,
case SND_SOC_DAPM_PRE_PMU:
wm8994->aif2clk_enable = 1;
break;
+ case SND_SOC_DAPM_POST_PMD:
+ wm8994->aif2clk_disable = 1;
+ break;
}
return 0;
}
+static int adc_mux_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ late_enable_ev(w, kcontrol, event);
+ return 0;
+}
+
+static int micbias_ev(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ late_enable_ev(w, kcontrol, event);
+ return 0;
+}
+
static int dac_ev(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -1398,16 +1424,32 @@ SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0,
static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = {
SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0),
-SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0),
+SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0),
SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0),
SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0),
};
+static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = {
+SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux,
+ adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
+SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux,
+ adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
+};
+
+static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = {
+SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux),
+SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux),
+};
+
static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("DMIC1DAT"),
SND_SOC_DAPM_INPUT("DMIC2DAT"),
SND_SOC_DAPM_INPUT("Clock"),
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8994_MICBIAS, 2, 0),
+SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev,
+ SND_SOC_DAPM_PRE_PMU),
+
SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
@@ -1497,9 +1539,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
-SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux),
-SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux),
-
SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
@@ -1726,6 +1765,8 @@ static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {
{ "AIF2DACDAT", NULL, "AIF1DACDAT" },
{ "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
{ "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
+ { "MICBIAS", NULL, "CLK_SYS" },
+ { "MICBIAS", NULL, "MICBIAS Supply" },
};
static const struct snd_soc_dapm_route wm8994_intercon[] = {
@@ -2854,6 +2895,13 @@ static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
else
snd_soc_add_controls(wm8994->codec, wm8994_eq_controls,
ARRAY_SIZE(wm8994_eq_controls));
+
+ for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) {
+ if (pdata->micbias[i]) {
+ snd_soc_write(codec, WM8958_MICBIAS1 + i,
+ pdata->micbias[i] & 0xffff);
+ }
+ }
}
/**
@@ -2964,46 +3012,18 @@ static void wm8958_default_micdet(u16 status, void *data)
int report = 0;
/* If nothing present then clear our statuses */
- if (!(status & WM8958_MICD_STS)) {
- wm8994->jack_is_video = false;
- wm8994->jack_is_mic = false;
+ if (!(status & WM8958_MICD_STS))
goto done;
- }
-
- /* Assume anything over 475 ohms is a microphone and remember
- * that we've seen one (since buttons override it) */
- if (status & 0x600)
- wm8994->jack_is_mic = true;
- if (wm8994->jack_is_mic)
- report |= SND_JACK_MICROPHONE;
- /* Video has an impedence of approximately 75 ohms; assume
- * this isn't used as a button and remember it since buttons
- * override it. */
- if (status & 0x40)
- wm8994->jack_is_video = true;
- if (wm8994->jack_is_video)
- report |= SND_JACK_VIDEOOUT;
+ report = SND_JACK_MICROPHONE;
/* Everything else is buttons; just assign slots */
- if (status & 0x4)
+ if (status & 0x1c0)
report |= SND_JACK_BTN_0;
- if (status & 0x8)
- report |= SND_JACK_BTN_1;
- if (status & 0x10)
- report |= SND_JACK_BTN_2;
- if (status & 0x20)
- report |= SND_JACK_BTN_3;
- if (status & 0x80)
- report |= SND_JACK_BTN_4;
- if (status & 0x100)
- report |= SND_JACK_BTN_5;
done:
snd_soc_jack_report(wm8994->micdet[0].jack, report,
- SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2 |
- SND_JACK_BTN_3 | SND_JACK_BTN_4 | SND_JACK_BTN_5 |
- SND_JACK_MICROPHONE | SND_JACK_VIDEOOUT);
+ SND_JACK_BTN_0 | SND_JACK_MICROPHONE);
}
/**
@@ -3102,13 +3122,19 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
wm8994->pdata = dev_get_platdata(codec->dev->parent);
wm8994->codec = codec;
+ if (wm8994->pdata && wm8994->pdata->micdet_irq)
+ wm8994->micdet_irq = wm8994->pdata->micdet_irq;
+ else if (wm8994->pdata && wm8994->pdata->irq_base)
+ wm8994->micdet_irq = wm8994->pdata->irq_base +
+ WM8994_IRQ_MIC1_DET;
+
pm_runtime_enable(codec->dev);
pm_runtime_resume(codec->dev);
/* Read our current status back from the chip - we don't want to
* reset as this may interfere with the GPIO or LDO operation. */
for (i = 0; i < WM8994_CACHE_SIZE; i++) {
- if (!wm8994_readable(i) || wm8994_volatile(i))
+ if (!wm8994_readable(codec, i) || wm8994_volatile(codec, i))
continue;
ret = wm8994_reg_read(codec->control_data, i);
@@ -3150,14 +3176,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
switch (control->type) {
case WM8994:
- ret = wm8994_request_irq(codec->control_data,
- WM8994_IRQ_MIC1_DET,
- wm8994_mic_irq, "Mic 1 detect",
- wm8994);
- if (ret != 0)
- dev_warn(codec->dev,
- "Failed to request Mic1 detect IRQ: %d\n",
- ret);
+ if (wm8994->micdet_irq) {
+ ret = request_threaded_irq(wm8994->micdet_irq, NULL,
+ wm8994_mic_irq,
+ IRQF_TRIGGER_RISING,
+ "Mic1 detect",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic1 detect IRQ: %d\n",
+ ret);
+ }
ret = wm8994_request_irq(codec->control_data,
WM8994_IRQ_MIC1_SHRT,
@@ -3188,15 +3217,17 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
break;
case WM8958:
- ret = wm8994_request_irq(codec->control_data,
- WM8994_IRQ_MIC1_DET,
- wm8958_mic_irq, "Mic detect",
- wm8994);
- if (ret != 0)
- dev_warn(codec->dev,
- "Failed to request Mic detect IRQ: %d\n",
- ret);
- break;
+ if (wm8994->micdet_irq) {
+ ret = request_threaded_irq(wm8994->micdet_irq, NULL,
+ wm8958_mic_irq,
+ IRQF_TRIGGER_RISING,
+ "Mic detect",
+ wm8994);
+ if (ret != 0)
+ dev_warn(codec->dev,
+ "Failed to request Mic detect IRQ: %d\n",
+ ret);
+ }
}
/* Remember if AIFnLRCLK is configured as a GPIO. This should be
@@ -3280,11 +3311,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
if (wm8994->revision < 4) {
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
ARRAY_SIZE(wm8994_lateclk_revd_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets,
+ ARRAY_SIZE(wm8994_adc_revd_widgets));
snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets,
ARRAY_SIZE(wm8994_dac_revd_widgets));
} else {
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
ARRAY_SIZE(wm8994_lateclk_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
+ ARRAY_SIZE(wm8994_adc_widgets));
snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
ARRAY_SIZE(wm8994_dac_widgets));
}
@@ -3292,6 +3327,12 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
case WM8958:
snd_soc_add_controls(codec, wm8958_snd_controls,
ARRAY_SIZE(wm8958_snd_controls));
+ snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
+ ARRAY_SIZE(wm8994_lateclk_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
+ ARRAY_SIZE(wm8994_adc_widgets));
+ snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
+ ARRAY_SIZE(wm8994_dac_widgets));
snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets,
ARRAY_SIZE(wm8958_dapm_widgets));
break;
@@ -3317,6 +3358,8 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec)
}
break;
case WM8958:
+ snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
+ ARRAY_SIZE(wm8994_lateclk_intercon));
snd_soc_dapm_add_routes(dapm, wm8958_intercon,
ARRAY_SIZE(wm8958_intercon));
break;
@@ -3328,7 +3371,8 @@ err_irq:
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
- wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET, wm8994);
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
err:
kfree(wm8994);
return ret;
@@ -3345,8 +3389,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec)
switch (control->type) {
case WM8994:
- wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT,
- wm8994);
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET,
wm8994);
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT,
@@ -3356,8 +3400,8 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec)
break;
case WM8958:
- wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET,
- wm8994);
+ if (wm8994->micdet_irq)
+ free_irq(wm8994->micdet_irq, wm8994);
break;
}
kfree(wm8994->retune_mobile_texts);
diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h
index 0c355bfc88f..999b8851226 100644
--- a/sound/soc/codecs/wm8994.h
+++ b/sound/soc/codecs/wm8994.h
@@ -43,6 +43,6 @@ struct wm8994_access_mask {
};
extern const struct wm8994_access_mask wm8994_access_masks[WM8994_CACHE_SIZE];
-extern const __devinitdata u16 wm8994_reg_defaults[WM8994_CACHE_SIZE];
+extern const u16 wm8994_reg_defaults[WM8994_CACHE_SIZE];
#endif
diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c
index 608c84c5aa8..67eaaecbb42 100644
--- a/sound/soc/codecs/wm8995.c
+++ b/sound/soc/codecs/wm8995.c
@@ -19,6 +19,7 @@
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -30,6 +31,18 @@
#include "wm8995.h"
+#define WM8995_NUM_SUPPLIES 8
+static const char *wm8995_supply_names[WM8995_NUM_SUPPLIES] = {
+ "DCVDD",
+ "DBVDD1",
+ "DBVDD2",
+ "DBVDD3",
+ "AVDD1",
+ "AVDD2",
+ "CPVDD",
+ "MICVDD"
+};
+
static const u16 wm8995_reg_defs[WM8995_MAX_REGISTER + 1] = {
[0] = 0x8995, [5] = 0x0100, [16] = 0x000b, [17] = 0x000b,
[24] = 0x02c0, [25] = 0x02c0, [26] = 0x02c0, [27] = 0x02c0,
@@ -126,8 +139,37 @@ struct wm8995_priv {
int mclk[2];
int aifclk[2];
struct fll_config fll[2], fll_suspend[2];
+ struct regulator_bulk_data supplies[WM8995_NUM_SUPPLIES];
+ struct notifier_block disable_nb[WM8995_NUM_SUPPLIES];
+ struct snd_soc_codec *codec;
};
+/*
+ * We can't use the same notifier block for more than one supply and
+ * there's no way I can see to get from a callback to the caller
+ * except container_of().
+ */
+#define WM8995_REGULATOR_EVENT(n) \
+static int wm8995_regulator_event_##n(struct notifier_block *nb, \
+ unsigned long event, void *data) \
+{ \
+ struct wm8995_priv *wm8995 = container_of(nb, struct wm8995_priv, \
+ disable_nb[n]); \
+ if (event & REGULATOR_EVENT_DISABLE) { \
+ wm8995->codec->cache_sync = 1; \
+ } \
+ return 0; \
+}
+
+WM8995_REGULATOR_EVENT(0)
+WM8995_REGULATOR_EVENT(1)
+WM8995_REGULATOR_EVENT(2)
+WM8995_REGULATOR_EVENT(3)
+WM8995_REGULATOR_EVENT(4)
+WM8995_REGULATOR_EVENT(5)
+WM8995_REGULATOR_EVENT(6)
+WM8995_REGULATOR_EVENT(7)
+
static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
static const DECLARE_TLV_DB_SCALE(in1lr_pga_tlv, -1650, 150, 0);
static const DECLARE_TLV_DB_SCALE(in1l_boost_tlv, 0, 600, 0);
@@ -909,7 +951,7 @@ static const struct snd_soc_dapm_route wm8995_intercon[] = {
{ "SPK2R", NULL, "SPK2R Driver" }
};
-static int wm8995_volatile(unsigned int reg)
+static int wm8995_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
/* out of bounds registers are generally considered
* volatile to support register banks that are partially
@@ -1483,6 +1525,11 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec,
break;
case SND_SOC_BIAS_STANDBY:
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret)
+ return ret;
+
ret = snd_soc_cache_sync(codec);
if (ret) {
dev_err(codec->dev,
@@ -1492,12 +1539,13 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec,
snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
WM8995_BG_ENA_MASK, WM8995_BG_ENA);
-
}
break;
case SND_SOC_BIAS_OFF:
snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1,
WM8995_BG_ENA_MASK, 0);
+ regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
break;
}
@@ -1536,10 +1584,12 @@ static int wm8995_remove(struct snd_soc_codec *codec)
static int wm8995_probe(struct snd_soc_codec *codec)
{
struct wm8995_priv *wm8995;
+ int i;
int ret;
codec->dapm.idle_bias_off = 1;
wm8995 = snd_soc_codec_get_drvdata(codec);
+ wm8995->codec = codec;
ret = snd_soc_codec_set_cache_io(codec, 16, 16, wm8995->control_type);
if (ret < 0) {
@@ -1547,21 +1597,58 @@ static int wm8995_probe(struct snd_soc_codec *codec)
return ret;
}
+ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++)
+ wm8995->supplies[i].supply = wm8995_supply_names[i];
+
+ ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ wm8995->disable_nb[0].notifier_call = wm8995_regulator_event_0;
+ wm8995->disable_nb[1].notifier_call = wm8995_regulator_event_1;
+ wm8995->disable_nb[2].notifier_call = wm8995_regulator_event_2;
+ wm8995->disable_nb[3].notifier_call = wm8995_regulator_event_3;
+ wm8995->disable_nb[4].notifier_call = wm8995_regulator_event_4;
+ wm8995->disable_nb[5].notifier_call = wm8995_regulator_event_5;
+ wm8995->disable_nb[6].notifier_call = wm8995_regulator_event_6;
+ wm8995->disable_nb[7].notifier_call = wm8995_regulator_event_7;
+
+ /* This should really be moved into the regulator core */
+ for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) {
+ ret = regulator_register_notifier(wm8995->supplies[i].consumer,
+ &wm8995->disable_nb[i]);
+ if (ret) {
+ dev_err(codec->dev,
+ "Failed to register regulator notifier: %d\n",
+ ret);
+ }
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies),
+ wm8995->supplies);
+ if (ret) {
+ dev_err(codec->dev, "Failed to enable supplies: %d\n", ret);
+ goto err_reg_get;
+ }
+
ret = snd_soc_read(codec, WM8995_SOFTWARE_RESET);
if (ret < 0) {
dev_err(codec->dev, "Failed to read device ID: %d\n", ret);
- return ret;
+ goto err_reg_enable;
}
if (ret != 0x8995) {
dev_err(codec->dev, "Invalid device ID: %#x\n", ret);
- return -EINVAL;
+ goto err_reg_enable;
}
ret = snd_soc_write(codec, WM8995_SOFTWARE_RESET, 0);
if (ret < 0) {
dev_err(codec->dev, "Failed to issue reset: %d\n", ret);
- return ret;
+ goto err_reg_enable;
}
wm8995_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
@@ -1596,6 +1683,12 @@ static int wm8995_probe(struct snd_soc_codec *codec)
ARRAY_SIZE(wm8995_intercon));
return 0;
+
+err_reg_enable:
+ regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies);
+err_reg_get:
+ regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies);
+ return ret;
}
#define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c
index 43825b2102a..55cdf298202 100644
--- a/sound/soc/codecs/wm9081.c
+++ b/sound/soc/codecs/wm9081.c
@@ -15,6 +15,7 @@
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
+#include <linux/device.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
@@ -166,10 +167,10 @@ struct wm9081_priv {
int fll_fref;
int fll_fout;
int tdm_width;
- struct wm9081_retune_mobile_config *retune;
+ struct wm9081_pdata pdata;
};
-static int wm9081_volatile_register(unsigned int reg)
+static int wm9081_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM9081_SOFTWARE_RESET:
@@ -388,27 +389,6 @@ SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0),
SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0),
};
-static int speaker_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
-{
- struct snd_soc_codec *codec = w->codec;
- unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT);
-
- switch (event) {
- case SND_SOC_DAPM_POST_PMU:
- reg |= WM9081_SPK_ENA;
- break;
-
- case SND_SOC_DAPM_PRE_PMD:
- reg &= ~WM9081_SPK_ENA;
- break;
- }
-
- snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg);
-
- return 0;
-}
-
struct _fll_div {
u16 fll_fratio;
u16 fll_outdiv;
@@ -746,9 +726,8 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0,
SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0),
-SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0,
- speaker_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0),
SND_SOC_DAPM_OUTPUT("LINEOUT"),
SND_SOC_DAPM_OUTPUT("SPKN"),
@@ -761,7 +740,7 @@ SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0),
};
-static const struct snd_soc_dapm_route audio_paths[] = {
+static const struct snd_soc_dapm_route wm9081_audio_paths[] = {
{ "DAC", NULL, "CLK_SYS" },
{ "DAC", NULL, "CLK_DSP" },
@@ -779,8 +758,10 @@ static const struct snd_soc_dapm_route audio_paths[] = {
{ "Speaker PGA", NULL, "TOCLK" },
{ "Speaker PGA", NULL, "CLK_SYS" },
- { "SPKN", NULL, "Speaker PGA" },
- { "SPKP", NULL, "Speaker PGA" },
+ { "Speaker", NULL, "Speaker PGA" },
+
+ { "SPKN", NULL, "Speaker" },
+ { "SPKP", NULL, "Speaker" },
};
static int wm9081_set_bias_level(struct snd_soc_codec *codec,
@@ -1081,21 +1062,22 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream,
aif4 |= wm9081->bclk / wm9081->fs;
/* Apply a ReTune Mobile configuration if it's in use */
- if (wm9081->retune) {
- struct wm9081_retune_mobile_config *retune = wm9081->retune;
+ if (wm9081->pdata.num_retune_configs) {
+ struct wm9081_pdata *pdata = &wm9081->pdata;
struct wm9081_retune_mobile_setting *s;
int eq1;
best = 0;
- best_val = abs(retune->configs[0].rate - wm9081->fs);
- for (i = 0; i < retune->num_configs; i++) {
- cur_val = abs(retune->configs[i].rate - wm9081->fs);
+ best_val = abs(pdata->retune_configs[0].rate - wm9081->fs);
+ for (i = 0; i < pdata->num_retune_configs; i++) {
+ cur_val = abs(pdata->retune_configs[i].rate -
+ wm9081->fs);
if (cur_val < best_val) {
best_val = cur_val;
best = i;
}
}
- s = &retune->configs[best];
+ s = &pdata->retune_configs[best];
dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n",
s->name, s->rate);
@@ -1138,10 +1120,9 @@ static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute)
return 0;
}
-static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai,
+static int wm9081_set_sysclk(struct snd_soc_codec *codec,
int clk_id, unsigned int freq, int dir)
{
- struct snd_soc_codec *codec = codec_dai->codec;
struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
switch (clk_id) {
@@ -1206,7 +1187,6 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai,
static struct snd_soc_dai_ops wm9081_dai_ops = {
.hw_params = wm9081_hw_params,
- .set_sysclk = wm9081_set_sysclk,
.set_fmt = wm9081_set_dai_fmt,
.digital_mute = wm9081_digital_mute,
.set_tdm_slot = wm9081_set_tdm_slot,
@@ -1230,7 +1210,6 @@ static struct snd_soc_dai_driver wm9081_dai = {
static int wm9081_probe(struct snd_soc_codec *codec)
{
struct wm9081_priv *wm9081 = snd_soc_codec_get_drvdata(codec);
- struct snd_soc_dapm_context *dapm = &codec->dapm;
int ret;
u16 reg;
@@ -1254,6 +1233,14 @@ static int wm9081_probe(struct snd_soc_codec *codec)
return ret;
}
+ reg = 0;
+ if (wm9081->pdata.irq_high)
+ reg |= WM9081_IRQ_POL;
+ if (!wm9081->pdata.irq_cmos)
+ reg |= WM9081_IRQ_OP_CTRL;
+ snd_soc_update_bits(codec, WM9081_INTERRUPT_CONTROL,
+ WM9081_IRQ_POL | WM9081_IRQ_OP_CTRL, reg);
+
wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
/* Enable zero cross by default */
@@ -1265,17 +1252,13 @@ static int wm9081_probe(struct snd_soc_codec *codec)
snd_soc_add_controls(codec, wm9081_snd_controls,
ARRAY_SIZE(wm9081_snd_controls));
- if (!wm9081->retune) {
+ if (!wm9081->pdata.num_retune_configs) {
dev_dbg(codec->dev,
"No ReTune Mobile data, using normal EQ\n");
snd_soc_add_controls(codec, wm9081_eq_controls,
ARRAY_SIZE(wm9081_eq_controls));
}
- snd_soc_dapm_new_controls(dapm, wm9081_dapm_widgets,
- ARRAY_SIZE(wm9081_dapm_widgets));
- snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
-
return ret;
}
@@ -1319,11 +1302,19 @@ static struct snd_soc_codec_driver soc_codec_dev_wm9081 = {
.remove = wm9081_remove,
.suspend = wm9081_suspend,
.resume = wm9081_resume,
+
+ .set_sysclk = wm9081_set_sysclk,
.set_bias_level = wm9081_set_bias_level,
+
.reg_cache_size = ARRAY_SIZE(wm9081_reg_defaults),
.reg_word_size = sizeof(u16),
.reg_cache_default = wm9081_reg_defaults,
.volatile_register = wm9081_volatile_register,
+
+ .dapm_widgets = wm9081_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(wm9081_dapm_widgets),
+ .dapm_routes = wm9081_audio_paths,
+ .num_dapm_routes = ARRAY_SIZE(wm9081_audio_paths),
};
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
@@ -1341,6 +1332,10 @@ static __devinit int wm9081_i2c_probe(struct i2c_client *i2c,
wm9081->control_type = SND_SOC_I2C;
wm9081->control_data = i2c;
+ if (dev_get_platdata(&i2c->dev))
+ memcpy(&wm9081->pdata, dev_get_platdata(&i2c->dev),
+ sizeof(wm9081->pdata));
+
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_wm9081, &wm9081_dai, 1);
if (ret < 0)
@@ -1363,7 +1358,7 @@ MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id);
static struct i2c_driver wm9081_i2c_driver = {
.driver = {
- .name = "wm9081-codec",
+ .name = "wm9081",
.owner = THIS_MODULE,
},
.probe = wm9081_i2c_probe,
diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c
index a788c429704..4de12203e61 100644
--- a/sound/soc/codecs/wm9090.c
+++ b/sound/soc/codecs/wm9090.c
@@ -144,7 +144,7 @@ struct wm9090_priv {
void *control_data;
};
-static int wm9090_volatile(unsigned int reg)
+static int wm9090_volatile(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM9090_SOFTWARE_RESET:
@@ -518,7 +518,7 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec,
for (i = 1; i < codec->driver->reg_cache_size; i++) {
if (reg_cache[i] == wm9090_reg_defaults[i])
continue;
- if (wm9090_volatile(i))
+ if (wm9090_volatile(codec, i))
continue;
ret = snd_soc_write(codec, i, reg_cache[i]);
@@ -551,7 +551,6 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec,
static int wm9090_probe(struct snd_soc_codec *codec)
{
struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec);
- u16 *reg_cache = codec->reg_cache;
int ret;
codec->control_data = wm9090->control_data;
@@ -576,22 +575,30 @@ static int wm9090_probe(struct snd_soc_codec *codec)
/* Configure some defaults; they will be written out when we
* bring the bias up.
*/
- reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU
- | WM9090_IN1A_ZC;
- reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU
- | WM9090_IN1B_ZC;
- reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU
- | WM9090_IN2A_ZC;
- reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU
- | WM9090_IN2B_ZC;
- reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |=
- WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC;
- reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |=
- WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC;
- reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |=
- WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC;
-
- reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA;
+ snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_A_VOLUME,
+ WM9090_IN1_VU | WM9090_IN1A_ZC,
+ WM9090_IN1_VU | WM9090_IN1A_ZC);
+ snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_B_VOLUME,
+ WM9090_IN1_VU | WM9090_IN1B_ZC,
+ WM9090_IN1_VU | WM9090_IN1B_ZC);
+ snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_A_VOLUME,
+ WM9090_IN2_VU | WM9090_IN2A_ZC,
+ WM9090_IN2_VU | WM9090_IN2A_ZC);
+ snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_B_VOLUME,
+ WM9090_IN2_VU | WM9090_IN2B_ZC,
+ WM9090_IN2_VU | WM9090_IN2B_ZC);
+ snd_soc_update_bits(codec, WM9090_SPEAKER_VOLUME_LEFT,
+ WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC,
+ WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC);
+ snd_soc_update_bits(codec, WM9090_LEFT_OUTPUT_VOLUME,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC);
+ snd_soc_update_bits(codec, WM9090_RIGHT_OUTPUT_VOLUME,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC,
+ WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC);
+
+ snd_soc_update_bits(codec, WM9090_CLOCKING_1,
+ WM9090_TOCLK_ENA, WM9090_TOCLK_ENA);
wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c
index 51689270606..7b6b3c18e29 100644
--- a/sound/soc/codecs/wm_hubs.c
+++ b/sound/soc/codecs/wm_hubs.c
@@ -82,7 +82,8 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op)
} while (reg & op && count < 400);
if (reg & op)
- dev_err(codec->dev, "Timed out waiting for DC Servo\n");
+ dev_err(codec->dev, "Timed out waiting for DC Servo %x\n",
+ op);
}
/*
diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c
index 9e0e565e6ed..d0d60b8a54d 100644
--- a/sound/soc/davinci/davinci-i2s.c
+++ b/sound/soc/davinci/davinci-i2s.c
@@ -658,7 +658,7 @@ static int davinci_i2s_probe(struct platform_device *pdev)
return -ENODEV;
}
- ioarea = request_mem_region(mem->start, (mem->end - mem->start) + 1,
+ ioarea = request_mem_region(mem->start, resource_size(mem),
pdev->name);
if (!ioarea) {
dev_err(&pdev->dev, "McBSP region already claimed\n");
@@ -694,20 +694,25 @@ static int davinci_i2s_probe(struct platform_device *pdev)
}
clk_enable(dev->clk);
- dev->base = (void __iomem *)IO_ADDRESS(mem->start);
+ dev->base = ioremap(mem->start, resource_size(mem));
+ if (!dev->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release_clk;
+ }
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_addr =
- (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DXR_REG);
+ (dma_addr_t)(mem->start + DAVINCI_MCBSP_DXR_REG);
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_addr =
- (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DRR_REG);
+ (dma_addr_t)(mem->start + DAVINCI_MCBSP_DRR_REG);
/* first TX, then RX */
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(&pdev->dev, "no DMA resource\n");
ret = -ENXIO;
- goto err_free_mem;
+ goto err_iounmap;
}
dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].channel = res->start;
@@ -715,7 +720,7 @@ static int davinci_i2s_probe(struct platform_device *pdev)
if (!res) {
dev_err(&pdev->dev, "no DMA resource\n");
ret = -ENXIO;
- goto err_free_mem;
+ goto err_iounmap;
}
dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].channel = res->start;
dev->dev = &pdev->dev;
@@ -724,14 +729,19 @@ static int davinci_i2s_probe(struct platform_device *pdev)
ret = snd_soc_register_dai(&pdev->dev, &davinci_i2s_dai);
if (ret != 0)
- goto err_free_mem;
+ goto err_iounmap;
return 0;
+err_iounmap:
+ iounmap(dev->base);
+err_release_clk:
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
err_free_mem:
kfree(dev);
err_release_region:
- release_mem_region(mem->start, (mem->end - mem->start) + 1);
+ release_mem_region(mem->start, resource_size(mem));
return ret;
}
@@ -747,7 +757,7 @@ static int davinci_i2s_remove(struct platform_device *pdev)
dev->clk = NULL;
kfree(dev);
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- release_mem_region(mem->start, (mem->end - mem->start) + 1);
+ release_mem_region(mem->start, resource_size(mem));
return 0;
}
diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c
index fb55d2c5d70..a5af834c8ef 100644
--- a/sound/soc/davinci/davinci-mcasp.c
+++ b/sound/soc/davinci/davinci-mcasp.c
@@ -868,7 +868,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
}
ioarea = request_mem_region(mem->start,
- (mem->end - mem->start) + 1, pdev->name);
+ resource_size(mem), pdev->name);
if (!ioarea) {
dev_err(&pdev->dev, "Audio region already claimed\n");
ret = -EBUSY;
@@ -885,7 +885,13 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
clk_enable(dev->clk);
dev->clk_active = 1;
- dev->base = (void __iomem *)IO_ADDRESS(mem->start);
+ dev->base = ioremap(mem->start, resource_size(mem));
+ if (!dev->base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release_clk;
+ }
+
dev->op_mode = pdata->op_mode;
dev->tdm_slots = pdata->tdm_slots;
dev->num_serializer = pdata->num_serializer;
@@ -899,14 +905,14 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
dma_data->asp_chan_q = pdata->asp_chan_q;
dma_data->ram_chan_q = pdata->ram_chan_q;
dma_data->dma_addr = (dma_addr_t) (pdata->tx_dma_offset +
- io_v2p(dev->base));
+ mem->start);
/* first TX, then RX */
res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
if (!res) {
dev_err(&pdev->dev, "no DMA resource\n");
ret = -ENODEV;
- goto err_release_region;
+ goto err_iounmap;
}
dma_data->channel = res->start;
@@ -915,13 +921,13 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
dma_data->asp_chan_q = pdata->asp_chan_q;
dma_data->ram_chan_q = pdata->ram_chan_q;
dma_data->dma_addr = (dma_addr_t)(pdata->rx_dma_offset +
- io_v2p(dev->base));
+ mem->start);
res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
if (!res) {
dev_err(&pdev->dev, "no DMA resource\n");
ret = -ENODEV;
- goto err_release_region;
+ goto err_iounmap;
}
dma_data->channel = res->start;
@@ -929,11 +935,16 @@ static int davinci_mcasp_probe(struct platform_device *pdev)
ret = snd_soc_register_dai(&pdev->dev, &davinci_mcasp_dai[pdata->op_mode]);
if (ret != 0)
- goto err_release_region;
+ goto err_iounmap;
return 0;
+err_iounmap:
+ iounmap(dev->base);
+err_release_clk:
+ clk_disable(dev->clk);
+ clk_put(dev->clk);
err_release_region:
- release_mem_region(mem->start, (mem->end - mem->start) + 1);
+ release_mem_region(mem->start, resource_size(mem));
err_release_data:
kfree(dev);
@@ -951,7 +962,7 @@ static int davinci_mcasp_remove(struct platform_device *pdev)
dev->clk = NULL;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- release_mem_region(mem->start, (mem->end - mem->start) + 1);
+ release_mem_region(mem->start, resource_size(mem));
kfree(dev);
diff --git a/sound/soc/ep93xx/Kconfig b/sound/soc/ep93xx/Kconfig
index 57429041189..91a28de9410 100644
--- a/sound/soc/ep93xx/Kconfig
+++ b/sound/soc/ep93xx/Kconfig
@@ -30,3 +30,12 @@ config SND_EP93XX_SOC_SIMONE
help
Say Y or M here if you want to add support for AC97 audio on the
Simplemachines Sim.One board.
+
+config SND_EP93XX_SOC_EDB93XX
+ tristate "SoC Audio support for Cirrus Logic EDB93xx boards"
+ depends on SND_EP93XX_SOC && (MACH_EDB9301 || MACH_EDB9302 || MACH_EDB9302A || MACH_EDB9307A || MACH_EDB9315A)
+ select SND_EP93XX_SOC_I2S
+ select SND_SOC_CS4271
+ help
+ Say Y or M here if you want to add support for I2S audio on the
+ Cirrus Logic EDB93xx boards.
diff --git a/sound/soc/ep93xx/Makefile b/sound/soc/ep93xx/Makefile
index 8e7977fb6b7..5514146cbdf 100644
--- a/sound/soc/ep93xx/Makefile
+++ b/sound/soc/ep93xx/Makefile
@@ -10,6 +10,8 @@ obj-$(CONFIG_SND_EP93XX_SOC_AC97) += snd-soc-ep93xx-ac97.o
# EP93XX Machine Support
snd-soc-snappercl15-objs := snappercl15.o
snd-soc-simone-objs := simone.o
+snd-soc-edb93xx-objs := edb93xx.o
obj-$(CONFIG_SND_EP93XX_SOC_SNAPPERCL15) += snd-soc-snappercl15.o
obj-$(CONFIG_SND_EP93XX_SOC_SIMONE) += snd-soc-simone.o
+obj-$(CONFIG_SND_EP93XX_SOC_EDB93XX) += snd-soc-edb93xx.o
diff --git a/sound/soc/ep93xx/edb93xx.c b/sound/soc/ep93xx/edb93xx.c
new file mode 100644
index 00000000000..d3aa15119d2
--- /dev/null
+++ b/sound/soc/ep93xx/edb93xx.c
@@ -0,0 +1,142 @@
+/*
+ * SoC audio for EDB93xx
+ *
+ * Copyright (c) 2010 Alexander Sverdlin <subaparts@yandex.ru>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * This driver support CS4271 codec being master or slave, working
+ * in control port mode, connected either via SPI or I2C.
+ * The data format accepted is I2S or left-justified.
+ * DAPM support not implemented.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include "ep93xx-pcm.h"
+
+#define edb93xx_has_audio() (machine_is_edb9301() || \
+ machine_is_edb9302() || \
+ machine_is_edb9302a() || \
+ machine_is_edb9307a() || \
+ machine_is_edb9315a())
+
+static int edb93xx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int err;
+ unsigned int mclk_rate;
+ unsigned int rate = params_rate(params);
+
+ /*
+ * According to CS4271 datasheet we use MCLK/LRCK=256 for
+ * rates below 50kHz and 128 for higher sample rates
+ */
+ if (rate < 50000)
+ mclk_rate = rate * 64 * 4;
+ else
+ mclk_rate = rate * 64 * 2;
+
+ err = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err)
+ return err;
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk_rate,
+ SND_SOC_CLOCK_IN);
+ if (err)
+ return err;
+
+ return snd_soc_dai_set_sysclk(cpu_dai, 0, mclk_rate,
+ SND_SOC_CLOCK_OUT);
+}
+
+static struct snd_soc_ops edb93xx_ops = {
+ .hw_params = edb93xx_hw_params,
+};
+
+static struct snd_soc_dai_link edb93xx_dai = {
+ .name = "CS4271",
+ .stream_name = "CS4271 HiFi",
+ .platform_name = "ep93xx-pcm-audio",
+ .cpu_dai_name = "ep93xx-i2s",
+ .codec_name = "spi0.0",
+ .codec_dai_name = "cs4271-hifi",
+ .ops = &edb93xx_ops,
+};
+
+static struct snd_soc_card snd_soc_edb93xx = {
+ .name = "EDB93XX",
+ .dai_link = &edb93xx_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *edb93xx_snd_device;
+
+static int __init edb93xx_init(void)
+{
+ int ret;
+
+ if (!edb93xx_has_audio())
+ return -ENODEV;
+
+ ret = ep93xx_i2s_acquire(EP93XX_SYSCON_DEVCFG_I2SONAC97,
+ EP93XX_SYSCON_I2SCLKDIV_ORIDE |
+ EP93XX_SYSCON_I2SCLKDIV_SPOL);
+ if (ret)
+ return ret;
+
+ edb93xx_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!edb93xx_snd_device) {
+ ret = -ENOMEM;
+ goto free_i2s;
+ }
+
+ platform_set_drvdata(edb93xx_snd_device, &snd_soc_edb93xx);
+ ret = platform_device_add(edb93xx_snd_device);
+ if (ret)
+ goto device_put;
+
+ return 0;
+
+device_put:
+ platform_device_put(edb93xx_snd_device);
+free_i2s:
+ ep93xx_i2s_release();
+ return ret;
+}
+module_init(edb93xx_init);
+
+static void __exit edb93xx_exit(void)
+{
+ platform_device_unregister(edb93xx_snd_device);
+ ep93xx_i2s_release();
+}
+module_exit(edb93xx_exit);
+
+MODULE_AUTHOR("Alexander Sverdlin <subaparts@yandex.ru>");
+MODULE_DESCRIPTION("ALSA SoC EDB93xx");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/ep93xx/ep93xx-ac97.c b/sound/soc/ep93xx/ep93xx-ac97.c
index 68a0bae1208..104e95cda0a 100644
--- a/sound/soc/ep93xx/ep93xx-ac97.c
+++ b/sound/soc/ep93xx/ep93xx-ac97.c
@@ -253,7 +253,6 @@ static int ep93xx_ac97_trigger(struct snd_pcm_substream *substream,
struct ep93xx_ac97_info *info = snd_soc_dai_get_drvdata(dai);
unsigned v = 0;
-
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
diff --git a/sound/soc/ep93xx/ep93xx-i2s.c b/sound/soc/ep93xx/ep93xx-i2s.c
index fff579a1c13..042f4e93746 100644
--- a/sound/soc/ep93xx/ep93xx-i2s.c
+++ b/sound/soc/ep93xx/ep93xx-i2s.c
@@ -242,7 +242,7 @@ static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
{
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
unsigned word_len, div, sdiv, lrdiv;
- int found = 0, err;
+ int err;
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
@@ -275,15 +275,14 @@ static int ep93xx_i2s_hw_params(struct snd_pcm_substream *substream,
* the codec uses.
*/
div = clk_get_rate(info->mclk) / params_rate(params);
- for (sdiv = 2; sdiv <= 4; sdiv += 2)
- for (lrdiv = 64; lrdiv <= 128; lrdiv <<= 1)
- if (sdiv * lrdiv == div) {
- found = 1;
- goto out;
- }
-out:
- if (!found)
- return -EINVAL;
+ sdiv = 4;
+ if (div > (256 + 512) / 2) {
+ lrdiv = 128;
+ } else {
+ lrdiv = 64;
+ if (div < (128 + 256) / 2)
+ sdiv = 2;
+ }
err = clk_set_rate(info->sclk, clk_get_rate(info->mclk) / sdiv);
if (err)
@@ -314,10 +313,12 @@ static int ep93xx_i2s_suspend(struct snd_soc_dai *dai)
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
if (!dai->active)
- return;
+ return 0;
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_PLAYBACK);
ep93xx_i2s_disable(info, SNDRV_PCM_STREAM_CAPTURE);
+
+ return 0;
}
static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
@@ -325,10 +326,12 @@ static int ep93xx_i2s_resume(struct snd_soc_dai *dai)
struct ep93xx_i2s_info *info = snd_soc_dai_get_drvdata(dai);
if (!dai->active)
- return;
+ return 0;
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_PLAYBACK);
ep93xx_i2s_enable(info, SNDRV_PCM_STREAM_CAPTURE);
+
+ return 0;
}
#else
#define ep93xx_i2s_suspend NULL
@@ -352,13 +355,13 @@ static struct snd_soc_dai_driver ep93xx_i2s_dai = {
.playback = {
.channels_min = 2,
.channels_max = 2,
- .rates = SNDRV_PCM_RATE_8000_96000,
+ .rates = SNDRV_PCM_RATE_8000_192000,
.formats = EP93XX_I2S_FORMATS,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
- .rates = SNDRV_PCM_RATE_8000_96000,
+ .rates = SNDRV_PCM_RATE_8000_192000,
.formats = EP93XX_I2S_FORMATS,
},
.ops = &ep93xx_i2s_dai_ops,
diff --git a/sound/soc/ep93xx/ep93xx-pcm.c b/sound/soc/ep93xx/ep93xx-pcm.c
index 06670776f64..a456e491155 100644
--- a/sound/soc/ep93xx/ep93xx-pcm.c
+++ b/sound/soc/ep93xx/ep93xx-pcm.c
@@ -35,9 +35,9 @@ static const struct snd_pcm_hardware ep93xx_pcm_hardware = {
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER),
- .rates = SNDRV_PCM_RATE_8000_96000,
+ .rates = SNDRV_PCM_RATE_8000_192000,
.rate_min = SNDRV_PCM_RATE_8000,
- .rate_max = SNDRV_PCM_RATE_96000,
+ .rate_max = SNDRV_PCM_RATE_192000,
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S24_LE |
diff --git a/sound/soc/fsl/mpc8610_hpcd.c b/sound/soc/fsl/mpc8610_hpcd.c
index 7d7847a1e66..c16c6b2eff9 100644
--- a/sound/soc/fsl/mpc8610_hpcd.c
+++ b/sound/soc/fsl/mpc8610_hpcd.c
@@ -53,9 +53,8 @@ struct mpc8610_hpcd_data {
*
* Here we program the DMACR and PMUXCR registers.
*/
-static int mpc8610_hpcd_machine_probe(struct platform_device *sound_device)
+static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
{
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
struct mpc8610_hpcd_data *machine_data =
container_of(card, struct mpc8610_hpcd_data, card);
struct ccsr_guts_86xx __iomem *guts;
@@ -138,9 +137,8 @@ static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
* This function is called to remove the sound device for one SSI. We
* de-program the DMACR and PMUXCR register.
*/
-static int mpc8610_hpcd_machine_remove(struct platform_device *sound_device)
+static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
{
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
struct mpc8610_hpcd_data *machine_data =
container_of(card, struct mpc8610_hpcd_data, card);
struct ccsr_guts_86xx __iomem *guts;
diff --git a/sound/soc/fsl/p1022_ds.c b/sound/soc/fsl/p1022_ds.c
index 026b756961e..66e0b68af14 100644
--- a/sound/soc/fsl/p1022_ds.c
+++ b/sound/soc/fsl/p1022_ds.c
@@ -85,9 +85,8 @@ struct machine_data {
*
* Here we program the DMACR and PMUXCR registers.
*/
-static int p1022_ds_machine_probe(struct platform_device *sound_device)
+static int p1022_ds_machine_probe(struct snd_soc_card *card)
{
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
struct machine_data *mdata =
container_of(card, struct machine_data, card);
struct ccsr_guts_85xx __iomem *guts;
@@ -160,9 +159,8 @@ static int p1022_ds_startup(struct snd_pcm_substream *substream)
* This function is called to remove the sound device for one SSI. We
* de-program the DMACR and PMUXCR register.
*/
-static int p1022_ds_machine_remove(struct platform_device *sound_device)
+static int p1022_ds_machine_remove(struct snd_soc_card *card)
{
- struct snd_soc_card *card = platform_get_drvdata(sound_device);
struct machine_data *mdata =
container_of(card, struct machine_data, card);
struct ccsr_guts_85xx __iomem *guts;
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
index 642270a635e..d8f130d39dd 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -30,6 +30,16 @@ config SND_MXC_SOC_WM1133_EV1
Enable support for audio on the i.MX31ADS with the WM1133-EV1
PMIC board with WM8835x fitted.
+config SND_SOC_MX27VIS_AIC32X4
+ tristate "SoC audio support for Visstrim M10 boards"
+ depends on MACH_IMX27_VISSTRIM_M10
+ select SND_SOC_TVL320AIC32X4
+ select SND_MXC_SOC_SSI
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio on Visstrim SM10
+ board with TLV320AIC32X4 codec.
+
config SND_SOC_PHYCORE_AC97
tristate "SoC Audio support for Phytec phyCORE (and phyCARD) boards"
depends on MACH_PCM043 || MACH_PCA100
@@ -44,7 +54,8 @@ config SND_SOC_EUKREA_TLV320
tristate "Eukrea TLV320"
depends on MACH_EUKREA_MBIMX27_BASEBOARD \
|| MACH_EUKREA_MBIMXSD25_BASEBOARD \
- || MACH_EUKREA_MBIMXSD35_BASEBOARD
+ || MACH_EUKREA_MBIMXSD35_BASEBOARD \
+ || MACH_EUKREA_MBIMXSD51_BASEBOARD
select SND_SOC_TLV320AIC23
select SND_MXC_SOC_SSI
select SND_MXC_SOC_FIQ
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index b67fc02a4ec..d6d609ba7e2 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -10,8 +10,10 @@ obj-$(CONFIG_SND_MXC_SOC_MX2) += snd-soc-imx-mx2.o
# i.MX Machine Support
snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
snd-soc-phycore-ac97-objs := phycore-ac97.o
+snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
snd-soc-wm1133-ev1-objs := wm1133-ev1.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
+obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
diff --git a/sound/soc/imx/eukrea-tlv320.c b/sound/soc/imx/eukrea-tlv320.c
index 1e9bccae4e8..75fb4b83548 100644
--- a/sound/soc/imx/eukrea-tlv320.c
+++ b/sound/soc/imx/eukrea-tlv320.c
@@ -98,7 +98,8 @@ static int __init eukrea_tlv320_init(void)
int ret;
if (!machine_is_eukrea_cpuimx27() && !machine_is_eukrea_cpuimx25sd()
- && !machine_is_eukrea_cpuimx35sd())
+ && !machine_is_eukrea_cpuimx35sd()
+ && !machine_is_eukrea_cpuimx51sd())
/* return happy. We might run on a totally different machine */
return 0;
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c
index 30894ea7f33..bc92ec62000 100644
--- a/sound/soc/imx/imx-ssi.c
+++ b/sound/soc/imx/imx-ssi.c
@@ -108,7 +108,7 @@ static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
break;
case SND_SOC_DAIFMT_DSP_B:
/* data on rising edge of bclk, frame high with data */
- strcr |= SSI_STCR_TFSL;
+ strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0;
break;
case SND_SOC_DAIFMT_DSP_A:
/* data on rising edge of bclk, frame high 1clk before data */
@@ -656,6 +656,9 @@ static int imx_ssi_probe(struct platform_device *pdev)
ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
+ ssi->dma_params_tx.burstsize = 4;
+ ssi->dma_params_rx.burstsize = 4;
+
res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
if (res)
ssi->dma_params_tx.dma = res->start;
diff --git a/sound/soc/imx/mx27vis-aic32x4.c b/sound/soc/imx/mx27vis-aic32x4.c
new file mode 100644
index 00000000000..054110b91d4
--- /dev/null
+++ b/sound/soc/imx/mx27vis-aic32x4.c
@@ -0,0 +1,137 @@
+/*
+ * mx27vis-aic32x4.c
+ *
+ * Copyright 2011 Vista Silicon S.L.
+ *
+ * Author: Javier Martin <javier.martin@vista-silicon.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.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+#include <mach/audmux.h>
+
+#include "../codecs/tlv320aic32x4.h"
+#include "imx-ssi.h"
+
+static int mx27vis_aic32x4_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ int ret;
+ u32 dai_format;
+
+ dai_format = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ snd_soc_dai_set_fmt(codec_dai, dai_format);
+
+ /* set cpu DAI configuration */
+ snd_soc_dai_set_fmt(cpu_dai, dai_format);
+
+ ret = snd_soc_dai_set_sysclk(codec_dai, 0,
+ 25000000, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ pr_err("%s: failed setting codec sysclk\n", __func__);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0,
+ SND_SOC_CLOCK_IN);
+ if (ret) {
+ pr_err("can't set CPU system clock IMX_SSP_SYS_CLK\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops mx27vis_aic32x4_snd_ops = {
+ .hw_params = mx27vis_aic32x4_hw_params,
+};
+
+static struct snd_soc_dai_link mx27vis_aic32x4_dai = {
+ .name = "tlv320aic32x4",
+ .stream_name = "TLV320AIC32X4",
+ .codec_dai_name = "tlv320aic32x4-hifi",
+ .platform_name = "imx-pcm-audio.0",
+ .codec_name = "tlv320aic32x4.0-0018",
+ .cpu_dai_name = "imx-ssi.0",
+ .ops = &mx27vis_aic32x4_snd_ops,
+};
+
+static struct snd_soc_card mx27vis_aic32x4 = {
+ .name = "visstrim_m10-audio",
+ .dai_link = &mx27vis_aic32x4_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *mx27vis_aic32x4_snd_device;
+
+static int __init mx27vis_aic32x4_init(void)
+{
+ int ret;
+
+ mx27vis_aic32x4_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!mx27vis_aic32x4_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(mx27vis_aic32x4_snd_device, &mx27vis_aic32x4);
+ ret = platform_device_add(mx27vis_aic32x4_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ platform_device_put(mx27vis_aic32x4_snd_device);
+ }
+
+ /* Connect SSI0 as clock slave to SSI1 external pins */
+ mxc_audmux_v1_configure_port(MX27_AUDMUX_HPCR1_SSI0,
+ MXC_AUDMUX_V1_PCR_SYN |
+ MXC_AUDMUX_V1_PCR_TFSDIR |
+ MXC_AUDMUX_V1_PCR_TCLKDIR |
+ MXC_AUDMUX_V1_PCR_TFCSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1) |
+ MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_PPCR1_SSI_PINS_1)
+ );
+ mxc_audmux_v1_configure_port(MX27_AUDMUX_PPCR1_SSI_PINS_1,
+ MXC_AUDMUX_V1_PCR_SYN |
+ MXC_AUDMUX_V1_PCR_RXDSEL(MX27_AUDMUX_HPCR1_SSI0)
+ );
+
+ return ret;
+}
+
+static void __exit mx27vis_aic32x4_exit(void)
+{
+ platform_device_unregister(mx27vis_aic32x4_snd_device);
+}
+
+module_init(mx27vis_aic32x4_init);
+module_exit(mx27vis_aic32x4_exit);
+
+MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
+MODULE_DESCRIPTION("ALSA SoC AIC32X4 mx27 visstrim");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mid-x86/Kconfig b/sound/soc/mid-x86/Kconfig
new file mode 100644
index 00000000000..29350428f1c
--- /dev/null
+++ b/sound/soc/mid-x86/Kconfig
@@ -0,0 +1,14 @@
+config SND_MFLD_MACHINE
+ tristate "SOC Machine Audio driver for Intel Medfield MID platform"
+ depends on INTEL_SCU_IPC
+ depends on SND_INTEL_SST
+ select SND_SOC_SN95031
+ select SND_SST_PLATFORM
+ help
+ This adds support for ASoC machine driver for Intel(R) MID Medfield platform
+ used as alsa device in audio substem in Intel(R) MID devices
+ Say Y if you have such a device
+ If unsure select "N".
+
+config SND_SST_PLATFORM
+ tristate
diff --git a/sound/soc/mid-x86/Makefile b/sound/soc/mid-x86/Makefile
new file mode 100644
index 00000000000..63988333946
--- /dev/null
+++ b/sound/soc/mid-x86/Makefile
@@ -0,0 +1,5 @@
+snd-soc-sst-platform-objs := sst_platform.o
+snd-soc-mfld-machine-objs := mfld_machine.o
+
+obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o
+obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o
diff --git a/sound/soc/mid-x86/mfld_machine.c b/sound/soc/mid-x86/mfld_machine.c
new file mode 100644
index 00000000000..429aa1be2cf
--- /dev/null
+++ b/sound/soc/mid-x86/mfld_machine.c
@@ -0,0 +1,452 @@
+/*
+ * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include "../codecs/sn95031.h"
+
+#define MID_MONO 1
+#define MID_STEREO 2
+#define MID_MAX_CAP 5
+#define MFLD_JACK_INSERT 0x04
+
+enum soc_mic_bias_zones {
+ MFLD_MV_START = 0,
+ /* mic bias volutage range for Headphones*/
+ MFLD_MV_HP = 400,
+ /* mic bias volutage range for American Headset*/
+ MFLD_MV_AM_HS = 650,
+ /* mic bias volutage range for Headset*/
+ MFLD_MV_HS = 2000,
+ MFLD_MV_UNDEFINED,
+};
+
+static unsigned int hs_switch;
+static unsigned int lo_dac;
+
+struct mfld_mc_private {
+ struct platform_device *socdev;
+ void __iomem *int_base;
+ struct snd_soc_codec *codec;
+ u8 interrupt_status;
+};
+
+struct snd_soc_jack mfld_jack;
+
+/*Headset jack detection DAPM pins */
+static struct snd_soc_jack_pin mfld_jack_pins[] = {
+ {
+ .pin = "Headphones",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "AMIC1",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+/* jack detection voltage zones */
+static struct snd_soc_jack_zone mfld_zones[] = {
+ {MFLD_MV_START, MFLD_MV_AM_HS, SND_JACK_HEADPHONE},
+ {MFLD_MV_AM_HS, MFLD_MV_HS, SND_JACK_HEADSET},
+};
+
+/* sound card controls */
+static const char *headset_switch_text[] = {"Earpiece", "Headset"};
+
+static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"};
+
+static const struct soc_enum headset_enum =
+ SOC_ENUM_SINGLE_EXT(2, headset_switch_text);
+
+static const struct soc_enum lo_enum =
+ SOC_ENUM_SINGLE_EXT(4, lo_text);
+
+static int headset_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = hs_switch;
+ return 0;
+}
+
+static int headset_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] == hs_switch)
+ return 0;
+
+ if (ucontrol->value.integer.value[0]) {
+ pr_debug("hs_set HS path\n");
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ } else {
+ pr_debug("hs_set EP path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT");
+ }
+ snd_soc_dapm_sync(&codec->dapm);
+ hs_switch = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+static void lo_enable_out_pins(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTL");
+ snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTR");
+ snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTL");
+ snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTR");
+ snd_soc_dapm_enable_pin(&codec->dapm, "VIB1OUT");
+ snd_soc_dapm_enable_pin(&codec->dapm, "VIB2OUT");
+ if (hs_switch) {
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ } else {
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT");
+ }
+}
+
+static int lo_get_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = lo_dac;
+ return 0;
+}
+
+static int lo_set_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (ucontrol->value.integer.value[0] == lo_dac)
+ return 0;
+
+ /* we dont want to work with last state of lineout so just enable all
+ * pins and then disable pins not required
+ */
+ lo_enable_out_pins(codec);
+ switch (ucontrol->value.integer.value[0]) {
+ case 0:
+ pr_debug("set vibra path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "VIB1OUT");
+ snd_soc_dapm_disable_pin(&codec->dapm, "VIB2OUT");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0);
+ break;
+
+ case 1:
+ pr_debug("set hs path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphones");
+ snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x22);
+ break;
+
+ case 2:
+ pr_debug("set spkr path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTL");
+ snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTR");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x44);
+ break;
+
+ case 3:
+ pr_debug("set null path\n");
+ snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTR");
+ snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x66);
+ break;
+ }
+ snd_soc_dapm_sync(&codec->dapm);
+ lo_dac = ucontrol->value.integer.value[0];
+ return 0;
+}
+
+static const struct snd_kcontrol_new mfld_snd_controls[] = {
+ SOC_ENUM_EXT("Playback Switch", headset_enum,
+ headset_get_switch, headset_set_switch),
+ SOC_ENUM_EXT("Lineout Mux", lo_enum,
+ lo_get_switch, lo_set_switch),
+};
+
+static const struct snd_soc_dapm_widget mfld_widgets[] = {
+ SND_SOC_DAPM_HP("Headphones", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route mfld_map[] = {
+ {"Headphones", NULL, "HPOUTR"},
+ {"Headphones", NULL, "HPOUTL"},
+ {"Mic", NULL, "AMIC1"},
+};
+
+static void mfld_jack_check(unsigned int intr_status)
+{
+ struct mfld_jack_data jack_data;
+
+ jack_data.mfld_jack = &mfld_jack;
+ jack_data.intr_id = intr_status;
+
+ sn95031_jack_detection(&jack_data);
+ /* TODO: add american headset detection post gpiolib support */
+}
+
+static int mfld_init(struct snd_soc_pcm_runtime *runtime)
+{
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret_val;
+
+ /* Add jack sense widgets */
+ snd_soc_dapm_new_controls(dapm, mfld_widgets, ARRAY_SIZE(mfld_widgets));
+
+ /* Set up the map */
+ snd_soc_dapm_add_routes(dapm, mfld_map, ARRAY_SIZE(mfld_map));
+
+ /* always connected */
+ snd_soc_dapm_enable_pin(dapm, "Headphones");
+ snd_soc_dapm_enable_pin(dapm, "Mic");
+ snd_soc_dapm_sync(dapm);
+
+ ret_val = snd_soc_add_controls(codec, mfld_snd_controls,
+ ARRAY_SIZE(mfld_snd_controls));
+ if (ret_val) {
+ pr_err("soc_add_controls failed %d", ret_val);
+ return ret_val;
+ }
+ /* default is earpiece pin, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "Headphones");
+ /* default is lineout NC, userspace sets it explcitly */
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTL");
+ snd_soc_dapm_disable_pin(dapm, "LINEOUTR");
+ lo_dac = 3;
+ hs_switch = 0;
+ /* we dont use linein in this so set to NC */
+ snd_soc_dapm_disable_pin(dapm, "LINEINL");
+ snd_soc_dapm_disable_pin(dapm, "LINEINR");
+ snd_soc_dapm_sync(dapm);
+
+ /* Headset and button jack detection */
+ ret_val = snd_soc_jack_new(codec, "Intel(R) MID Audio Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1, &mfld_jack);
+ if (ret_val) {
+ pr_err("jack creation failed\n");
+ return ret_val;
+ }
+
+ ret_val = snd_soc_jack_add_pins(&mfld_jack,
+ ARRAY_SIZE(mfld_jack_pins), mfld_jack_pins);
+ if (ret_val) {
+ pr_err("adding jack pins failed\n");
+ return ret_val;
+ }
+ ret_val = snd_soc_jack_add_zones(&mfld_jack,
+ ARRAY_SIZE(mfld_zones), mfld_zones);
+ if (ret_val) {
+ pr_err("adding jack zones failed\n");
+ return ret_val;
+ }
+
+ /* we want to check if anything is inserted at boot,
+ * so send a fake event to codec and it will read adc
+ * to find if anything is there or not */
+ mfld_jack_check(MFLD_JACK_INSERT);
+ return ret_val;
+}
+
+struct snd_soc_dai_link mfld_msic_dailink[] = {
+ {
+ .name = "Medfield Headset",
+ .stream_name = "Headset",
+ .cpu_dai_name = "Headset-cpu-dai",
+ .codec_dai_name = "SN95031 Headset",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = mfld_init,
+ },
+ {
+ .name = "Medfield Speaker",
+ .stream_name = "Speaker",
+ .cpu_dai_name = "Speaker-cpu-dai",
+ .codec_dai_name = "SN95031 Speaker",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Vibra",
+ .stream_name = "Vibra1",
+ .cpu_dai_name = "Vibra1-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra1",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+ {
+ .name = "Medfield Haptics",
+ .stream_name = "Vibra2",
+ .cpu_dai_name = "Vibra2-cpu-dai",
+ .codec_dai_name = "SN95031 Vibra2",
+ .codec_name = "sn95031",
+ .platform_name = "sst-platform",
+ .init = NULL,
+ },
+};
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_mfld = {
+ .name = "medfield_audio",
+ .dai_link = mfld_msic_dailink,
+ .num_links = ARRAY_SIZE(mfld_msic_dailink),
+};
+
+static irqreturn_t snd_mfld_jack_intr_handler(int irq, void *dev)
+{
+ struct mfld_mc_private *mc_private = (struct mfld_mc_private *) dev;
+
+ memcpy_fromio(&mc_private->interrupt_status,
+ ((void *)(mc_private->int_base)),
+ sizeof(u8));
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t snd_mfld_jack_detection(int irq, void *data)
+{
+ struct mfld_mc_private *mc_drv_ctx = (struct mfld_mc_private *) data;
+
+ if (mfld_jack.codec == NULL)
+ return IRQ_HANDLED;
+ mfld_jack_check(mc_drv_ctx->interrupt_status);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit snd_mfld_mc_probe(struct platform_device *pdev)
+{
+ int ret_val = 0, irq;
+ struct mfld_mc_private *mc_drv_ctx;
+ struct resource *irq_mem;
+
+ pr_debug("snd_mfld_mc_probe called\n");
+
+ /* retrive the irq number */
+ irq = platform_get_irq(pdev, 0);
+
+ /* audio interrupt base of SRAM location where
+ * interrupts are stored by System FW */
+ mc_drv_ctx = kzalloc(sizeof(*mc_drv_ctx), GFP_ATOMIC);
+ if (!mc_drv_ctx) {
+ pr_err("allocation failed\n");
+ return -ENOMEM;
+ }
+
+ irq_mem = platform_get_resource_byname(
+ pdev, IORESOURCE_MEM, "IRQ_BASE");
+ if (!irq_mem) {
+ pr_err("no mem resource given\n");
+ ret_val = -ENODEV;
+ goto unalloc;
+ }
+ mc_drv_ctx->int_base = ioremap_nocache(irq_mem->start,
+ resource_size(irq_mem));
+ if (!mc_drv_ctx->int_base) {
+ pr_err("Mapping of cache failed\n");
+ ret_val = -ENOMEM;
+ goto unalloc;
+ }
+ /* register for interrupt */
+ ret_val = request_threaded_irq(irq, snd_mfld_jack_intr_handler,
+ snd_mfld_jack_detection,
+ IRQF_SHARED, pdev->dev.driver->name, mc_drv_ctx);
+ if (ret_val) {
+ pr_err("cannot register IRQ\n");
+ goto unalloc;
+ }
+ /* register the soc card */
+ snd_soc_card_mfld.dev = &pdev->dev;
+ ret_val = snd_soc_register_card(&snd_soc_card_mfld);
+ if (ret_val) {
+ pr_debug("snd_soc_register_card failed %d\n", ret_val);
+ goto freeirq;
+ }
+ platform_set_drvdata(pdev, mc_drv_ctx);
+ pr_debug("successfully exited probe\n");
+ return ret_val;
+
+freeirq:
+ free_irq(irq, mc_drv_ctx);
+unalloc:
+ kfree(mc_drv_ctx);
+ return ret_val;
+}
+
+static int __devexit snd_mfld_mc_remove(struct platform_device *pdev)
+{
+ struct mfld_mc_private *mc_drv_ctx = platform_get_drvdata(pdev);
+
+ pr_debug("snd_mfld_mc_remove called\n");
+ free_irq(platform_get_irq(pdev, 0), mc_drv_ctx);
+ snd_soc_unregister_card(&snd_soc_card_mfld);
+ kfree(mc_drv_ctx);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver snd_mfld_mc_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "msic_audio",
+ },
+ .probe = snd_mfld_mc_probe,
+ .remove = __devexit_p(snd_mfld_mc_remove),
+};
+
+static int __init snd_mfld_driver_init(void)
+{
+ pr_debug("snd_mfld_driver_init called\n");
+ return platform_driver_register(&snd_mfld_mc_driver);
+}
+module_init(snd_mfld_driver_init);
+
+static void __exit snd_mfld_driver_exit(void)
+{
+ pr_debug("snd_mfld_driver_exit called\n");
+ platform_driver_unregister(&snd_mfld_mc_driver);
+}
+module_exit(snd_mfld_driver_exit);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:msic-audio");
diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c
new file mode 100644
index 00000000000..ee2c22475a7
--- /dev/null
+++ b/sound/soc/mid-x86/sst_platform.c
@@ -0,0 +1,474 @@
+/*
+ * sst_platform.c - Intel MID Platform driver
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../../../drivers/staging/intel_sst/intel_sst_ioctl.h"
+#include "../../../drivers/staging/intel_sst/intel_sst.h"
+#include "sst_platform.h"
+
+static struct snd_pcm_hardware sst_platform_pcm_hw = {
+ .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_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 |
+ SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 |
+ SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32),
+ .rates = (SNDRV_PCM_RATE_8000|
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000),
+ .rate_min = SST_MIN_RATE,
+ .rate_max = SST_MAX_RATE,
+ .channels_min = SST_MIN_CHANNEL,
+ .channels_max = SST_MAX_CHANNEL,
+ .buffer_bytes_max = SST_MAX_BUFFER,
+ .period_bytes_min = SST_MIN_PERIOD_BYTES,
+ .period_bytes_max = SST_MAX_PERIOD_BYTES,
+ .periods_min = SST_MIN_PERIODS,
+ .periods_max = SST_MAX_PERIODS,
+ .fifo_size = SST_FIFO_SIZE,
+};
+
+/* MFLD - MSIC */
+struct snd_soc_dai_driver sst_platform_dai[] = {
+{
+ .name = "Headset-cpu-dai",
+ .id = 0,
+ .playback = {
+ .channels_min = SST_STEREO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 5,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Speaker-cpu-dai",
+ .id = 1,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Vibra1-cpu-dai",
+ .id = 2,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_MONO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+{
+ .name = "Vibra2-cpu-dai",
+ .id = 3,
+ .playback = {
+ .channels_min = SST_MONO,
+ .channels_max = SST_STEREO,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S24_LE,
+ },
+},
+};
+
+/* helper functions */
+static inline void sst_set_stream_status(struct sst_runtime_stream *stream,
+ int state)
+{
+ spin_lock(&stream->status_lock);
+ stream->stream_status = state;
+ spin_unlock(&stream->status_lock);
+}
+
+static inline int sst_get_stream_status(struct sst_runtime_stream *stream)
+{
+ int state;
+
+ spin_lock(&stream->status_lock);
+ state = stream->stream_status;
+ spin_unlock(&stream->status_lock);
+ return state;
+}
+
+static void sst_fill_pcm_params(struct snd_pcm_substream *substream,
+ struct snd_sst_stream_params *param)
+{
+
+ param->uc.pcm_params.codec = SST_CODEC_TYPE_PCM;
+ param->uc.pcm_params.num_chan = (u8) substream->runtime->channels;
+ param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits;
+ param->uc.pcm_params.reserved = 0;
+ param->uc.pcm_params.sfreq = substream->runtime->rate;
+ param->uc.pcm_params.ring_buffer_size =
+ snd_pcm_lib_buffer_bytes(substream);
+ param->uc.pcm_params.period_count = substream->runtime->period_size;
+ param->uc.pcm_params.ring_buffer_addr =
+ virt_to_phys(substream->dma_buffer.area);
+ pr_debug("period_cnt = %d\n", param->uc.pcm_params.period_count);
+ pr_debug("sfreq= %d, wd_sz = %d\n",
+ param->uc.pcm_params.sfreq, param->uc.pcm_params.pcm_wd_sz);
+}
+
+static int sst_platform_alloc_stream(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ struct snd_sst_stream_params param = {{{0,},},};
+ struct snd_sst_params str_params = {0};
+ int ret_val;
+
+ /* set codec params and inform SST driver the same */
+ sst_fill_pcm_params(substream, &param);
+ substream->runtime->dma_area = substream->dma_buffer.area;
+ str_params.sparams = param;
+ str_params.codec = param.uc.pcm_params.codec;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ str_params.ops = STREAM_OPS_PLAYBACK;
+ str_params.device_type = substream->pcm->device + 1;
+ pr_debug("Playbck stream,Device %d\n",
+ substream->pcm->device);
+ } else {
+ str_params.ops = STREAM_OPS_CAPTURE;
+ str_params.device_type = SND_SST_DEVICE_CAPTURE;
+ pr_debug("Capture stream,Device %d\n",
+ substream->pcm->device);
+ }
+ ret_val = stream->sstdrv_ops->pcm_control->open(&str_params);
+ pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val);
+ if (ret_val < 0)
+ return ret_val;
+
+ stream->stream_info.str_id = ret_val;
+ pr_debug("str id : %d\n", stream->stream_info.str_id);
+ return ret_val;
+}
+
+static void sst_period_elapsed(void *mad_substream)
+{
+ struct snd_pcm_substream *substream = mad_substream;
+ struct sst_runtime_stream *stream;
+ int status;
+
+ if (!substream || !substream->runtime)
+ return;
+ stream = substream->runtime->private_data;
+ if (!stream)
+ return;
+ status = sst_get_stream_status(stream);
+ if (status != SST_PLATFORM_RUNNING)
+ return;
+ snd_pcm_period_elapsed(substream);
+}
+
+static int sst_platform_init_stream(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream =
+ substream->runtime->private_data;
+ int ret_val;
+
+ pr_debug("setting buffer ptr param\n");
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ stream->stream_info.period_elapsed = sst_period_elapsed;
+ stream->stream_info.mad_substream = substream;
+ stream->stream_info.buffer_ptr = 0;
+ stream->stream_info.sfreq = substream->runtime->rate;
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_STREAM_INIT, &stream->stream_info);
+ if (ret_val)
+ pr_err("control_set ret error %d\n", ret_val);
+ return ret_val;
+
+}
+/* end -- helper functions */
+
+static int sst_platform_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime;
+ struct sst_runtime_stream *stream;
+ int ret_val = 0;
+
+ pr_debug("sst_platform_open called\n");
+ runtime = substream->runtime;
+ runtime->hw = sst_platform_pcm_hw;
+ stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+ if (!stream)
+ return -ENOMEM;
+ spin_lock_init(&stream->status_lock);
+ stream->stream_info.str_id = 0;
+ sst_set_stream_status(stream, SST_PLATFORM_INIT);
+ stream->stream_info.mad_substream = substream;
+ /* allocate memory for SST API set */
+ stream->sstdrv_ops = kzalloc(sizeof(*stream->sstdrv_ops),
+ GFP_KERNEL);
+ if (!stream->sstdrv_ops) {
+ pr_err("sst: mem allocation for ops fail\n");
+ kfree(stream);
+ return -ENOMEM;
+ }
+ stream->sstdrv_ops->vendor_id = MSIC_VENDOR_ID;
+ /* registering with SST driver to get access to SST APIs to use */
+ ret_val = register_sst_card(stream->sstdrv_ops);
+ if (ret_val) {
+ pr_err("sst: sst card registration failed\n");
+ return ret_val;
+ }
+ runtime->private_data = stream;
+ return snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int sst_platform_close(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ pr_debug("sst_platform_close called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ if (str_id)
+ ret_val = stream->sstdrv_ops->pcm_control->close(str_id);
+ kfree(stream->sstdrv_ops);
+ kfree(stream);
+ return ret_val;
+}
+
+static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val = 0, str_id;
+
+ pr_debug("sst_platform_pcm_prepare called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ if (stream->stream_info.str_id) {
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_DROP, &str_id);
+ return ret_val;
+ }
+
+ ret_val = sst_platform_alloc_stream(substream);
+ if (ret_val < 0)
+ return ret_val;
+ snprintf(substream->pcm->id, sizeof(substream->pcm->id),
+ "%d", stream->stream_info.str_id);
+
+ ret_val = sst_platform_init_stream(substream);
+ if (ret_val)
+ return ret_val;
+ substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER;
+ return ret_val;
+}
+
+static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int ret_val = 0, str_id;
+ struct sst_runtime_stream *stream;
+ int str_cmd, status;
+
+ pr_debug("sst_platform_pcm_trigger called\n");
+ stream = substream->runtime->private_data;
+ str_id = stream->stream_info.str_id;
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ pr_debug("sst: Trigger Start\n");
+ str_cmd = SST_SND_START;
+ status = SST_PLATFORM_RUNNING;
+ stream->stream_info.mad_substream = substream;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ pr_debug("sst: in stop\n");
+ str_cmd = SST_SND_DROP;
+ status = SST_PLATFORM_DROPPED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ pr_debug("sst: in pause\n");
+ str_cmd = SST_SND_PAUSE;
+ status = SST_PLATFORM_PAUSED;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ pr_debug("sst: in pause release\n");
+ str_cmd = SST_SND_RESUME;
+ status = SST_PLATFORM_RUNNING;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(str_cmd,
+ &str_id);
+ if (!ret_val)
+ sst_set_stream_status(stream, status);
+
+ return ret_val;
+}
+
+
+static snd_pcm_uframes_t sst_platform_pcm_pointer
+ (struct snd_pcm_substream *substream)
+{
+ struct sst_runtime_stream *stream;
+ int ret_val, status;
+ struct pcm_stream_info *str_info;
+
+ stream = substream->runtime->private_data;
+ status = sst_get_stream_status(stream);
+ if (status == SST_PLATFORM_INIT)
+ return 0;
+ str_info = &stream->stream_info;
+ ret_val = stream->sstdrv_ops->pcm_control->device_control(
+ SST_SND_BUFFER_POINTER, str_info);
+ if (ret_val) {
+ pr_err("sst: error code = %d\n", ret_val);
+ return ret_val;
+ }
+ return stream->stream_info.buffer_ptr;
+}
+
+static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+ memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
+
+ return 0;
+}
+
+static struct snd_pcm_ops sst_platform_ops = {
+ .open = sst_platform_open,
+ .close = sst_platform_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .prepare = sst_platform_pcm_prepare,
+ .trigger = sst_platform_pcm_trigger,
+ .pointer = sst_platform_pcm_pointer,
+ .hw_params = sst_platform_pcm_hw_params,
+};
+
+static void sst_pcm_free(struct snd_pcm *pcm)
+{
+ pr_debug("sst_pcm_free called\n");
+ snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int retval = 0;
+
+ pr_debug("sst_pcm_new called\n");
+ if (dai->driver->playback.channels_min ||
+ dai->driver->capture.channels_min) {
+ retval = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_CONTINUOUS,
+ snd_dma_continuous_data(GFP_KERNEL),
+ SST_MIN_BUFFER, SST_MAX_BUFFER);
+ if (retval) {
+ pr_err("dma buffer allocationf fail\n");
+ return retval;
+ }
+ }
+ return retval;
+}
+struct snd_soc_platform_driver sst_soc_platform_drv = {
+ .ops = &sst_platform_ops,
+ .pcm_new = sst_pcm_new,
+ .pcm_free = sst_pcm_free,
+};
+
+static int sst_platform_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ pr_debug("sst_platform_probe called\n");
+ ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv);
+ if (ret) {
+ pr_err("registering soc platform failed\n");
+ return ret;
+ }
+
+ ret = snd_soc_register_dais(&pdev->dev,
+ sst_platform_dai, ARRAY_SIZE(sst_platform_dai));
+ if (ret) {
+ pr_err("registering cpu dais failed\n");
+ snd_soc_unregister_platform(&pdev->dev);
+ }
+ return ret;
+}
+
+static int sst_platform_remove(struct platform_device *pdev)
+{
+
+ snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sst_platform_dai));
+ snd_soc_unregister_platform(&pdev->dev);
+ pr_debug("sst_platform_remove sucess\n");
+ return 0;
+}
+
+static struct platform_driver sst_platform_driver = {
+ .driver = {
+ .name = "sst-platform",
+ .owner = THIS_MODULE,
+ },
+ .probe = sst_platform_probe,
+ .remove = sst_platform_remove,
+};
+
+static int __init sst_soc_platform_init(void)
+{
+ pr_debug("sst_soc_platform_init called\n");
+ return platform_driver_register(&sst_platform_driver);
+}
+module_init(sst_soc_platform_init);
+
+static void __exit sst_soc_platform_exit(void)
+{
+ platform_driver_unregister(&sst_platform_driver);
+ pr_debug("sst_soc_platform_exit sucess\n");
+}
+module_exit(sst_soc_platform_exit);
+
+MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>");
+MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:sst-platform");
diff --git a/sound/soc/mid-x86/sst_platform.h b/sound/soc/mid-x86/sst_platform.h
new file mode 100644
index 00000000000..df370286694
--- /dev/null
+++ b/sound/soc/mid-x86/sst_platform.h
@@ -0,0 +1,63 @@
+/*
+ * sst_platform.h - Intel MID Platform driver header file
+ *
+ * Copyright (C) 2010 Intel Corp
+ * Author: Vinod Koul <vinod.koul@intel.com>
+ * Author: Harsha Priya <priya.harsha@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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *
+ */
+
+#ifndef __SST_PLATFORMDRV_H__
+#define __SST_PLATFORMDRV_H__
+
+#define SST_MONO 1
+#define SST_STEREO 2
+#define SST_MAX_CAP 5
+
+#define SST_MIN_RATE 8000
+#define SST_MAX_RATE 48000
+#define SST_MIN_CHANNEL 1
+#define SST_MAX_CHANNEL 5
+#define SST_MAX_BUFFER (800*1024)
+#define SST_MIN_BUFFER (800*1024)
+#define SST_MIN_PERIOD_BYTES 32
+#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER
+#define SST_MIN_PERIODS 2
+#define SST_MAX_PERIODS (1024*2)
+#define SST_FIFO_SIZE 0
+#define SST_CARD_NAMES "intel_mid_card"
+#define MSIC_VENDOR_ID 3
+
+struct sst_runtime_stream {
+ int stream_status;
+ struct pcm_stream_info stream_info;
+ struct intel_sst_card_ops *sstdrv_ops;
+ spinlock_t status_lock;
+};
+
+enum sst_drv_status {
+ SST_PLATFORM_INIT = 1,
+ SST_PLATFORM_STARTED,
+ SST_PLATFORM_RUNNING,
+ SST_PLATFORM_PAUSED,
+ SST_PLATFORM_DROPPED,
+};
+
+#endif
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig
index a088db6d509..b5922984eac 100644
--- a/sound/soc/omap/Kconfig
+++ b/sound/soc/omap/Kconfig
@@ -24,6 +24,7 @@ config SND_OMAP_SOC_RX51
select OMAP_MCBSP
select SND_OMAP_SOC_MCBSP
select SND_SOC_TLV320AIC3X
+ select SND_SOC_TPA6130A2
help
Say Y if you want to add support for SoC audio on Nokia RX-51
hardware. This is also known as Nokia N900 product.
diff --git a/sound/soc/omap/rx51.c b/sound/soc/omap/rx51.c
index 09fb0df8d41..d0986220eff 100644
--- a/sound/soc/omap/rx51.c
+++ b/sound/soc/omap/rx51.c
@@ -31,6 +31,7 @@
#include <sound/pcm.h>
#include <sound/soc.h>
#include <plat/mcbsp.h>
+#include "../codecs/tpa6130a2.h"
#include <asm/mach-types.h>
@@ -39,6 +40,7 @@
#define RX51_TVOUT_SEL_GPIO 40
#define RX51_JACK_DETECT_GPIO 177
+#define RX51_ECI_SW_GPIO 182
/*
* REVISIT: TWL4030 GPIO base in RX-51. Now statically defined to 192. This
* gpio is reserved in arch/arm/mach-omap2/board-rx51-peripherals.c
@@ -47,7 +49,9 @@
enum {
RX51_JACK_DISABLED,
- RX51_JACK_TVOUT, /* tv-out */
+ RX51_JACK_TVOUT, /* tv-out with stereo output */
+ RX51_JACK_HP, /* headphone: stereo output, no mic */
+ RX51_JACK_HS, /* headset: stereo output with mic */
};
static int rx51_spk_func;
@@ -57,6 +61,19 @@ static int rx51_jack_func;
static void rx51_ext_control(struct snd_soc_codec *codec)
{
struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int hp = 0, hs = 0, tvout = 0;
+
+ switch (rx51_jack_func) {
+ case RX51_JACK_TVOUT:
+ tvout = 1;
+ hp = 1;
+ break;
+ case RX51_JACK_HS:
+ hs = 1;
+ case RX51_JACK_HP:
+ hp = 1;
+ break;
+ }
if (rx51_spk_func)
snd_soc_dapm_enable_pin(dapm, "Ext Spk");
@@ -66,9 +83,16 @@ static void rx51_ext_control(struct snd_soc_codec *codec)
snd_soc_dapm_enable_pin(dapm, "DMic");
else
snd_soc_dapm_disable_pin(dapm, "DMic");
+ if (hp)
+ snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
+ if (hs)
+ snd_soc_dapm_enable_pin(dapm, "HS Mic");
+ else
+ snd_soc_dapm_disable_pin(dapm, "HS Mic");
- gpio_set_value(RX51_TVOUT_SEL_GPIO,
- rx51_jack_func == RX51_JACK_TVOUT);
+ gpio_set_value(RX51_TVOUT_SEL_GPIO, tvout);
snd_soc_dapm_sync(dapm);
}
@@ -153,6 +177,19 @@ static int rx51_spk_event(struct snd_soc_dapm_widget *w,
return 0;
}
+static int rx51_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_codec *codec = w->dapm->codec;
+
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ tpa6130a2_stereo_enable(codec, 1);
+ else
+ tpa6130a2_stereo_enable(codec, 0);
+
+ return 0;
+}
+
static int rx51_get_input(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
@@ -203,7 +240,7 @@ static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
{
.gpio = RX51_JACK_DETECT_GPIO,
.name = "avdet-gpio",
- .report = SND_JACK_VIDEOOUT,
+ .report = SND_JACK_HEADSET,
.invert = 1,
.debounce_time = 200,
},
@@ -212,19 +249,38 @@ static struct snd_soc_jack_gpio rx51_av_jack_gpios[] = {
static const struct snd_soc_dapm_widget aic34_dapm_widgets[] = {
SND_SOC_DAPM_SPK("Ext Spk", rx51_spk_event),
SND_SOC_DAPM_MIC("DMic", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", rx51_hp_event),
+ SND_SOC_DAPM_MIC("HS Mic", NULL),
+ SND_SOC_DAPM_LINE("FM Transmitter", NULL),
+};
+
+static const struct snd_soc_dapm_widget aic34_dapm_widgetsb[] = {
+ SND_SOC_DAPM_SPK("Earphone", NULL),
};
static const struct snd_soc_dapm_route audio_map[] = {
{"Ext Spk", NULL, "HPLOUT"},
{"Ext Spk", NULL, "HPROUT"},
+ {"Headphone Jack", NULL, "LLOUT"},
+ {"Headphone Jack", NULL, "RLOUT"},
+ {"FM Transmitter", NULL, "LLOUT"},
+ {"FM Transmitter", NULL, "RLOUT"},
{"DMic Rate 64", NULL, "Mic Bias 2V"},
{"Mic Bias 2V", NULL, "DMic"},
};
+static const struct snd_soc_dapm_route audio_mapb[] = {
+ {"b LINE2R", NULL, "MONO_LOUT"},
+ {"Earphone", NULL, "b HPLOUT"},
+
+ {"LINE1L", NULL, "b Mic Bias 2.5V"},
+ {"b Mic Bias 2.5V", NULL, "HS Mic"}
+};
+
static const char *spk_function[] = {"Off", "On"};
static const char *input_function[] = {"ADC", "Digital Mic"};
-static const char *jack_function[] = {"Off", "TV-OUT"};
+static const char *jack_function[] = {"Off", "TV-OUT", "Headphone", "Headset"};
static const struct soc_enum rx51_enum[] = {
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function),
@@ -239,6 +295,11 @@ static const struct snd_kcontrol_new aic34_rx51_controls[] = {
rx51_get_input, rx51_set_input),
SOC_ENUM_EXT("Jack Function", rx51_enum[2],
rx51_get_jack, rx51_set_jack),
+ SOC_DAPM_PIN_SWITCH("FM Transmitter"),
+};
+
+static const struct snd_kcontrol_new aic34_rx51_controlsb[] = {
+ SOC_DAPM_PIN_SWITCH("Earphone"),
};
static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
@@ -265,11 +326,21 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
/* Set up RX-51 specific audio path audio_map */
snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
+ err = tpa6130a2_add_controls(codec);
+ if (err < 0)
+ return err;
+ snd_soc_limit_volume(codec, "TPA6130A2 Headphone Playback Volume", 42);
+
+ err = omap_mcbsp_st_add_controls(codec, 1);
+ if (err < 0)
+ return err;
+
snd_soc_dapm_sync(dapm);
/* AV jack detection */
err = snd_soc_jack_new(codec, "AV Jack",
- SND_JACK_VIDEOOUT, &rx51_av_jack);
+ SND_JACK_HEADSET | SND_JACK_VIDEOOUT,
+ &rx51_av_jack);
if (err)
return err;
err = snd_soc_jack_add_gpios(&rx51_av_jack,
@@ -279,6 +350,24 @@ static int rx51_aic34_init(struct snd_soc_pcm_runtime *rtd)
return err;
}
+static int rx51_aic34b_init(struct snd_soc_dapm_context *dapm)
+{
+ int err;
+
+ err = snd_soc_add_controls(dapm->codec, aic34_rx51_controlsb,
+ ARRAY_SIZE(aic34_rx51_controlsb));
+ if (err < 0)
+ return err;
+
+ err = snd_soc_dapm_new_controls(dapm, aic34_dapm_widgetsb,
+ ARRAY_SIZE(aic34_dapm_widgetsb));
+ if (err < 0)
+ return 0;
+
+ return snd_soc_dapm_add_routes(dapm, audio_mapb,
+ ARRAY_SIZE(audio_mapb));
+}
+
/* Digital audio interface glue - connects codec <--> CPU */
static struct snd_soc_dai_link rx51_dai[] = {
{
@@ -293,11 +382,30 @@ static struct snd_soc_dai_link rx51_dai[] = {
},
};
+struct snd_soc_aux_dev rx51_aux_dev[] = {
+ {
+ .name = "TLV320AIC34b",
+ .codec_name = "tlv320aic3x-codec.2-0019",
+ .init = rx51_aic34b_init,
+ },
+};
+
+static struct snd_soc_codec_conf rx51_codec_conf[] = {
+ {
+ .dev_name = "tlv320aic3x-codec.2-0019",
+ .name_prefix = "b",
+ },
+};
+
/* Audio card */
static struct snd_soc_card rx51_sound_card = {
.name = "RX-51",
.dai_link = rx51_dai,
.num_links = ARRAY_SIZE(rx51_dai),
+ .aux_dev = rx51_aux_dev,
+ .num_aux_devs = ARRAY_SIZE(rx51_aux_dev),
+ .codec_conf = rx51_codec_conf,
+ .num_configs = ARRAY_SIZE(rx51_codec_conf),
};
static struct platform_device *rx51_snd_device;
@@ -309,10 +417,14 @@ static int __init rx51_soc_init(void)
if (!machine_is_nokia_rx51())
return -ENODEV;
- err = gpio_request(RX51_TVOUT_SEL_GPIO, "tvout_sel");
+ err = gpio_request_one(RX51_TVOUT_SEL_GPIO,
+ GPIOF_DIR_OUT | GPIOF_INIT_LOW, "tvout_sel");
if (err)
goto err_gpio_tvout_sel;
- gpio_direction_output(RX51_TVOUT_SEL_GPIO, 0);
+ err = gpio_request_one(RX51_ECI_SW_GPIO,
+ GPIOF_DIR_OUT | GPIOF_INIT_HIGH, "eci_sw");
+ if (err)
+ goto err_gpio_eci_sw;
rx51_snd_device = platform_device_alloc("soc-audio", -1);
if (!rx51_snd_device) {
@@ -330,6 +442,8 @@ static int __init rx51_soc_init(void)
err2:
platform_device_put(rx51_snd_device);
err1:
+ gpio_free(RX51_ECI_SW_GPIO);
+err_gpio_eci_sw:
gpio_free(RX51_TVOUT_SEL_GPIO);
err_gpio_tvout_sel:
@@ -342,6 +456,7 @@ static void __exit rx51_soc_exit(void)
rx51_av_jack_gpios);
platform_device_unregister(rx51_snd_device);
+ gpio_free(RX51_ECI_SW_GPIO);
gpio_free(RX51_TVOUT_SEL_GPIO);
}
diff --git a/sound/soc/pxa/raumfeld.c b/sound/soc/pxa/raumfeld.c
index 0fd60f42303..2afabaf5949 100644
--- a/sound/soc/pxa/raumfeld.c
+++ b/sound/soc/pxa/raumfeld.c
@@ -151,13 +151,13 @@ static struct snd_soc_ops raumfeld_cs4270_ops = {
.hw_params = raumfeld_cs4270_hw_params,
};
-static int raumfeld_line_suspend(struct platform_device *pdev, pm_message_t state)
+static int raumfeld_line_suspend(struct snd_soc_card *card)
{
raumfeld_enable_audio(false);
return 0;
}
-static int raumfeld_line_resume(struct platform_device *pdev)
+static int raumfeld_line_resume(struct snd_soc_card *card)
{
raumfeld_enable_audio(true);
return 0;
@@ -229,19 +229,19 @@ static struct snd_soc_dai_link raumfeld_dai[] = {
{
.name = "ak4104",
.stream_name = "Playback",
- .cpu_dai_name = "pxa-ssp-dai.1",
- .codec_dai_name = "ak4104-hifi",
- .platform_name = "pxa-pcm-audio",
+ .cpu_dai_name = "pxa-ssp-dai.1",
+ .codec_dai_name = "ak4104-hifi",
+ .platform_name = "pxa-pcm-audio",
.ops = &raumfeld_ak4104_ops,
- .codec_name = "ak4104-codec.0",
+ .codec_name = "ak4104-codec.0",
},
{
.name = "CS4270",
.stream_name = "CS4270",
- .cpu_dai_name = "pxa-ssp-dai.0",
- .platform_name = "pxa-pcm-audio",
- .codec_dai_name = "cs4270-hifi",
- .codec_name = "cs4270-codec.0-0048",
+ .cpu_dai_name = "pxa-ssp-dai.0",
+ .platform_name = "pxa-pcm-audio",
+ .codec_dai_name = "cs4270-hifi",
+ .codec_name = "cs4270-codec.0-0048",
.ops = &raumfeld_cs4270_ops,
},};
diff --git a/sound/soc/pxa/tosa.c b/sound/soc/pxa/tosa.c
index 4b6e5d608b4..9a235136695 100644
--- a/sound/soc/pxa/tosa.c
+++ b/sound/soc/pxa/tosa.c
@@ -237,7 +237,7 @@ static struct snd_soc_dai_link tosa_dai[] = {
},
};
-static int tosa_probe(struct platform_device *dev)
+static int tosa_probe(struct snd_soc_card *card)
{
int ret;
@@ -251,7 +251,7 @@ static int tosa_probe(struct platform_device *dev)
return ret;
}
-static int tosa_remove(struct platform_device *dev)
+static int tosa_remove(struct snd_soc_card *card)
{
gpio_free(TOSA_GPIO_L_MUTE);
return 0;
diff --git a/sound/soc/pxa/z2.c b/sound/soc/pxa/z2.c
index 3ceaef68e01..d69d9fc3223 100644
--- a/sound/soc/pxa/z2.c
+++ b/sound/soc/pxa/z2.c
@@ -95,6 +95,11 @@ static struct snd_soc_jack_pin hs_jack_pins[] = {
.pin = "Headphone Jack",
.mask = SND_JACK_HEADPHONE,
},
+ {
+ .pin = "Ext Spk",
+ .mask = SND_JACK_HEADPHONE,
+ .invert = 1
+ },
};
/* Headset jack detection gpios */
@@ -147,7 +152,7 @@ static int z2_wm8750_init(struct snd_soc_pcm_runtime *rtd)
snd_soc_dapm_disable_pin(dapm, "LINPUT3");
snd_soc_dapm_disable_pin(dapm, "RINPUT3");
snd_soc_dapm_disable_pin(dapm, "OUT3");
- snd_soc_dapm_disable_pin(dapm, "MONO");
+ snd_soc_dapm_disable_pin(dapm, "MONO1");
/* Add z2 specific widgets */
snd_soc_dapm_new_controls(dapm, wm8750_dapm_widgets,
diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c
index 25bba108fea..ac577263b3e 100644
--- a/sound/soc/pxa/zylonite.c
+++ b/sound/soc/pxa/zylonite.c
@@ -189,7 +189,7 @@ static struct snd_soc_dai_link zylonite_dai[] = {
},
};
-static int zylonite_probe(struct platform_device *pdev)
+static int zylonite_probe(struct snd_soc_card *card)
{
int ret;
@@ -216,7 +216,7 @@ static int zylonite_probe(struct platform_device *pdev)
return 0;
}
-static int zylonite_remove(struct platform_device *pdev)
+static int zylonite_remove(struct snd_soc_card *card)
{
if (clk_pout) {
clk_disable(pout);
@@ -226,8 +226,7 @@ static int zylonite_remove(struct platform_device *pdev)
return 0;
}
-static int zylonite_suspend_post(struct platform_device *pdev,
- pm_message_t state)
+static int zylonite_suspend_post(struct snd_soc_card *card)
{
if (clk_pout)
clk_disable(pout);
@@ -235,7 +234,7 @@ static int zylonite_suspend_post(struct platform_device *pdev,
return 0;
}
-static int zylonite_resume_pre(struct platform_device *pdev)
+static int zylonite_resume_pre(struct snd_soc_card *card)
{
int ret = 0;
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index a6a6b5fa2f2..a08237acc53 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -35,23 +35,16 @@ config SND_SAMSUNG_I2S
tristate
config SND_SOC_SAMSUNG_NEO1973_WM8753
- tristate "SoC I2S Audio support for NEO1973 - WM8753"
- depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA01
+ tristate "Audio support for Openmoko Neo1973 Smartphones (GTA01/GTA02)"
+ depends on SND_SOC_SAMSUNG && (MACH_NEO1973_GTA01 || MACH_NEO1973_GTA02)
select SND_S3C24XX_I2S
select SND_SOC_WM8753
+ select SND_SOC_LM4857 if MACH_NEO1973_GTA01
+ select SND_SOC_DFBMCS320
help
- Say Y if you want to add support for SoC audio on smdk2440
- with the WM8753.
+ Say Y here to enable audio support for the Openmoko Neo1973
+ Smartphones.
-config SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753
- tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)"
- depends on SND_SOC_SAMSUNG && MACH_NEO1973_GTA02
- select SND_S3C24XX_I2S
- select SND_SOC_WM8753
- help
- This driver provides audio support for the Openmoko Neo FreeRunner
- smartphone.
-
config SND_SOC_SAMSUNG_JIVE_WM8750
tristate "SoC I2S Audio support for Jive"
depends on SND_SOC_SAMSUNG && MACH_JIVE
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
index 705d4e8a672..294dec05c26 100644
--- a/sound/soc/samsung/Makefile
+++ b/sound/soc/samsung/Makefile
@@ -20,7 +20,6 @@ obj-$(CONFIG_SND_SAMSUNG_I2S) += snd-soc-i2s.o
# S3C24XX Machine Support
snd-soc-jive-wm8750-objs := jive_wm8750.o
snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
-snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o
snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o
snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o
snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o
@@ -38,7 +37,6 @@ snd-soc-smdk-spdif-objs := smdk_spdif.o
obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o
obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o
-obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o
obj-$(CONFIG_SND_SOC_SAMSUNG_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o
obj-$(CONFIG_SND_SOC_SAMSUNG_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o
obj-$(CONFIG_SND_SOC_SAMSUNG_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o
diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c
index 4770a955034..f97110e72e8 100644
--- a/sound/soc/samsung/ac97.c
+++ b/sound/soc/samsung/ac97.c
@@ -12,24 +12,24 @@
* published by the Free Software Foundation.
*/
-#include <linux/init.h>
-#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <sound/soc.h>
-#include <plat/regs-ac97.h>
#include <mach/dma.h>
+#include <plat/regs-ac97.h>
#include <plat/audio.h>
#include "dma.h"
-#include "ac97.h"
#define AC_CMD_ADDR(x) (x << 16)
#define AC_CMD_DATA(x) (x & 0xffff)
+#define S3C_AC97_DAI_PCM 0
+#define S3C_AC97_DAI_MIC 1
+
struct s3c_ac97_info {
struct clk *ac97_clk;
void __iomem *regs;
diff --git a/sound/soc/samsung/ac97.h b/sound/soc/samsung/ac97.h
deleted file mode 100644
index 0d0e1b51145..00000000000
--- a/sound/soc/samsung/ac97.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* sound/soc/samsung/ac97.h
- *
- * ALSA SoC Audio Layer - S3C AC97 Controller driver
- * Evolved from s3c2443-ac97.h
- *
- * Copyright (c) 2010 Samsung Electronics Co. Ltd
- * Author: Jaswinder Singh <jassi.brar@samsung.com>
- * Credits: Graeme Gregory, Sean Choi
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#ifndef __S3C_AC97_H_
-#define __S3C_AC97_H_
-
-#define S3C_AC97_DAI_PCM 0
-#define S3C_AC97_DAI_MIC 1
-
-#endif /* __S3C_AC97_H_ */
diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c
index 21240198c5d..5cb3b880f0d 100644
--- a/sound/soc/samsung/dma.c
+++ b/sound/soc/samsung/dma.c
@@ -14,17 +14,11 @@
* option) any later version.
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <asm/dma.h>
#include <mach/hardware.h>
@@ -32,6 +26,9 @@
#include "dma.h"
+#define ST_RUNNING (1<<0)
+#define ST_OPENED (1<<1)
+
static const struct snd_pcm_hardware dma_hardware = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
@@ -313,7 +310,7 @@ dma_pointer(struct snd_pcm_substream *substream)
/* we seem to be getting the odd error from the pcm library due
* to out-of-bounds pointers. this is maybe due to the dma engine
* not having loaded the new values for the channel before being
- * callled... (todo - fix )
+ * called... (todo - fix )
*/
if (res >= snd_pcm_lib_buffer_bytes(substream)) {
diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h
index f8cd2b4223a..c50659269a4 100644
--- a/sound/soc/samsung/dma.h
+++ b/sound/soc/samsung/dma.h
@@ -12,9 +12,6 @@
#ifndef _S3C_AUDIO_H
#define _S3C_AUDIO_H
-#define ST_RUNNING (1<<0)
-#define ST_OPENED (1<<1)
-
struct s3c_dma_params {
struct s3c2410_dma_client *client; /* stream identifier */
int channel; /* Channel ID */
@@ -22,9 +19,4 @@ struct s3c_dma_params {
int dma_size; /* Size of the DMA transfer */
};
-#define S3C24XX_DAI_I2S 0
-
-/* platform data */
-extern struct snd_ac97_bus_ops s3c24xx_ac97_ops;
-
#endif
diff --git a/sound/soc/samsung/goni_wm8994.c b/sound/soc/samsung/goni_wm8994.c
index 34dd9ef1b9c..f6b3a3ce591 100644
--- a/sound/soc/samsung/goni_wm8994.c
+++ b/sound/soc/samsung/goni_wm8994.c
@@ -11,21 +11,13 @@
*
*/
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/io.h>
-#include <linux/platform_device.h>
#include <sound/soc.h>
#include <sound/jack.h>
+
#include <asm/mach-types.h>
#include <mach/gpio.h>
-#include <mach/regs-clock.h>
-#include <linux/mfd/wm8994/core.h>
-#include <linux/mfd/wm8994/registers.h>
#include "../codecs/wm8994.h"
-#include "dma.h"
-#include "i2s.h"
#define MACHINE_NAME 0
#define CPU_VOICE_DAI 1
diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c
index c45f7ce14d6..241f55d0066 100644
--- a/sound/soc/samsung/h1940_uda1380.c
+++ b/sound/soc/samsung/h1940_uda1380.c
@@ -13,25 +13,16 @@
*
*/
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/platform_device.h>
-#include <linux/i2c.h>
#include <linux/gpio.h>
#include <sound/soc.h>
-#include <sound/uda1380.h>
#include <sound/jack.h>
#include <plat/regs-iis.h>
-
#include <mach/h1940-latch.h>
-
#include <asm/mach-types.h>
-#include "dma.h"
#include "s3c24xx-i2s.h"
-#include "../codecs/uda1380.h"
static unsigned int rates[] = {
11025,
diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c
index d00ac3a7102..ffa09b3b2ca 100644
--- a/sound/soc/samsung/i2s.c
+++ b/sound/soc/samsung/i2s.c
@@ -15,9 +15,8 @@
#include <linux/clk.h>
#include <linux/io.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <plat/audio.h>
diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c
index 08802520e01..3b53ad54bc3 100644
--- a/sound/soc/samsung/jive_wm8750.c
+++ b/sound/soc/samsung/jive_wm8750.c
@@ -11,22 +11,11 @@
* published by the Free Software Foundation.
*/
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/timer.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/clk.h>
-
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
#include <asm/mach-types.h>
-#include "dma.h"
#include "s3c2412-i2s.h"
-
#include "../codecs/wm8750.h"
static const struct snd_soc_dapm_route audio_map[] = {
diff --git a/sound/soc/samsung/lm4857.h b/sound/soc/samsung/lm4857.h
deleted file mode 100644
index 0cf5b7011d6..00000000000
--- a/sound/soc/samsung/lm4857.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * lm4857.h -- ALSA Soc Audio Layer
- *
- * Copyright 2007 Wolfson Microelectronics PLC.
- * Author: Graeme Gregory
- * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.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.
- *
- * Revision history
- * 18th Jun 2007 Initial version.
- */
-
-#ifndef LM4857_H_
-#define LM4857_H_
-
-/* The register offsets in the cache array */
-#define LM4857_MVOL 0
-#define LM4857_LVOL 1
-#define LM4857_RVOL 2
-#define LM4857_CTRL 3
-
-/* the shifts required to set these bits */
-#define LM4857_3D 5
-#define LM4857_WAKEUP 5
-#define LM4857_EPGAIN 4
-
-#endif /*LM4857_H_*/
-
diff --git a/sound/soc/samsung/ln2440sbc_alc650.c b/sound/soc/samsung/ln2440sbc_alc650.c
index a2bb34def74..bd91c19a6c0 100644
--- a/sound/soc/samsung/ln2440sbc_alc650.c
+++ b/sound/soc/samsung/ln2440sbc_alc650.c
@@ -16,15 +16,8 @@
*
*/
-#include <linux/module.h>
-#include <linux/device.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
-#include "dma.h"
-#include "ac97.h"
-
static struct snd_soc_card ln2440sbc;
static struct snd_soc_dai_link ln2440sbc_dai[] = {
diff --git a/sound/soc/samsung/neo1973_gta02_wm8753.c b/sound/soc/samsung/neo1973_gta02_wm8753.c
deleted file mode 100644
index 0d0ae2b9eef..00000000000
--- a/sound/soc/samsung/neo1973_gta02_wm8753.c
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02)
- *
- * Copyright 2007 Openmoko Inc
- * Author: Graeme Gregory <graeme@openmoko.org>
- * Copyright 2007 Wolfson Microelectronics PLC.
- * Author: Graeme Gregory <linux@wolfsonmicro.com>
- * Copyright 2009 Wolfson Microelectronics
- *
- * 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/moduleparam.h>
-#include <linux/timer.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/gpio.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/soc.h>
-
-#include <asm/mach-types.h>
-
-#include <plat/regs-iis.h>
-
-#include <mach/regs-clock.h>
-#include <asm/io.h>
-#include <mach/gta02.h>
-#include "../codecs/wm8753.h"
-#include "dma.h"
-#include "s3c24xx-i2s.h"
-
-static struct snd_soc_card neo1973_gta02;
-
-static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
- unsigned int pll_out = 0, bclk = 0;
- int ret = 0;
- unsigned long iis_clkrate;
-
- iis_clkrate = s3c24xx_i2s_get_clockrate();
-
- switch (params_rate(params)) {
- case 8000:
- case 16000:
- pll_out = 12288000;
- break;
- case 48000:
- bclk = WM8753_BCLK_DIV_4;
- pll_out = 12288000;
- break;
- case 96000:
- bclk = WM8753_BCLK_DIV_2;
- pll_out = 12288000;
- break;
- case 11025:
- bclk = WM8753_BCLK_DIV_16;
- pll_out = 11289600;
- break;
- case 22050:
- bclk = WM8753_BCLK_DIV_8;
- pll_out = 11289600;
- break;
- case 44100:
- bclk = WM8753_BCLK_DIV_4;
- pll_out = 11289600;
- break;
- case 88200:
- bclk = WM8753_BCLK_DIV_2;
- pll_out = 11289600;
- break;
- }
-
- /* set codec DAI configuration */
- ret = snd_soc_dai_set_fmt(codec_dai,
- SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
- return ret;
-
- /* set cpu DAI configuration */
- ret = snd_soc_dai_set_fmt(cpu_dai,
- SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
- SND_SOC_DAIFMT_CBM_CFM);
- if (ret < 0)
- return ret;
-
- /* set the codec system clock for DAC and ADC */
- ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out,
- SND_SOC_CLOCK_IN);
- if (ret < 0)
- return ret;
-
- /* set MCLK division for sample rate */
- ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK,
- S3C2410_IISMOD_32FS);
- if (ret < 0)
- return ret;
-
- /* set codec BCLK division for sample rate */
- ret = snd_soc_dai_set_clkdiv(codec_dai,
- WM8753_BCLKDIV, bclk);
- if (ret < 0)
- return ret;
-
- /* set prescaler division for sample rate */
- ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
- S3C24XX_PRESCALE(4, 4));
- if (ret < 0)
- return ret;
-
- /* codec PLL input is PCLK/4 */
- ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0,
- iis_clkrate / 4, pll_out);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
-static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
-
- /* disable the PLL */
- return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
-}
-
-/*
- * Neo1973 WM8753 HiFi DAI opserations.
- */
-static struct snd_soc_ops neo1973_gta02_hifi_ops = {
- .hw_params = neo1973_gta02_hifi_hw_params,
- .hw_free = neo1973_gta02_hifi_hw_free,
-};
-
-static int neo1973_gta02_voice_hw_params(
- struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *params)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
- unsigned int pcmdiv = 0;
- int ret = 0;
- unsigned long iis_clkrate;
-
- iis_clkrate = s3c24xx_i2s_get_clockrate();
-
- if (params_rate(params) != 8000)
- return -EINVAL;
- if (params_channels(params) != 1)
- return -EINVAL;
-
- pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */
-
- /* todo: gg check mode (DSP_B) against CSR datasheet */
- /* set codec DAI configuration */
- ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B |
- SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
- if (ret < 0)
- return ret;
-
- /* set the codec system clock for DAC and ADC */
- ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK,
- 12288000, SND_SOC_CLOCK_IN);
- if (ret < 0)
- return ret;
-
- /* set codec PCM division for sample rate */
- ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV,
- pcmdiv);
- if (ret < 0)
- return ret;
-
- /* configure and enable PLL for 12.288MHz output */
- ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0,
- iis_clkrate / 4, 12288000);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
-static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream)
-{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *codec_dai = rtd->codec_dai;
-
- /* disable the PLL */
- return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
-}
-
-static struct snd_soc_ops neo1973_gta02_voice_ops = {
- .hw_params = neo1973_gta02_voice_hw_params,
- .hw_free = neo1973_gta02_voice_hw_free,
-};
-
-#define LM4853_AMP 1
-#define LM4853_SPK 2
-
-static u8 lm4853_state;
-
-/* This has no effect, it exists only to maintain compatibility with
- * existing ALSA state files.
- */
-static int lm4853_set_state(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- int val = ucontrol->value.integer.value[0];
-
- if (val)
- lm4853_state |= LM4853_AMP;
- else
- lm4853_state &= ~LM4853_AMP;
-
- return 0;
-}
-
-static int lm4853_get_state(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP;
-
- return 0;
-}
-
-static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- int val = ucontrol->value.integer.value[0];
-
- if (val) {
- lm4853_state |= LM4853_SPK;
- gpio_set_value(GTA02_GPIO_HP_IN, 0);
- } else {
- lm4853_state &= ~LM4853_SPK;
- gpio_set_value(GTA02_GPIO_HP_IN, 1);
- }
-
- return 0;
-}
-
-static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1;
-
- return 0;
-}
-
-static int lm4853_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *k,
- int event)
-{
- gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event));
-
- return 0;
-}
-
-static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
- SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
- SND_SOC_DAPM_LINE("GSM Line Out", NULL),
- SND_SOC_DAPM_LINE("GSM Line In", NULL),
- SND_SOC_DAPM_MIC("Headset Mic", NULL),
- SND_SOC_DAPM_MIC("Handset Mic", NULL),
- SND_SOC_DAPM_SPK("Handset Spk", NULL),
-};
-
-
-/* example machine audio_mapnections */
-static const struct snd_soc_dapm_route audio_map[] = {
-
- /* Connections to the lm4853 amp */
- {"Stereo Out", NULL, "LOUT1"},
- {"Stereo Out", NULL, "ROUT1"},
-
- /* Connections to the GSM Module */
- {"GSM Line Out", NULL, "MONO1"},
- {"GSM Line Out", NULL, "MONO2"},
- {"RXP", NULL, "GSM Line In"},
- {"RXN", NULL, "GSM Line In"},
-
- /* Connections to Headset */
- {"MIC1", NULL, "Mic Bias"},
- {"Mic Bias", NULL, "Headset Mic"},
-
- /* Call Mic */
- {"MIC2", NULL, "Mic Bias"},
- {"MIC2N", NULL, "Mic Bias"},
- {"Mic Bias", NULL, "Handset Mic"},
-
- /* Call Speaker */
- {"Handset Spk", NULL, "LOUT2"},
- {"Handset Spk", NULL, "ROUT2"},
-
- /* Connect the ALC pins */
- {"ACIN", NULL, "ACOP"},
-};
-
-static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = {
- SOC_DAPM_PIN_SWITCH("Stereo Out"),
- SOC_DAPM_PIN_SWITCH("GSM Line Out"),
- SOC_DAPM_PIN_SWITCH("GSM Line In"),
- SOC_DAPM_PIN_SWITCH("Headset Mic"),
- SOC_DAPM_PIN_SWITCH("Handset Mic"),
- SOC_DAPM_PIN_SWITCH("Handset Spk"),
-
- /* This has no effect, it exists only to maintain compatibility with
- * existing ALSA state files.
- */
- SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0,
- lm4853_get_state,
- lm4853_set_state),
- SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0,
- lm4853_get_spk,
- lm4853_set_spk),
-};
-
-/*
- * This is an example machine initialisation for a wm8753 connected to a
- * neo1973 GTA02.
- */
-static int neo1973_gta02_wm8753_init(struct snd_soc_pcm_runtime *rtd)
-{
- struct snd_soc_codec *codec = rtd->codec;
- struct snd_soc_dapm_context *dapm = &codec->dapm;
- int err;
-
- /* set up NC codec pins */
- snd_soc_dapm_nc_pin(dapm, "OUT3");
- snd_soc_dapm_nc_pin(dapm, "OUT4");
- snd_soc_dapm_nc_pin(dapm, "LINE1");
- snd_soc_dapm_nc_pin(dapm, "LINE2");
-
- /* Add neo1973 gta02 specific widgets */
- snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets,
- ARRAY_SIZE(wm8753_dapm_widgets));
-
- /* add neo1973 gta02 specific controls */
- err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls,
- ARRAY_SIZE(wm8753_neo1973_gta02_controls));
-
- if (err < 0)
- return err;
-
- /* set up neo1973 gta02 specific audio path audio_map */
- snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
-
- /* set endpoints to default off mode */
- snd_soc_dapm_disable_pin(dapm, "Stereo Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Handset Mic");
- snd_soc_dapm_disable_pin(dapm, "Handset Spk");
-
- /* allow audio paths from the GSM modem to run during suspend */
- snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
- snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out");
- snd_soc_dapm_ignore_suspend(dapm, "GSM Line In");
- snd_soc_dapm_ignore_suspend(dapm, "Headset Mic");
- snd_soc_dapm_ignore_suspend(dapm, "Handset Mic");
- snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
-
- snd_soc_dapm_sync(dapm);
-
- return 0;
-}
-
-/*
- * BT Codec DAI
- */
-static struct snd_soc_dai_driver bt_dai = {
- .name = "bluetooth-dai",
- .playback = {
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
-};
-
-static struct snd_soc_dai_link neo1973_gta02_dai[] = {
-{ /* Hifi Playback - for similatious use with voice below */
- .name = "WM8753",
- .stream_name = "WM8753 HiFi",
- .cpu_dai_name = "s3c24xx-iis",
- .codec_dai_name = "wm8753-hifi",
- .init = neo1973_gta02_wm8753_init,
- .platform_name = "samsung-audio",
- .codec_name = "wm8753-codec.0-001a",
- .ops = &neo1973_gta02_hifi_ops,
-},
-{ /* Voice via BT */
- .name = "Bluetooth",
- .stream_name = "Voice",
- .cpu_dai_name = "bluetooth-dai",
- .codec_dai_name = "wm8753-voice",
- .ops = &neo1973_gta02_voice_ops,
- .codec_name = "wm8753-codec.0-001a",
- .platform_name = "samsung-audio",
-},
-};
-
-static struct snd_soc_card neo1973_gta02 = {
- .name = "neo1973-gta02",
- .dai_link = neo1973_gta02_dai,
- .num_links = ARRAY_SIZE(neo1973_gta02_dai),
-};
-
-static struct platform_device *neo1973_gta02_snd_device;
-
-static int __init neo1973_gta02_init(void)
-{
- int ret;
-
- if (!machine_is_neo1973_gta02()) {
- printk(KERN_INFO
- "Only GTA02 is supported by this ASoC driver\n");
- return -ENODEV;
- }
-
- neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1);
- if (!neo1973_gta02_snd_device)
- return -ENOMEM;
-
- /* register bluetooth DAI here */
- ret = snd_soc_register_dai(&neo1973_gta02_snd_device->dev, &bt_dai);
- if (ret)
- goto err_put_device;
-
- platform_set_drvdata(neo1973_gta02_snd_device, &neo1973_gta02);
- ret = platform_device_add(neo1973_gta02_snd_device);
-
- if (ret)
- goto err_unregister_dai;
-
- /* Initialise GPIOs used by amp */
- ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN");
- if (ret) {
- pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN);
- goto err_del_device;
- }
-
- ret = gpio_direction_output(GTA02_GPIO_HP_IN, 1);
- if (ret) {
- pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN);
- goto err_free_gpio_hp_in;
- }
-
- ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT");
- if (ret) {
- pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT);
- goto err_free_gpio_hp_in;
- }
-
- ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1);
- if (ret) {
- pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT);
- goto err_free_gpio_amp_shut;
- }
-
- return 0;
-
-err_free_gpio_amp_shut:
- gpio_free(GTA02_GPIO_AMP_SHUT);
-err_free_gpio_hp_in:
- gpio_free(GTA02_GPIO_HP_IN);
-err_del_device:
- platform_device_del(neo1973_gta02_snd_device);
-err_unregister_dai:
- snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev);
-err_put_device:
- platform_device_put(neo1973_gta02_snd_device);
- return ret;
-}
-module_init(neo1973_gta02_init);
-
-static void __exit neo1973_gta02_exit(void)
-{
- snd_soc_unregister_dai(&neo1973_gta02_snd_device->dev);
- platform_device_unregister(neo1973_gta02_snd_device);
- gpio_free(GTA02_GPIO_HP_IN);
- gpio_free(GTA02_GPIO_AMP_SHUT);
-}
-module_exit(neo1973_gta02_exit);
-
-/* Module information */
-MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org");
-MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02");
-MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/neo1973_wm8753.c b/sound/soc/samsung/neo1973_wm8753.c
index d20815d5ab2..78bfdb3f5d7 100644
--- a/sound/soc/samsung/neo1973_wm8753.c
+++ b/sound/soc/samsung/neo1973_wm8753.c
@@ -1,57 +1,32 @@
/*
- * neo1973_wm8753.c -- SoC audio for Neo1973
+ * neo1973_wm8753.c -- SoC audio for Openmoko Neo1973 and Freerunner devices
*
+ * Copyright 2007 Openmoko Inc
+ * Author: Graeme Gregory <graeme@openmoko.org>
* Copyright 2007 Wolfson Microelectronics PLC.
* Author: Graeme Gregory
* graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ * Copyright 2009 Wolfson Microelectronics
*
* 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/moduleparam.h>
-#include <linux/timer.h>
-#include <linux/interrupt.h>
#include <linux/platform_device.h>
-#include <linux/i2c.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
+#include <linux/gpio.h>
+
#include <sound/soc.h>
-#include <sound/tlv.h>
#include <asm/mach-types.h>
-#include <asm/hardware/scoop.h>
-#include <mach/regs-clock.h>
-#include <mach/regs-gpio.h>
-#include <mach/hardware.h>
-#include <linux/io.h>
-#include <mach/spi-gpio.h>
-
#include <plat/regs-iis.h>
+#include <mach/gta02.h>
#include "../codecs/wm8753.h"
-#include "lm4857.h"
-#include "dma.h"
#include "s3c24xx-i2s.h"
-/* define the scenarios */
-#define NEO_AUDIO_OFF 0
-#define NEO_GSM_CALL_AUDIO_HANDSET 1
-#define NEO_GSM_CALL_AUDIO_HEADSET 2
-#define NEO_GSM_CALL_AUDIO_BLUETOOTH 3
-#define NEO_STEREO_TO_SPEAKERS 4
-#define NEO_STEREO_TO_HEADPHONES 5
-#define NEO_CAPTURE_HANDSET 6
-#define NEO_CAPTURE_HEADSET 7
-#define NEO_CAPTURE_BLUETOOTH 8
-
-static struct snd_soc_card neo1973;
-static struct i2c_client *i2c;
-
static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@@ -62,8 +37,6 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream,
int ret = 0;
unsigned long iis_clkrate;
- pr_debug("Entered %s\n", __func__);
-
iis_clkrate = s3c24xx_i2s_get_clockrate();
switch (params_rate(params)) {
@@ -148,8 +121,6 @@ static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
- pr_debug("Entered %s\n", __func__);
-
/* disable the PLL */
return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0);
}
@@ -171,8 +142,6 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream,
int ret = 0;
unsigned long iis_clkrate;
- pr_debug("Entered %s\n", __func__);
-
iis_clkrate = s3c24xx_i2s_get_clockrate();
if (params_rate(params) != 8000)
@@ -214,8 +183,6 @@ static int neo1973_voice_hw_free(struct snd_pcm_substream *substream)
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
- pr_debug("Entered %s\n", __func__);
-
/* disable the PLL */
return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0);
}
@@ -225,335 +192,232 @@ static struct snd_soc_ops neo1973_voice_ops = {
.hw_free = neo1973_voice_hw_free,
};
-static int neo1973_scenario;
-
-static int neo1973_get_scenario(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- ucontrol->value.integer.value[0] = neo1973_scenario;
- return 0;
-}
-
-static int set_scenario_endpoints(struct snd_soc_codec *codec, int scenario)
-{
- struct snd_soc_dapm_context *dapm = &codec->dapm;
-
- pr_debug("Entered %s\n", __func__);
-
- switch (neo1973_scenario) {
- case NEO_AUDIO_OFF:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_GSM_CALL_AUDIO_HANDSET:
- snd_soc_dapm_enable_pin(dapm, "Audio Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_enable_pin(dapm, "Call Mic");
- break;
- case NEO_GSM_CALL_AUDIO_HEADSET:
- snd_soc_dapm_enable_pin(dapm, "Audio Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line In");
- snd_soc_dapm_enable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_GSM_CALL_AUDIO_BLUETOOTH:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_enable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_STEREO_TO_SPEAKERS:
- snd_soc_dapm_enable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_STEREO_TO_HEADPHONES:
- snd_soc_dapm_enable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_CAPTURE_HANDSET:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_enable_pin(dapm, "Call Mic");
- break;
- case NEO_CAPTURE_HEADSET:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_enable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- case NEO_CAPTURE_BLUETOOTH:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- break;
- default:
- snd_soc_dapm_disable_pin(dapm, "Audio Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
- snd_soc_dapm_disable_pin(dapm, "GSM Line In");
- snd_soc_dapm_disable_pin(dapm, "Headset Mic");
- snd_soc_dapm_disable_pin(dapm, "Call Mic");
- }
+/* Shared routes and controls */
- snd_soc_dapm_sync(dapm);
+static const struct snd_soc_dapm_widget neo1973_wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_LINE("GSM Line Out", NULL),
+ SND_SOC_DAPM_LINE("GSM Line In", NULL),
+ SND_SOC_DAPM_MIC("Headset Mic", NULL),
+ SND_SOC_DAPM_MIC("Handset Mic", NULL),
+};
- return 0;
-}
+static const struct snd_soc_dapm_route neo1973_wm8753_routes[] = {
+ /* Connections to the GSM Module */
+ {"GSM Line Out", NULL, "MONO1"},
+ {"GSM Line Out", NULL, "MONO2"},
+ {"RXP", NULL, "GSM Line In"},
+ {"RXN", NULL, "GSM Line In"},
-static int neo1973_set_scenario(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ /* Connections to Headset */
+ {"MIC1", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Headset Mic"},
- pr_debug("Entered %s\n", __func__);
+ /* Call Mic */
+ {"MIC2", NULL, "Mic Bias"},
+ {"MIC2N", NULL, "Mic Bias"},
+ {"Mic Bias", NULL, "Handset Mic"},
- if (neo1973_scenario == ucontrol->value.integer.value[0])
- return 0;
+ /* Connect the ALC pins */
+ {"ACIN", NULL, "ACOP"},
+};
- neo1973_scenario = ucontrol->value.integer.value[0];
- set_scenario_endpoints(codec, neo1973_scenario);
- return 1;
-}
+static const struct snd_kcontrol_new neo1973_wm8753_controls[] = {
+ SOC_DAPM_PIN_SWITCH("GSM Line Out"),
+ SOC_DAPM_PIN_SWITCH("GSM Line In"),
+ SOC_DAPM_PIN_SWITCH("Headset Mic"),
+ SOC_DAPM_PIN_SWITCH("Handset Mic"),
+};
-static u8 lm4857_regs[4] = {0x00, 0x40, 0x80, 0xC0};
+/* GTA02 specific routes and controlls */
-static void lm4857_write_regs(void)
-{
- pr_debug("Entered %s\n", __func__);
+#ifdef CONFIG_MACH_NEO1973_GTA02
- if (i2c_master_send(i2c, lm4857_regs, 4) != 4)
- printk(KERN_ERR "lm4857: i2c write failed\n");
-}
+static int gta02_speaker_enabled;
-static int lm4857_get_reg(struct snd_kcontrol *kcontrol,
+static int lm4853_set_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- int reg = mc->reg;
- int shift = mc->shift;
- int mask = mc->max;
+ gta02_speaker_enabled = ucontrol->value.integer.value[0];
- pr_debug("Entered %s\n", __func__);
+ gpio_set_value(GTA02_GPIO_HP_IN, !gta02_speaker_enabled);
- ucontrol->value.integer.value[0] = (lm4857_regs[reg] >> shift) & mask;
return 0;
}
-static int lm4857_set_reg(struct snd_kcontrol *kcontrol,
+static int lm4853_get_spk(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct soc_mixer_control *mc =
- (struct soc_mixer_control *)kcontrol->private_value;
- int reg = mc->reg;
- int shift = mc->shift;
- int mask = mc->max;
-
- if (((lm4857_regs[reg] >> shift) & mask) ==
- ucontrol->value.integer.value[0])
- return 0;
-
- lm4857_regs[reg] &= ~(mask << shift);
- lm4857_regs[reg] |= ucontrol->value.integer.value[0] << shift;
- lm4857_write_regs();
- return 1;
+ ucontrol->value.integer.value[0] = gta02_speaker_enabled;
+ return 0;
}
-static int lm4857_get_mode(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+static int lm4853_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
{
- u8 value = lm4857_regs[LM4857_CTRL] & 0x0F;
-
- pr_debug("Entered %s\n", __func__);
-
- if (value)
- value -= 5;
+ gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(event));
- ucontrol->value.integer.value[0] = value;
return 0;
}
-static int lm4857_set_mode(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- u8 value = ucontrol->value.integer.value[0];
-
- pr_debug("Entered %s\n", __func__);
-
- if (value)
- value += 5;
-
- if ((lm4857_regs[LM4857_CTRL] & 0x0F) == value)
- return 0;
-
- lm4857_regs[LM4857_CTRL] &= 0xF0;
- lm4857_regs[LM4857_CTRL] |= value;
- lm4857_write_regs();
- return 1;
-}
+static const struct snd_soc_dapm_route neo1973_gta02_routes[] = {
+ /* Connections to the amp */
+ {"Stereo Out", NULL, "LOUT1"},
+ {"Stereo Out", NULL, "ROUT1"},
-static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {
- SND_SOC_DAPM_LINE("Audio Out", NULL),
- SND_SOC_DAPM_LINE("GSM Line Out", NULL),
- SND_SOC_DAPM_LINE("GSM Line In", NULL),
- SND_SOC_DAPM_MIC("Headset Mic", NULL),
- SND_SOC_DAPM_MIC("Call Mic", NULL),
+ /* Call Speaker */
+ {"Handset Spk", NULL, "LOUT2"},
+ {"Handset Spk", NULL, "ROUT2"},
};
+static const struct snd_kcontrol_new neo1973_gta02_wm8753_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Handset Spk"),
+ SOC_DAPM_PIN_SWITCH("Stereo Out"),
-static const struct snd_soc_dapm_route dapm_routes[] = {
-
- /* Connections to the lm4857 amp */
- {"Audio Out", NULL, "LOUT1"},
- {"Audio Out", NULL, "ROUT1"},
-
- /* Connections to the GSM Module */
- {"GSM Line Out", NULL, "MONO1"},
- {"GSM Line Out", NULL, "MONO2"},
- {"RXP", NULL, "GSM Line In"},
- {"RXN", NULL, "GSM Line In"},
+ SOC_SINGLE_BOOL_EXT("Amp Spk Switch", 0,
+ lm4853_get_spk,
+ lm4853_set_spk),
+};
- /* Connections to Headset */
- {"MIC1", NULL, "Mic Bias"},
- {"Mic Bias", NULL, "Headset Mic"},
+static const struct snd_soc_dapm_widget neo1973_gta02_wm8753_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Handset Spk", NULL),
+ SND_SOC_DAPM_SPK("Stereo Out", lm4853_event),
+};
- /* Call Mic */
- {"MIC2", NULL, "Mic Bias"},
- {"MIC2N", NULL, "Mic Bias"},
- {"Mic Bias", NULL, "Call Mic"},
+static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
- /* Connect the ALC pins */
- {"ACIN", NULL, "ACOP"},
-};
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_gta02_wm8753_dapm_widgets,
+ ARRAY_SIZE(neo1973_gta02_wm8753_dapm_widgets));
+ if (ret)
+ return ret;
-static const char *lm4857_mode[] = {
- "Off",
- "Call Speaker",
- "Stereo Speakers",
- "Stereo Speakers + Headphones",
- "Headphones"
-};
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_gta02_routes,
+ ARRAY_SIZE(neo1973_gta02_routes));
+ if (ret)
+ return ret;
-static const struct soc_enum lm4857_mode_enum[] = {
- SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lm4857_mode), lm4857_mode),
-};
+ ret = snd_soc_add_controls(codec, neo1973_gta02_wm8753_controls,
+ ARRAY_SIZE(neo1973_gta02_wm8753_controls));
+ if (ret)
+ return ret;
-static const char *neo_scenarios[] = {
- "Off",
- "GSM Handset",
- "GSM Headset",
- "GSM Bluetooth",
- "Speakers",
- "Headphones",
- "Capture Handset",
- "Capture Headset",
- "Capture Bluetooth"
-};
+ snd_soc_dapm_disable_pin(dapm, "Stereo Out");
+ snd_soc_dapm_disable_pin(dapm, "Handset Spk");
+ snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
-static const struct soc_enum neo_scenario_enum[] = {
- SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(neo_scenarios), neo_scenarios),
-};
+ return 0;
+}
-static const DECLARE_TLV_DB_SCALE(stereo_tlv, -4050, 150, 0);
-static const DECLARE_TLV_DB_SCALE(mono_tlv, -3450, 150, 0);
-
-static const struct snd_kcontrol_new wm8753_neo1973_controls[] = {
- SOC_SINGLE_EXT_TLV("Amp Left Playback Volume", LM4857_LVOL, 0, 31, 0,
- lm4857_get_reg, lm4857_set_reg, stereo_tlv),
- SOC_SINGLE_EXT_TLV("Amp Right Playback Volume", LM4857_RVOL, 0, 31, 0,
- lm4857_get_reg, lm4857_set_reg, stereo_tlv),
- SOC_SINGLE_EXT_TLV("Amp Mono Playback Volume", LM4857_MVOL, 0, 31, 0,
- lm4857_get_reg, lm4857_set_reg, mono_tlv),
- SOC_ENUM_EXT("Amp Mode", lm4857_mode_enum[0],
- lm4857_get_mode, lm4857_set_mode),
- SOC_ENUM_EXT("Neo Mode", neo_scenario_enum[0],
- neo1973_get_scenario, neo1973_set_scenario),
- SOC_SINGLE_EXT("Amp Spk 3D Playback Switch", LM4857_LVOL, 5, 1, 0,
- lm4857_get_reg, lm4857_set_reg),
- SOC_SINGLE_EXT("Amp HP 3d Playback Switch", LM4857_RVOL, 5, 1, 0,
- lm4857_get_reg, lm4857_set_reg),
- SOC_SINGLE_EXT("Amp Fast Wakeup Playback Switch", LM4857_CTRL, 5, 1, 0,
- lm4857_get_reg, lm4857_set_reg),
- SOC_SINGLE_EXT("Amp Earpiece 6dB Playback Switch", LM4857_CTRL, 4, 1, 0,
- lm4857_get_reg, lm4857_set_reg),
-};
+#else
+static int neo1973_gta02_wm8753_init(struct snd_soc_code *codec) { return 0; }
+#endif
-/*
- * This is an example machine initialisation for a wm8753 connected to a
- * neo1973 II. It is missing logic to detect hp/mic insertions and logic
- * to re-route the audio in such an event.
- */
static int neo1973_wm8753_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_codec *codec = rtd->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
- int err;
-
- pr_debug("Entered %s\n", __func__);
+ int ret;
/* set up NC codec pins */
- snd_soc_dapm_nc_pin(dapm, "LOUT2");
- snd_soc_dapm_nc_pin(dapm, "ROUT2");
+ if (machine_is_neo1973_gta01()) {
+ snd_soc_dapm_nc_pin(dapm, "LOUT2");
+ snd_soc_dapm_nc_pin(dapm, "ROUT2");
+ }
snd_soc_dapm_nc_pin(dapm, "OUT3");
snd_soc_dapm_nc_pin(dapm, "OUT4");
snd_soc_dapm_nc_pin(dapm, "LINE1");
snd_soc_dapm_nc_pin(dapm, "LINE2");
/* Add neo1973 specific widgets */
- snd_soc_dapm_new_controls(dapm, wm8753_dapm_widgets,
- ARRAY_SIZE(wm8753_dapm_widgets));
-
- /* set endpoints to default mode */
- set_scenario_endpoints(codec, NEO_AUDIO_OFF);
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_wm8753_dapm_widgets,
+ ARRAY_SIZE(neo1973_wm8753_dapm_widgets));
+ if (ret)
+ return ret;
/* add neo1973 specific controls */
- err = snd_soc_add_controls(codec, wm8753_neo1973_controls,
- ARRAY_SIZE(8753_neo1973_controls));
- if (err < 0)
- return err;
+ ret = snd_soc_add_controls(codec, neo1973_wm8753_controls,
+ ARRAY_SIZE(neo1973_wm8753_controls));
+ if (ret)
+ return ret;
/* set up neo1973 specific audio routes */
- err = snd_soc_dapm_add_routes(dapm, dapm_routes,
- ARRAY_SIZE(dapm_routes));
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_wm8753_routes,
+ ARRAY_SIZE(neo1973_wm8753_routes));
+ if (ret)
+ return ret;
+
+ /* set endpoints to default off mode */
+ snd_soc_dapm_disable_pin(dapm, "GSM Line Out");
+ snd_soc_dapm_disable_pin(dapm, "GSM Line In");
+ snd_soc_dapm_disable_pin(dapm, "Headset Mic");
+ snd_soc_dapm_disable_pin(dapm, "Handset Mic");
+
+ /* allow audio paths from the GSM modem to run during suspend */
+ snd_soc_dapm_ignore_suspend(dapm, "GSM Line Out");
+ snd_soc_dapm_ignore_suspend(dapm, "GSM Line In");
+ snd_soc_dapm_ignore_suspend(dapm, "Headset Mic");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Mic");
+
+ if (machine_is_neo1973_gta02()) {
+ ret = neo1973_gta02_wm8753_init(codec);
+ if (ret)
+ return ret;
+ }
snd_soc_dapm_sync(dapm);
+
return 0;
}
-/*
- * BT Codec DAI
- */
-static struct snd_soc_dai bt_dai = {
- .name = "bluetooth-dai",
- .playback = {
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .capture = {
- .channels_min = 1,
- .channels_max = 1,
- .rates = SNDRV_PCM_RATE_8000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,},
+/* GTA01 specific controlls */
+
+#ifdef CONFIG_MACH_NEO1973_GTA01
+
+static const struct snd_soc_dapm_route neo1973_lm4857_routes[] = {
+ {"Amp IN", NULL, "ROUT1"},
+ {"Amp IN", NULL, "LOUT1"},
+
+ {"Handset Spk", NULL, "Amp EP"},
+ {"Stereo Out", NULL, "Amp LS"},
+ {"Headphone", NULL, "Amp HP"},
+};
+
+static const struct snd_soc_dapm_widget neo1973_lm4857_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Handset Spk", NULL),
+ SND_SOC_DAPM_SPK("Stereo Out", NULL),
+ SND_SOC_DAPM_HP("Headphone", NULL),
};
+static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm)
+{
+ int ret;
+
+ ret = snd_soc_dapm_new_controls(dapm, neo1973_lm4857_dapm_widgets,
+ ARRAY_SIZE(neo1973_lm4857_dapm_widgets));
+ if (ret)
+ return ret;
+
+ ret = snd_soc_dapm_add_routes(dapm, neo1973_lm4857_routes,
+ ARRAY_SIZE(neo1973_lm4857_routes));
+ if (ret)
+ return ret;
+
+ snd_soc_dapm_ignore_suspend(dapm, "Stereo Out");
+ snd_soc_dapm_ignore_suspend(dapm, "Handset Spk");
+ snd_soc_dapm_ignore_suspend(dapm, "Headphone");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+#else
+static int neo1973_lm4857_init(struct snd_soc_dapm_context *dapm) { return 0; };
+#endif
+
static struct snd_soc_dai_link neo1973_dai[] = {
{ /* Hifi Playback - for similatious use with voice below */
.name = "WM8753",
@@ -569,90 +433,49 @@ static struct snd_soc_dai_link neo1973_dai[] = {
.name = "Bluetooth",
.stream_name = "Voice",
.platform_name = "samsung-audio",
- .cpu_dai_name = "bluetooth-dai",
+ .cpu_dai_name = "dfbmcs320-pcm",
.codec_dai_name = "wm8753-voice",
.codec_name = "wm8753-codec.0-001a",
.ops = &neo1973_voice_ops,
},
};
-static struct snd_soc_card neo1973 = {
- .name = "neo1973",
- .dai_link = neo1973_dai,
- .num_links = ARRAY_SIZE(neo1973_dai),
+static struct snd_soc_aux_dev neo1973_aux_devs[] = {
+ {
+ .name = "dfbmcs320",
+ .codec_name = "dfbmcs320.0",
+ },
+ {
+ .name = "lm4857",
+ .codec_name = "lm4857.0-007c",
+ .init = neo1973_lm4857_init,
+ },
};
-static int lm4857_i2c_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- pr_debug("Entered %s\n", __func__);
-
- i2c = client;
-
- lm4857_write_regs();
- return 0;
-}
-
-static int lm4857_i2c_remove(struct i2c_client *client)
-{
- pr_debug("Entered %s\n", __func__);
-
- i2c = NULL;
-
- return 0;
-}
-
-static u8 lm4857_state;
-
-static int lm4857_suspend(struct i2c_client *dev, pm_message_t state)
-{
- pr_debug("Entered %s\n", __func__);
-
- dev_dbg(&dev->dev, "lm4857_suspend\n");
- lm4857_state = lm4857_regs[LM4857_CTRL] & 0xf;
- if (lm4857_state) {
- lm4857_regs[LM4857_CTRL] &= 0xf0;
- lm4857_write_regs();
- }
- return 0;
-}
-
-static int lm4857_resume(struct i2c_client *dev)
-{
- pr_debug("Entered %s\n", __func__);
-
- if (lm4857_state) {
- lm4857_regs[LM4857_CTRL] |= (lm4857_state & 0x0f);
- lm4857_write_regs();
- }
- return 0;
-}
-
-static void lm4857_shutdown(struct i2c_client *dev)
-{
- pr_debug("Entered %s\n", __func__);
-
- dev_dbg(&dev->dev, "lm4857_shutdown\n");
- lm4857_regs[LM4857_CTRL] &= 0xf0;
- lm4857_write_regs();
-}
+static struct snd_soc_codec_conf neo1973_codec_conf[] = {
+ {
+ .dev_name = "lm4857.0-007c",
+ .name_prefix = "Amp",
+ },
+};
-static const struct i2c_device_id lm4857_i2c_id[] = {
- { "neo1973_lm4857", 0 },
- { }
+#ifdef CONFIG_MACH_NEO1973_GTA02
+static const struct gpio neo1973_gta02_gpios[] = {
+ { GTA02_GPIO_HP_IN, GPIOF_OUT_INIT_HIGH, "GTA02_HP_IN" },
+ { GTA02_GPIO_AMP_SHUT, GPIOF_OUT_INIT_HIGH, "GTA02_AMP_SHUT" },
};
+#else
+static const struct gpio neo1973_gta02_gpios[] = {};
+#endif
-static struct i2c_driver lm4857_i2c_driver = {
- .driver = {
- .name = "LM4857 I2C Amp",
- .owner = THIS_MODULE,
- },
- .suspend = lm4857_suspend,
- .resume = lm4857_resume,
- .shutdown = lm4857_shutdown,
- .probe = lm4857_i2c_probe,
- .remove = lm4857_i2c_remove,
- .id_table = lm4857_i2c_id,
+static struct snd_soc_card neo1973 = {
+ .name = "neo1973",
+ .dai_link = neo1973_dai,
+ .num_links = ARRAY_SIZE(neo1973_dai),
+ .aux_dev = neo1973_aux_devs,
+ .num_aux_devs = ARRAY_SIZE(neo1973_aux_devs),
+ .codec_conf = neo1973_codec_conf,
+ .num_configs = ARRAY_SIZE(neo1973_codec_conf),
};
static struct platform_device *neo1973_snd_device;
@@ -661,46 +484,56 @@ static int __init neo1973_init(void)
{
int ret;
- pr_debug("Entered %s\n", __func__);
-
- if (!machine_is_neo1973_gta01()) {
- printk(KERN_INFO
- "Only GTA01 hardware supported by ASoC driver\n");
+ if (!machine_is_neo1973_gta01() && !machine_is_neo1973_gta02())
return -ENODEV;
+
+ if (machine_is_neo1973_gta02()) {
+ neo1973.name = "neo1973gta02";
+ neo1973.num_aux_devs = 1;
+
+ ret = gpio_request_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ if (ret)
+ return ret;
}
neo1973_snd_device = platform_device_alloc("soc-audio", -1);
- if (!neo1973_snd_device)
- return -ENOMEM;
+ if (!neo1973_snd_device) {
+ ret = -ENOMEM;
+ goto err_gpio_free;
+ }
platform_set_drvdata(neo1973_snd_device, &neo1973);
ret = platform_device_add(neo1973_snd_device);
- if (ret) {
- platform_device_put(neo1973_snd_device);
- return ret;
- }
-
- ret = i2c_add_driver(&lm4857_i2c_driver);
+ if (ret)
+ goto err_put_device;
- if (ret != 0)
- platform_device_unregister(neo1973_snd_device);
+ return 0;
+err_put_device:
+ platform_device_put(neo1973_snd_device);
+err_gpio_free:
+ if (machine_is_neo1973_gta02()) {
+ gpio_free_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ }
return ret;
}
+module_init(neo1973_init);
static void __exit neo1973_exit(void)
{
- pr_debug("Entered %s\n", __func__);
-
- i2c_del_driver(&lm4857_i2c_driver);
platform_device_unregister(neo1973_snd_device);
-}
-module_init(neo1973_init);
+ if (machine_is_neo1973_gta02()) {
+ gpio_free_array(neo1973_gta02_gpios,
+ ARRAY_SIZE(neo1973_gta02_gpios));
+ }
+}
module_exit(neo1973_exit);
/* Module information */
MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org, www.openmoko.org");
-MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973");
+MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 and Frerunner");
MODULE_LICENSE("GPL");
diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c
index 48d0b750406..38aac7d57a5 100644
--- a/sound/soc/samsung/pcm.c
+++ b/sound/soc/samsung/pcm.c
@@ -11,20 +11,11 @@
* published by the Free Software Foundation.
*/
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/device.h>
-#include <linux/delay.h>
#include <linux/clk.h>
-#include <linux/kernel.h>
-#include <linux/gpio.h>
#include <linux/io.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include <sound/initval.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <plat/audio.h>
#include <plat/dma.h>
@@ -32,6 +23,113 @@
#include "dma.h"
#include "pcm.h"
+/*Register Offsets */
+#define S3C_PCM_CTL 0x00
+#define S3C_PCM_CLKCTL 0x04
+#define S3C_PCM_TXFIFO 0x08
+#define S3C_PCM_RXFIFO 0x0C
+#define S3C_PCM_IRQCTL 0x10
+#define S3C_PCM_IRQSTAT 0x14
+#define S3C_PCM_FIFOSTAT 0x18
+#define S3C_PCM_CLRINT 0x20
+
+/* PCM_CTL Bit-Fields */
+#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13
+#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f
+#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7
+#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6)
+#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5)
+#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4)
+#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3)
+#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2)
+#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1)
+#define S3C_PCM_CTL_ENABLE (0x1 << 0)
+
+/* PCM_CLKCTL Bit-Fields */
+#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19)
+#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18)
+#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff
+#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9
+#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0
+
+/* PCM_TXFIFO Bit-Fields */
+#define S3C_PCM_TXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_RXFIFO Bit-Fields */
+#define S3C_PCM_RXFIFO_DVALID (0x1 << 16)
+#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0)
+
+/* PCM_IRQCTL Bit-Fields */
+#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14)
+#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12)
+#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11)
+#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10)
+#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9)
+#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8)
+#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7)
+#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6)
+#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5)
+#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4)
+#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3)
+#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2)
+#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1)
+#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0)
+
+/* PCM_IRQSTAT Bit-Fields */
+#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13)
+#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12)
+#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11)
+#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10)
+#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9)
+#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8)
+#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7)
+#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6)
+#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5)
+#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4)
+#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3)
+#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2)
+#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1)
+#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0)
+
+/* PCM_FIFOSTAT Bit-Fields */
+#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14)
+#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12)
+#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11)
+#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10)
+#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4)
+#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2)
+#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1)
+#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0)
+
+/**
+ * struct s3c_pcm_info - S3C PCM Controller information
+ * @dev: The parent device passed to use from the probe.
+ * @regs: The pointer to the device register block.
+ * @dma_playback: DMA information for playback channel.
+ * @dma_capture: DMA information for capture channel.
+ */
+struct s3c_pcm_info {
+ spinlock_t lock;
+ struct device *dev;
+ void __iomem *regs;
+
+ unsigned int sclk_per_fs;
+
+ /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
+ unsigned int idleclk;
+
+ struct clk *pclk;
+ struct clk *cclk;
+
+ struct s3c_dma_params *dma_playback;
+ struct s3c_dma_params *dma_capture;
+};
+
static struct s3c2410_dma_client s3c_pcm_dma_client_out = {
.name = "PCM Stereo out"
};
diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h
index 03393dcf852..726baf81461 100644
--- a/sound/soc/samsung/pcm.h
+++ b/sound/soc/samsung/pcm.h
@@ -9,116 +9,9 @@
#ifndef __S3C_PCM_H
#define __S3C_PCM_H __FILE__
-/*Register Offsets */
-#define S3C_PCM_CTL (0x00)
-#define S3C_PCM_CLKCTL (0x04)
-#define S3C_PCM_TXFIFO (0x08)
-#define S3C_PCM_RXFIFO (0x0C)
-#define S3C_PCM_IRQCTL (0x10)
-#define S3C_PCM_IRQSTAT (0x14)
-#define S3C_PCM_FIFOSTAT (0x18)
-#define S3C_PCM_CLRINT (0x20)
-
-/* PCM_CTL Bit-Fields */
-#define S3C_PCM_CTL_TXDIPSTICK_MASK (0x3f)
-#define S3C_PCM_CTL_TXDIPSTICK_SHIFT (13)
-#define S3C_PCM_CTL_RXDIPSTICK_MASK (0x3f)
-#define S3C_PCM_CTL_RXDIPSTICK_SHIFT (7)
-#define S3C_PCM_CTL_TXDMA_EN (0x1<<6)
-#define S3C_PCM_CTL_RXDMA_EN (0x1<<5)
-#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1<<4)
-#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1<<3)
-#define S3C_PCM_CTL_TXFIFO_EN (0x1<<2)
-#define S3C_PCM_CTL_RXFIFO_EN (0x1<<1)
-#define S3C_PCM_CTL_ENABLE (0x1<<0)
-
-/* PCM_CLKCTL Bit-Fields */
-#define S3C_PCM_CLKCTL_SERCLK_EN (0x1<<19)
-#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1<<18)
-#define S3C_PCM_CLKCTL_SCLKDIV_MASK (0x1ff)
-#define S3C_PCM_CLKCTL_SYNCDIV_MASK (0x1ff)
-#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT (9)
-#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT (0)
-
-/* PCM_TXFIFO Bit-Fields */
-#define S3C_PCM_TXFIFO_DVALID (0x1<<16)
-#define S3C_PCM_TXFIFO_DATA_MSK (0xffff<<0)
-
-/* PCM_RXFIFO Bit-Fields */
-#define S3C_PCM_RXFIFO_DVALID (0x1<<16)
-#define S3C_PCM_RXFIFO_DATA_MSK (0xffff<<0)
-
-/* PCM_IRQCTL Bit-Fields */
-#define S3C_PCM_IRQCTL_IRQEN (0x1<<14)
-#define S3C_PCM_IRQCTL_WRDEN (0x1<<12)
-#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1<<11)
-#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1<<10)
-#define S3C_PCM_IRQCTL_TXFULLEN (0x1<<9)
-#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1<<8)
-#define S3C_PCM_IRQCTL_TXSTARVEN (0x1<<7)
-#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1<<6)
-#define S3C_PCM_IRQCTL_RXEMPTEN (0x1<<5)
-#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1<<4)
-#define S3C_PCM_IRQCTL_RXFULLEN (0x1<<3)
-#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1<<2)
-#define S3C_PCM_IRQCTL_RXSTARVEN (0x1<<1)
-#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1<<0)
-
-/* PCM_IRQSTAT Bit-Fields */
-#define S3C_PCM_IRQSTAT_IRQPND (0x1<<13)
-#define S3C_PCM_IRQSTAT_WRD_XFER (0x1<<12)
-#define S3C_PCM_IRQSTAT_TXEMPTY (0x1<<11)
-#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1<<10)
-#define S3C_PCM_IRQSTAT_TXFULL (0x1<<9)
-#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1<<8)
-#define S3C_PCM_IRQSTAT_TXSTARV (0x1<<7)
-#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1<<6)
-#define S3C_PCM_IRQSTAT_RXEMPT (0x1<<5)
-#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1<<4)
-#define S3C_PCM_IRQSTAT_RXFULL (0x1<<3)
-#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1<<2)
-#define S3C_PCM_IRQSTAT_RXSTARV (0x1<<1)
-#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1<<0)
-
-/* PCM_FIFOSTAT Bit-Fields */
-#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f<<14)
-#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1<<13)
-#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1<<12)
-#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1<<11)
-#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1<<10)
-#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f<<4)
-#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1<<3)
-#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1<<2)
-#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1<<1)
-#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1<<0)
-
#define S3C_PCM_CLKSRC_PCLK 0
#define S3C_PCM_CLKSRC_MUX 1
#define S3C_PCM_SCLK_PER_FS 0
-/**
- * struct s3c_pcm_info - S3C PCM Controller information
- * @dev: The parent device passed to use from the probe.
- * @regs: The pointer to the device register block.
- * @dma_playback: DMA information for playback channel.
- * @dma_capture: DMA information for capture channel.
- */
-struct s3c_pcm_info {
- spinlock_t lock;
- struct device *dev;
- void __iomem *regs;
-
- unsigned int sclk_per_fs;
-
- /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */
- unsigned int idleclk;
-
- struct clk *pclk;
- struct clk *cclk;
-
- struct s3c_dma_params *dma_playback;
- struct s3c_dma_params *dma_capture;
-};
-
#endif /* __S3C_PCM_H */
diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c
index f40027445dd..1e574a5d440 100644
--- a/sound/soc/samsung/rx1950_uda1380.c
+++ b/sound/soc/samsung/rx1950_uda1380.c
@@ -17,26 +17,15 @@
*
*/
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/platform_device.h>
-#include <linux/i2c.h>
#include <linux/gpio.h>
-#include <linux/clk.h>
#include <sound/soc.h>
-#include <sound/uda1380.h>
#include <sound/jack.h>
#include <plat/regs-iis.h>
-
-#include <mach/regs-clock.h>
-
#include <asm/mach-types.h>
-#include "dma.h"
#include "s3c24xx-i2s.h"
-#include "../codecs/uda1380.h"
static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd);
static int rx1950_startup(struct snd_pcm_substream *substream);
diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c
index 094f36e41e8..52074a2b069 100644
--- a/sound/soc/samsung/s3c-i2s-v2.c
+++ b/sound/soc/samsung/s3c-i2s-v2.c
@@ -20,9 +20,8 @@
#include <linux/clk.h>
#include <linux/io.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <mach/dma.h>
diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c
index 7ea83786712..841ab14c110 100644
--- a/sound/soc/samsung/s3c2412-i2s.c
+++ b/sound/soc/samsung/s3c2412-i2s.c
@@ -16,21 +16,13 @@
* option) any later version.
*/
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/clk.h>
-#include <linux/kernel.h>
#include <linux/io.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include <sound/initval.h>
#include <sound/soc.h>
-#include <mach/hardware.h>
+#include <sound/pcm_params.h>
#include <mach/regs-gpio.h>
#include <mach/dma.h>
@@ -39,8 +31,6 @@
#include "regs-i2s-v2.h"
#include "s3c2412-i2s.h"
-#define S3C2412_I2S_DEBUG 0
-
static struct s3c2410_dma_client s3c2412_dma_client_out = {
.name = "I2S PCM Stereo out"
};
diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c
index 13e41ed8e22..63d8849d80b 100644
--- a/sound/soc/samsung/s3c24xx-i2s.c
+++ b/sound/soc/samsung/s3c24xx-i2s.c
@@ -14,28 +14,16 @@
* option) any later version.
*/
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
-#include <linux/jiffies.h>
#include <linux/io.h>
#include <linux/gpio.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include <sound/initval.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
-#include <mach/hardware.h>
#include <mach/regs-gpio.h>
-#include <mach/regs-clock.h>
-
-#include <asm/dma.h>
#include <mach/dma.h>
-
#include <plat/regs-iis.h>
#include "dma.h"
diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c
index a434032d183..349566f0686 100644
--- a/sound/soc/samsung/s3c24xx_simtec.c
+++ b/sound/soc/samsung/s3c24xx_simtec.c
@@ -7,20 +7,13 @@
* published by the Free Software Foundation.
*/
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/clk.h>
-#include <linux/i2c.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
#include <plat/audio-simtec.h>
-#include "dma.h"
#include "s3c24xx-i2s.h"
#include "s3c24xx_simtec.h"
diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c
index 08fcaaa6690..ce6aef60417 100644
--- a/sound/soc/samsung/s3c24xx_simtec_hermes.c
+++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c
@@ -7,18 +7,8 @@
* published by the Free Software Foundation.
*/
-#include <linux/module.h>
-#include <linux/clk.h>
-#include <linux/platform_device.h>
-
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
-#include <plat/audio-simtec.h>
-
-#include "dma.h"
-#include "s3c24xx-i2s.h"
#include "s3c24xx_simtec.h"
static const struct snd_soc_dapm_widget dapm_widgets[] = {
diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
index 116e3e67016..a7ef7db5468 100644
--- a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
+++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c
@@ -7,22 +7,10 @@
* published by the Free Software Foundation.
*/
-#include <linux/module.h>
-#include <linux/clk.h>
-#include <linux/platform_device.h>
-
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
-#include <plat/audio-simtec.h>
-
-#include "dma.h"
-#include "s3c24xx-i2s.h"
#include "s3c24xx_simtec.h"
-#include "../codecs/tlv320aic23.h"
-
/* supported machines:
*
* Machine Connections AMP
diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c
index 2c09e93dd56..3cb70075107 100644
--- a/sound/soc/samsung/s3c24xx_uda134x.c
+++ b/sound/soc/samsung/s3c24xx_uda134x.c
@@ -11,22 +11,15 @@
* published by the Free Software Foundation.
*/
-#include <linux/module.h>
#include <linux/clk.h>
-#include <linux/mutex.h>
#include <linux/gpio.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
+
#include <sound/soc.h>
#include <sound/s3c24xx_uda134x.h>
-#include <sound/uda134x.h>
#include <plat/regs-iis.h>
-#include "dma.h"
#include "s3c24xx-i2s.h"
-#include "../codecs/uda134x.h"
-
/* #define ENFORCE_RATES 1 */
/*
diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c
index 61e2b5219d4..0a2c4f22303 100644
--- a/sound/soc/samsung/smartq_wm8987.c
+++ b/sound/soc/samsung/smartq_wm8987.c
@@ -13,20 +13,14 @@
*
*/
-#include <linux/module.h>
-#include <linux/platform_device.h>
#include <linux/gpio.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <asm/mach-types.h>
-#include "dma.h"
#include "i2s.h"
-
#include "../codecs/wm8750.h"
/*
diff --git a/sound/soc/samsung/smdk2443_wm9710.c b/sound/soc/samsung/smdk2443_wm9710.c
index 3be7e7e92d6..3a0dbfc793f 100644
--- a/sound/soc/samsung/smdk2443_wm9710.c
+++ b/sound/soc/samsung/smdk2443_wm9710.c
@@ -12,15 +12,8 @@
*
*/
-#include <linux/module.h>
-#include <linux/device.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
#include <sound/soc.h>
-#include "dma.h"
-#include "ac97.h"
-
static struct snd_soc_card smdk2443;
static struct snd_soc_dai_link smdk2443_dai[] = {
diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c
index b5c3fad01bb..e8ac961c6ba 100644
--- a/sound/soc/samsung/smdk_spdif.c
+++ b/sound/soc/samsung/smdk_spdif.c
@@ -10,15 +10,10 @@
*
*/
-#include <linux/module.h>
-#include <linux/device.h>
#include <linux/clk.h>
-#include <plat/devs.h>
-
#include <sound/soc.h>
-#include "dma.h"
#include "spdif.h"
/* Audio clock settings are belonged to board specific part. Every
diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c
index b2cff1a44ae..8aacf23d6f3 100644
--- a/sound/soc/samsung/smdk_wm8580.c
+++ b/sound/soc/samsung/smdk_wm8580.c
@@ -10,17 +10,12 @@
* option) any later version.
*/
-#include <linux/platform_device.h>
-#include <linux/clk.h>
-#include <sound/core.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <asm/mach-types.h>
#include "../codecs/wm8580.h"
-#include "dma.h"
#include "i2s.h"
/*
diff --git a/sound/soc/samsung/smdk_wm9713.c b/sound/soc/samsung/smdk_wm9713.c
index ae5fed6f772..fffe3c1dd1b 100644
--- a/sound/soc/samsung/smdk_wm9713.c
+++ b/sound/soc/samsung/smdk_wm9713.c
@@ -11,13 +11,8 @@
*
*/
-#include <linux/module.h>
-#include <linux/device.h>
#include <sound/soc.h>
-#include "dma.h"
-#include "ac97.h"
-
static struct snd_soc_card smdk;
/*
diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c
index f0816404ea3..28c491dacf7 100644
--- a/sound/soc/samsung/spdif.c
+++ b/sound/soc/samsung/spdif.c
@@ -13,9 +13,8 @@
#include <linux/clk.h>
#include <linux/io.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/pcm_params.h>
#include <plat/audio.h>
#include <mach/dma.h>
diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c
index a14820ac966..d6f4703b3c0 100644
--- a/sound/soc/sh/fsi-ak4642.c
+++ b/sound/soc/sh/fsi-ak4642.c
@@ -18,18 +18,26 @@ struct fsi_ak4642_data {
const char *cpu_dai;
const char *codec;
const char *platform;
+ int id;
};
static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_soc_dai *dai = rtd->codec_dai;
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
int ret;
- ret = snd_soc_dai_set_fmt(dai, SND_SOC_DAIFMT_CBM_CFM);
+ ret = snd_soc_dai_set_fmt(codec, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBM_CFM);
if (ret < 0)
return ret;
- ret = snd_soc_dai_set_sysclk(dai, 0, 11289600, 0);
+ ret = snd_soc_dai_set_sysclk(codec, 0, 11289600, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_LEFT_J |
+ SND_SOC_DAIFMT_CBS_CFS);
return ret;
}
@@ -60,7 +68,7 @@ static int fsi_ak4642_probe(struct platform_device *pdev)
pdata = (struct fsi_ak4642_data *)id_entry->driver_data;
- fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A);
+ fsi_snd_device = platform_device_alloc("soc-audio", pdata->id);
if (!fsi_snd_device)
goto out;
@@ -93,6 +101,7 @@ static struct fsi_ak4642_data fsi_a_ak4642 = {
.cpu_dai = "fsia-dai",
.codec = "ak4642-codec.0-0012",
.platform = "sh_fsi.0",
+ .id = FSI_PORT_A,
};
static struct fsi_ak4642_data fsi_b_ak4642 = {
@@ -101,6 +110,7 @@ static struct fsi_ak4642_data fsi_b_ak4642 = {
.cpu_dai = "fsib-dai",
.codec = "ak4642-codec.0-0012",
.platform = "sh_fsi.0",
+ .id = FSI_PORT_B,
};
static struct fsi_ak4642_data fsi_a_ak4643 = {
@@ -109,6 +119,7 @@ static struct fsi_ak4642_data fsi_a_ak4643 = {
.cpu_dai = "fsia-dai",
.codec = "ak4642-codec.0-0013",
.platform = "sh_fsi.0",
+ .id = FSI_PORT_A,
};
static struct fsi_ak4642_data fsi_b_ak4643 = {
@@ -117,6 +128,7 @@ static struct fsi_ak4642_data fsi_b_ak4643 = {
.cpu_dai = "fsib-dai",
.codec = "ak4642-codec.0-0013",
.platform = "sh_fsi.0",
+ .id = FSI_PORT_B,
};
static struct fsi_ak4642_data fsi2_a_ak4642 = {
@@ -125,6 +137,7 @@ static struct fsi_ak4642_data fsi2_a_ak4642 = {
.cpu_dai = "fsia-dai",
.codec = "ak4642-codec.0-0012",
.platform = "sh_fsi2",
+ .id = FSI_PORT_A,
};
static struct fsi_ak4642_data fsi2_b_ak4642 = {
@@ -133,6 +146,7 @@ static struct fsi_ak4642_data fsi2_b_ak4642 = {
.cpu_dai = "fsib-dai",
.codec = "ak4642-codec.0-0012",
.platform = "sh_fsi2",
+ .id = FSI_PORT_B,
};
static struct fsi_ak4642_data fsi2_a_ak4643 = {
@@ -141,6 +155,7 @@ static struct fsi_ak4642_data fsi2_a_ak4643 = {
.cpu_dai = "fsia-dai",
.codec = "ak4642-codec.0-0013",
.platform = "sh_fsi2",
+ .id = FSI_PORT_A,
};
static struct fsi_ak4642_data fsi2_b_ak4643 = {
@@ -149,6 +164,7 @@ static struct fsi_ak4642_data fsi2_b_ak4643 = {
.cpu_dai = "fsib-dai",
.codec = "ak4642-codec.0-0013",
.platform = "sh_fsi2",
+ .id = FSI_PORT_B,
};
static struct platform_device_id fsi_id_table[] = {
diff --git a/sound/soc/sh/fsi-da7210.c b/sound/soc/sh/fsi-da7210.c
index e8df9da92f7..dbafd7ac559 100644
--- a/sound/soc/sh/fsi-da7210.c
+++ b/sound/soc/sh/fsi-da7210.c
@@ -15,11 +15,20 @@
static int fsi_da7210_init(struct snd_soc_pcm_runtime *rtd)
{
- struct snd_soc_dai *dai = rtd->codec_dai;
+ struct snd_soc_dai *codec = rtd->codec_dai;
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ int ret;
- return snd_soc_dai_set_fmt(dai,
+ ret = snd_soc_dai_set_fmt(codec,
SND_SOC_DAIFMT_I2S |
SND_SOC_DAIFMT_CBM_CFM);
+ if (ret < 0)
+ return ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_CBS_CFS);
+
+ return ret;
}
static struct snd_soc_dai_link fsi_da7210_dai = {
diff --git a/sound/soc/sh/fsi-hdmi.c b/sound/soc/sh/fsi-hdmi.c
index a52dd8ec71d..9719985eb82 100644
--- a/sound/soc/sh/fsi-hdmi.c
+++ b/sound/soc/sh/fsi-hdmi.c
@@ -12,31 +12,59 @@
#include <linux/platform_device.h>
#include <sound/sh_fsi.h>
+struct fsi_hdmi_data {
+ const char *cpu_dai;
+ const char *card;
+ int id;
+};
+
+static int fsi_hdmi_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *cpu = rtd->cpu_dai;
+ int ret;
+
+ ret = snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBM_CFM);
+
+ return ret;
+}
+
static struct snd_soc_dai_link fsi_dai_link = {
.name = "HDMI",
.stream_name = "HDMI",
- .cpu_dai_name = "fsib-dai", /* fsi B */
.codec_dai_name = "sh_mobile_hdmi-hifi",
.platform_name = "sh_fsi2",
.codec_name = "sh-mobile-hdmi",
+ .init = fsi_hdmi_dai_init,
};
static struct snd_soc_card fsi_soc_card = {
- .name = "FSI (SH MOBILE HDMI)",
.dai_link = &fsi_dai_link,
.num_links = 1,
};
static struct platform_device *fsi_snd_device;
-static int __init fsi_hdmi_init(void)
+static int fsi_hdmi_probe(struct platform_device *pdev)
{
int ret = -ENOMEM;
+ const struct platform_device_id *id_entry;
+ struct fsi_hdmi_data *pdata;
+
+ id_entry = pdev->id_entry;
+ if (!id_entry) {
+ dev_err(&pdev->dev, "unknown fsi hdmi\n");
+ return -ENODEV;
+ }
- fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_B);
+ pdata = (struct fsi_hdmi_data *)id_entry->driver_data;
+
+ fsi_snd_device = platform_device_alloc("soc-audio", pdata->id);
if (!fsi_snd_device)
goto out;
+ fsi_dai_link.cpu_dai_name = pdata->cpu_dai;
+ fsi_soc_card.name = pdata->card;
+
platform_set_drvdata(fsi_snd_device, &fsi_soc_card);
ret = platform_device_add(fsi_snd_device);
@@ -47,9 +75,48 @@ out:
return ret;
}
-static void __exit fsi_hdmi_exit(void)
+static int fsi_hdmi_remove(struct platform_device *pdev)
{
platform_device_unregister(fsi_snd_device);
+ return 0;
+}
+
+static struct fsi_hdmi_data fsi2_a_hdmi = {
+ .cpu_dai = "fsia-dai",
+ .card = "FSI2A (SH MOBILE HDMI)",
+ .id = FSI_PORT_A,
+};
+
+static struct fsi_hdmi_data fsi2_b_hdmi = {
+ .cpu_dai = "fsib-dai",
+ .card = "FSI2B (SH MOBILE HDMI)",
+ .id = FSI_PORT_B,
+};
+
+static struct platform_device_id fsi_id_table[] = {
+ /* FSI 2 */
+ { "sh_fsi2_a_hdmi", (kernel_ulong_t)&fsi2_a_hdmi },
+ { "sh_fsi2_b_hdmi", (kernel_ulong_t)&fsi2_b_hdmi },
+ {},
+};
+
+static struct platform_driver fsi_hdmi = {
+ .driver = {
+ .name = "fsi-hdmi-audio",
+ },
+ .probe = fsi_hdmi_probe,
+ .remove = fsi_hdmi_remove,
+ .id_table = fsi_id_table,
+};
+
+static int __init fsi_hdmi_init(void)
+{
+ return platform_driver_register(&fsi_hdmi);
+}
+
+static void __exit fsi_hdmi_exit(void)
+{
+ platform_driver_unregister(&fsi_hdmi);
}
module_init(fsi_hdmi_init);
diff --git a/sound/soc/sh/fsi.c b/sound/soc/sh/fsi.c
index 2b06402801e..0c9997e2d8c 100644
--- a/sound/soc/sh/fsi.c
+++ b/sound/soc/sh/fsi.c
@@ -78,6 +78,8 @@
/* CKG1 */
#define ACKMD_MASK 0x00007000
#define BPFMD_MASK 0x00000700
+#define DIMD (1 << 4)
+#define DOMD (1 << 0)
/* A/B MST_CTLR */
#define BP (1 << 4) /* Fix the signal of Biphase output */
@@ -111,6 +113,8 @@
#define FSI_FMTS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE)
+typedef int (*set_rate_func)(struct device *dev, int is_porta, int rate, int enable);
+
/*
* FSI driver use below type name for variable
*
@@ -128,7 +132,6 @@ struct fsi_stream {
struct snd_pcm_substream *substream;
int fifo_max_num;
- int chan_num;
int buff_offset;
int buff_len;
@@ -143,6 +146,7 @@ struct fsi_priv {
void __iomem *base;
struct fsi_master *master;
+ int chan_num;
struct fsi_stream playback;
struct fsi_stream capture;
@@ -252,9 +256,8 @@ static struct snd_soc_dai *fsi_get_dai(struct snd_pcm_substream *substream)
return rtd->cpu_dai;
}
-static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
+static struct fsi_priv *fsi_get_priv_frm_dai(struct snd_soc_dai *dai)
{
- struct snd_soc_dai *dai = fsi_get_dai(substream);
struct fsi_master *master = snd_soc_dai_get_drvdata(dai);
if (dai->id == 0)
@@ -263,11 +266,27 @@ static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
return &master->fsib;
}
+static struct fsi_priv *fsi_get_priv(struct snd_pcm_substream *substream)
+{
+ return fsi_get_priv_frm_dai(fsi_get_dai(substream));
+}
+
+static set_rate_func fsi_get_info_set_rate(struct fsi_master *master)
+{
+ if (!master->info)
+ return NULL;
+
+ return master->info->set_rate;
+}
+
static u32 fsi_get_info_flags(struct fsi_priv *fsi)
{
int is_porta = fsi_is_port_a(fsi);
struct fsi_master *master = fsi_get_master(fsi);
+ if (!master->info)
+ return 0;
+
return is_porta ? master->info->porta_flags :
master->info->portb_flags;
}
@@ -288,21 +307,6 @@ static inline struct fsi_stream *fsi_get_stream(struct fsi_priv *fsi,
return is_play ? &fsi->playback : &fsi->capture;
}
-static int fsi_is_master_mode(struct fsi_priv *fsi, int is_play)
-{
- u32 mode;
- u32 flags = fsi_get_info_flags(fsi);
-
- mode = is_play ? SH_FSI_OUT_SLAVE_MODE : SH_FSI_IN_SLAVE_MODE;
-
- /* return
- * 1 : master mode
- * 0 : slave mode
- */
-
- return (mode & flags) != mode;
-}
-
static u32 fsi_get_port_shift(struct fsi_priv *fsi, int is_play)
{
int is_porta = fsi_is_port_a(fsi);
@@ -357,7 +361,6 @@ static void fsi_stream_pop(struct fsi_priv *fsi, int is_play)
static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play)
{
u32 status;
- struct fsi_stream *io = fsi_get_stream(fsi, is_play);
int data_num;
status = is_play ?
@@ -365,7 +368,7 @@ static int fsi_get_fifo_data_num(struct fsi_priv *fsi, int is_play)
fsi_reg_read(fsi, DIFF_ST);
data_num = 0x1ff & (status >> 8);
- data_num *= io->chan_num;
+ data_num *= fsi->chan_num;
return data_num;
}
@@ -387,7 +390,7 @@ static int fsi_get_frame_width(struct fsi_priv *fsi, int is_play)
struct snd_pcm_substream *substream = io->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
- return frames_to_bytes(runtime, 1) / io->chan_num;
+ return frames_to_bytes(runtime, 1) / fsi->chan_num;
}
static void fsi_count_fifo_err(struct fsi_priv *fsi)
@@ -580,10 +583,10 @@ static void fsi_fifo_init(struct fsi_priv *fsi,
* 7 channels: 32 ( 32 x 7 = 224)
* 8 channels: 32 ( 32 x 8 = 256)
*/
- for (i = 1; i < io->chan_num; i <<= 1)
+ for (i = 1; i < fsi->chan_num; i <<= 1)
io->fifo_max_num >>= 1;
dev_dbg(dai->dev, "%d channel %d store\n",
- io->chan_num, io->fifo_max_num);
+ fsi->chan_num, io->fifo_max_num);
/*
* set interrupt generation factor
@@ -659,7 +662,7 @@ static int fsi_fifo_data_ctrl(struct fsi_priv *fsi, int stream)
* data_num_max : number of FSI fifo free space
* data_num : number of ALSA residue data
*/
- data_num_max = io->fifo_max_num * io->chan_num;
+ data_num_max = io->fifo_max_num * fsi->chan_num;
data_num_max -= fsi_get_fifo_data_num(fsi, is_play);
data_num = data_residue_num;
@@ -754,25 +757,12 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct fsi_priv *fsi = fsi_get_priv(substream);
- struct fsi_master *master = fsi_get_master(fsi);
- struct fsi_stream *io;
u32 flags = fsi_get_info_flags(fsi);
- u32 fmt;
u32 data;
int is_play = fsi_is_play(substream);
- int is_master;
-
- io = fsi_get_stream(fsi, is_play);
pm_runtime_get_sync(dai->dev);
- /* CKG1 */
- data = is_play ? (1 << 0) : (1 << 4);
- is_master = fsi_is_master_mode(fsi, is_play);
- if (is_master)
- fsi_reg_mask_set(fsi, CKG1, data, data);
- else
- fsi_reg_mask_set(fsi, CKG1, data, 0);
/* clock inversion (CKG2) */
data = 0;
@@ -787,54 +777,6 @@ static int fsi_dai_startup(struct snd_pcm_substream *substream,
fsi_reg_write(fsi, CKG2, data);
- /* do fmt, di fmt */
- data = 0;
- fmt = is_play ? SH_FSI_GET_OFMT(flags) : SH_FSI_GET_IFMT(flags);
- switch (fmt) {
- case SH_FSI_FMT_MONO:
- data = CR_MONO;
- io->chan_num = 1;
- break;
- case SH_FSI_FMT_MONO_DELAY:
- data = CR_MONO_D;
- io->chan_num = 1;
- break;
- case SH_FSI_FMT_PCM:
- data = CR_PCM;
- io->chan_num = 2;
- break;
- case SH_FSI_FMT_I2S:
- data = CR_I2S;
- io->chan_num = 2;
- break;
- case SH_FSI_FMT_TDM:
- io->chan_num = is_play ?
- SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
- data = CR_TDM | (io->chan_num - 1);
- break;
- case SH_FSI_FMT_TDM_DELAY:
- io->chan_num = is_play ?
- SH_FSI_GET_CH_O(flags) : SH_FSI_GET_CH_I(flags);
- data = CR_TDM_D | (io->chan_num - 1);
- break;
- case SH_FSI_FMT_SPDIF:
- if (master->core->ver < 2) {
- dev_err(dai->dev, "This FSI can not use SPDIF\n");
- return -EINVAL;
- }
- data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM;
- io->chan_num = 2;
- fsi_spdif_clk_ctrl(fsi, 1);
- fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
- break;
- default:
- dev_err(dai->dev, "unknown format.\n");
- return -EINVAL;
- }
- is_play ?
- fsi_reg_write(fsi, DO_FMT, data) :
- fsi_reg_write(fsi, DI_FMT, data);
-
/* irq clear */
fsi_irq_disable(fsi, is_play);
fsi_irq_clear_status(fsi);
@@ -851,12 +793,12 @@ static void fsi_dai_shutdown(struct snd_pcm_substream *substream,
struct fsi_priv *fsi = fsi_get_priv(substream);
int is_play = fsi_is_play(substream);
struct fsi_master *master = fsi_get_master(fsi);
- int (*set_rate)(struct device *dev, int is_porta, int rate, int enable);
+ set_rate_func set_rate;
fsi_irq_disable(fsi, is_play);
fsi_clk_ctrl(fsi, 0);
- set_rate = master->info->set_rate;
+ set_rate = fsi_get_info_set_rate(master);
if (set_rate && fsi->rate)
set_rate(dai->dev, fsi_is_port_a(fsi), fsi->rate, 0);
fsi->rate = 0;
@@ -889,18 +831,100 @@ static int fsi_dai_trigger(struct snd_pcm_substream *substream, int cmd,
return ret;
}
+static int fsi_set_fmt_dai(struct fsi_priv *fsi, unsigned int fmt)
+{
+ u32 data = 0;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ data = CR_I2S;
+ fsi->chan_num = 2;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ data = CR_PCM;
+ fsi->chan_num = 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fsi_reg_write(fsi, DO_FMT, data);
+ fsi_reg_write(fsi, DI_FMT, data);
+
+ return 0;
+}
+
+static int fsi_set_fmt_spdif(struct fsi_priv *fsi)
+{
+ struct fsi_master *master = fsi_get_master(fsi);
+ u32 data = 0;
+
+ if (master->core->ver < 2)
+ return -EINVAL;
+
+ data = CR_BWS_16 | CR_DTMD_SPDIF_PCM | CR_PCM;
+ fsi->chan_num = 2;
+ fsi_spdif_clk_ctrl(fsi, 1);
+ fsi_reg_mask_set(fsi, OUT_SEL, DMMD, DMMD);
+
+ fsi_reg_write(fsi, DO_FMT, data);
+ fsi_reg_write(fsi, DI_FMT, data);
+
+ return 0;
+}
+
+static int fsi_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct fsi_priv *fsi = fsi_get_priv_frm_dai(dai);
+ u32 flags = fsi_get_info_flags(fsi);
+ u32 data = 0;
+ int ret;
+
+ pm_runtime_get_sync(dai->dev);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ data = DIMD | DOMD;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ ret = -EINVAL;
+ goto set_fmt_exit;
+ }
+ fsi_reg_mask_set(fsi, CKG1, (DIMD | DOMD), data);
+
+ /* set format */
+ switch (flags & SH_FSI_FMT_MASK) {
+ case SH_FSI_FMT_DAI:
+ ret = fsi_set_fmt_dai(fsi, fmt & SND_SOC_DAIFMT_FORMAT_MASK);
+ break;
+ case SH_FSI_FMT_SPDIF:
+ ret = fsi_set_fmt_spdif(fsi);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+set_fmt_exit:
+ pm_runtime_put_sync(dai->dev);
+
+ return ret;
+}
+
static int fsi_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct fsi_priv *fsi = fsi_get_priv(substream);
struct fsi_master *master = fsi_get_master(fsi);
- int (*set_rate)(struct device *dev, int is_porta, int rate, int enable);
+ set_rate_func set_rate;
int fsi_ver = master->core->ver;
long rate = params_rate(params);
int ret;
- set_rate = master->info->set_rate;
+ set_rate = fsi_get_info_set_rate(master);
if (!set_rate)
return 0;
@@ -975,6 +999,7 @@ static struct snd_soc_dai_ops fsi_dai_ops = {
.startup = fsi_dai_startup,
.shutdown = fsi_dai_shutdown,
.trigger = fsi_dai_trigger,
+ .set_fmt = fsi_dai_set_fmt,
.hw_params = fsi_dai_hw_params,
};
diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c
index 8c2a21a978a..5d76da43b14 100644
--- a/sound/soc/soc-cache.c
+++ b/sound/soc/soc-cache.c
@@ -18,6 +18,8 @@
#include <linux/bitmap.h>
#include <linux/rbtree.h>
+#include <trace/events/asoc.h>
+
static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
unsigned int reg)
{
@@ -25,7 +27,8 @@ static unsigned int snd_soc_4_12_read(struct snd_soc_codec *codec,
unsigned int val;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -49,7 +52,8 @@ static int snd_soc_4_12_write(struct snd_soc_codec *codec, unsigned int reg,
data[1] = value & 0x00ff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -106,7 +110,8 @@ static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec,
unsigned int val;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -130,7 +135,8 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg,
data[1] = value & 0x00ff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -191,7 +197,8 @@ static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg,
data[1] = value & 0xff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -216,7 +223,8 @@ static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec,
reg &= 0xff;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -271,7 +279,8 @@ static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg,
data[2] = value & 0xff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -295,7 +304,8 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec,
unsigned int val;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -450,7 +460,8 @@ static unsigned int snd_soc_16_8_read(struct snd_soc_codec *codec,
reg &= 0xff;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -476,7 +487,8 @@ static int snd_soc_16_8_write(struct snd_soc_codec *codec, unsigned int reg,
reg &= 0xff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -568,7 +580,8 @@ static unsigned int snd_soc_16_16_read(struct snd_soc_codec *codec,
unsigned int val;
if (reg >= codec->driver->reg_cache_size ||
- snd_soc_codec_volatile_register(codec, reg)) {
+ snd_soc_codec_volatile_register(codec, reg) ||
+ codec->cache_bypass) {
if (codec->cache_only)
return -1;
@@ -595,7 +608,8 @@ static int snd_soc_16_16_write(struct snd_soc_codec *codec, unsigned int reg,
data[3] = value & 0xff;
if (!snd_soc_codec_volatile_register(codec, reg) &&
- reg < codec->driver->reg_cache_size) {
+ reg < codec->driver->reg_cache_size &&
+ !codec->cache_bypass) {
ret = snd_soc_cache_write(codec, reg, value);
if (ret < 0)
return -1;
@@ -761,6 +775,49 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec,
}
EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io);
+static bool snd_soc_set_cache_val(void *base, unsigned int idx,
+ unsigned int val, unsigned int word_size)
+{
+ switch (word_size) {
+ case 1: {
+ u8 *cache = base;
+ if (cache[idx] == val)
+ return true;
+ cache[idx] = val;
+ break;
+ }
+ case 2: {
+ u16 *cache = base;
+ if (cache[idx] == val)
+ return true;
+ cache[idx] = val;
+ break;
+ }
+ default:
+ BUG();
+ }
+ return false;
+}
+
+static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx,
+ unsigned int word_size)
+{
+ switch (word_size) {
+ case 1: {
+ const u8 *cache = base;
+ return cache[idx];
+ }
+ case 2: {
+ const u16 *cache = base;
+ return cache[idx];
+ }
+ default:
+ BUG();
+ }
+ /* unreachable */
+ return -1;
+}
+
struct snd_soc_rbtree_node {
struct rb_node node;
unsigned int reg;
@@ -835,7 +892,9 @@ static int snd_soc_rbtree_cache_sync(struct snd_soc_codec *codec)
ret = snd_soc_cache_read(codec, rbnode->reg, &val);
if (ret)
return ret;
+ codec->cache_bypass = 1;
ret = snd_soc_write(codec, rbnode->reg, val);
+ codec->cache_bypass = 0;
if (ret)
return ret;
dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
@@ -924,7 +983,12 @@ static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec)
static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
{
+ struct snd_soc_rbtree_node *rbtree_node;
struct snd_soc_rbtree_ctx *rbtree_ctx;
+ unsigned int val;
+ unsigned int word_size;
+ int i;
+ int ret;
codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL);
if (!codec->reg_cache)
@@ -936,53 +1000,25 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec)
if (!codec->reg_def_copy)
return 0;
-/*
- * populate the rbtree with the initialized registers. All other
- * registers will be inserted into the tree when they are first written.
- *
- * The reasoning behind this, is that we need to step through and
- * dereference the cache in u8/u16 increments without sacrificing
- * portability. This could also be done using memcpy() but that would
- * be slightly more cryptic.
- */
-#define snd_soc_rbtree_populate(cache) \
-({ \
- int ret, i; \
- struct snd_soc_rbtree_node *rbtree_node; \
- \
- ret = 0; \
- cache = codec->reg_def_copy; \
- for (i = 0; i < codec->driver->reg_cache_size; ++i) { \
- if (!cache[i]) \
- continue; \
- rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); \
- if (!rbtree_node) { \
- ret = -ENOMEM; \
- snd_soc_cache_exit(codec); \
- break; \
- } \
- rbtree_node->reg = i; \
- rbtree_node->value = cache[i]; \
- rbtree_node->defval = cache[i]; \
- snd_soc_rbtree_insert(&rbtree_ctx->root, \
- rbtree_node); \
- } \
- ret; \
-})
-
- switch (codec->driver->reg_word_size) {
- case 1: {
- const u8 *cache;
-
- return snd_soc_rbtree_populate(cache);
- }
- case 2: {
- const u16 *cache;
-
- return snd_soc_rbtree_populate(cache);
- }
- default:
- BUG();
+ /*
+ * populate the rbtree with the initialized registers. All other
+ * registers will be inserted when they are first modified.
+ */
+ word_size = codec->driver->reg_word_size;
+ for (i = 0; i < codec->driver->reg_cache_size; ++i) {
+ val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size);
+ if (!val)
+ continue;
+ rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL);
+ if (!rbtree_node) {
+ ret = -ENOMEM;
+ snd_soc_cache_exit(codec);
+ break;
+ }
+ rbtree_node->reg = i;
+ rbtree_node->value = val;
+ rbtree_node->defval = val;
+ snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node);
}
return 0;
@@ -1080,34 +1116,28 @@ static inline int snd_soc_lzo_get_blkindex(struct snd_soc_codec *codec,
unsigned int reg)
{
const struct snd_soc_codec_driver *codec_drv;
- size_t reg_size;
codec_drv = codec->driver;
- reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
return (reg * codec_drv->reg_word_size) /
- DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count());
+ DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count());
}
static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec,
unsigned int reg)
{
const struct snd_soc_codec_driver *codec_drv;
- size_t reg_size;
codec_drv = codec->driver;
- reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
- return reg % (DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()) /
+ return reg % (DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()) /
codec_drv->reg_word_size);
}
static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec)
{
const struct snd_soc_codec_driver *codec_drv;
- size_t reg_size;
codec_drv = codec->driver;
- reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
- return DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count());
+ return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count());
}
static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec)
@@ -1122,7 +1152,9 @@ static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec)
ret = snd_soc_cache_read(codec, i, &val);
if (ret)
return ret;
+ codec->cache_bypass = 1;
ret = snd_soc_write(codec, i, val);
+ codec->cache_bypass = 0;
if (ret)
return ret;
dev_dbg(codec->dev, "Synced register %#x, value = %#x\n",
@@ -1165,29 +1197,10 @@ static int snd_soc_lzo_cache_write(struct snd_soc_codec *codec,
}
/* write the new value to the cache */
- switch (codec->driver->reg_word_size) {
- case 1: {
- u8 *cache;
- cache = lzo_block->dst;
- if (cache[blkpos] == value) {
- kfree(lzo_block->dst);
- goto out;
- }
- cache[blkpos] = value;
- }
- break;
- case 2: {
- u16 *cache;
- cache = lzo_block->dst;
- if (cache[blkpos] == value) {
- kfree(lzo_block->dst);
- goto out;
- }
- cache[blkpos] = value;
- }
- break;
- default:
- BUG();
+ if (snd_soc_set_cache_val(lzo_block->dst, blkpos, value,
+ codec->driver->reg_word_size)) {
+ kfree(lzo_block->dst);
+ goto out;
}
/* prepare the source to be the decompressed block */
@@ -1241,25 +1254,10 @@ static int snd_soc_lzo_cache_read(struct snd_soc_codec *codec,
/* decompress the block */
ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block);
- if (ret >= 0) {
+ if (ret >= 0)
/* fetch the value from the cache */
- switch (codec->driver->reg_word_size) {
- case 1: {
- u8 *cache;
- cache = lzo_block->dst;
- *value = cache[blkpos];
- }
- break;
- case 2: {
- u16 *cache;
- cache = lzo_block->dst;
- *value = cache[blkpos];
- }
- break;
- default:
- BUG();
- }
- }
+ *value = snd_soc_get_cache_val(lzo_block->dst, blkpos,
+ codec->driver->reg_word_size);
kfree(lzo_block->dst);
/* restore the pointer and length of the compressed block */
@@ -1301,7 +1299,7 @@ static int snd_soc_lzo_cache_exit(struct snd_soc_codec *codec)
static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec)
{
struct snd_soc_lzo_ctx **lzo_blocks;
- size_t reg_size, bmp_size;
+ size_t bmp_size;
const struct snd_soc_codec_driver *codec_drv;
int ret, tofree, i, blksize, blkcount;
const char *p, *end;
@@ -1309,7 +1307,6 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec)
ret = 0;
codec_drv = codec->driver;
- reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
/*
* If we have not been given a default register cache
@@ -1321,8 +1318,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec)
tofree = 1;
if (!codec->reg_def_copy) {
- codec->reg_def_copy = kzalloc(reg_size,
- GFP_KERNEL);
+ codec->reg_def_copy = kzalloc(codec->reg_size, GFP_KERNEL);
if (!codec->reg_def_copy)
return -ENOMEM;
}
@@ -1370,7 +1366,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec)
blksize = snd_soc_lzo_get_blksize(codec);
p = codec->reg_def_copy;
- end = codec->reg_def_copy + reg_size;
+ end = codec->reg_def_copy + codec->reg_size;
/* compress the register map and fill the lzo blocks */
for (i = 0; i < blkcount; ++i, p += blksize) {
lzo_blocks[i]->src = p;
@@ -1414,28 +1410,10 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
ret = snd_soc_cache_read(codec, i, &val);
if (ret)
return ret;
- if (codec_drv->reg_cache_default) {
- switch (codec_drv->reg_word_size) {
- case 1: {
- const u8 *cache;
-
- cache = codec_drv->reg_cache_default;
- if (cache[i] == val)
- continue;
- }
- break;
- case 2: {
- const u16 *cache;
-
- cache = codec_drv->reg_cache_default;
- if (cache[i] == val)
- continue;
- }
- break;
- default:
- BUG();
- }
- }
+ if (codec->reg_def_copy)
+ if (snd_soc_get_cache_val(codec->reg_def_copy,
+ i, codec_drv->reg_word_size) == val)
+ continue;
ret = snd_soc_write(codec, i, val);
if (ret)
return ret;
@@ -1448,50 +1426,16 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec)
static int snd_soc_flat_cache_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value)
{
- switch (codec->driver->reg_word_size) {
- case 1: {
- u8 *cache;
-
- cache = codec->reg_cache;
- cache[reg] = value;
- }
- break;
- case 2: {
- u16 *cache;
-
- cache = codec->reg_cache;
- cache[reg] = value;
- }
- break;
- default:
- BUG();
- }
-
+ snd_soc_set_cache_val(codec->reg_cache, reg, value,
+ codec->driver->reg_word_size);
return 0;
}
static int snd_soc_flat_cache_read(struct snd_soc_codec *codec,
unsigned int reg, unsigned int *value)
{
- switch (codec->driver->reg_word_size) {
- case 1: {
- u8 *cache;
-
- cache = codec->reg_cache;
- *value = cache[reg];
- }
- break;
- case 2: {
- u16 *cache;
-
- cache = codec->reg_cache;
- *value = cache[reg];
- }
- break;
- default:
- BUG();
- }
-
+ *value = snd_soc_get_cache_val(codec->reg_cache, reg,
+ codec->driver->reg_word_size);
return 0;
}
@@ -1507,24 +1451,14 @@ static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec)
static int snd_soc_flat_cache_init(struct snd_soc_codec *codec)
{
const struct snd_soc_codec_driver *codec_drv;
- size_t reg_size;
codec_drv = codec->driver;
- reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
- /*
- * for flat compression, we don't need to keep a copy of the
- * original defaults register cache as it will definitely not
- * be marked as __devinitconst
- */
- kfree(codec->reg_def_copy);
- codec->reg_def_copy = NULL;
-
- if (codec_drv->reg_cache_default)
- codec->reg_cache = kmemdup(codec_drv->reg_cache_default,
- reg_size, GFP_KERNEL);
+ if (codec->reg_def_copy)
+ codec->reg_cache = kmemdup(codec->reg_def_copy,
+ codec->reg_size, GFP_KERNEL);
else
- codec->reg_cache = kzalloc(reg_size, GFP_KERNEL);
+ codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL);
if (!codec->reg_cache)
return -ENOMEM;
@@ -1669,21 +1603,77 @@ EXPORT_SYMBOL_GPL(snd_soc_cache_write);
int snd_soc_cache_sync(struct snd_soc_codec *codec)
{
int ret;
+ const char *name;
if (!codec->cache_sync) {
return 0;
}
- if (codec->cache_ops && codec->cache_ops->sync) {
- if (codec->cache_ops->name)
- dev_dbg(codec->dev, "Syncing %s cache for %s codec\n",
- codec->cache_ops->name, codec->name);
- ret = codec->cache_ops->sync(codec);
- if (!ret)
- codec->cache_sync = 0;
- return ret;
- }
+ if (!codec->cache_ops || !codec->cache_ops->sync)
+ return -EINVAL;
- return -EINVAL;
+ if (codec->cache_ops->name)
+ name = codec->cache_ops->name;
+ else
+ name = "unknown";
+
+ if (codec->cache_ops->name)
+ dev_dbg(codec->dev, "Syncing %s cache for %s codec\n",
+ codec->cache_ops->name, codec->name);
+ trace_snd_soc_cache_sync(codec, name, "start");
+ ret = codec->cache_ops->sync(codec);
+ if (!ret)
+ codec->cache_sync = 0;
+ trace_snd_soc_cache_sync(codec, name, "end");
+ return ret;
}
EXPORT_SYMBOL_GPL(snd_soc_cache_sync);
+
+static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ const struct snd_soc_codec_driver *codec_drv;
+ unsigned int min, max, index;
+
+ codec_drv = codec->driver;
+ min = 0;
+ max = codec_drv->reg_access_size - 1;
+ do {
+ index = (min + max) / 2;
+ if (codec_drv->reg_access_default[index].reg == reg)
+ return index;
+ if (codec_drv->reg_access_default[index].reg < reg)
+ min = index + 1;
+ else
+ max = index;
+ } while (min <= max);
+ return -1;
+}
+
+int snd_soc_default_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int index;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].vol;
+}
+EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register);
+
+int snd_soc_default_readable_register(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ int index;
+
+ if (reg >= codec->driver->reg_cache_size)
+ return 1;
+ index = snd_soc_get_reg_access_index(codec, reg);
+ if (index < 0)
+ return 0;
+ return codec->driver->reg_access_default[index].read;
+}
+EXPORT_SYMBOL_GPL(snd_soc_default_readable_register);
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index c3f6f1e7279..17efacdb248 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -48,7 +48,8 @@ static DEFINE_MUTEX(pcm_mutex);
static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq);
#ifdef CONFIG_DEBUG_FS
-static struct dentry *debugfs_root;
+struct dentry *snd_soc_debugfs_root;
+EXPORT_SYMBOL_GPL(snd_soc_debugfs_root);
#endif
static DEFINE_MUTEX(client_mutex);
@@ -57,8 +58,6 @@ static LIST_HEAD(dai_list);
static LIST_HEAD(platform_list);
static LIST_HEAD(codec_list);
-static int snd_soc_register_card(struct snd_soc_card *card);
-static int snd_soc_unregister_card(struct snd_soc_card *card);
static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num);
/*
@@ -70,10 +69,73 @@ static int pmdown_time = 5000;
module_param(pmdown_time, int, 0);
MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
+/* returns the minimum number of bytes needed to represent
+ * a particular given value */
+static int min_bytes_needed(unsigned long val)
+{
+ int c = 0;
+ int i;
+
+ for (i = (sizeof val * 8) - 1; i >= 0; --i, ++c)
+ if (val & (1UL << i))
+ break;
+ c = (sizeof val * 8) - c;
+ if (!c || (c % 8))
+ c = (c + 8) / 8;
+ else
+ c /= 8;
+ return c;
+}
+
+/* fill buf which is 'len' bytes with a formatted
+ * string of the form 'reg: value\n' */
+static int format_register_str(struct snd_soc_codec *codec,
+ unsigned int reg, char *buf, size_t len)
+{
+ int wordsize = codec->driver->reg_word_size * 2;
+ int regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
+ int ret;
+ char tmpbuf[len + 1];
+ char regbuf[regsize + 1];
+
+ /* since tmpbuf is allocated on the stack, warn the callers if they
+ * try to abuse this function */
+ WARN_ON(len > 63);
+
+ /* +2 for ': ' and + 1 for '\n' */
+ if (wordsize + regsize + 2 + 1 != len)
+ return -EINVAL;
+
+ ret = snd_soc_read(codec , reg);
+ if (ret < 0) {
+ memset(regbuf, 'X', regsize);
+ regbuf[regsize] = '\0';
+ } else {
+ snprintf(regbuf, regsize + 1, "%.*x", regsize, ret);
+ }
+
+ /* prepare the buffer */
+ snprintf(tmpbuf, len + 1, "%.*x: %s\n", wordsize, reg, regbuf);
+ /* copy it back to the caller without the '\0' */
+ memcpy(buf, tmpbuf, len);
+
+ return 0;
+}
+
/* codec register dump */
-static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
+static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf,
+ size_t count, loff_t pos)
{
- int ret, i, step = 1, count = 0;
+ int i, step = 1;
+ int wordsize, regsize;
+ int len;
+ size_t total = 0;
+ loff_t p = 0;
+
+ wordsize = codec->driver->reg_word_size * 2;
+ regsize = min_bytes_needed(codec->driver->reg_cache_size) * 2;
+
+ len = wordsize + regsize + 2 + 1;
if (!codec->driver->reg_cache_size)
return 0;
@@ -81,55 +143,37 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf)
if (codec->driver->reg_cache_step)
step = codec->driver->reg_cache_step;
- count += sprintf(buf, "%s registers\n", codec->name);
for (i = 0; i < codec->driver->reg_cache_size; i += step) {
- if (codec->driver->readable_register && !codec->driver->readable_register(i))
+ if (codec->readable_register && !codec->readable_register(codec, i))
continue;
-
- count += sprintf(buf + count, "%2x: ", i);
- if (count >= PAGE_SIZE - 1)
- break;
-
if (codec->driver->display_register) {
count += codec->driver->display_register(codec, buf + count,
PAGE_SIZE - count, i);
} else {
- /* If the read fails it's almost certainly due to
- * the register being volatile and the device being
- * powered off.
- */
- ret = snd_soc_read(codec, i);
- if (ret >= 0)
- count += snprintf(buf + count,
- PAGE_SIZE - count,
- "%4x", ret);
- else
- count += snprintf(buf + count,
- PAGE_SIZE - count,
- "<no data: %d>", ret);
+ /* only support larger than PAGE_SIZE bytes debugfs
+ * entries for the default case */
+ if (p >= pos) {
+ if (total + len >= count - 1)
+ break;
+ format_register_str(codec, i, buf + total, len);
+ total += len;
+ }
+ p += len;
}
-
- if (count >= PAGE_SIZE - 1)
- break;
-
- count += snprintf(buf + count, PAGE_SIZE - count, "\n");
- if (count >= PAGE_SIZE - 1)
- break;
}
- /* Truncate count; min() would cause a warning */
- if (count >= PAGE_SIZE)
- count = PAGE_SIZE - 1;
+ total = min(total, count - 1);
- return count;
+ return total;
}
+
static ssize_t codec_reg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct snd_soc_pcm_runtime *rtd =
container_of(dev, struct snd_soc_pcm_runtime, dev);
- return soc_codec_reg_show(rtd->codec, buf);
+ return soc_codec_reg_show(rtd->codec, buf, PAGE_SIZE, 0);
}
static DEVICE_ATTR(codec_reg, 0444, codec_reg_show, NULL);
@@ -168,16 +212,28 @@ static int codec_reg_open_file(struct inode *inode, struct file *file)
}
static ssize_t codec_reg_read_file(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
+ size_t count, loff_t *ppos)
{
ssize_t ret;
struct snd_soc_codec *codec = file->private_data;
- char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ char *buf;
+
+ if (*ppos < 0 || !count)
+ return -EINVAL;
+
+ buf = kmalloc(count, GFP_KERNEL);
if (!buf)
return -ENOMEM;
- ret = soc_codec_reg_show(codec, buf);
- if (ret >= 0)
- ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+ ret = soc_codec_reg_show(codec, buf, count, *ppos);
+ if (ret >= 0) {
+ if (copy_to_user(user_buf, buf, ret)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+ *ppos += ret;
+ }
+
kfree(buf);
return ret;
}
@@ -209,6 +265,10 @@ static ssize_t codec_reg_write_file(struct file *file,
start++;
if (strict_strtoul(start, 16, &value))
return -EINVAL;
+
+ /* Userspace has been fiddling around behind the kernel's back */
+ add_taint(TAINT_USER);
+
snd_soc_write(codec, reg, value);
return buf_size;
}
@@ -232,6 +292,11 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec)
return;
}
+ debugfs_create_bool("cache_sync", 0444, codec->debugfs_codec_root,
+ &codec->cache_sync);
+ debugfs_create_bool("cache_only", 0444, codec->debugfs_codec_root,
+ &codec->cache_only);
+
codec->debugfs_reg = debugfs_create_file("codec_reg", 0644,
codec->debugfs_codec_root,
codec, &codec_reg_fops);
@@ -356,7 +421,7 @@ static const struct file_operations platform_list_fops = {
static void soc_init_card_debugfs(struct snd_soc_card *card)
{
card->debugfs_card_root = debugfs_create_dir(card->name,
- debugfs_root);
+ snd_soc_debugfs_root);
if (!card->debugfs_card_root) {
dev_warn(card->dev,
"ASoC: Failed to create codec debugfs directory\n");
@@ -435,20 +500,30 @@ static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream)
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
- if (codec_dai->driver->symmetric_rates || cpu_dai->driver->symmetric_rates ||
- rtd->dai_link->symmetric_rates) {
- dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n",
- rtd->rate);
+ if (!codec_dai->driver->symmetric_rates &&
+ !cpu_dai->driver->symmetric_rates &&
+ !rtd->dai_link->symmetric_rates)
+ return 0;
- ret = snd_pcm_hw_constraint_minmax(substream->runtime,
- SNDRV_PCM_HW_PARAM_RATE,
- rtd->rate,
- rtd->rate);
- if (ret < 0) {
- dev_err(&rtd->dev,
- "Unable to apply rate symmetry constraint: %d\n", ret);
- return ret;
- }
+ /* This can happen if multiple streams are starting simultaneously -
+ * the second can need to get its constraints before the first has
+ * picked a rate. Complain and allow the application to carry on.
+ */
+ if (!rtd->rate) {
+ dev_warn(&rtd->dev,
+ "Not enforcing symmetric_rates due to race\n");
+ return 0;
+ }
+
+ dev_dbg(&rtd->dev, "Symmetry forces %dHz rate\n", rtd->rate);
+
+ ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ rtd->rate, rtd->rate);
+ if (ret < 0) {
+ dev_err(&rtd->dev,
+ "Unable to apply rate symmetry constraint: %d\n", ret);
+ return ret;
}
return 0;
@@ -962,12 +1037,11 @@ static struct snd_pcm_ops soc_pcm_ops = {
.pointer = soc_pcm_pointer,
};
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
/* powers down audio subsystem for suspend */
-static int soc_suspend(struct device *dev)
+int snd_soc_suspend(struct device *dev)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_card *card = dev_get_drvdata(dev);
struct snd_soc_codec *codec;
int i;
@@ -1008,7 +1082,7 @@ static int soc_suspend(struct device *dev)
}
if (card->suspend_pre)
- card->suspend_pre(pdev, PMSG_SUSPEND);
+ card->suspend_pre(card);
for (i = 0; i < card->num_rtd; i++) {
struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai;
@@ -1075,10 +1149,11 @@ static int soc_suspend(struct device *dev)
}
if (card->suspend_post)
- card->suspend_post(pdev, PMSG_SUSPEND);
+ card->suspend_post(card);
return 0;
}
+EXPORT_SYMBOL_GPL(snd_soc_suspend);
/* deferred resume work, so resume can complete before we finished
* setting our codec back up, which can be very slow on I2C
@@ -1087,7 +1162,6 @@ static void soc_resume_deferred(struct work_struct *work)
{
struct snd_soc_card *card =
container_of(work, struct snd_soc_card, deferred_resume_work);
- struct platform_device *pdev = to_platform_device(card->dev);
struct snd_soc_codec *codec;
int i;
@@ -1101,7 +1175,7 @@ static void soc_resume_deferred(struct work_struct *work)
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2);
if (card->resume_pre)
- card->resume_pre(pdev);
+ card->resume_pre(card);
/* resume AC97 DAIs */
for (i = 0; i < card->num_rtd; i++) {
@@ -1176,7 +1250,7 @@ static void soc_resume_deferred(struct work_struct *work)
}
if (card->resume_post)
- card->resume_post(pdev);
+ card->resume_post(card);
dev_dbg(card->dev, "resume work completed\n");
@@ -1185,10 +1259,9 @@ static void soc_resume_deferred(struct work_struct *work)
}
/* powers up audio subsystem after a suspend */
-static int soc_resume(struct device *dev)
+int snd_soc_resume(struct device *dev)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_card *card = dev_get_drvdata(dev);
int i;
/* AC97 devices might have other drivers hanging off them so
@@ -1210,9 +1283,10 @@ static int soc_resume(struct device *dev)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_soc_resume);
#else
-#define soc_suspend NULL
-#define soc_resume NULL
+#define snd_soc_suspend NULL
+#define snd_soc_resume NULL
#endif
static struct snd_soc_dai_ops null_dai_ops = {
@@ -1400,31 +1474,44 @@ static int soc_probe_codec(struct snd_soc_card *card,
struct snd_soc_codec *codec)
{
int ret = 0;
+ const struct snd_soc_codec_driver *driver = codec->driver;
codec->card = card;
codec->dapm.card = card;
soc_set_name_prefix(card, codec);
- if (codec->driver->probe) {
- ret = codec->driver->probe(codec);
+ if (!try_module_get(codec->dev->driver->owner))
+ return -ENODEV;
+
+ if (driver->probe) {
+ ret = driver->probe(codec);
if (ret < 0) {
dev_err(codec->dev,
"asoc: failed to probe CODEC %s: %d\n",
codec->name, ret);
- return ret;
+ goto err_probe;
}
}
+ if (driver->dapm_widgets)
+ snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets,
+ driver->num_dapm_widgets);
+ if (driver->dapm_routes)
+ snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes,
+ driver->num_dapm_routes);
+
soc_init_codec_debugfs(codec);
/* mark codec as probed and add to card codec list */
- if (!try_module_get(codec->dev->driver->owner))
- return -ENODEV;
-
codec->probed = 1;
list_add(&codec->card_list, &card->codec_dev_list);
list_add(&codec->dapm.list, &card->dapm_list);
+ return 0;
+
+err_probe:
+ module_put(codec->dev->driver->owner);
+
return ret;
}
@@ -1468,7 +1555,6 @@ static int soc_post_component_init(struct snd_soc_card *card,
/* Make sure all DAPM widgets are instantiated */
snd_soc_dapm_new_widgets(&codec->dapm);
- snd_soc_dapm_sync(&codec->dapm);
/* register the rtd device */
rtd->codec = codec;
@@ -1543,19 +1629,19 @@ static int soc_probe_dai_link(struct snd_soc_card *card, int num)
/* probe the platform */
if (!platform->probed) {
+ if (!try_module_get(platform->dev->driver->owner))
+ return -ENODEV;
+
if (platform->driver->probe) {
ret = platform->driver->probe(platform);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to probe platform %s\n",
platform->name);
+ module_put(platform->dev->driver->owner);
return ret;
}
}
/* mark platform as probed and add to card platform list */
-
- if (!try_module_get(platform->dev->driver->owner))
- return -ENODEV;
-
platform->probed = 1;
list_add(&platform->card_list, &card->platform_dev_list);
}
@@ -1713,7 +1799,6 @@ static int snd_soc_init_codec_cache(struct snd_soc_codec *codec,
static void snd_soc_instantiate_card(struct snd_soc_card *card)
{
- struct platform_device *pdev = to_platform_device(card->dev);
struct snd_soc_codec *codec;
struct snd_soc_codec_conf *codec_conf;
enum snd_soc_compress_type compress_type;
@@ -1740,6 +1825,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init)
continue;
+ /* by default we don't override the compress_type */
+ compress_type = 0;
/* check to see if we need to override the compress_type */
for (i = 0; i < card->num_configs; ++i) {
codec_conf = &card->codec_conf[i];
@@ -1750,18 +1837,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
break;
}
}
- if (i == card->num_configs) {
- /* no need to override the compress_type so
- * go ahead and do the standard thing */
- ret = snd_soc_init_codec_cache(codec, 0);
- if (ret < 0) {
- mutex_unlock(&card->mutex);
- return;
- }
- continue;
- }
- /* override the compress_type with the one supplied in
- * the machine driver */
ret = snd_soc_init_codec_cache(codec, compress_type);
if (ret < 0) {
mutex_unlock(&card->mutex);
@@ -1780,14 +1855,19 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
}
card->snd_card->dev = card->dev;
-#ifdef CONFIG_PM
+ card->dapm.bias_level = SND_SOC_BIAS_OFF;
+ card->dapm.dev = card->dev;
+ card->dapm.card = card;
+ list_add(&card->dapm.list, &card->dapm_list);
+
+#ifdef CONFIG_PM_SLEEP
/* deferred resume work */
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
#endif
/* initialise the sound card only once */
if (card->probe) {
- ret = card->probe(pdev);
+ ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
@@ -1810,11 +1890,37 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
}
}
+ if (card->dapm_widgets)
+ snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
+ card->num_dapm_widgets);
+ if (card->dapm_routes)
+ snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
+ card->num_dapm_routes);
+
+#ifdef CONFIG_DEBUG_FS
+ card->dapm.debugfs_dapm = debugfs_create_dir("dapm",
+ card->debugfs_card_root);
+ if (!card->dapm.debugfs_dapm)
+ printk(KERN_WARNING
+ "Failed to create card DAPM debugfs directory\n");
+
+ snd_soc_dapm_debugfs_init(&card->dapm);
+#endif
+
snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
"%s", card->name);
snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
"%s", card->name);
+ if (card->late_probe) {
+ ret = card->late_probe(card);
+ if (ret < 0) {
+ dev_err(card->dev, "%s late_probe() failed: %d\n",
+ card->name, ret);
+ goto probe_aux_dev_err;
+ }
+ }
+
ret = snd_card_register(card->snd_card);
if (ret < 0) {
printk(KERN_ERR "asoc: failed to register soundcard for %s\n", card->name);
@@ -1848,7 +1954,7 @@ probe_dai_err:
card_probe_error:
if (card->remove)
- card->remove(pdev);
+ card->remove(card);
snd_card_free(card->snd_card);
@@ -1872,16 +1978,15 @@ static int soc_probe(struct platform_device *pdev)
struct snd_soc_card *card = platform_get_drvdata(pdev);
int ret = 0;
+ /*
+ * no card, so machine driver should be registering card
+ * we should not be here in that case so ret error
+ */
+ if (!card)
+ return -EINVAL;
+
/* Bodge while we unpick instantiation */
card->dev = &pdev->dev;
- INIT_LIST_HEAD(&card->dai_dev_list);
- INIT_LIST_HEAD(&card->codec_dev_list);
- INIT_LIST_HEAD(&card->platform_dev_list);
- INIT_LIST_HEAD(&card->widgets);
- INIT_LIST_HEAD(&card->paths);
- INIT_LIST_HEAD(&card->dapm_list);
-
- soc_init_card_debugfs(card);
ret = snd_soc_register_card(card);
if (ret != 0) {
@@ -1892,45 +1997,48 @@ static int soc_probe(struct platform_device *pdev)
return 0;
}
-/* removes a socdev */
-static int soc_remove(struct platform_device *pdev)
+static int soc_cleanup_card_resources(struct snd_soc_card *card)
{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
int i;
- if (card->instantiated) {
+ /* make sure any delayed work runs */
+ for (i = 0; i < card->num_rtd; i++) {
+ struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
+ flush_delayed_work_sync(&rtd->delayed_work);
+ }
- /* make sure any delayed work runs */
- for (i = 0; i < card->num_rtd; i++) {
- struct snd_soc_pcm_runtime *rtd = &card->rtd[i];
- flush_delayed_work_sync(&rtd->delayed_work);
- }
+ /* remove auxiliary devices */
+ for (i = 0; i < card->num_aux_devs; i++)
+ soc_remove_aux_dev(card, i);
- /* remove auxiliary devices */
- for (i = 0; i < card->num_aux_devs; i++)
- soc_remove_aux_dev(card, i);
+ /* remove and free each DAI */
+ for (i = 0; i < card->num_rtd; i++)
+ soc_remove_dai_link(card, i);
- /* remove and free each DAI */
- for (i = 0; i < card->num_rtd; i++)
- soc_remove_dai_link(card, i);
+ soc_cleanup_card_debugfs(card);
- soc_cleanup_card_debugfs(card);
+ /* remove the card */
+ if (card->remove)
+ card->remove(card);
+
+ kfree(card->rtd);
+ snd_card_free(card->snd_card);
+ return 0;
- /* remove the card */
- if (card->remove)
- card->remove(pdev);
+}
+
+/* removes a socdev */
+static int soc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
- kfree(card->rtd);
- snd_card_free(card->snd_card);
- }
snd_soc_unregister_card(card);
return 0;
}
-static int soc_poweroff(struct device *dev)
+int snd_soc_poweroff(struct device *dev)
{
- struct platform_device *pdev = to_platform_device(dev);
- struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct snd_soc_card *card = dev_get_drvdata(dev);
int i;
if (!card->instantiated)
@@ -1947,11 +2055,12 @@ static int soc_poweroff(struct device *dev)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_soc_poweroff);
-static const struct dev_pm_ops soc_pm_ops = {
- .suspend = soc_suspend,
- .resume = soc_resume,
- .poweroff = soc_poweroff,
+const struct dev_pm_ops snd_soc_pm_ops = {
+ .suspend = snd_soc_suspend,
+ .resume = snd_soc_resume,
+ .poweroff = snd_soc_poweroff,
};
/* ASoC platform driver */
@@ -1959,7 +2068,7 @@ static struct platform_driver soc_driver = {
.driver = {
.name = "soc-audio",
.owner = THIS_MODULE,
- .pm = &soc_pm_ops,
+ .pm = &snd_soc_pm_ops,
},
.probe = soc_probe,
.remove = soc_remove,
@@ -2029,10 +2138,11 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
*
* Boolean function indiciating if a CODEC register is volatile.
*/
-int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg)
+int snd_soc_codec_volatile_register(struct snd_soc_codec *codec,
+ unsigned int reg)
{
- if (codec->driver->volatile_register)
- return codec->driver->volatile_register(reg);
+ if (codec->volatile_register)
+ return codec->volatile_register(codec, reg);
else
return 0;
}
@@ -2129,19 +2239,27 @@ EXPORT_SYMBOL_GPL(snd_soc_write);
*
* Writes new register value.
*
- * Returns 1 for change else 0.
+ * Returns 1 for change, 0 for no change, or negative error code.
*/
int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg,
unsigned int mask, unsigned int value)
{
int change;
unsigned int old, new;
+ int ret;
- old = snd_soc_read(codec, reg);
+ ret = snd_soc_read(codec, reg);
+ if (ret < 0)
+ return ret;
+
+ old = ret;
new = (old & ~mask) | value;
change = old != new;
- if (change)
- snd_soc_write(codec, reg, new);
+ if (change) {
+ ret = snd_soc_write(codec, reg, new);
+ if (ret < 0)
+ return ret;
+ }
return change;
}
@@ -2226,22 +2344,45 @@ EXPORT_SYMBOL_GPL(snd_soc_set_runtime_hwparams);
* @_template: control template
* @data: control private data
* @long_name: control long name
+ * @prefix: control name prefix
*
* Create a new mixer control from a template control.
*
* Returns 0 for success, else error.
*/
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
- void *data, char *long_name)
+ void *data, char *long_name,
+ const char *prefix)
{
struct snd_kcontrol_new template;
+ struct snd_kcontrol *kcontrol;
+ char *name = NULL;
+ int name_len;
memcpy(&template, _template, sizeof(template));
- if (long_name)
- template.name = long_name;
template.index = 0;
- return snd_ctl_new1(&template, data);
+ if (!long_name)
+ long_name = template.name;
+
+ if (prefix) {
+ name_len = strlen(long_name) + strlen(prefix) + 2;
+ name = kmalloc(name_len, GFP_ATOMIC);
+ if (!name)
+ return NULL;
+
+ snprintf(name, name_len, "%s %s", prefix, long_name);
+
+ template.name = name;
+ } else {
+ template.name = long_name;
+ }
+
+ kcontrol = snd_ctl_new1(&template, data);
+
+ kfree(name);
+
+ return kcontrol;
}
EXPORT_SYMBOL_GPL(snd_soc_cnew);
@@ -2260,22 +2401,16 @@ int snd_soc_add_controls(struct snd_soc_codec *codec,
const struct snd_kcontrol_new *controls, int num_controls)
{
struct snd_card *card = codec->card->snd_card;
- char prefixed_name[44], *name;
int err, i;
for (i = 0; i < num_controls; i++) {
const struct snd_kcontrol_new *control = &controls[i];
- if (codec->name_prefix) {
- snprintf(prefixed_name, sizeof(prefixed_name), "%s %s",
- codec->name_prefix, control->name);
- name = prefixed_name;
- } else {
- name = control->name;
- }
- err = snd_ctl_add(card, snd_soc_cnew(control, codec, name));
+ err = snd_ctl_add(card, snd_soc_cnew(control, codec,
+ control->name,
+ codec->name_prefix));
if (err < 0) {
dev_err(codec->dev, "%s: Failed to add %s: %d\n",
- codec->name, name, err);
+ codec->name, control->name, err);
return err;
}
}
@@ -2956,12 +3091,34 @@ int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
{
if (dai->driver && dai->driver->ops->set_sysclk)
return dai->driver->ops->set_sysclk(dai, clk_id, freq, dir);
+ else if (dai->codec && dai->codec->driver->set_sysclk)
+ return dai->codec->driver->set_sysclk(dai->codec, clk_id,
+ freq, dir);
else
return -EINVAL;
}
EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk);
/**
+ * snd_soc_codec_set_sysclk - configure CODEC system or master clock.
+ * @codec: CODEC
+ * @clk_id: DAI specific clock ID
+ * @freq: new clock frequency in Hz
+ * @dir: new clock direction - input/output.
+ *
+ * Configures the CODEC master (MCLK) or system (SYSCLK) clocking.
+ */
+int snd_soc_codec_set_sysclk(struct snd_soc_codec *codec, int clk_id,
+ unsigned int freq, int dir)
+{
+ if (codec->driver->set_sysclk)
+ return codec->driver->set_sysclk(codec, clk_id, freq, dir);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_sysclk);
+
+/**
* snd_soc_dai_set_clkdiv - configure DAI clock dividers.
* @dai: DAI
* @div_id: DAI specific clock divider ID
@@ -2997,11 +3154,35 @@ int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source,
if (dai->driver && dai->driver->ops->set_pll)
return dai->driver->ops->set_pll(dai, pll_id, source,
freq_in, freq_out);
+ else if (dai->codec && dai->codec->driver->set_pll)
+ return dai->codec->driver->set_pll(dai->codec, pll_id, source,
+ freq_in, freq_out);
else
return -EINVAL;
}
EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll);
+/*
+ * snd_soc_codec_set_pll - configure codec PLL.
+ * @codec: CODEC
+ * @pll_id: DAI specific PLL ID
+ * @source: DAI specific source for the PLL
+ * @freq_in: PLL input clock frequency in Hz
+ * @freq_out: requested PLL output clock frequency in Hz
+ *
+ * Configures and enables PLL to generate output clock based on input clock.
+ */
+int snd_soc_codec_set_pll(struct snd_soc_codec *codec, int pll_id, int source,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ if (codec->driver->set_pll)
+ return codec->driver->set_pll(codec, pll_id, source,
+ freq_in, freq_out);
+ else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(snd_soc_codec_set_pll);
+
/**
* snd_soc_dai_set_fmt - configure DAI hardware audio format.
* @dai: DAI
@@ -3101,17 +3282,18 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute);
*
* @card: Card to register
*
- * Note that currently this is an internal only function: it will be
- * exposed to machine drivers after further backporting of ASoC v2
- * registration APIs.
*/
-static int snd_soc_register_card(struct snd_soc_card *card)
+int snd_soc_register_card(struct snd_soc_card *card)
{
int i;
if (!card->name || !card->dev)
return -EINVAL;
+ snd_soc_initialize_card_lists(card);
+
+ soc_init_card_debugfs(card);
+
card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
(card->num_links + card->num_aux_devs),
GFP_KERNEL);
@@ -3135,18 +3317,18 @@ static int snd_soc_register_card(struct snd_soc_card *card)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_soc_register_card);
/**
* snd_soc_unregister_card - Unregister a card with the ASoC core
*
* @card: Card to unregister
*
- * Note that currently this is an internal only function: it will be
- * exposed to machine drivers after further backporting of ASoC v2
- * registration APIs.
*/
-static int snd_soc_unregister_card(struct snd_soc_card *card)
+int snd_soc_unregister_card(struct snd_soc_card *card)
{
+ if (card->instantiated)
+ soc_cleanup_card_resources(card);
mutex_lock(&client_mutex);
list_del(&card->list);
mutex_unlock(&client_mutex);
@@ -3154,6 +3336,7 @@ static int snd_soc_unregister_card(struct snd_soc_card *card)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_soc_unregister_card);
/*
* Simplify DAI link configuration by removing ".-1" from device names
@@ -3483,9 +3666,12 @@ int snd_soc_register_codec(struct device *dev,
codec->write = codec_drv->write;
codec->read = codec_drv->read;
+ codec->volatile_register = codec_drv->volatile_register;
+ codec->readable_register = codec_drv->readable_register;
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
codec->dapm.dev = dev;
codec->dapm.codec = codec;
+ codec->dapm.seq_notifier = codec_drv->seq_notifier;
codec->dev = dev;
codec->driver = codec_drv;
codec->num_dai = num_dai;
@@ -3494,20 +3680,30 @@ int snd_soc_register_codec(struct device *dev,
/* allocate CODEC register cache */
if (codec_drv->reg_cache_size && codec_drv->reg_word_size) {
reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size;
+ codec->reg_size = reg_size;
/* it is necessary to make a copy of the default register cache
* because in the case of using a compression type that requires
* the default register cache to be marked as __devinitconst the
* kernel might have freed the array by the time we initialize
* the cache.
*/
- codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
- reg_size, GFP_KERNEL);
- if (!codec->reg_def_copy) {
- ret = -ENOMEM;
- goto fail;
+ if (codec_drv->reg_cache_default) {
+ codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default,
+ reg_size, GFP_KERNEL);
+ if (!codec->reg_def_copy) {
+ ret = -ENOMEM;
+ goto fail;
+ }
}
}
+ if (codec_drv->reg_access_size && codec_drv->reg_access_default) {
+ if (!codec->volatile_register)
+ codec->volatile_register = snd_soc_default_volatile_register;
+ if (!codec->readable_register)
+ codec->readable_register = snd_soc_default_readable_register;
+ }
+
for (i = 0; i < num_dai; i++) {
fixup_codec_formats(&dai_drv[i].playback);
fixup_codec_formats(&dai_drv[i].capture);
@@ -3574,22 +3770,22 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec);
static int __init snd_soc_init(void)
{
#ifdef CONFIG_DEBUG_FS
- debugfs_root = debugfs_create_dir("asoc", NULL);
- if (IS_ERR(debugfs_root) || !debugfs_root) {
+ snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL);
+ if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) {
printk(KERN_WARNING
"ASoC: Failed to create debugfs directory\n");
- debugfs_root = NULL;
+ snd_soc_debugfs_root = NULL;
}
- if (!debugfs_create_file("codecs", 0444, debugfs_root, NULL,
+ if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL,
&codec_list_fops))
pr_warn("ASoC: Failed to create CODEC list debugfs file\n");
- if (!debugfs_create_file("dais", 0444, debugfs_root, NULL,
+ if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,
&dai_list_fops))
pr_warn("ASoC: Failed to create DAI list debugfs file\n");
- if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL,
+ if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL,
&platform_list_fops))
pr_warn("ASoC: Failed to create platform list debugfs file\n");
#endif
@@ -3601,7 +3797,7 @@ module_init(snd_soc_init);
static void __exit snd_soc_exit(void)
{
#ifdef CONFIG_DEBUG_FS
- debugfs_remove_recursive(debugfs_root);
+ debugfs_remove_recursive(snd_soc_debugfs_root);
#endif
platform_driver_unregister(&soc_driver);
}
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 25e54230cc6..81c4052c127 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -32,6 +32,7 @@
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
+#include <linux/async.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/bitops.h>
@@ -125,17 +126,17 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget(
/**
* snd_soc_dapm_set_bias_level - set the bias level for the system
- * @card: audio device
+ * @dapm: DAPM context
* @level: level to configure
*
* Configure the bias (power) levels for the SoC audio device.
*
* Returns 0 for success else error.
*/
-static int snd_soc_dapm_set_bias_level(struct snd_soc_card *card,
- struct snd_soc_dapm_context *dapm,
+static int snd_soc_dapm_set_bias_level(struct snd_soc_dapm_context *dapm,
enum snd_soc_bias_level level)
{
+ struct snd_soc_card *card = dapm->card;
int ret = 0;
switch (level) {
@@ -365,9 +366,20 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
struct snd_soc_dapm_widget *w)
{
int i, ret = 0;
- size_t name_len;
+ size_t name_len, prefix_len;
struct snd_soc_dapm_path *path;
- struct snd_card *card = dapm->codec->card->snd_card;
+ struct snd_card *card = dapm->card->snd_card;
+ const char *prefix;
+
+ if (dapm->codec)
+ prefix = dapm->codec->name_prefix;
+ else
+ prefix = NULL;
+
+ if (prefix)
+ prefix_len = strlen(prefix) + 1;
+ else
+ prefix_len = 0;
/* add kcontrol */
for (i = 0; i < w->num_kcontrols; i++) {
@@ -396,8 +408,15 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
switch (w->id) {
default:
+ /* The control will get a prefix from
+ * the control creation process but
+ * we're also using the same prefix
+ * for widgets so cut the prefix off
+ * the front of the widget name.
+ */
snprintf(path->long_name, name_len, "%s %s",
- w->name, w->kcontrols[i].name);
+ w->name + prefix_len,
+ w->kcontrols[i].name);
break;
case snd_soc_dapm_mixer_named_ctl:
snprintf(path->long_name, name_len, "%s",
@@ -408,7 +427,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm,
path->long_name[name_len - 1] = '\0';
path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w,
- path->long_name);
+ path->long_name, prefix);
ret = snd_ctl_add(card, path->kcontrol);
if (ret < 0) {
dev_err(dapm->dev,
@@ -429,7 +448,9 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
{
struct snd_soc_dapm_path *path = NULL;
struct snd_kcontrol *kcontrol;
- struct snd_card *card = dapm->codec->card->snd_card;
+ struct snd_card *card = dapm->card->snd_card;
+ const char *prefix;
+ size_t prefix_len;
int ret = 0;
if (!w->num_kcontrols) {
@@ -437,7 +458,22 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm,
return -EINVAL;
}
- kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name);
+ if (dapm->codec)
+ prefix = dapm->codec->name_prefix;
+ else
+ prefix = NULL;
+
+ if (prefix)
+ prefix_len = strlen(prefix) + 1;
+ else
+ prefix_len = 0;
+
+ /* The control will get a prefix from the control creation
+ * process but we're also using the same prefix for widgets so
+ * cut the prefix off the front of the widget name.
+ */
+ kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name + prefix_len,
+ prefix);
ret = snd_ctl_add(card, kcontrol);
if (ret < 0)
@@ -479,7 +515,7 @@ static inline void dapm_clear_walk(struct snd_soc_dapm_context *dapm)
*/
static int snd_soc_dapm_suspend_check(struct snd_soc_dapm_widget *widget)
{
- int level = snd_power_get_state(widget->dapm->codec->card->snd_card);
+ int level = snd_power_get_state(widget->dapm->card->snd_card);
switch (level) {
case SNDRV_CTL_POWER_D3hot:
@@ -734,10 +770,23 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w)
static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
struct snd_soc_dapm_widget *b,
- int sort[])
+ bool power_up)
{
+ int *sort;
+
+ if (power_up)
+ sort = dapm_up_seq;
+ else
+ sort = dapm_down_seq;
+
if (sort[a->id] != sort[b->id])
return sort[a->id] - sort[b->id];
+ if (a->subseq != b->subseq) {
+ if (power_up)
+ return a->subseq - b->subseq;
+ else
+ return b->subseq - a->subseq;
+ }
if (a->reg != b->reg)
return a->reg - b->reg;
if (a->dapm != b->dapm)
@@ -749,12 +798,12 @@ static int dapm_seq_compare(struct snd_soc_dapm_widget *a,
/* Insert a widget in order into a DAPM power sequence. */
static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget,
struct list_head *list,
- int sort[])
+ bool power_up)
{
struct snd_soc_dapm_widget *w;
list_for_each_entry(w, list, power_list)
- if (dapm_seq_compare(new_widget, w, sort) < 0) {
+ if (dapm_seq_compare(new_widget, w, power_up) < 0) {
list_add_tail(&new_widget->power_list, &w->power_list);
return;
}
@@ -865,26 +914,42 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm,
* handled.
*/
static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
- struct list_head *list, int event, int sort[])
+ struct list_head *list, int event, bool power_up)
{
struct snd_soc_dapm_widget *w, *n;
LIST_HEAD(pending);
int cur_sort = -1;
+ int cur_subseq = -1;
int cur_reg = SND_SOC_NOPM;
struct snd_soc_dapm_context *cur_dapm = NULL;
- int ret;
+ int ret, i;
+ int *sort;
+
+ if (power_up)
+ sort = dapm_up_seq;
+ else
+ sort = dapm_down_seq;
list_for_each_entry_safe(w, n, list, power_list) {
ret = 0;
/* Do we need to apply any queued changes? */
if (sort[w->id] != cur_sort || w->reg != cur_reg ||
- w->dapm != cur_dapm) {
+ w->dapm != cur_dapm || w->subseq != cur_subseq) {
if (!list_empty(&pending))
dapm_seq_run_coalesced(cur_dapm, &pending);
+ if (cur_dapm && cur_dapm->seq_notifier) {
+ for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
+ if (sort[i] == cur_sort)
+ cur_dapm->seq_notifier(cur_dapm,
+ i,
+ cur_subseq);
+ }
+
INIT_LIST_HEAD(&pending);
cur_sort = -1;
+ cur_subseq = -1;
cur_reg = SND_SOC_NOPM;
cur_dapm = NULL;
}
@@ -929,6 +994,7 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
default:
/* Queue it up for application */
cur_sort = sort[w->id];
+ cur_subseq = w->subseq;
cur_reg = w->reg;
cur_dapm = w->dapm;
list_move(&w->power_list, &pending);
@@ -941,7 +1007,14 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm,
}
if (!list_empty(&pending))
- dapm_seq_run_coalesced(dapm, &pending);
+ dapm_seq_run_coalesced(cur_dapm, &pending);
+
+ if (cur_dapm && cur_dapm->seq_notifier) {
+ for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++)
+ if (sort[i] == cur_sort)
+ cur_dapm->seq_notifier(cur_dapm,
+ i, cur_subseq);
+ }
}
static void dapm_widget_update(struct snd_soc_dapm_context *dapm)
@@ -977,7 +1050,62 @@ static void dapm_widget_update(struct snd_soc_dapm_context *dapm)
}
}
+/* Async callback run prior to DAPM sequences - brings to _PREPARE if
+ * they're changing state.
+ */
+static void dapm_pre_sequence_async(void *data, async_cookie_t cookie)
+{
+ struct snd_soc_dapm_context *d = data;
+ int ret;
+ if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ dev_err(d->dev,
+ "Failed to turn on bias: %d\n", ret);
+ }
+
+ /* If we're changing to all on or all off then prepare */
+ if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) ||
+ (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_PREPARE);
+ if (ret != 0)
+ dev_err(d->dev,
+ "Failed to prepare bias: %d\n", ret);
+ }
+}
+
+/* Async callback run prior to DAPM sequences - brings to their final
+ * state.
+ */
+static void dapm_post_sequence_async(void *data, async_cookie_t cookie)
+{
+ struct snd_soc_dapm_context *d = data;
+ int ret;
+
+ /* If we just powered the last thing off drop to standby bias */
+ if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_STANDBY);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to apply standby bias: %d\n",
+ ret);
+ }
+
+ /* If we're in standby and can support bias off then do that */
+ if (d->bias_level == SND_SOC_BIAS_STANDBY && d->idle_bias_off) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_OFF);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to turn off bias: %d\n", ret);
+ }
+
+ /* If we just powered up then move to active bias */
+ if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) {
+ ret = snd_soc_dapm_set_bias_level(d, SND_SOC_BIAS_ON);
+ if (ret != 0)
+ dev_err(d->dev, "Failed to apply active bias: %d\n",
+ ret);
+ }
+}
/*
* Scan each dapm widget for complete audio path.
@@ -990,12 +1118,12 @@ static void dapm_widget_update(struct snd_soc_dapm_context *dapm)
*/
static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
{
- struct snd_soc_card *card = dapm->codec->card;
+ struct snd_soc_card *card = dapm->card;
struct snd_soc_dapm_widget *w;
struct snd_soc_dapm_context *d;
LIST_HEAD(up_list);
LIST_HEAD(down_list);
- int ret = 0;
+ LIST_HEAD(async_domain);
int power;
trace_snd_soc_dapm_start(card);
@@ -1010,10 +1138,10 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
list_for_each_entry(w, &card->widgets, list) {
switch (w->id) {
case snd_soc_dapm_pre:
- dapm_seq_insert(w, &down_list, dapm_down_seq);
+ dapm_seq_insert(w, &down_list, false);
break;
case snd_soc_dapm_post:
- dapm_seq_insert(w, &up_list, dapm_up_seq);
+ dapm_seq_insert(w, &up_list, true);
break;
default:
@@ -1033,9 +1161,9 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
trace_snd_soc_dapm_widget_power(w, power);
if (power)
- dapm_seq_insert(w, &up_list, dapm_up_seq);
+ dapm_seq_insert(w, &up_list, true);
else
- dapm_seq_insert(w, &down_list, dapm_down_seq);
+ dapm_seq_insert(w, &down_list, false);
w->power = power;
break;
@@ -1073,65 +1201,25 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event)
}
}
- list_for_each_entry(d, &dapm->card->dapm_list, list) {
- if (d->dev_power && d->bias_level == SND_SOC_BIAS_OFF) {
- ret = snd_soc_dapm_set_bias_level(card, d,
- SND_SOC_BIAS_STANDBY);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to turn on bias: %d\n", ret);
- }
-
- /* If we're changing to all on or all off then prepare */
- if ((d->dev_power && d->bias_level == SND_SOC_BIAS_STANDBY) ||
- (!d->dev_power && d->bias_level == SND_SOC_BIAS_ON)) {
- ret = snd_soc_dapm_set_bias_level(card, d,
- SND_SOC_BIAS_PREPARE);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to prepare bias: %d\n", ret);
- }
- }
+ /* Run all the bias changes in parallel */
+ list_for_each_entry(d, &dapm->card->dapm_list, list)
+ async_schedule_domain(dapm_pre_sequence_async, d,
+ &async_domain);
+ async_synchronize_full_domain(&async_domain);
/* Power down widgets first; try to avoid amplifying pops. */
- dapm_seq_run(dapm, &down_list, event, dapm_down_seq);
+ dapm_seq_run(dapm, &down_list, event, false);
dapm_widget_update(dapm);
/* Now power up. */
- dapm_seq_run(dapm, &up_list, event, dapm_up_seq);
-
- list_for_each_entry(d, &dapm->card->dapm_list, list) {
- /* If we just powered the last thing off drop to standby bias */
- if (d->bias_level == SND_SOC_BIAS_PREPARE && !d->dev_power) {
- ret = snd_soc_dapm_set_bias_level(card, d,
- SND_SOC_BIAS_STANDBY);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to apply standby bias: %d\n",
- ret);
- }
+ dapm_seq_run(dapm, &up_list, event, true);
- /* If we're in standby and can support bias off then do that */
- if (d->bias_level == SND_SOC_BIAS_STANDBY &&
- d->idle_bias_off) {
- ret = snd_soc_dapm_set_bias_level(card, d,
- SND_SOC_BIAS_OFF);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to turn off bias: %d\n", ret);
- }
-
- /* If we just powered up then move to active bias */
- if (d->bias_level == SND_SOC_BIAS_PREPARE && d->dev_power) {
- ret = snd_soc_dapm_set_bias_level(card, d,
- SND_SOC_BIAS_ON);
- if (ret != 0)
- dev_err(d->dev,
- "Failed to apply active bias: %d\n",
- ret);
- }
- }
+ /* Run all the bias changes in parallel */
+ list_for_each_entry(d, &dapm->card->dapm_list, list)
+ async_schedule_domain(dapm_post_sequence_async, d,
+ &async_domain);
+ async_synchronize_full_domain(&async_domain);
pop_dbg(dapm->dev, card->pop_time,
"DAPM sequencing finished, waiting %dms\n", card->pop_time);
@@ -1189,7 +1277,7 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
if (p->connect)
ret += snprintf(buf + ret, PAGE_SIZE - ret,
- " in %s %s\n",
+ " in \"%s\" \"%s\"\n",
p->name ? p->name : "static",
p->source->name);
}
@@ -1199,7 +1287,7 @@ static ssize_t dapm_widget_power_read_file(struct file *file,
if (p->connect)
ret += snprintf(buf + ret, PAGE_SIZE - ret,
- " out %s %s\n",
+ " out \"%s\" \"%s\"\n",
p->name ? p->name : "static",
p->sink->name);
}
@@ -1464,7 +1552,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm,
char prefixed_source[80];
int ret = 0;
- if (dapm->codec->name_prefix) {
+ if (dapm->codec && dapm->codec->name_prefix) {
snprintf(prefixed_sink, sizeof(prefixed_sink), "%s %s",
dapm->codec->name_prefix, route->sink);
sink = prefixed_sink;
@@ -2114,14 +2202,14 @@ int snd_soc_dapm_new_control(struct snd_soc_dapm_context *dapm,
return -ENOMEM;
name_len = strlen(widget->name) + 1;
- if (dapm->codec->name_prefix)
+ if (dapm->codec && dapm->codec->name_prefix)
name_len += 1 + strlen(dapm->codec->name_prefix);
w->name = kmalloc(name_len, GFP_KERNEL);
if (w->name == NULL) {
kfree(w);
return -ENOMEM;
}
- if (dapm->codec->name_prefix)
+ if (dapm->codec && dapm->codec->name_prefix)
snprintf(w->name, name_len, "%s %s",
dapm->codec->name_prefix, widget->name);
else
@@ -2226,7 +2314,6 @@ int snd_soc_dapm_stream_event(struct snd_soc_pcm_runtime *rtd,
mutex_unlock(&codec->mutex);
return 0;
}
-EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event);
/**
* snd_soc_dapm_enable_pin - enable pin.
@@ -2393,7 +2480,7 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm)
if (w->dapm != dapm)
continue;
if (w->power) {
- dapm_seq_insert(w, &down_list, dapm_down_seq);
+ dapm_seq_insert(w, &down_list, false);
w->power = 0;
powerdown = 1;
}
@@ -2403,9 +2490,9 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm)
* standby.
*/
if (powerdown) {
- snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_PREPARE);
- dapm_seq_run(dapm, &down_list, 0, dapm_down_seq);
- snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_STANDBY);
+ snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_PREPARE);
+ dapm_seq_run(dapm, &down_list, 0, false);
+ snd_soc_dapm_set_bias_level(dapm, SND_SOC_BIAS_STANDBY);
}
}
@@ -2418,7 +2505,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card)
list_for_each_entry(codec, &card->codec_dev_list, list) {
soc_dapm_shutdown_codec(&codec->dapm);
- snd_soc_dapm_set_bias_level(card, &codec->dapm, SND_SOC_BIAS_OFF);
+ snd_soc_dapm_set_bias_level(&codec->dapm, SND_SOC_BIAS_OFF);
}
}
diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c
index ac5a5bc7375..fcab80b36a3 100644
--- a/sound/soc/soc-jack.c
+++ b/sound/soc/soc-jack.c
@@ -37,6 +37,7 @@ int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type,
{
jack->codec = codec;
INIT_LIST_HEAD(&jack->pins);
+ INIT_LIST_HEAD(&jack->jack_zones);
BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier);
return snd_jack_new(codec->card->snd_card, id, type, &jack->jack);
@@ -100,7 +101,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask)
}
/* Report before the DAPM sync to help users updating micbias status */
- blocking_notifier_call_chain(&jack->notifier, status, NULL);
+ blocking_notifier_call_chain(&jack->notifier, status, jack);
snd_soc_dapm_sync(dapm);
@@ -112,6 +113,51 @@ out:
EXPORT_SYMBOL_GPL(snd_soc_jack_report);
/**
+ * snd_soc_jack_add_zones - Associate voltage zones with jack
+ *
+ * @jack: ASoC jack
+ * @count: Number of zones
+ * @zone: Array of zones
+ *
+ * After this function has been called the zones specified in the
+ * array will be associated with the jack.
+ */
+int snd_soc_jack_add_zones(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_zone *zones)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ INIT_LIST_HEAD(&zones[i].list);
+ list_add(&(zones[i].list), &jack->jack_zones);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_add_zones);
+
+/**
+ * snd_soc_jack_get_type - Based on the mic bias value, this function returns
+ * the type of jack from the zones delcared in the jack type
+ *
+ * @micbias_voltage: mic bias voltage at adc channel when jack is plugged in
+ *
+ * Based on the mic bias value passed, this function helps identify
+ * the type of jack from the already delcared jack zones
+ */
+int snd_soc_jack_get_type(struct snd_soc_jack *jack, int micbias_voltage)
+{
+ struct snd_soc_jack_zone *zone;
+
+ list_for_each_entry(zone, &jack->jack_zones, list) {
+ if (micbias_voltage >= zone->min_mv &&
+ micbias_voltage < zone->max_mv)
+ return zone->jack_type;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_soc_jack_get_type);
+
+/**
* snd_soc_jack_add_pins - Associate DAPM pins with an ASoC jack
*
* @jack: ASoC jack
@@ -194,7 +240,7 @@ static void snd_soc_jack_gpio_detect(struct snd_soc_jack_gpio *gpio)
int enable;
int report;
- enable = gpio_get_value(gpio->gpio);
+ enable = gpio_get_value_cansleep(gpio->gpio);
if (gpio->invert)
enable = !enable;
@@ -284,6 +330,14 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
if (ret)
goto err;
+ if (gpios[i].wake) {
+ ret = set_irq_wake(gpio_to_irq(gpios[i].gpio), 1);
+ if (ret != 0)
+ printk(KERN_ERR
+ "Failed to mark GPIO %d as wake source: %d\n",
+ gpios[i].gpio, ret);
+ }
+
#ifdef CONFIG_GPIO_SYSFS
/* Expose GPIO value over sysfs for diagnostic purposes */
gpio_export(gpios[i].gpio, false);
diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c
index 1d07b931f3d..3f45e6a439b 100644
--- a/sound/soc/soc-utils.c
+++ b/sound/soc/soc-utils.c
@@ -28,26 +28,9 @@ int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params)
{
int sample_size;
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
- case SNDRV_PCM_FORMAT_S16_BE:
- sample_size = 16;
- break;
- case SNDRV_PCM_FORMAT_S20_3LE:
- case SNDRV_PCM_FORMAT_S20_3BE:
- sample_size = 20;
- break;
- case SNDRV_PCM_FORMAT_S24_LE:
- case SNDRV_PCM_FORMAT_S24_BE:
- sample_size = 24;
- break;
- case SNDRV_PCM_FORMAT_S32_LE:
- case SNDRV_PCM_FORMAT_S32_BE:
- sample_size = 32;
- break;
- default:
- return -ENOTSUPP;
- }
+ sample_size = snd_pcm_format_width(params_format(params));
+ if (sample_size < 0)
+ return sample_size;
return snd_soc_calc_frame_size(sample_size, params_channels(params),
1);
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig
new file mode 100644
index 00000000000..66b504f06c2
--- /dev/null
+++ b/sound/soc/tegra/Kconfig
@@ -0,0 +1,26 @@
+config SND_TEGRA_SOC
+ tristate "SoC Audio for the Tegra System-on-Chip"
+ depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA
+ default m
+ help
+ Say Y or M here if you want support for SoC audio on Tegra.
+
+config SND_TEGRA_SOC_I2S
+ tristate
+ depends on SND_TEGRA_SOC
+ default m
+ help
+ Say Y or M if you want to add support for codecs attached to the
+ Tegra I2S interface. You will also need to select the individual
+ machine drivers to support below.
+
+config SND_TEGRA_SOC_HARMONY
+ tristate "SoC Audio support for Tegra Harmony reference board"
+ depends on SND_TEGRA_SOC && MACH_HARMONY && I2C
+ default m
+ select SND_TEGRA_SOC_I2S
+ select SND_SOC_WM8903
+ help
+ Say Y or M here if you want to add support for SoC audio on the
+ Tegra Harmony reference board.
+
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
new file mode 100644
index 00000000000..fd183d3ab4f
--- /dev/null
+++ b/sound/soc/tegra/Makefile
@@ -0,0 +1,15 @@
+# Tegra platform Support
+snd-soc-tegra-das-objs := tegra_das.o
+snd-soc-tegra-pcm-objs := tegra_pcm.o
+snd-soc-tegra-i2s-objs := tegra_i2s.o
+snd-soc-tegra-utils-objs += tegra_asoc_utils.o
+
+obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-utils.o
+obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-das.o
+obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-pcm.o
+obj-$(CONFIG_SND_TEGRA_SOC_I2S) += snd-soc-tegra-i2s.o
+
+# Tegra machine Support
+snd-soc-tegra-harmony-objs := harmony.o
+
+obj-$(CONFIG_SND_TEGRA_SOC_HARMONY) += snd-soc-tegra-harmony.o
diff --git a/sound/soc/tegra/harmony.c b/sound/soc/tegra/harmony.c
new file mode 100644
index 00000000000..8585957477e
--- /dev/null
+++ b/sound/soc/tegra/harmony.c
@@ -0,0 +1,393 @@
+/*
+ * harmony.c - Harmony machine ASoC driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010-2011 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <asm/mach-types.h>
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+
+#include <mach/harmony_audio.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8903.h"
+
+#include "tegra_das.h"
+#include "tegra_i2s.h"
+#include "tegra_pcm.h"
+#include "tegra_asoc_utils.h"
+
+#define DRV_NAME "tegra-snd-harmony"
+
+#define GPIO_SPKR_EN BIT(0)
+#define GPIO_INT_MIC_EN BIT(1)
+#define GPIO_EXT_MIC_EN BIT(2)
+
+struct tegra_harmony {
+ struct tegra_asoc_utils_data util_data;
+ struct harmony_audio_platform_data *pdata;
+ int gpio_requested;
+};
+
+static int harmony_asoc_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card);
+ int srate, mclk, mclk_change;
+ int err;
+
+ srate = params_rate(params);
+ switch (srate) {
+ case 64000:
+ case 88200:
+ case 96000:
+ mclk = 128 * srate;
+ break;
+ default:
+ mclk = 256 * srate;
+ break;
+ }
+ /* FIXME: Codec only requires >= 3MHz if OSR==0 */
+ while (mclk < 6000000)
+ mclk *= 2;
+
+ err = tegra_asoc_utils_set_rate(&harmony->util_data, srate, mclk,
+ &mclk_change);
+ if (err < 0) {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ dev_err(card->dev, "cpu_dai fmt not set\n");
+ return err;
+ }
+
+ if (mclk_change) {
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai clock not set\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static struct snd_soc_ops harmony_asoc_ops = {
+ .hw_params = harmony_asoc_hw_params,
+};
+
+static struct snd_soc_jack harmony_hp_jack;
+
+static struct snd_soc_jack_pin harmony_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_gpio harmony_hp_jack_gpios[] = {
+ {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 150,
+ .invert = 1,
+ }
+};
+
+static struct snd_soc_jack harmony_mic_jack;
+
+static struct snd_soc_jack_pin harmony_mic_jack_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static int harmony_event_int_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card);
+ struct harmony_audio_platform_data *pdata = harmony->pdata;
+
+ gpio_set_value_cansleep(pdata->gpio_spkr_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget harmony_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Int Spk", harmony_event_int_spk),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+};
+
+static const struct snd_soc_dapm_route harmony_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOUTR"},
+ {"Headphone Jack", NULL, "HPOUTL"},
+ {"Int Spk", NULL, "ROP"},
+ {"Int Spk", NULL, "RON"},
+ {"Int Spk", NULL, "LOP"},
+ {"Int Spk", NULL, "LON"},
+ {"Mic Bias", NULL, "Mic Jack"},
+ {"IN1L", NULL, "Mic Bias"},
+};
+
+static const struct snd_kcontrol_new harmony_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Int Spk"),
+};
+
+static int harmony_asoc_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card);
+ struct harmony_audio_platform_data *pdata = harmony->pdata;
+ int ret;
+
+ ret = gpio_request(pdata->gpio_spkr_en, "spkr_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get spkr_en gpio\n");
+ return ret;
+ }
+ harmony->gpio_requested |= GPIO_SPKR_EN;
+
+ gpio_direction_output(pdata->gpio_spkr_en, 0);
+
+ ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get int_mic_en gpio\n");
+ return ret;
+ }
+ harmony->gpio_requested |= GPIO_INT_MIC_EN;
+
+ /* Disable int mic; enable signal is active-high */
+ gpio_direction_output(pdata->gpio_int_mic_en, 0);
+
+ ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get ext_mic_en gpio\n");
+ return ret;
+ }
+ harmony->gpio_requested |= GPIO_EXT_MIC_EN;
+
+ /* Enable ext mic; enable signal is active-low */
+ gpio_direction_output(pdata->gpio_ext_mic_en, 0);
+
+ ret = snd_soc_add_controls(codec, harmony_controls,
+ ARRAY_SIZE(harmony_controls));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_new_controls(dapm, harmony_dapm_widgets,
+ ARRAY_SIZE(harmony_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, harmony_audio_map,
+ ARRAY_SIZE(harmony_audio_map));
+
+ harmony_hp_jack_gpios[0].gpio = pdata->gpio_hp_det;
+ snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+ &harmony_hp_jack);
+ snd_soc_jack_add_pins(&harmony_hp_jack,
+ ARRAY_SIZE(harmony_hp_jack_pins),
+ harmony_hp_jack_pins);
+ snd_soc_jack_add_gpios(&harmony_hp_jack,
+ ARRAY_SIZE(harmony_hp_jack_gpios),
+ harmony_hp_jack_gpios);
+
+ snd_soc_jack_new(codec, "Mic Jack", SND_JACK_MICROPHONE,
+ &harmony_mic_jack);
+ snd_soc_jack_add_pins(&harmony_mic_jack,
+ ARRAY_SIZE(harmony_mic_jack_pins),
+ harmony_mic_jack_pins);
+ wm8903_mic_detect(codec, &harmony_mic_jack, SND_JACK_MICROPHONE, 0);
+
+ snd_soc_dapm_force_enable_pin(dapm, "Mic Bias");
+
+ snd_soc_dapm_nc_pin(dapm, "IN3L");
+ snd_soc_dapm_nc_pin(dapm, "IN3R");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUTL");
+ snd_soc_dapm_nc_pin(dapm, "LINEOUTR");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link harmony_wm8903_dai = {
+ .name = "WM8903",
+ .stream_name = "WM8903 PCM",
+ .codec_name = "wm8903.0-001a",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "tegra-i2s.0",
+ .codec_dai_name = "wm8903-hifi",
+ .init = harmony_asoc_init,
+ .ops = &harmony_asoc_ops,
+};
+
+static struct snd_soc_card snd_soc_harmony = {
+ .name = "tegra-harmony",
+ .dai_link = &harmony_wm8903_dai,
+ .num_links = 1,
+};
+
+static __devinit int tegra_snd_harmony_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_harmony;
+ struct tegra_harmony *harmony;
+ struct harmony_audio_platform_data *pdata;
+ int ret;
+
+ if (!machine_is_harmony()) {
+ dev_err(&pdev->dev, "Not running on Tegra Harmony!\n");
+ return -ENODEV;
+ }
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ return -EINVAL;
+ }
+
+ harmony = kzalloc(sizeof(struct tegra_harmony), GFP_KERNEL);
+ if (!harmony) {
+ dev_err(&pdev->dev, "Can't allocate tegra_harmony\n");
+ return -ENOMEM;
+ }
+
+ harmony->pdata = pdata;
+
+ ret = tegra_asoc_utils_init(&harmony->util_data, &pdev->dev);
+ if (ret)
+ goto err_free_harmony;
+
+ card->dev = &pdev->dev;
+ platform_set_drvdata(pdev, card);
+ snd_soc_card_set_drvdata(card, harmony);
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto err_clear_drvdata;
+ }
+
+ return 0;
+
+err_clear_drvdata:
+ snd_soc_card_set_drvdata(card, NULL);
+ platform_set_drvdata(pdev, NULL);
+ card->dev = NULL;
+ tegra_asoc_utils_fini(&harmony->util_data);
+err_free_harmony:
+ kfree(harmony);
+ return ret;
+}
+
+static int __devexit tegra_snd_harmony_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct tegra_harmony *harmony = snd_soc_card_get_drvdata(card);
+ struct harmony_audio_platform_data *pdata = harmony->pdata;
+
+ snd_soc_unregister_card(card);
+
+ snd_soc_card_set_drvdata(card, NULL);
+ platform_set_drvdata(pdev, NULL);
+ card->dev = NULL;
+
+ tegra_asoc_utils_fini(&harmony->util_data);
+
+ if (harmony->gpio_requested & GPIO_EXT_MIC_EN)
+ gpio_free(pdata->gpio_ext_mic_en);
+ if (harmony->gpio_requested & GPIO_INT_MIC_EN)
+ gpio_free(pdata->gpio_int_mic_en);
+ if (harmony->gpio_requested & GPIO_SPKR_EN)
+ gpio_free(pdata->gpio_spkr_en);
+
+ kfree(harmony);
+
+ return 0;
+}
+
+static struct platform_driver tegra_snd_harmony_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_snd_harmony_probe,
+ .remove = __devexit_p(tegra_snd_harmony_remove),
+};
+
+static int __init snd_tegra_harmony_init(void)
+{
+ return platform_driver_register(&tegra_snd_harmony_driver);
+}
+module_init(snd_tegra_harmony_init);
+
+static void __exit snd_tegra_harmony_exit(void)
+{
+ platform_driver_unregister(&tegra_snd_harmony_driver);
+}
+module_exit(snd_tegra_harmony_exit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Harmony machine ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c
new file mode 100644
index 00000000000..52f0a3f9ce4
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_utils.c
@@ -0,0 +1,155 @@
+/*
+ * tegra_asoc_utils.c - Harmony machine ASoC driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+
+#include "tegra_asoc_utils.h"
+
+int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
+ int mclk, int *mclk_change)
+{
+ int new_baseclock;
+ int err;
+
+ switch (srate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ new_baseclock = 56448000;
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ new_baseclock = 73728000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *mclk_change = ((new_baseclock != data->set_baseclock) ||
+ (mclk != data->set_mclk));
+ if (!*mclk_change)
+ return 0;
+
+ data->set_baseclock = 0;
+ data->set_mclk = 0;
+
+ clk_disable(data->clk_cdev1);
+ clk_disable(data->clk_pll_a_out0);
+ clk_disable(data->clk_pll_a);
+
+ err = clk_set_rate(data->clk_pll_a, new_baseclock);
+ if (err) {
+ dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
+ return err;
+ }
+
+ err = clk_set_rate(data->clk_pll_a_out0, mclk);
+ if (err) {
+ dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
+ return err;
+ }
+
+ /* Don't set cdev1 rate; its locked to pll_a_out0 */
+
+ err = clk_enable(data->clk_pll_a);
+ if (err) {
+ dev_err(data->dev, "Can't enable pll_a: %d\n", err);
+ return err;
+ }
+
+ err = clk_enable(data->clk_pll_a_out0);
+ if (err) {
+ dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err);
+ return err;
+ }
+
+ err = clk_enable(data->clk_cdev1);
+ if (err) {
+ dev_err(data->dev, "Can't enable cdev1: %d\n", err);
+ return err;
+ }
+
+ data->set_baseclock = new_baseclock;
+ data->set_mclk = mclk;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate);
+
+int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
+ struct device *dev)
+{
+ int ret;
+
+ data->dev = dev;
+
+ data->clk_pll_a = clk_get_sys(NULL, "pll_a");
+ if (IS_ERR(data->clk_pll_a)) {
+ dev_err(data->dev, "Can't retrieve clk pll_a\n");
+ ret = PTR_ERR(data->clk_pll_a);
+ goto err;
+ }
+
+ data->clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0");
+ if (IS_ERR(data->clk_pll_a_out0)) {
+ dev_err(data->dev, "Can't retrieve clk pll_a_out0\n");
+ ret = PTR_ERR(data->clk_pll_a_out0);
+ goto err_put_pll_a;
+ }
+
+ data->clk_cdev1 = clk_get_sys(NULL, "cdev1");
+ if (IS_ERR(data->clk_cdev1)) {
+ dev_err(data->dev, "Can't retrieve clk cdev1\n");
+ ret = PTR_ERR(data->clk_cdev1);
+ goto err_put_pll_a_out0;
+ }
+
+ return 0;
+
+err_put_pll_a_out0:
+ clk_put(data->clk_pll_a_out0);
+err_put_pll_a:
+ clk_put(data->clk_pll_a);
+err:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_init);
+
+void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data)
+{
+ clk_put(data->clk_cdev1);
+ clk_put(data->clk_pll_a_out0);
+ clk_put(data->clk_pll_a);
+}
+EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra ASoC utility code");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h
new file mode 100644
index 00000000000..bbba7afdfc2
--- /dev/null
+++ b/sound/soc/tegra/tegra_asoc_utils.h
@@ -0,0 +1,45 @@
+/*
+ * tegra_asoc_utils.h - Definitions for Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_ASOC_UTILS_H__
+#define __TEGRA_ASOC_UTILS_H_
+
+struct clk;
+struct device;
+
+struct tegra_asoc_utils_data {
+ struct device *dev;
+ struct clk *clk_pll_a;
+ struct clk *clk_pll_a_out0;
+ struct clk *clk_cdev1;
+ int set_baseclock;
+ int set_mclk;
+};
+
+int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
+ int mclk, int *mclk_change);
+int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
+ struct device *dev);
+void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data);
+
+#endif
+
diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c
new file mode 100644
index 00000000000..9f24ef73f2c
--- /dev/null
+++ b/sound/soc/tegra/tegra_das.c
@@ -0,0 +1,265 @@
+/*
+ * tegra_das.c - Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <mach/iomap.h>
+#include <sound/soc.h>
+#include "tegra_das.h"
+
+#define DRV_NAME "tegra-das"
+
+static struct tegra_das *das;
+
+static inline void tegra_das_write(u32 reg, u32 val)
+{
+ __raw_writel(val, das->regs + reg);
+}
+
+static inline u32 tegra_das_read(u32 reg)
+{
+ return __raw_readl(das->regs + reg);
+}
+
+int tegra_das_connect_dap_to_dac(int dap, int dac)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac);
+
+int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master,
+ int sdata1rx, int sdata2rx)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P |
+ !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P |
+ !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P |
+ !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap);
+
+int tegra_das_connect_dac_to_dap(int dac, int dap)
+{
+ u32 addr;
+ u32 reg;
+
+ if (!das)
+ return -ENODEV;
+
+ addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL +
+ (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE);
+ reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P |
+ dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P |
+ dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P;
+
+ tegra_das_write(addr, reg);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap);
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_das_show(struct seq_file *s, void *unused)
+{
+ int i;
+ u32 addr;
+ u32 reg;
+
+ for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) {
+ addr = TEGRA_DAS_DAP_CTRL_SEL +
+ (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE);
+ reg = tegra_das_read(addr);
+ seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg);
+ }
+
+ for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) {
+ addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL +
+ (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE);
+ reg = tegra_das_read(addr);
+ seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n",
+ i, reg);
+ }
+
+ return 0;
+}
+
+static int tegra_das_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_das_show, inode->i_private);
+}
+
+static const struct file_operations tegra_das_debug_fops = {
+ .open = tegra_das_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void tegra_das_debug_add(struct tegra_das *das)
+{
+ das->debug = debugfs_create_file(DRV_NAME, S_IRUGO,
+ snd_soc_debugfs_root, das,
+ &tegra_das_debug_fops);
+}
+
+static void tegra_das_debug_remove(struct tegra_das *das)
+{
+ if (das->debug)
+ debugfs_remove(das->debug);
+}
+#else
+static inline void tegra_das_debug_add(struct tegra_das *das)
+{
+}
+
+static inline void tegra_das_debug_remove(struct tegra_das *das)
+{
+}
+#endif
+
+static int __devinit tegra_das_probe(struct platform_device *pdev)
+{
+ struct resource *res, *region;
+ int ret = 0;
+
+ if (das)
+ return -ENODEV;
+
+ das = kzalloc(sizeof(struct tegra_das), GFP_KERNEL);
+ if (!das) {
+ dev_err(&pdev->dev, "Can't allocate tegra_das\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ das->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -ENODEV;
+ goto err_free;
+ }
+
+ region = request_mem_region(res->start, resource_size(res),
+ pdev->name);
+ if (!region) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ das->regs = ioremap(res->start, resource_size(res));
+ if (!das->regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release;
+ }
+
+ tegra_das_debug_add(das);
+
+ platform_set_drvdata(pdev, das);
+
+ return 0;
+
+err_release:
+ release_mem_region(res->start, resource_size(res));
+err_free:
+ kfree(das);
+ das = 0;
+exit:
+ return ret;
+}
+
+static int __devexit tegra_das_remove(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ if (!das)
+ return -ENODEV;
+
+ platform_set_drvdata(pdev, NULL);
+
+ tegra_das_debug_remove(das);
+
+ iounmap(das->regs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ kfree(das);
+ das = 0;
+
+ return 0;
+}
+
+static struct platform_driver tegra_das_driver = {
+ .probe = tegra_das_probe,
+ .remove = __devexit_p(tegra_das_remove),
+ .driver = {
+ .name = DRV_NAME,
+ },
+};
+
+static int __init tegra_das_modinit(void)
+{
+ return platform_driver_register(&tegra_das_driver);
+}
+module_init(tegra_das_modinit);
+
+static void __exit tegra_das_modexit(void)
+{
+ platform_driver_unregister(&tegra_das_driver);
+}
+module_exit(tegra_das_modexit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra DAS driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h
new file mode 100644
index 00000000000..2c96c7b3c45
--- /dev/null
+++ b/sound/soc/tegra/tegra_das.h
@@ -0,0 +1,135 @@
+/*
+ * tegra_das.h - Definitions for Tegra DAS driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_DAS_H__
+#define __TEGRA_DAS_H__
+
+/* Register TEGRA_DAS_DAP_CTRL_SEL */
+#define TEGRA_DAS_DAP_CTRL_SEL 0x00
+#define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5
+#define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0
+#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5
+
+/* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */
+#define TEGRA_DAS_DAP_SEL_DAC1 0
+#define TEGRA_DAS_DAP_SEL_DAC2 1
+#define TEGRA_DAS_DAP_SEL_DAC3 2
+#define TEGRA_DAS_DAP_SEL_DAP1 16
+#define TEGRA_DAS_DAP_SEL_DAP2 17
+#define TEGRA_DAS_DAP_SEL_DAP3 18
+#define TEGRA_DAS_DAP_SEL_DAP4 19
+#define TEGRA_DAS_DAP_SEL_DAP5 20
+
+/* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0
+#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4
+
+/*
+ * Values for:
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL
+ * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL
+ */
+#define TEGRA_DAS_DAC_SEL_DAP1 0
+#define TEGRA_DAS_DAC_SEL_DAP2 1
+#define TEGRA_DAS_DAC_SEL_DAP3 2
+#define TEGRA_DAS_DAC_SEL_DAP4 3
+#define TEGRA_DAS_DAC_SEL_DAP5 4
+
+/*
+ * Names/IDs of the DACs/DAPs.
+ */
+
+#define TEGRA_DAS_DAP_ID_1 0
+#define TEGRA_DAS_DAP_ID_2 1
+#define TEGRA_DAS_DAP_ID_3 2
+#define TEGRA_DAS_DAP_ID_4 3
+#define TEGRA_DAS_DAP_ID_5 4
+
+#define TEGRA_DAS_DAC_ID_1 0
+#define TEGRA_DAS_DAC_ID_2 1
+#define TEGRA_DAS_DAC_ID_3 2
+
+struct tegra_das {
+ struct device *dev;
+ void __iomem *regs;
+ struct dentry *debug;
+};
+
+/*
+ * Terminology:
+ * DAS: Digital audio switch (HW module controlled by this driver)
+ * DAP: Digital audio port (port/pins on Tegra device)
+ * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere)
+ *
+ * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific
+ * DAC, or another DAP. When DAPs are connected, one must be the master and
+ * one the slave. Each DAC allows selection of a specific DAP for input, to
+ * cater for the case where N DAPs are connected to 1 DAC for broadcast
+ * output.
+ *
+ * This driver is dumb; no attempt is made to ensure that a valid routing
+ * configuration is programmed.
+ */
+
+/*
+ * Connect a DAP to to a DAC
+ * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_*
+ * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC*
+ */
+extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel);
+
+/*
+ * Connect a DAP to to another DAP
+ * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_*
+ * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP*
+ * master: Is this DAP the master (1) or slave (0)
+ * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0)
+ * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0)
+ */
+extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel,
+ int master, int sdata1rx,
+ int sdata2rx);
+
+/*
+ * Connect a DAC's input to a DAP
+ * (DAC outputs are selected by the DAP)
+ * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_*
+ * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP*
+ */
+extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel);
+
+#endif
diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c
new file mode 100644
index 00000000000..4f5e2c90b02
--- /dev/null
+++ b/sound/soc/tegra/tegra_i2s.c
@@ -0,0 +1,503 @@
+/*
+ * tegra_i2s.c - Tegra I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <mach/iomap.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "tegra_das.h"
+#include "tegra_i2s.h"
+
+#define DRV_NAME "tegra-i2s"
+
+static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val)
+{
+ __raw_writel(val, i2s->regs + reg);
+}
+
+static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg)
+{
+ return __raw_readl(i2s->regs + reg);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int tegra_i2s_show(struct seq_file *s, void *unused)
+{
+#define REG(r) { r, #r }
+ static const struct {
+ int offset;
+ const char *name;
+ } regs[] = {
+ REG(TEGRA_I2S_CTRL),
+ REG(TEGRA_I2S_STATUS),
+ REG(TEGRA_I2S_TIMING),
+ REG(TEGRA_I2S_FIFO_SCR),
+ REG(TEGRA_I2S_PCM_CTRL),
+ REG(TEGRA_I2S_NW_CTRL),
+ REG(TEGRA_I2S_TDM_CTRL),
+ REG(TEGRA_I2S_TDM_TX_RX_CTRL),
+ };
+#undef REG
+
+ struct tegra_i2s *i2s = s->private;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ u32 val = tegra_i2s_read(i2s, regs[i].offset);
+ seq_printf(s, "%s = %08x\n", regs[i].name, val);
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, tegra_i2s_show, inode->i_private);
+}
+
+static const struct file_operations tegra_i2s_debug_fops = {
+ .open = tegra_i2s_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id)
+{
+ char name[] = DRV_NAME ".0";
+
+ snprintf(name, sizeof(name), DRV_NAME".%1d", id);
+ i2s->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root,
+ i2s, &tegra_i2s_debug_fops);
+}
+
+static void tegra_i2s_debug_remove(struct tegra_i2s *i2s)
+{
+ if (i2s->debug)
+ debugfs_remove(i2s->debug);
+}
+#else
+static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s)
+{
+}
+
+static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s)
+{
+}
+#endif
+
+static int tegra_i2s_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE;
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK |
+ TEGRA_I2S_CTRL_LRCK_MASK);
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_DSP_A:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM;
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct device *dev = substream->pcm->card->dev;
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+ u32 reg;
+ int ret, sample_size, srate, i2sclock, bitcnt;
+
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16;
+ sample_size = 16;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24;
+ sample_size = 24;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32;
+ sample_size = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ srate = params_rate(params);
+
+ /* Final "* 2" required by Tegra hardware */
+ i2sclock = srate * params_channels(params) * sample_size * 2;
+
+ ret = clk_set_rate(i2s->clk_i2s, i2sclock);
+ if (ret) {
+ dev_err(dev, "Can't set I2S clock rate: %d\n", ret);
+ return ret;
+ }
+
+ bitcnt = (i2sclock / (2 * srate)) - 1;
+ if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US)
+ return -EINVAL;
+ reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT;
+
+ if (i2sclock % (2 * srate))
+ reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE;
+
+ tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg);
+
+ tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR,
+ TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS |
+ TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS);
+
+ return 0;
+}
+
+static void tegra_i2s_start_playback(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_stop_playback(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_start_capture(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static void tegra_i2s_stop_capture(struct tegra_i2s *i2s)
+{
+ i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE;
+ tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl);
+}
+
+static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ if (!i2s->clk_refs)
+ clk_enable(i2s->clk_i2s);
+ i2s->clk_refs++;
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra_i2s_start_playback(i2s);
+ else
+ tegra_i2s_start_capture(i2s);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ tegra_i2s_stop_playback(i2s);
+ else
+ tegra_i2s_stop_capture(i2s);
+ i2s->clk_refs--;
+ if (!i2s->clk_refs)
+ clk_disable(i2s->clk_i2s);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tegra_i2s_probe(struct snd_soc_dai *dai)
+{
+ struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai);
+
+ dai->capture_dma_data = &i2s->capture_dma_data;
+ dai->playback_dma_data = &i2s->playback_dma_data;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops tegra_i2s_dai_ops = {
+ .set_fmt = tegra_i2s_set_fmt,
+ .hw_params = tegra_i2s_hw_params,
+ .trigger = tegra_i2s_trigger,
+};
+
+struct snd_soc_dai_driver tegra_i2s_dai[] = {
+ {
+ .name = DRV_NAME ".0",
+ .probe = tegra_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
+ .symmetric_rates = 1,
+ },
+ {
+ .name = DRV_NAME ".1",
+ .probe = tegra_i2s_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tegra_i2s_dai_ops,
+ .symmetric_rates = 1,
+ },
+};
+
+static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev)
+{
+ struct tegra_i2s * i2s;
+ char clk_name[12]; /* tegra-i2s.0 */
+ struct resource *mem, *memregion, *dmareq;
+ int ret;
+
+ if ((pdev->id < 0) ||
+ (pdev->id >= ARRAY_SIZE(tegra_i2s_dai))) {
+ dev_err(&pdev->dev, "ID %d out of range\n", pdev->id);
+ return -EINVAL;
+ }
+
+ /*
+ * FIXME: Until a codec driver exists for the tegra DAS, hard-code a
+ * 1:1 mapping between audio controllers and audio ports.
+ */
+ ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1 + pdev->id,
+ TEGRA_DAS_DAP_SEL_DAC1 + pdev->id);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't set up DAP connection\n");
+ return ret;
+ }
+ ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1 + pdev->id,
+ TEGRA_DAS_DAC_SEL_DAP1 + pdev->id);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't set up DAC connection\n");
+ return ret;
+ }
+
+ i2s = kzalloc(sizeof(struct tegra_i2s), GFP_KERNEL);
+ if (!i2s) {
+ dev_err(&pdev->dev, "Can't allocate tegra_i2s\n");
+ ret = -ENOMEM;
+ goto exit;
+ }
+ dev_set_drvdata(&pdev->dev, i2s);
+
+ snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id);
+ i2s->clk_i2s = clk_get_sys(clk_name, NULL);
+ if (IS_ERR(i2s->clk_i2s)) {
+ dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err_free;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "No memory resource\n");
+ ret = -ENODEV;
+ goto err_clk_put;
+ }
+
+ dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+ if (!dmareq) {
+ dev_err(&pdev->dev, "No DMA resource\n");
+ ret = -ENODEV;
+ goto err_clk_put;
+ }
+
+ memregion = request_mem_region(mem->start, resource_size(mem),
+ DRV_NAME);
+ if (!memregion) {
+ dev_err(&pdev->dev, "Memory region already claimed\n");
+ ret = -EBUSY;
+ goto err_clk_put;
+ }
+
+ i2s->regs = ioremap(mem->start, resource_size(mem));
+ if (!i2s->regs) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err_release;
+ }
+
+ i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2;
+ i2s->capture_dma_data.wrap = 4;
+ i2s->capture_dma_data.width = 32;
+ i2s->capture_dma_data.req_sel = dmareq->start;
+
+ i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1;
+ i2s->playback_dma_data.wrap = 4;
+ i2s->playback_dma_data.width = 32;
+ i2s->playback_dma_data.req_sel = dmareq->start;
+
+ i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED;
+
+ ret = snd_soc_register_dai(&pdev->dev, &tegra_i2s_dai[pdev->id]);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ goto err_unmap;
+ }
+
+ tegra_i2s_debug_add(i2s, pdev->id);
+
+ return 0;
+
+err_unmap:
+ iounmap(i2s->regs);
+err_release:
+ release_mem_region(mem->start, resource_size(mem));
+err_clk_put:
+ clk_put(i2s->clk_i2s);
+err_free:
+ kfree(i2s);
+exit:
+ return ret;
+}
+
+static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev)
+{
+ struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev);
+ struct resource *res;
+
+ snd_soc_unregister_dai(&pdev->dev);
+
+ tegra_i2s_debug_remove(i2s);
+
+ iounmap(i2s->regs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ release_mem_region(res->start, resource_size(res));
+
+ clk_put(i2s->clk_i2s);
+
+ kfree(i2s);
+
+ return 0;
+}
+
+static struct platform_driver tegra_i2s_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_i2s_platform_probe,
+ .remove = __devexit_p(tegra_i2s_platform_remove),
+};
+
+static int __init snd_tegra_i2s_init(void)
+{
+ return platform_driver_register(&tegra_i2s_driver);
+}
+module_init(snd_tegra_i2s_init);
+
+static void __exit snd_tegra_i2s_exit(void)
+{
+ platform_driver_unregister(&tegra_i2s_driver);
+}
+module_exit(snd_tegra_i2s_exit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra I2S ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h
new file mode 100644
index 00000000000..2b38a096f46
--- /dev/null
+++ b/sound/soc/tegra/tegra_i2s.h
@@ -0,0 +1,165 @@
+/*
+ * tegra_i2s.h - Definitions for Tegra I2S driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_I2S_H__
+#define __TEGRA_I2S_H__
+
+#include "tegra_pcm.h"
+
+/* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */
+
+#define TEGRA_I2S_CTRL 0x00
+#define TEGRA_I2S_STATUS 0x04
+#define TEGRA_I2S_TIMING 0x08
+#define TEGRA_I2S_FIFO_SCR 0x0c
+#define TEGRA_I2S_PCM_CTRL 0x10
+#define TEGRA_I2S_NW_CTRL 0x14
+#define TEGRA_I2S_TDM_CTRL 0x20
+#define TEGRA_I2S_TDM_TX_RX_CTRL 0x24
+#define TEGRA_I2S_FIFO1 0x40
+#define TEGRA_I2S_FIFO2 0x80
+
+/* Fields in TEGRA_I2S_CTRL */
+
+#define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30)
+#define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29)
+#define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28)
+#define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27)
+#define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26)
+#define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25)
+
+#define TEGRA_I2S_LRCK_LEFT_LOW 0
+#define TEGRA_I2S_LRCK_RIGHT_LOW 1
+
+#define TEGRA_I2S_CTRL_LRCK_SHIFT 24
+#define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT)
+#define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT)
+
+#define TEGRA_I2S_BIT_FORMAT_I2S 0
+#define TEGRA_I2S_BIT_FORMAT_RJM 1
+#define TEGRA_I2S_BIT_FORMAT_LJM 2
+#define TEGRA_I2S_BIT_FORMAT_DSP 3
+
+#define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10
+#define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT)
+
+#define TEGRA_I2S_BIT_SIZE_16 0
+#define TEGRA_I2S_BIT_SIZE_20 1
+#define TEGRA_I2S_BIT_SIZE_24 2
+#define TEGRA_I2S_BIT_SIZE_32 3
+
+#define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8
+#define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+#define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT)
+
+#define TEGRA_I2S_FIFO_16_LSB 0
+#define TEGRA_I2S_FIFO_20_LSB 1
+#define TEGRA_I2S_FIFO_24_LSB 2
+#define TEGRA_I2S_FIFO_32 3
+#define TEGRA_I2S_FIFO_PACKED 7
+
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+#define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT)
+
+#define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3)
+#define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2)
+#define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1)
+#define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0)
+
+/* Fields in TEGRA_I2S_STATUS */
+
+#define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31)
+#define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30)
+#define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29)
+#define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28)
+#define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3)
+#define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2)
+#define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1)
+#define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0)
+
+/* Fields in TEGRA_I2S_TIMING */
+
+#define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12)
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff
+#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT)
+
+/* Fields in TEGRA_I2S_FIFO_SCR */
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24
+#define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16
+#define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8)
+
+#define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0
+#define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1
+#define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2
+#define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3
+
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT)
+
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT)
+
+struct tegra_i2s {
+ struct clk *clk_i2s;
+ int clk_refs;
+ struct tegra_pcm_dma_params capture_dma_data;
+ struct tegra_pcm_dma_params playback_dma_data;
+ void __iomem *regs;
+ struct dentry *debug;
+ u32 reg_ctrl;
+};
+
+#endif
diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c
new file mode 100644
index 00000000000..3c271f95358
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.c
@@ -0,0 +1,404 @@
+/*
+ * tegra_pcm.c - Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ * Vijay Mali <vmali@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "tegra_pcm.h"
+
+#define DRV_NAME "tegra-pcm-audio"
+
+static const struct snd_pcm_hardware tegra_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_INTERLEAVED,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .period_bytes_min = 1024,
+ .period_bytes_max = PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 8,
+ .buffer_bytes_max = PAGE_SIZE * 8,
+ .fifo_size = 4,
+};
+
+static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd)
+{
+ struct snd_pcm_substream *substream = prtd->substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ struct tegra_dma_req *dma_req;
+ unsigned long addr;
+
+ dma_req = &prtd->dma_req[prtd->dma_req_idx];
+ prtd->dma_req_idx = 1 - prtd->dma_req_idx;
+
+ addr = buf->addr + prtd->dma_pos;
+ prtd->dma_pos += dma_req->size;
+ if (prtd->dma_pos >= prtd->dma_pos_end)
+ prtd->dma_pos = 0;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dma_req->source_addr = addr;
+ else
+ dma_req->dest_addr = addr;
+
+ tegra_dma_enqueue_req(prtd->dma_chan, dma_req);
+}
+
+static void dma_complete_callback(struct tegra_dma_req *req)
+{
+ struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev;
+ struct snd_pcm_substream *substream = prtd->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ spin_lock(&prtd->lock);
+
+ if (!prtd->running) {
+ spin_unlock(&prtd->lock);
+ return;
+ }
+
+ if (++prtd->period_index >= runtime->periods)
+ prtd->period_index = 0;
+
+ tegra_pcm_queue_dma(prtd);
+
+ spin_unlock(&prtd->lock);
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static void setup_dma_tx_request(struct tegra_dma_req *req,
+ struct tegra_pcm_dma_params * dmap)
+{
+ req->complete = dma_complete_callback;
+ req->to_memory = false;
+ req->dest_addr = dmap->addr;
+ req->dest_wrap = dmap->wrap;
+ req->source_bus_width = 32;
+ req->source_wrap = 0;
+ req->dest_bus_width = dmap->width;
+ req->req_sel = dmap->req_sel;
+}
+
+static void setup_dma_rx_request(struct tegra_dma_req *req,
+ struct tegra_pcm_dma_params * dmap)
+{
+ req->complete = dma_complete_callback;
+ req->to_memory = true;
+ req->source_addr = dmap->addr;
+ req->dest_wrap = 0;
+ req->source_bus_width = dmap->width;
+ req->source_wrap = dmap->wrap;
+ req->dest_bus_width = 32;
+ req->req_sel = dmap->req_sel;
+}
+
+static int tegra_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra_pcm_dma_params * dmap;
+ int ret = 0;
+
+ prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = prtd;
+ prtd->substream = substream;
+
+ spin_lock_init(&prtd->lock);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ setup_dma_tx_request(&prtd->dma_req[0], dmap);
+ setup_dma_tx_request(&prtd->dma_req[1], dmap);
+ } else {
+ dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ setup_dma_rx_request(&prtd->dma_req[0], dmap);
+ setup_dma_rx_request(&prtd->dma_req[1], dmap);
+ }
+
+ prtd->dma_req[0].dev = prtd;
+ prtd->dma_req[1].dev = prtd;
+
+ prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT);
+ if (prtd->dma_chan == NULL) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* Set HW params now that initialization is complete */
+ snd_soc_set_runtime_hwparams(substream, &tegra_pcm_hardware);
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+
+err:
+ if (prtd->dma_chan) {
+ tegra_dma_free_channel(prtd->dma_chan);
+ }
+
+ kfree(prtd);
+
+ return ret;
+}
+
+static int tegra_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ tegra_dma_free_channel(prtd->dma_chan);
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int tegra_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ prtd->dma_req[0].size = params_period_bytes(params);
+ prtd->dma_req[1].size = prtd->dma_req[0].size;
+
+ return 0;
+}
+
+static int tegra_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+
+ return 0;
+}
+
+static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+ unsigned long flags;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ prtd->dma_pos = 0;
+ prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size);
+ prtd->period_index = 0;
+ prtd->dma_req_idx = 0;
+ /* Fall-through */
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock_irqsave(&prtd->lock, flags);
+ prtd->running = 1;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ tegra_pcm_queue_dma(prtd);
+ tegra_pcm_queue_dma(prtd);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ spin_lock_irqsave(&prtd->lock, flags);
+ prtd->running = 0;
+ spin_unlock_irqrestore(&prtd->lock, flags);
+ tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]);
+ tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct tegra_runtime_data *prtd = runtime->private_data;
+
+ return prtd->period_index * runtime->period_size;
+}
+
+
+static int tegra_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops tegra_pcm_ops = {
+ .open = tegra_pcm_open,
+ .close = tegra_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = tegra_pcm_hw_params,
+ .hw_free = tegra_pcm_hw_free,
+ .trigger = tegra_pcm_trigger,
+ .pointer = tegra_pcm_pointer,
+ .mmap = tegra_pcm_mmap,
+};
+
+static int tegra_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = tegra_pcm_hardware.buffer_bytes_max;
+
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void tegra_pcm_deallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ if (!buf->area)
+ return;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+}
+
+static u64 tegra_dma_mask = DMA_BIT_MASK(32);
+
+static int tegra_pcm_new(struct snd_card *card,
+ struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &tegra_dma_mask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = 0xffffffff;
+
+ if (dai->driver->playback.channels_min) {
+ ret = tegra_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto err;
+ }
+
+ if (dai->driver->capture.channels_min) {
+ ret = tegra_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto err_free_play;
+ }
+
+ return 0;
+
+err_free_play:
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+err:
+ return ret;
+}
+
+static void tegra_pcm_free(struct snd_pcm *pcm)
+{
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE);
+ tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK);
+}
+
+struct snd_soc_platform_driver tegra_pcm_platform = {
+ .ops = &tegra_pcm_ops,
+ .pcm_new = tegra_pcm_new,
+ .pcm_free = tegra_pcm_free,
+};
+
+static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform);
+}
+
+static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver tegra_pcm_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = tegra_pcm_platform_probe,
+ .remove = __devexit_p(tegra_pcm_platform_remove),
+};
+
+static int __init snd_tegra_pcm_init(void)
+{
+ return platform_driver_register(&tegra_pcm_driver);
+}
+module_init(snd_tegra_pcm_init);
+
+static void __exit snd_tegra_pcm_exit(void)
+{
+ platform_driver_unregister(&tegra_pcm_driver);
+}
+module_exit(snd_tegra_pcm_exit);
+
+MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
+MODULE_DESCRIPTION("Tegra PCM ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h
new file mode 100644
index 00000000000..dbb90339fe0
--- /dev/null
+++ b/sound/soc/tegra/tegra_pcm.h
@@ -0,0 +1,55 @@
+/*
+ * tegra_pcm.h - Definitions for Tegra PCM driver
+ *
+ * Author: Stephen Warren <swarren@nvidia.com>
+ * Copyright (C) 2010 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * Copyright (c) 2009-2010, NVIDIA Corporation.
+ * Scott Peterson <speterson@nvidia.com>
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Iliyan Malchev <malchev@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef __TEGRA_PCM_H__
+#define __TEGRA_PCM_H__
+
+#include <mach/dma.h>
+
+struct tegra_pcm_dma_params {
+ unsigned long addr;
+ unsigned long wrap;
+ unsigned long width;
+ unsigned long req_sel;
+};
+
+struct tegra_runtime_data {
+ struct snd_pcm_substream *substream;
+ spinlock_t lock;
+ int running;
+ int dma_pos;
+ int dma_pos_end;
+ int period_index;
+ int dma_req_idx;
+ struct tegra_dma_req dma_req[2];
+ struct tegra_dma_channel *dma_chan;
+};
+
+#endif