v4.19.13 snapshot.
diff --git a/sound/pci/cs5535audio/Makefile b/sound/pci/cs5535audio/Makefile
new file mode 100644
index 0000000..a8f75f8
--- /dev/null
+++ b/sound/pci/cs5535audio/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for cs5535audio
+#
+
+snd-cs5535audio-y := cs5535audio.o cs5535audio_pcm.o
+snd-cs5535audio-$(CONFIG_PM_SLEEP) += cs5535audio_pm.o
+snd-cs5535audio-$(CONFIG_OLPC) += cs5535audio_olpc.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_CS5535AUDIO) += snd-cs5535audio.o
diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c
new file mode 100644
index 0000000..4590086
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio.c
@@ -0,0 +1,411 @@
+/*
+ * Driver for audio on multifunction CS5535/6 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * Based on Jaroslav Kysela and Takashi Iwai's examples.
+ * This work was sponsored by CIS(M) Sdn Bhd.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include "cs5535audio.h"
+
+#define DRIVER_NAME "cs5535audio"
+
+static char *ac97_quirk;
+module_param(ac97_quirk, charp, 0444);
+MODULE_PARM_DESC(ac97_quirk, "AC'97 board specific workarounds.");
+
+static const struct ac97_quirk ac97_quirks[] = {
+#if 0 /* Not yet confirmed if all 5536 boards are HP only */
+	{
+		.subvendor = PCI_VENDOR_ID_AMD, 
+		.subdevice = PCI_DEVICE_ID_AMD_CS5536_AUDIO, 
+		.name = "AMD RDK",     
+		.type = AC97_TUNE_HP_ONLY
+	},
+#endif
+	{}
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " DRIVER_NAME);
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " DRIVER_NAME);
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " DRIVER_NAME);
+
+static const struct pci_device_id snd_cs5535audio_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_AUDIO) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_AUDIO) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_cs5535audio_ids);
+
+static void wait_till_cmd_acked(struct cs5535audio *cs5535au, unsigned long timeout)
+{
+	unsigned int tmp;
+	do {
+		tmp = cs_readl(cs5535au, ACC_CODEC_CNTL);
+		if (!(tmp & CMD_NEW))
+			break;
+		udelay(1);
+	} while (--timeout);
+	if (!timeout)
+		dev_err(cs5535au->card->dev,
+			"Failure writing to cs5535 codec\n");
+}
+
+static unsigned short snd_cs5535audio_codec_read(struct cs5535audio *cs5535au,
+						 unsigned short reg)
+{
+	unsigned int regdata;
+	unsigned int timeout;
+	unsigned int val;
+
+	regdata = ((unsigned int) reg) << 24;
+	regdata |= ACC_CODEC_CNTL_RD_CMD;
+	regdata |= CMD_NEW;
+
+	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
+	wait_till_cmd_acked(cs5535au, 50);
+
+	timeout = 50;
+	do {
+		val = cs_readl(cs5535au, ACC_CODEC_STATUS);
+		if ((val & STS_NEW) && reg == (val >> 24))
+			break;
+		udelay(1);
+	} while (--timeout);
+	if (!timeout)
+		dev_err(cs5535au->card->dev,
+			"Failure reading codec reg 0x%x, Last value=0x%x\n",
+			reg, val);
+
+	return (unsigned short) val;
+}
+
+static void snd_cs5535audio_codec_write(struct cs5535audio *cs5535au,
+					unsigned short reg, unsigned short val)
+{
+	unsigned int regdata;
+
+	regdata = ((unsigned int) reg) << 24;
+	regdata |= val;
+	regdata &= CMD_MASK;
+	regdata |= CMD_NEW;
+	regdata &= ACC_CODEC_CNTL_WR_CMD;
+
+	cs_writel(cs5535au, ACC_CODEC_CNTL, regdata);
+	wait_till_cmd_acked(cs5535au, 50);
+}
+
+static void snd_cs5535audio_ac97_codec_write(struct snd_ac97 *ac97,
+					     unsigned short reg, unsigned short val)
+{
+	struct cs5535audio *cs5535au = ac97->private_data;
+	snd_cs5535audio_codec_write(cs5535au, reg, val);
+}
+
+static unsigned short snd_cs5535audio_ac97_codec_read(struct snd_ac97 *ac97,
+						      unsigned short reg)
+{
+	struct cs5535audio *cs5535au = ac97->private_data;
+	return snd_cs5535audio_codec_read(cs5535au, reg);
+}
+
+static int snd_cs5535audio_mixer(struct cs5535audio *cs5535au)
+{
+	struct snd_card *card = cs5535au->card;
+	struct snd_ac97_bus *pbus;
+	struct snd_ac97_template ac97;
+	int err;
+	static struct snd_ac97_bus_ops ops = {
+		.write = snd_cs5535audio_ac97_codec_write,
+		.read = snd_cs5535audio_ac97_codec_read,
+	};
+
+	if ((err = snd_ac97_bus(card, 0, &ops, NULL, &pbus)) < 0)
+		return err;
+
+	memset(&ac97, 0, sizeof(ac97));
+	ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM
+			| AC97_SCAP_POWER_SAVE;
+	ac97.private_data = cs5535au;
+	ac97.pci = cs5535au->pci;
+
+	/* set any OLPC-specific scaps */
+	olpc_prequirks(card, &ac97);
+
+	if ((err = snd_ac97_mixer(pbus, &ac97, &cs5535au->ac97)) < 0) {
+		dev_err(card->dev, "mixer failed\n");
+		return err;
+	}
+
+	snd_ac97_tune_hardware(cs5535au->ac97, ac97_quirks, ac97_quirk);
+
+	err = olpc_quirks(card, cs5535au->ac97);
+	if (err < 0) {
+		dev_err(card->dev, "olpc quirks failed\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static void process_bm0_irq(struct cs5535audio *cs5535au)
+{
+	u8 bm_stat;
+	spin_lock(&cs5535au->reg_lock);
+	bm_stat = cs_readb(cs5535au, ACC_BM0_STATUS);
+	spin_unlock(&cs5535au->reg_lock);
+	if (bm_stat & EOP) {
+		snd_pcm_period_elapsed(cs5535au->playback_substream);
+	} else {
+		dev_err(cs5535au->card->dev,
+			"unexpected bm0 irq src, bm_stat=%x\n",
+			bm_stat);
+	}
+}
+
+static void process_bm1_irq(struct cs5535audio *cs5535au)
+{
+	u8 bm_stat;
+	spin_lock(&cs5535au->reg_lock);
+	bm_stat = cs_readb(cs5535au, ACC_BM1_STATUS);
+	spin_unlock(&cs5535au->reg_lock);
+	if (bm_stat & EOP)
+		snd_pcm_period_elapsed(cs5535au->capture_substream);
+}
+
+static irqreturn_t snd_cs5535audio_interrupt(int irq, void *dev_id)
+{
+	u16 acc_irq_stat;
+	unsigned char count;
+	struct cs5535audio *cs5535au = dev_id;
+
+	if (cs5535au == NULL)
+		return IRQ_NONE;
+
+	acc_irq_stat = cs_readw(cs5535au, ACC_IRQ_STATUS);
+
+	if (!acc_irq_stat)
+		return IRQ_NONE;
+	for (count = 0; count < 4; count++) {
+		if (acc_irq_stat & (1 << count)) {
+			switch (count) {
+			case IRQ_STS:
+				cs_readl(cs5535au, ACC_GPIO_STATUS);
+				break;
+			case WU_IRQ_STS:
+				cs_readl(cs5535au, ACC_GPIO_STATUS);
+				break;
+			case BM0_IRQ_STS:
+				process_bm0_irq(cs5535au);
+				break;
+			case BM1_IRQ_STS:
+				process_bm1_irq(cs5535au);
+				break;
+			default:
+				dev_err(cs5535au->card->dev,
+					"Unexpected irq src: 0x%x\n",
+					acc_irq_stat);
+				break;
+			}
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+static int snd_cs5535audio_free(struct cs5535audio *cs5535au)
+{
+	synchronize_irq(cs5535au->irq);
+	pci_set_power_state(cs5535au->pci, PCI_D3hot);
+
+	if (cs5535au->irq >= 0)
+		free_irq(cs5535au->irq, cs5535au);
+
+	pci_release_regions(cs5535au->pci);
+	pci_disable_device(cs5535au->pci);
+	kfree(cs5535au);
+	return 0;
+}
+
+static int snd_cs5535audio_dev_free(struct snd_device *device)
+{
+	struct cs5535audio *cs5535au = device->device_data;
+	return snd_cs5535audio_free(cs5535au);
+}
+
+static int snd_cs5535audio_create(struct snd_card *card,
+				  struct pci_dev *pci,
+				  struct cs5535audio **rcs5535au)
+{
+	struct cs5535audio *cs5535au;
+
+	int err;
+	static struct snd_device_ops ops = {
+		.dev_free =	snd_cs5535audio_dev_free,
+	};
+
+	*rcs5535au = NULL;
+	if ((err = pci_enable_device(pci)) < 0)
+		return err;
+
+	if (dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0 ||
+	    dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) {
+		dev_warn(card->dev, "unable to get 32bit dma\n");
+		err = -ENXIO;
+		goto pcifail;
+	}
+
+	cs5535au = kzalloc(sizeof(*cs5535au), GFP_KERNEL);
+	if (cs5535au == NULL) {
+		err = -ENOMEM;
+		goto pcifail;
+	}
+
+	spin_lock_init(&cs5535au->reg_lock);
+	cs5535au->card = card;
+	cs5535au->pci = pci;
+	cs5535au->irq = -1;
+
+	if ((err = pci_request_regions(pci, "CS5535 Audio")) < 0) {
+		kfree(cs5535au);
+		goto pcifail;
+	}
+
+	cs5535au->port = pci_resource_start(pci, 0);
+
+	if (request_irq(pci->irq, snd_cs5535audio_interrupt,
+			IRQF_SHARED, KBUILD_MODNAME, cs5535au)) {
+		dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+		err = -EBUSY;
+		goto sndfail;
+	}
+
+	cs5535au->irq = pci->irq;
+	pci_set_master(pci);
+
+	if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,
+				  cs5535au, &ops)) < 0)
+		goto sndfail;
+
+	*rcs5535au = cs5535au;
+	return 0;
+
+sndfail: /* leave the device alive, just kill the snd */
+	snd_cs5535audio_free(cs5535au);
+	return err;
+
+pcifail:
+	pci_disable_device(pci);
+	return err;
+}
+
+static int snd_cs5535audio_probe(struct pci_dev *pci,
+				 const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct cs5535audio *cs5535au;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+			   0, &card);
+	if (err < 0)
+		return err;
+
+	if ((err = snd_cs5535audio_create(card, pci, &cs5535au)) < 0)
+		goto probefail_out;
+
+	card->private_data = cs5535au;
+
+	if ((err = snd_cs5535audio_mixer(cs5535au)) < 0)
+		goto probefail_out;
+
+	if ((err = snd_cs5535audio_pcm(cs5535au)) < 0)
+		goto probefail_out;
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	strcpy(card->shortname, "CS5535 Audio");
+	sprintf(card->longname, "%s %s at 0x%lx, irq %i",
+		card->shortname, card->driver,
+		cs5535au->port, cs5535au->irq);
+
+	if ((err = snd_card_register(card)) < 0)
+		goto probefail_out;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return 0;
+
+probefail_out:
+	snd_card_free(card);
+	return err;
+}
+
+static void snd_cs5535audio_remove(struct pci_dev *pci)
+{
+	olpc_quirks_cleanup();
+	snd_card_free(pci_get_drvdata(pci));
+}
+
+static struct pci_driver cs5535audio_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = snd_cs5535audio_ids,
+	.probe = snd_cs5535audio_probe,
+	.remove = snd_cs5535audio_remove,
+#ifdef CONFIG_PM_SLEEP
+	.driver = {
+		.pm = &snd_cs5535audio_pm,
+	},
+#endif
+};
+
+module_pci_driver(cs5535audio_driver);
+
+MODULE_AUTHOR("Jaya Kumar");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("CS5535 Audio");
+MODULE_SUPPORTED_DEVICE("CS5535 Audio");
diff --git a/sound/pci/cs5535audio/cs5535audio.h b/sound/pci/cs5535audio/cs5535audio.h
new file mode 100644
index 0000000..d84620a
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio.h
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_CS5535AUDIO_H
+#define __SOUND_CS5535AUDIO_H
+
+#define cs_writel(cs5535au, reg, val)	outl(val, (cs5535au)->port + reg)
+#define cs_writeb(cs5535au, reg, val)	outb(val, (cs5535au)->port + reg)
+#define cs_readl(cs5535au, reg)		inl((cs5535au)->port + reg)
+#define cs_readw(cs5535au, reg)		inw((cs5535au)->port + reg)
+#define cs_readb(cs5535au, reg)		inb((cs5535au)->port + reg)
+
+#define CS5535AUDIO_MAX_DESCRIPTORS	128
+
+/* acc_codec bar0 reg addrs */
+#define ACC_GPIO_STATUS			0x00
+#define ACC_CODEC_STATUS		0x08
+#define ACC_CODEC_CNTL			0x0C
+#define ACC_IRQ_STATUS			0x12
+#define ACC_BM0_CMD			0x20
+#define ACC_BM1_CMD			0x28
+#define ACC_BM0_PRD			0x24
+#define ACC_BM1_PRD			0x2C
+#define ACC_BM0_STATUS			0x21
+#define ACC_BM1_STATUS			0x29
+#define ACC_BM0_PNTR			0x60
+#define ACC_BM1_PNTR			0x64
+
+/* acc_codec bar0 reg bits */
+/* ACC_IRQ_STATUS */
+#define IRQ_STS 			0
+#define WU_IRQ_STS 			1
+#define BM0_IRQ_STS 			2
+#define BM1_IRQ_STS 			3
+/* ACC_BMX_STATUS */
+#define EOP				(1<<0)
+#define BM_EOP_ERR			(1<<1)
+/* ACC_BMX_CTL */
+#define BM_CTL_EN			0x01
+#define BM_CTL_PAUSE			0x03
+#define BM_CTL_DIS			0x00
+#define BM_CTL_BYTE_ORD_LE		0x00
+#define BM_CTL_BYTE_ORD_BE		0x04
+/* cs5535 specific ac97 codec register defines */
+#define CMD_MASK			0xFF00FFFF
+#define CMD_NEW				0x00010000
+#define STS_NEW				0x00020000
+#define PRM_RDY_STS			0x00800000
+#define ACC_CODEC_CNTL_WR_CMD		(~0x80000000)
+#define ACC_CODEC_CNTL_RD_CMD		0x80000000
+#define ACC_CODEC_CNTL_LNK_SHUTDOWN	0x00040000
+#define ACC_CODEC_CNTL_LNK_WRM_RST	0x00020000
+#define PRD_JMP				0x2000
+#define PRD_EOP				0x4000
+#define PRD_EOT				0x8000
+
+enum { CS5535AUDIO_DMA_PLAYBACK, CS5535AUDIO_DMA_CAPTURE, NUM_CS5535AUDIO_DMAS };
+
+struct cs5535audio;
+
+struct cs5535audio_dma_ops {
+	int type;
+	void (*enable_dma)(struct cs5535audio *cs5535au);
+	void (*disable_dma)(struct cs5535audio *cs5535au);
+	void (*pause_dma)(struct cs5535audio *cs5535au);
+	void (*setup_prd)(struct cs5535audio *cs5535au, u32 prd_addr);
+	u32 (*read_prd)(struct cs5535audio *cs5535au);
+	u32 (*read_dma_pntr)(struct cs5535audio *cs5535au);
+};
+
+struct cs5535audio_dma_desc {
+	__le32 addr;
+	__le16 size;
+	__le16 ctlreserved;
+};
+
+struct cs5535audio_dma {
+	const struct cs5535audio_dma_ops *ops;
+	struct snd_dma_buffer desc_buf;
+	struct snd_pcm_substream *substream;
+	unsigned int buf_addr, buf_bytes;
+	unsigned int period_bytes, periods;
+	u32 saved_prd;
+	int pcm_open_flag;
+};
+
+struct cs5535audio {
+	struct snd_card *card;
+	struct snd_ac97 *ac97;
+	struct snd_pcm *pcm;
+	int irq;
+	struct pci_dev *pci;
+	unsigned long port;
+	spinlock_t reg_lock;
+	struct snd_pcm_substream *playback_substream;
+	struct snd_pcm_substream *capture_substream;
+	struct cs5535audio_dma dmas[NUM_CS5535AUDIO_DMAS];
+};
+
+extern const struct dev_pm_ops snd_cs5535audio_pm;
+
+#ifdef CONFIG_OLPC
+void olpc_prequirks(struct snd_card *card,
+		    struct snd_ac97_template *ac97);
+int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97);
+void olpc_quirks_cleanup(void);
+void olpc_analog_input(struct snd_ac97 *ac97, int on);
+void olpc_mic_bias(struct snd_ac97 *ac97, int on);
+
+static inline void olpc_capture_open(struct snd_ac97 *ac97)
+{
+	/* default to Analog Input off */
+	olpc_analog_input(ac97, 0);
+	/* enable MIC Bias for recording */
+	olpc_mic_bias(ac97, 1);
+}
+
+static inline void olpc_capture_close(struct snd_ac97 *ac97)
+{
+	/* disable Analog Input */
+	olpc_analog_input(ac97, 0);
+	/* disable the MIC Bias (so the recording LED turns off) */
+	olpc_mic_bias(ac97, 0);
+}
+#else
+static inline void olpc_prequirks(struct snd_card *card,
+		struct snd_ac97_template *ac97) { }
+static inline int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
+{
+	return 0;
+}
+static inline void olpc_quirks_cleanup(void) { }
+static inline void olpc_analog_input(struct snd_ac97 *ac97, int on) { }
+static inline void olpc_mic_bias(struct snd_ac97 *ac97, int on) { }
+static inline void olpc_capture_open(struct snd_ac97 *ac97) { }
+static inline void olpc_capture_close(struct snd_ac97 *ac97) { }
+#endif
+
+int snd_cs5535audio_pcm(struct cs5535audio *cs5535audio);
+
+#endif /* __SOUND_CS5535AUDIO_H */
+
diff --git a/sound/pci/cs5535audio/cs5535audio_olpc.c b/sound/pci/cs5535audio/cs5535audio_olpc.c
new file mode 100644
index 0000000..3b0fdac
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_olpc.c
@@ -0,0 +1,192 @@
+/*
+ * OLPC XO-1 additional sound features
+ *
+ * Copyright © 2006  Jaya Kumar <jayakumar.lkml@gmail.com>
+ * Copyright © 2007-2008  Andres Salomon <dilinger@debian.org>
+ *
+ * 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 <sound/core.h>
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <linux/gpio.h>
+
+#include <asm/olpc.h>
+#include "cs5535audio.h"
+
+#define DRV_NAME "cs5535audio-olpc"
+
+/*
+ * OLPC has an additional feature on top of the regular AD1888 codec features.
+ * It has an Analog Input mode that is switched into (after disabling the
+ * High Pass Filter) via GPIO.  It is supported on B2 and later models.
+ */
+void olpc_analog_input(struct snd_ac97 *ac97, int on)
+{
+	int err;
+
+	if (!machine_is_olpc())
+		return;
+
+	/* update the High Pass Filter (via AC97_AD_TEST2) */
+	err = snd_ac97_update_bits(ac97, AC97_AD_TEST2,
+			1 << AC97_AD_HPFD_SHIFT, on << AC97_AD_HPFD_SHIFT);
+	if (err < 0) {
+		dev_err(ac97->bus->card->dev,
+			"setting High Pass Filter - %d\n", err);
+		return;
+	}
+
+	/* set Analog Input through GPIO */
+	gpio_set_value(OLPC_GPIO_MIC_AC, on);
+}
+
+/*
+ * OLPC XO-1's V_REFOUT is a mic bias enable.
+ */
+void olpc_mic_bias(struct snd_ac97 *ac97, int on)
+{
+	int err;
+
+	if (!machine_is_olpc())
+		return;
+
+	on = on ? 0 : 1;
+	err = snd_ac97_update_bits(ac97, AC97_AD_MISC,
+			1 << AC97_AD_VREFD_SHIFT, on << AC97_AD_VREFD_SHIFT);
+	if (err < 0)
+		dev_err(ac97->bus->card->dev, "setting MIC Bias - %d\n", err);
+}
+
+static int olpc_dc_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int olpc_dc_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	v->value.integer.value[0] = gpio_get_value(OLPC_GPIO_MIC_AC);
+	return 0;
+}
+
+static int olpc_dc_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+
+	olpc_analog_input(cs5535au->ac97, v->value.integer.value[0]);
+	return 1;
+}
+
+static int olpc_mic_info(struct snd_kcontrol *kctl,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int olpc_mic_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+	struct snd_ac97 *ac97 = cs5535au->ac97;
+	int i;
+
+	i = (snd_ac97_read(ac97, AC97_AD_MISC) >> AC97_AD_VREFD_SHIFT) & 0x1;
+	v->value.integer.value[0] = i ? 0 : 1;
+	return 0;
+}
+
+static int olpc_mic_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *v)
+{
+	struct cs5535audio *cs5535au = snd_kcontrol_chip(kctl);
+
+	olpc_mic_bias(cs5535au->ac97, v->value.integer.value[0]);
+	return 1;
+}
+
+static struct snd_kcontrol_new olpc_cs5535audio_ctls[] = {
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "DC Mode Enable",
+	.info = olpc_dc_info,
+	.get = olpc_dc_get,
+	.put = olpc_dc_put,
+	.private_value = 0,
+},
+{
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "MIC Bias Enable",
+	.info = olpc_mic_info,
+	.get = olpc_mic_get,
+	.put = olpc_mic_put,
+	.private_value = 0,
+},
+};
+
+void olpc_prequirks(struct snd_card *card,
+		    struct snd_ac97_template *ac97)
+{
+	if (!machine_is_olpc())
+		return;
+
+	/* invert EAPD if on an OLPC B3 or higher */
+	if (olpc_board_at_least(olpc_board_pre(0xb3)))
+		ac97->scaps |= AC97_SCAP_INV_EAPD;
+}
+
+int olpc_quirks(struct snd_card *card, struct snd_ac97 *ac97)
+{
+	struct snd_ctl_elem_id elem;
+	int i, err;
+
+	if (!machine_is_olpc())
+		return 0;
+
+	if (gpio_request(OLPC_GPIO_MIC_AC, DRV_NAME)) {
+		dev_err(card->dev, "unable to allocate MIC GPIO\n");
+		return -EIO;
+	}
+	gpio_direction_output(OLPC_GPIO_MIC_AC, 0);
+
+	/* drop the original AD1888 HPF control */
+	memset(&elem, 0, sizeof(elem));
+	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strlcpy(elem.name, "High Pass Filter Enable", sizeof(elem.name));
+	snd_ctl_remove_id(card, &elem);
+
+	/* drop the original V_REFOUT control */
+	memset(&elem, 0, sizeof(elem));
+	elem.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	strlcpy(elem.name, "V_REFOUT Enable", sizeof(elem.name));
+	snd_ctl_remove_id(card, &elem);
+
+	/* add the OLPC-specific controls */
+	for (i = 0; i < ARRAY_SIZE(olpc_cs5535audio_ctls); i++) {
+		err = snd_ctl_add(card, snd_ctl_new1(&olpc_cs5535audio_ctls[i],
+				ac97->private_data));
+		if (err < 0) {
+			gpio_free(OLPC_GPIO_MIC_AC);
+			return err;
+		}
+	}
+
+	/* turn off the mic by default */
+	olpc_mic_bias(ac97, 0);
+	return 0;
+}
+
+void olpc_quirks_cleanup(void)
+{
+	gpio_free(OLPC_GPIO_MIC_AC);
+}
diff --git a/sound/pci/cs5535audio/cs5535audio_pcm.c b/sound/pci/cs5535audio/cs5535audio_pcm.c
new file mode 100644
index 0000000..326caec
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_pcm.c
@@ -0,0 +1,454 @@
+/*
+ * Driver for audio on multifunction CS5535 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * Based on Jaroslav Kysela and Takashi Iwai's examples.
+ * This work was sponsored by CIS(M) Sdn Bhd.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * todo: add be fmt support, spdif, pm
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/ac97_codec.h>
+#include "cs5535audio.h"
+
+static const struct snd_pcm_hardware snd_cs5535audio_playback =
+{
+	.info =			(
+				SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 		SNDRV_PCM_INFO_MMAP_VALID |
+		 		SNDRV_PCM_INFO_PAUSE |
+				SNDRV_PCM_INFO_RESUME
+				),
+	.formats =		(
+				SNDRV_PCM_FMTBIT_S16_LE
+				),
+	.rates =		(
+				SNDRV_PCM_RATE_CONTINUOUS |
+				SNDRV_PCM_RATE_8000_48000
+				),
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024 - 16),
+	.periods_min =		1,
+	.periods_max =		CS5535AUDIO_MAX_DESCRIPTORS,
+	.fifo_size =		0,
+};
+
+static const struct snd_pcm_hardware snd_cs5535audio_capture =
+{
+	.info =			(
+				SNDRV_PCM_INFO_MMAP |
+				SNDRV_PCM_INFO_INTERLEAVED |
+		 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		 		SNDRV_PCM_INFO_MMAP_VALID
+				),
+	.formats =		(
+				SNDRV_PCM_FMTBIT_S16_LE
+				),
+	.rates =		(
+				SNDRV_PCM_RATE_CONTINUOUS |
+				SNDRV_PCM_RATE_8000_48000
+				),
+	.rate_min =		4000,
+	.rate_max =		48000,
+	.channels_min =		2,
+	.channels_max =		2,
+	.buffer_bytes_max =	(128*1024),
+	.period_bytes_min =	64,
+	.period_bytes_max =	(64*1024 - 16),
+	.periods_min =		1,
+	.periods_max =		CS5535AUDIO_MAX_DESCRIPTORS,
+	.fifo_size =		0,
+};
+
+static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_cs5535audio_playback;
+	runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC];
+	snd_pcm_limit_hw_rates(runtime);
+	cs5535au->playback_substream = substream;
+	runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+				SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+
+	return 0;
+}
+
+static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+#define CS5535AUDIO_DESC_LIST_SIZE \
+	PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc))
+
+static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au,
+					 struct cs5535audio_dma *dma,
+					 struct snd_pcm_substream *substream,
+					 unsigned int periods,
+					 unsigned int period_bytes)
+{
+	unsigned int i;
+	u32 addr, desc_addr, jmpprd_addr;
+	struct cs5535audio_dma_desc *lastdesc;
+
+	if (periods > CS5535AUDIO_MAX_DESCRIPTORS)
+		return -ENOMEM;
+
+	if (dma->desc_buf.area == NULL) {
+		if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					snd_dma_pci_data(cs5535au->pci),
+					CS5535AUDIO_DESC_LIST_SIZE+1,
+					&dma->desc_buf) < 0)
+			return -ENOMEM;
+		dma->period_bytes = dma->periods = 0;
+	}
+
+	if (dma->periods == periods && dma->period_bytes == period_bytes)
+		return 0;
+
+	/* the u32 cast is okay because in snd*create we successfully told
+   	   pci alloc that we're only 32 bit capable so the uppper will be 0 */
+	addr = (u32) substream->runtime->dma_addr;
+	desc_addr = (u32) dma->desc_buf.addr;
+	for (i = 0; i < periods; i++) {
+		struct cs5535audio_dma_desc *desc =
+			&((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i];
+		desc->addr = cpu_to_le32(addr);
+		desc->size = cpu_to_le16(period_bytes);
+		desc->ctlreserved = cpu_to_le16(PRD_EOP);
+		desc_addr += sizeof(struct cs5535audio_dma_desc);
+		addr += period_bytes;
+	}
+	/* we reserved one dummy descriptor at the end to do the PRD jump */
+	lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods];
+	lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);
+	lastdesc->size = 0;
+	lastdesc->ctlreserved = cpu_to_le16(PRD_JMP);
+	jmpprd_addr = (u32)dma->desc_buf.addr +
+		sizeof(struct cs5535audio_dma_desc) * periods;
+
+	dma->substream = substream;
+	dma->period_bytes = period_bytes;
+	dma->periods = periods;
+	spin_lock_irq(&cs5535au->reg_lock);
+	dma->ops->disable_dma(cs5535au);
+	dma->ops->setup_prd(cs5535au, jmpprd_addr);
+	spin_unlock_irq(&cs5535au->reg_lock);
+	return 0;
+}
+
+static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);
+}
+
+static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, 0);
+}
+
+static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);
+}
+
+static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,
+					   u32 prd_addr)
+{
+	cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);
+}
+
+static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM0_PRD);
+}
+
+static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM0_PNTR);
+}
+
+static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);
+}
+
+static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, 0);
+}
+
+static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)
+{
+	cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);
+}
+
+static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,
+					  u32 prd_addr)
+{
+	cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);
+}
+
+static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM1_PRD);
+}
+
+static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)
+{
+	return cs_readl(cs5535au, ACC_BM1_PNTR);
+}
+
+static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,
+					  struct cs5535audio_dma *dma,
+					  struct snd_pcm_substream *substream)
+{
+	snd_dma_free_pages(&dma->desc_buf);
+	dma->desc_buf.area = NULL;
+	dma->substream = NULL;
+}
+
+static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,
+				     struct snd_pcm_hw_params *hw_params)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	dma->buf_addr = substream->runtime->dma_addr;
+	dma->buf_bytes = params_buffer_bytes(hw_params);
+
+	err = cs5535audio_build_dma_packets(cs5535au, dma, substream,
+					    params_periods(hw_params),
+					    params_period_bytes(hw_params));
+	if (!err)
+		dma->pcm_open_flag = 1;
+
+	return err;
+}
+
+static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+
+	if (dma->pcm_open_flag) {
+		if (substream == cs5535au->playback_substream)
+			snd_ac97_update_power(cs5535au->ac97,
+					AC97_PCM_FRONT_DAC_RATE, 0);
+		else
+			snd_ac97_update_power(cs5535au->ac97,
+					AC97_PCM_LR_ADC_RATE, 0);
+		dma->pcm_open_flag = 0;
+	}
+	cs5535audio_clear_dma_packets(cs5535au, dma, substream);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,
+				 substream->runtime->rate);
+}
+
+static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct cs5535audio_dma *dma = substream->runtime->private_data;
+	int err = 0;
+
+	spin_lock(&cs5535au->reg_lock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dma->ops->pause_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+		dma->ops->enable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		dma->ops->disable_dma(cs5535au);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		dma->ops->disable_dma(cs5535au);
+		break;
+	default:
+		dev_err(cs5535au->card->dev, "unhandled trigger\n");
+		err = -EINVAL;
+		break;
+	}
+	spin_unlock(&cs5535au->reg_lock);
+	return err;
+}
+
+static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream
+							*substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	u32 curdma;
+	struct cs5535audio_dma *dma;
+
+	dma = substream->runtime->private_data;
+	curdma = dma->ops->read_dma_pntr(cs5535au);
+	if (curdma < dma->buf_addr) {
+		dev_err(cs5535au->card->dev, "curdma=%x < %x bufaddr.\n",
+					curdma, dma->buf_addr);
+		return 0;
+	}
+	curdma -= dma->buf_addr;
+	if (curdma >= dma->buf_bytes) {
+		dev_err(cs5535au->card->dev, "diff=%x >= %x buf_bytes.\n",
+					curdma, dma->buf_bytes);
+		return 0;
+	}
+	return bytes_to_frames(substream->runtime, curdma);
+}
+
+static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)
+{
+	int err;
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	runtime->hw = snd_cs5535audio_capture;
+	runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];
+	snd_pcm_limit_hw_rates(runtime);
+	cs5535au->capture_substream = substream;
+	runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);
+	if ((err = snd_pcm_hw_constraint_integer(runtime,
+					 SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
+		return err;
+	olpc_capture_open(cs5535au->ac97);
+	return 0;
+}
+
+static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	olpc_capture_close(cs5535au->ac97);
+	return 0;
+}
+
+static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
+	return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,
+				 substream->runtime->rate);
+}
+
+static const struct snd_pcm_ops snd_cs5535audio_playback_ops = {
+	.open =		snd_cs5535audio_playback_open,
+	.close =	snd_cs5535audio_playback_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs5535audio_hw_params,
+	.hw_free =	snd_cs5535audio_hw_free,
+	.prepare =	snd_cs5535audio_playback_prepare,
+	.trigger =	snd_cs5535audio_trigger,
+	.pointer =	snd_cs5535audio_pcm_pointer,
+};
+
+static const struct snd_pcm_ops snd_cs5535audio_capture_ops = {
+	.open =		snd_cs5535audio_capture_open,
+	.close =	snd_cs5535audio_capture_close,
+	.ioctl =	snd_pcm_lib_ioctl,
+	.hw_params =	snd_cs5535audio_hw_params,
+	.hw_free =	snd_cs5535audio_hw_free,
+	.prepare =	snd_cs5535audio_capture_prepare,
+	.trigger =	snd_cs5535audio_trigger,
+	.pointer =	snd_cs5535audio_pcm_pointer,
+};
+
+static const struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {
+        .type = CS5535AUDIO_DMA_PLAYBACK,
+        .enable_dma = cs5535audio_playback_enable_dma,
+        .disable_dma = cs5535audio_playback_disable_dma,
+        .setup_prd = cs5535audio_playback_setup_prd,
+        .read_prd = cs5535audio_playback_read_prd,
+        .pause_dma = cs5535audio_playback_pause_dma,
+        .read_dma_pntr = cs5535audio_playback_read_dma_pntr,
+};
+
+static const struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {
+        .type = CS5535AUDIO_DMA_CAPTURE,
+        .enable_dma = cs5535audio_capture_enable_dma,
+        .disable_dma = cs5535audio_capture_disable_dma,
+        .setup_prd = cs5535audio_capture_setup_prd,
+        .read_prd = cs5535audio_capture_read_prd,
+        .pause_dma = cs5535audio_capture_pause_dma,
+        .read_dma_pntr = cs5535audio_capture_read_dma_pntr,
+};
+
+int snd_cs5535audio_pcm(struct cs5535audio *cs5535au)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =
+					&snd_cs5535audio_playback_dma_ops;
+	cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =
+					&snd_cs5535audio_capture_dma_ops;
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+					&snd_cs5535audio_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+					&snd_cs5535audio_capture_ops);
+
+	pcm->private_data = cs5535au;
+	pcm->info_flags = 0;
+	strcpy(pcm->name, "CS5535 Audio");
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					snd_dma_pci_data(cs5535au->pci),
+					64*1024, 128*1024);
+	cs5535au->pcm = pcm;
+
+	return 0;
+}
+
diff --git a/sound/pci/cs5535audio/cs5535audio_pm.c b/sound/pci/cs5535audio/cs5535audio_pm.c
new file mode 100644
index 0000000..82bd10b
--- /dev/null
+++ b/sound/pci/cs5535audio/cs5535audio_pm.c
@@ -0,0 +1,115 @@
+/*
+ * Power management for audio on multifunction CS5535 companion device
+ * Copyright (C) Jaya Kumar
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+#include <sound/asoundef.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include "cs5535audio.h"
+
+static void snd_cs5535audio_stop_hardware(struct cs5535audio *cs5535au)
+{
+	/* 
+	we depend on snd_ac97_suspend to tell the
+	AC97 codec to shutdown. the amd spec suggests
+	that the LNK_SHUTDOWN be done at the same time
+	that the codec power-down is issued. instead,
+	we do it just after rather than at the same 
+	time. excluding codec specific build_ops->suspend
+	ac97 powerdown hits:
+	0x8000 EAPD 
+	0x4000 Headphone amplifier 
+	0x0300 ADC & DAC 
+	0x0400 Analog Mixer powerdown (Vref on) 
+	I am not sure if this is the best that we can do.
+	The remainder to be investigated are:
+	- analog mixer (vref off) 0x0800
+	- AC-link powerdown 0x1000
+	- codec internal clock 0x2000
+	*/
+
+	/* set LNK_SHUTDOWN to shutdown AC link */
+	cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_SHUTDOWN);
+
+}
+
+static int __maybe_unused snd_cs5535audio_suspend(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct cs5535audio *cs5535au = card->private_data;
+	int i;
+
+	snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+	snd_pcm_suspend_all(cs5535au->pcm);
+	snd_ac97_suspend(cs5535au->ac97);
+	for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) {
+		struct cs5535audio_dma *dma = &cs5535au->dmas[i];
+		if (dma && dma->substream)
+			dma->saved_prd = dma->ops->read_prd(cs5535au);
+	}
+	/* save important regs, then disable aclink in hw */
+	snd_cs5535audio_stop_hardware(cs5535au);
+	return 0;
+}
+
+static int __maybe_unused snd_cs5535audio_resume(struct device *dev)
+{
+	struct snd_card *card = dev_get_drvdata(dev);
+	struct cs5535audio *cs5535au = card->private_data;
+	u32 tmp;
+	int timeout;
+	int i;
+
+	/* set LNK_WRM_RST to reset AC link */
+	cs_writel(cs5535au, ACC_CODEC_CNTL, ACC_CODEC_CNTL_LNK_WRM_RST);
+
+	timeout = 50;
+	do {
+		tmp = cs_readl(cs5535au, ACC_CODEC_STATUS);
+		if (tmp & PRM_RDY_STS)
+			break;
+		udelay(1);
+	} while (--timeout);
+
+	if (!timeout)
+		dev_err(cs5535au->card->dev, "Failure getting AC Link ready\n");
+
+	/* set up rate regs, dma. actual initiation is done in trig */
+	for (i = 0; i < NUM_CS5535AUDIO_DMAS; i++) {
+		struct cs5535audio_dma *dma = &cs5535au->dmas[i];
+		if (dma && dma->substream) {
+			dma->substream->ops->prepare(dma->substream);
+			dma->ops->setup_prd(cs5535au, dma->saved_prd);
+		}
+	}
+
+	/* we depend on ac97 to perform the codec power up */
+	snd_ac97_resume(cs5535au->ac97);
+	snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+	return 0;
+}
+
+SIMPLE_DEV_PM_OPS(snd_cs5535audio_pm, snd_cs5535audio_suspend, snd_cs5535audio_resume);