This is a new ALSA driver driver for Rockwell WaveArtist RWA010 chips found
on some (rare) ISA sound cards, such as DCS Multimedia S717.

I wasn't able to get the old OSS WaveArtist driver to work with my card but it
was a great source of information about the chip (as the full datasheet is not
available - only a brief one and a design guide).
However, the OSS driver only supports few of the mixer registers so I had to
install the card in Windows and dump mixer registers while changing the mixer
settings. Then tried what the rest of the registers do and the result is a
fully working mixer which even supports more controls than the Windows driver.

Someone with a NetWinder can add support for it to this driver and then remove
the old OSS one.

Signed-off-by: Ondrej Zary <li...@rainbow-software.org>
---
 include/sound/mpu401.h |    1 +
 sound/isa/Kconfig      |   11 +
 sound/isa/Makefile     |    2 +
 sound/isa/waveartist.c | 1399 ++++++++++++++++++++++++++++++++++++++++++++++++
 sound/isa/waveartist.h |   74 +++
 5 files changed, 1487 insertions(+)
 create mode 100644 sound/isa/waveartist.c
 create mode 100644 sound/isa/waveartist.h

diff --git a/include/sound/mpu401.h b/include/sound/mpu401.h
index e942096..8fceeba 100644
--- a/include/sound/mpu401.h
+++ b/include/sound/mpu401.h
@@ -44,6 +44,7 @@
 #define MPU401_HW_INTEL8X0             17      /* Intel8x0 driver */
 #define MPU401_HW_PC98II               18      /* Roland PC98II */
 #define MPU401_HW_AUREAL               19      /* Aureal Vortex */
+#define MPU401_HW_WAVEARTIST           20      /* Rockwell WaveArtist */
 
 #define MPU401_INFO_INPUT      (1 << 0)        /* input stream */
 #define MPU401_INFO_OUTPUT     (1 << 1)        /* output stream */
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig
index 0216475..7a3b4a2 100644
--- a/sound/isa/Kconfig
+++ b/sound/isa/Kconfig
@@ -454,5 +454,16 @@ config SND_MSND_CLASSIC
          To compile this driver as a module, choose M here: the module
          will be called snd-msnd-classic.
 
+config SND_WAVEARTIST
+       tristate "Rockwell WaveArtist RWA010"
+       select SND_OPL3_LIB
+       select SND_MPU401_UART
+       select SND_PCM
+       help
+         Say Y here to include support for Rockwell WaveArtist RWA010 chips.
+
+         To compile this driver as a module, choose M here: the module
+         will be called snd-waveartist.
+
 endif  # SND_ISA
 
diff --git a/sound/isa/Makefile b/sound/isa/Makefile
index 9a15f14..23e11e2 100644
--- a/sound/isa/Makefile
+++ b/sound/isa/Makefile
@@ -12,6 +12,7 @@ snd-es18xx-objs := es18xx.o
 snd-opl3sa2-objs := opl3sa2.o
 snd-sc6000-objs := sc6000.o
 snd-sscape-objs := sscape.o
+snd-waveartist-objs := waveartist.o
 
 # Toplevel Module Dependency
 obj-$(CONFIG_SND_ADLIB) += snd-adlib.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o
 obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o
 obj-$(CONFIG_SND_SC6000) += snd-sc6000.o
 obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o
+obj-$(CONFIG_SND_WAVEARTIST) += snd-waveartist.o
 
 obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ galaxy/ gus/ msnd/ 
opti9xx/ \
                     sb/ wavefront/ wss/
diff --git a/sound/isa/waveartist.c b/sound/isa/waveartist.c
new file mode 100644
index 0000000..e5bda5c
--- /dev/null
+++ b/sound/isa/waveartist.c
@@ -0,0 +1,1399 @@
+/*
+ *  Driver for Rockwell WaveArtist RWA010 soundcards
+ *
+ *  Copyright (c) 2015 Ondrej Zary
+ *
+ *  HW-related parts based on OSS WaveArtist driver by Hannu Savolainen
+ *
+ *  ALSA code based on ES18xx driver by Christian Fischbach & Abramo Bagnara
+ *  and also by OPL3-SA2 driver by Jaroslav Kysela
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/isa.h>
+#include <linux/pnp.h>
+#include <linux/isapnp.h>
+#include <asm/dma.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/mpu401.h>
+#include <sound/opl3.h>
+#include <sound/initval.h>
+#include "waveartist.h"
+
+MODULE_AUTHOR("Ondrej Zary");
+MODULE_DESCRIPTION("Driver for Rockwell WaveArtist RWA010 sound cards");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;     /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;      /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP;
+#ifdef CONFIG_PNP
+static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
+#endif
+static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;    /* 0x250-0x3f0 */
+static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x388-0x3f0 */
+static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x300-0x3f0 */
+static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;       /* 5,7,10,11 */
+static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;      /* 5,6,7 */
+static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;      /* 0,1,3 */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for WaveArtist soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for WaveArtist soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable WaveArtist soundcard.");
+#ifdef CONFIG_PNP
+module_param_array(isapnp, bool, NULL, 0444);
+MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard.");
+#endif
+module_param_array(port, long, NULL, 0444);
+MODULE_PARM_DESC(port, "Port # for WaveArtist driver.");
+module_param_array(fm_port, long, NULL, 0444);
+MODULE_PARM_DESC(fm_port, "FM port # for WaveArtist driver.");
+module_param_array(midi_port, long, NULL, 0444);
+MODULE_PARM_DESC(midi_port, "MIDI port # for WaveArtist driver.");
+module_param_array(irq, int, NULL, 0444);
+MODULE_PARM_DESC(irq, "IRQ # for WaveArtist driver.");
+module_param_array(dma1, int, NULL, 0444);
+MODULE_PARM_DESC(dma1, "DMA1 # for WaveArtist driver.");
+module_param_array(dma2, int, NULL, 0444);
+MODULE_PARM_DESC(dma2, "DMA2 # for WaveArtist driver.");
+
+#ifdef CONFIG_PNP
+static int isa_registered;
+static int pnp_registered;
+#endif
+
+#define PFX    "waveartist: "
+
+#ifdef CONFIG_PNP
+#define WA_DEVICE(pnpid) { \
+       .id = pnpid, \
+       .devs = { {"RSS5000"}, {"RSS5001"}, {"RSS5002"} } \
+}
+/*
+ * RSS5000 = WaveArtist, RSS5001 = SB, RSS5002 = MPU-401, RSS5003 = IDE,
+ * RSS5004 = gameport, RSS5005 = modem, RSS5006 = 3D
+ */
+
+static struct pnp_card_device_id snd_waveartist_pnpids[] = {
+       WA_DEVICE("RSS5000"), /* 16-bit decode */
+       WA_DEVICE("RSS5100"), /* 16-bit decode + modem */
+       WA_DEVICE("RSS5200"), /* 10-bit decode + modem */
+       WA_DEVICE("RSS5300"), /* 10-bit decode */
+       WA_DEVICE("RSS5400"), /* 16-bit decode + IDE */
+       WA_DEVICE("RSS5500"), /* 16-bit decode + modem + IDE */
+       WA_DEVICE("RSS5600"), /* 10-bit decode + IDE */
+       WA_DEVICE("RSS5700"), /* 10-bit decode + modem + IDE */
+       WA_DEVICE("RSS5800"), /* 10-bit decode + modem + 3D */
+       WA_DEVICE("RSS5900"), /* 10-bit decode + 3D */
+       WA_DEVICE("RSS5A00"), /* 16-bit decode + modem + 3D */
+       WA_DEVICE("RSS5B00"), /* 16-bit decode + 3D */
+       { .id = "" }    /* end */
+};
+MODULE_DEVICE_TABLE(pnp_card, snd_waveartist_pnpids);
+#endif /* CONFIG_PNP */
+
+struct snd_waveartist {
+#ifdef CONFIG_PNP
+       struct pnp_dev *wa;     /* WaveArtist device */
+       struct pnp_dev *sb;     /* SB emulation device */
+       struct pnp_dev *mpu;    /* MPU-401 device */
+#endif
+       unsigned long port;     /* base port */
+       struct resource *res_port; /* base port resource */
+       int irq;
+       int dma_playback;
+       int dma_capture;
+
+       struct snd_card *card;
+       struct snd_pcm *pcm;
+       struct snd_pcm_substream *playback_substream;
+       struct snd_pcm_substream *capture_substream;
+
+       spinlock_t reg_lock;
+       struct snd_hwdep *synth;
+       struct snd_rawmidi *rmidi;
+
+       u16 image[20];  /* mixer registers image */
+};
+
+static inline void wa_outb(struct snd_waveartist *chip, u8 reg, u8 val)
+{
+       outb(val, chip->port + reg);
+}
+
+static inline u8 wa_inb(struct snd_waveartist *chip, u8 reg)
+{
+       return inb(chip->port + reg);
+}
+
+static inline void wa_outw(struct snd_waveartist *chip, u8 reg, u16 val)
+{
+       outw(val, chip->port + reg);
+}
+
+static inline u16 wa_inw(struct snd_waveartist *chip, u8 reg)
+{
+       return inw(chip->port + reg);
+}
+
+static inline void waveartist_set_ctlr(struct snd_waveartist *chip, u8 clear,
+                                      u8 set)
+{
+       clear = ~clear & wa_inb(chip, CTLR);
+       wa_outb(chip, CTLR, clear | set);
+}
+
+/* acknowledge IRQ */
+static inline void waveartist_iack(struct snd_waveartist *chip)
+{
+       u8 old_ctlr = wa_inb(chip, CTLR) & ~IRQ_ACK;
+
+       wa_outb(chip, CTLR, old_ctlr | IRQ_ACK);
+       wa_outb(chip, CTLR, old_ctlr);
+}
+
+static int waveartist_reset(struct snd_waveartist *chip)
+{
+       unsigned int timeout, res = -1;
+
+       waveartist_set_ctlr(chip, -1, RESET);
+       msleep(200);
+       waveartist_set_ctlr(chip, RESET, 0);
+
+       timeout = 500;
+       do {
+               mdelay(2);
+
+               if (wa_inb(chip, STATR) & CMD_RF) {
+                       res = wa_inw(chip, CMDR);
+                       if (res == 0x55aa)
+                               break;
+               }
+       } while (--timeout);
+
+       if (timeout == 0) {
+               dev_warn(chip->card->dev, "WaveArtist: reset timeout 
(res=0x%x)\n",
+                        res);
+               return 1;
+       }
+
+       return 0;
+}
+
+/* Helper function to send and receive words
+ * from WaveArtist. It handles all the handshaking
+ * and can send or receive multiple words.
+ */
+static int waveartist_cmd(struct snd_waveartist *chip,
+                         int nr_cmd, u16 *cmd,
+                         int nr_resp, u16 *resp)
+{
+       unsigned long flags;
+       unsigned int timed_out = 0, i;
+
+       spin_lock_irqsave(&chip->reg_lock, flags);
+       /*
+        * The chip can hang if we access the STATR register too quickly
+        * after a write. Do a dummy read to slow down.
+        */
+       wa_inb(chip, CTLR);
+
+       if (wa_inb(chip, STATR) & CMD_RF) {
+               /* flush the port */
+               wa_inw(chip, CMDR);
+               udelay(10);
+       }
+
+       for (i = 0; !timed_out && i < nr_cmd; i++) {
+               int count;
+
+               for (count = 5000; count; count--)
+                       if (wa_inb(chip, STATR) & CMD_WE)
+                               break;
+
+               if (!count)
+                       timed_out = 1;
+               else
+                       wa_outw(chip, CMDR, cmd[i]);
+               /* Another dummy read */
+               wa_inb(chip, CTLR);
+       }
+
+       for (i = 0; !timed_out && i < nr_resp; i++) {
+               int count;
+
+               for (count = 5000; count; count--)
+                       if (wa_inb(chip, STATR) & CMD_RF)
+                               break;
+
+               if (!count)
+                       timed_out = 1;
+               else
+                       resp[i] = wa_inw(chip, CMDR);
+       }
+       spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+       return timed_out ? 1 : 0;
+}
+
+/* Send one command word */
+static inline int waveartist_cmd1(struct snd_waveartist *chip, u16 cmd)
+{
+       return waveartist_cmd(chip, 1, &cmd, 0, NULL);
+}
+
+/* Send one command, receive one word */
+static inline u16 waveartist_cmd1_r(struct snd_waveartist *chip, u16 cmd)
+{
+       u16 ret;
+
+       waveartist_cmd(chip, 1, &cmd, 1, &ret);
+
+       return ret;
+}
+
+/* Send a double command, receive one word (and throw it away) */
+static inline int waveartist_cmd2(struct snd_waveartist *chip, u16 cmd, u16 
arg)
+{
+       u16 vals[2] = { cmd, arg };
+
+       return waveartist_cmd(chip, 2, vals, 1, vals);
+}
+
+/* Send a triple command */
+static inline int waveartist_cmd3(struct snd_waveartist *chip, u16 cmd,
+                                 u16 arg1, u16 arg2)
+{
+       u16 vals[3] = { cmd, arg1, arg2 };
+
+       return waveartist_cmd(chip, 3, vals, 0, NULL);
+}
+
+static u16 waveartist_getrev(struct snd_waveartist *chip)
+{
+       u16 temp[2];
+       u16 cmd = WACMD_GETREV;
+
+       waveartist_cmd(chip, 1, &cmd, 2, temp);
+
+       return temp[0];
+}
+
+static irqreturn_t snd_waveartist_interrupt(int irq, void *dev_id)
+{
+       u8 status, irqstatus;
+       struct snd_card *card = dev_id;
+       struct snd_waveartist *chip;
+
+       if (card == NULL)
+               return IRQ_NONE;
+
+       chip = card->private_data;
+
+       irqstatus = wa_inb(chip, IRQSTAT);
+       status    = wa_inb(chip, STATR);
+
+       if (status & IRQ_REQ)   /* clear interrupt */
+               waveartist_iack(chip);
+
+       if (irqstatus & IRQ_PCM) { /* PCM buffer done */
+               if ((status & DMA1) && chip->playback_substream)
+                       snd_pcm_period_elapsed(chip->playback_substream);
+               if ((status & DMA0) && chip->capture_substream)
+                       snd_pcm_period_elapsed(chip->capture_substream);
+               if (!(status & (DMA0 | DMA1)))
+                       dev_warn(chip->card->dev, "Unknown PCM interrupt\n");
+       }
+
+       if (irqstatus & IRQ_SB) /* we do not use SB mode */
+               dev_warn(chip->card->dev, "Unexpected SB interrupt\n");
+
+       if ((irqstatus & IRQ_MPU) && chip->rmidi)
+               snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
+
+       return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_suspend(struct snd_card *card, pm_message_t state)
+{
+       struct snd_waveartist *chip = card->private_data;
+       int i;
+
+       if (!card)
+               return 0;
+
+       snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+       snd_pcm_suspend_all(chip->pcm);
+
+       /* save mixer registers */
+       for (i = 0; i < ARRAY_SIZE(chip->image); i++)
+               chip->image[i] = waveartist_cmd1_r(chip,
+                                                  WACMD_GET_LEVEL | i << 8);
+
+       return 0;
+}
+
+static int snd_waveartist_resume(struct snd_card *card)
+{
+       struct snd_waveartist *chip;
+       int i;
+
+       if (!card)
+               return 0;
+
+       chip = card->private_data;
+
+       /* restore mixer registers */
+       for (i = 0; i < 10; i += 2)
+               waveartist_cmd3(chip, WACMD_SET_MIXER,
+                               chip->image[i], chip->image[i + 1]);
+       for (i = 10; i < ARRAY_SIZE(chip->image); i += 2)
+               waveartist_cmd3(chip, WACMD_SET_LEVEL |
+                                     ((i - 10) / 2) << 8,
+                                     chip->image[i], chip->image[i + 1]);
+
+
+       snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+
+       return 0;
+}
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_PNP
+static void snd_waveartist_set_irq(struct pnp_dev *pdev, int irq)
+{
+       if (!pdev->active)
+               return;
+       isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev));
+       isapnp_write_byte(0x70, irq);   /* ISAPNP_CFG_IRQ */
+       isapnp_cfg_end();
+}
+
+static int snd_waveartist_pnp(int dev, struct snd_waveartist *chip,
+                             struct pnp_card_link *card,
+                             const struct pnp_card_device_id *id)
+{
+       chip->wa = pnp_request_card_device(card, id->devs[0].id, NULL);
+       if (!chip->wa)
+               return -EBUSY;
+
+       chip->sb = pnp_request_card_device(card, id->devs[1].id, NULL);
+       if (!chip->sb)
+               return -EBUSY;
+
+       chip->mpu = pnp_request_card_device(card, id->devs[2].id, NULL);
+       if (!chip->mpu)
+               return -EBUSY;
+
+       if (pnp_activate_dev(chip->wa) < 0) {
+               dev_err(chip->card->dev, "WA PnP configure failure\n");
+               return -EBUSY;
+       }
+       if (pnp_activate_dev(chip->sb) < 0) {
+               dev_err(chip->card->dev, "SB PnP configure failure\n");
+               return -EBUSY;
+       }
+       port[dev] = pnp_port_start(chip->wa, 0);
+       dma2[dev] = pnp_dma(chip->wa, 0);
+       fm_port[dev] = pnp_port_start(chip->sb, 1);
+       dma1[dev] = pnp_dma(chip->sb, 0);
+       irq[dev] = pnp_irq(chip->sb, 0);
+
+       /*
+        * The card uses only one IRQ (listed in the resources of SB device)
+        * which needs to be shared by WaveArtist and MPU-401 devices. They
+        * don't have an IRQ resource so it must be forced.
+        */
+       snd_waveartist_set_irq(chip->wa, irq[dev]);
+
+       /* allocate MPU-401 resources */
+       if (pnp_activate_dev(chip->mpu) < 0)
+               dev_err(chip->card->dev, "MPU-401 PnP configure failure: will 
be disabled\n");
+       else {
+               midi_port[dev] = pnp_port_start(chip->mpu, 0);
+               snd_waveartist_set_irq(chip->mpu, irq[dev]);
+       }
+
+       dev_dbg(chip->card->dev, "PnP WaveArtist: port=0x%lx, fm port=0x%lx, 
midi port=0x%lx\n",
+               port[dev], fm_port[dev], midi_port[dev]);
+       dev_dbg(chip->card->dev, "PnP WaveArtist: dma1=%i, dma2=%i, irq=%i\n",
+               dma1[dev], dma2[dev], irq[dev]);
+
+       return 0;
+}
+#endif /* CONFIG_PNP */
+
+static int snd_waveartist_playback_hw_params(
+                                       struct snd_pcm_substream *substream,
+                                       struct snd_pcm_hw_params *hw_params)
+{
+       int err = snd_pcm_lib_malloc_pages(substream,
+                                          params_buffer_bytes(hw_params));
+
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+static int snd_waveartist_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static enum wa_format waveartist_format(snd_pcm_format_t format)
+{
+       if (snd_pcm_format_width(format) == 16)
+               return WA_FMT_S16;
+       if (snd_pcm_format_unsigned(format))
+               return WA_FMT_U8;
+       else
+               return WA_FMT_S8;
+}
+
+static u16 waveartist_rate(struct snd_pcm_runtime *runtime)
+{
+       return (runtime->rate << 16) / 44100;
+}
+
+static int snd_waveartist_playback_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+       unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+       /* Set rate */
+       if (waveartist_cmd2(chip, WACMD_OUTPUTSPEED, waveartist_rate(runtime)))
+               dev_warn(chip->card->dev, "error setting playback rate %dHz\n",
+                        runtime->rate);
+       /* Set channel count */
+       if (waveartist_cmd2(chip, WACMD_OUTPUTCHANNELS, runtime->channels))
+               dev_warn(chip->card->dev, "error setting playback %d 
channels\n",
+                        runtime->channels);
+       /* Set DMA channel */
+       if (waveartist_cmd2(chip, WACMD_OUTPUTDMA,
+                       chip->dma_playback > 3 ? WA_DMA_16BIT : WA_DMA_8BIT))
+               dev_warn(chip->card->dev, "error setting playback data path\n");
+       /* Set format */
+       if (waveartist_cmd2(chip, WACMD_OUTPUTFORMAT,
+                           waveartist_format(runtime->format)))
+               dev_warn(chip->card->dev, "error setting playback format %d\n",
+                        runtime->format);
+       /* Set sample count */
+       if (waveartist_cmd2(chip, WACMD_OUTPUTSIZE, count - 1))
+               dev_warn(chip->card->dev, "error setting playback count %d\n",
+                        count);
+       /* Configure DMA controller */
+       snd_dma_program(chip->dma_playback, runtime->dma_addr, size,
+                       DMA_MODE_WRITE | DMA_AUTOINIT);
+
+       return 0;
+}
+
+static int snd_waveartist_playback_trigger(struct snd_pcm_substream *substream,
+                                          int cmd)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               waveartist_cmd1(chip, WACMD_OUTPUTSTART);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               waveartist_cmd1(chip, WACMD_OUTPUTSTOP);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               waveartist_cmd1(chip, WACMD_OUTPUTPAUSE);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               waveartist_cmd1(chip, WACMD_OUTPUTRESUME);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int snd_waveartist_capture_hw_params(struct snd_pcm_substream 
*substream,
+                                           struct snd_pcm_hw_params *hw_params)
+{
+       int err = snd_pcm_lib_malloc_pages(substream,
+                                          params_buffer_bytes(hw_params));
+
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+static int snd_waveartist_capture_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       unsigned int size = snd_pcm_lib_buffer_bytes(substream);
+       unsigned int count = snd_pcm_lib_period_bytes(substream);
+
+       /* Set rate */
+       if (waveartist_cmd2(chip, WACMD_INPUTSPEED, waveartist_rate(runtime)))
+               dev_warn(chip->card->dev, "error setting capture rate %dHz\n",
+                        runtime->rate);
+       /* Set channel count */
+       if (waveartist_cmd2(chip, WACMD_INPUTCHANNELS, runtime->channels))
+               dev_warn(chip->card->dev, "error setting capture %d channels\n",
+                        runtime->channels);
+       /* Set DMA channel */
+       if (waveartist_cmd2(chip, WACMD_INPUTDMA,
+                           chip->dma_capture > 3 ? WA_DMA_16BIT : WA_DMA_8BIT))
+               dev_warn(chip->card->dev, "error setting capture data path\n");
+       /* Set format */
+       if (waveartist_cmd2(chip, WACMD_INPUTFORMAT,
+                           waveartist_format(runtime->format)))
+               dev_warn(chip->card->dev, "error setting capture format %d\n",
+                        runtime->format);
+       /* Set sample count */
+       if (waveartist_cmd2(chip, WACMD_INPUTSIZE, count - 1))
+               dev_warn(chip->card->dev, "error setting capture count %d\n",
+                        count);
+       /* Configure DMA controller */
+       snd_dma_program(chip->dma_capture, runtime->dma_addr, size,
+                       DMA_MODE_READ | DMA_AUTOINIT);
+
+       return 0;
+}
+
+static int snd_waveartist_capture_trigger(struct snd_pcm_substream *substream,
+                                         int cmd)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               waveartist_cmd1(chip, WACMD_INPUTSTART);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               waveartist_cmd1(chip, WACMD_INPUTSTOP);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               waveartist_cmd1(chip, WACMD_INPUTPAUSE);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               waveartist_cmd1(chip, WACMD_INPUTRESUME);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static snd_pcm_uframes_t snd_waveartist_playback_pointer(
+                                       struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+       size_t size = snd_pcm_lib_buffer_bytes(substream);
+       size_t ptr = snd_dma_pointer(chip->dma_playback, size);
+
+       return bytes_to_frames(substream->runtime, ptr);
+}
+
+static snd_pcm_uframes_t snd_waveartist_capture_pointer(
+                                       struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+       size_t size = snd_pcm_lib_buffer_bytes(substream);
+       size_t ptr = snd_dma_pointer(chip->dma_capture, size);
+
+       return bytes_to_frames(substream->runtime, ptr);
+}
+
+static struct snd_pcm_hardware snd_waveartist_playback = {
+       .info =                 SNDRV_PCM_INFO_MMAP |
+                               SNDRV_PCM_INFO_INTERLEAVED |
+                               SNDRV_PCM_INFO_PAUSE |
+                               SNDRV_PCM_INFO_MMAP_VALID,
+       .formats =              SNDRV_PCM_FMTBIT_U8 |
+                               SNDRV_PCM_FMTBIT_S8 |
+                               SNDRV_PCM_FMTBIT_S16_LE,
+       .rates =                SNDRV_PCM_RATE_CONTINUOUS |
+                               SNDRV_PCM_RATE_8000_44100,
+       .rate_min =             4000,
+       .rate_max =             44100,
+       .channels_min =         1,
+       .channels_max =         2,
+       .buffer_bytes_max =     (128*1024),
+       .period_bytes_min =     64,
+       .period_bytes_max =     (128*1024),
+       .periods_min =          1,
+       .periods_max =          1024,
+};
+
+static struct snd_pcm_hardware snd_waveartist_capture = {
+       .info =                 SNDRV_PCM_INFO_MMAP |
+                               SNDRV_PCM_INFO_INTERLEAVED |
+                               SNDRV_PCM_INFO_PAUSE |
+                               SNDRV_PCM_INFO_MMAP_VALID,
+       .formats =              SNDRV_PCM_FMTBIT_U8 |
+                               SNDRV_PCM_FMTBIT_S8 |
+                               SNDRV_PCM_FMTBIT_S16_LE,
+       .rates =                SNDRV_PCM_RATE_CONTINUOUS |
+                               SNDRV_PCM_RATE_8000_44100,
+       .rate_min =             4000,
+       .rate_max =             44100,
+       .channels_min =         1,
+       .channels_max =         2,
+       .buffer_bytes_max =     (128*1024),
+       .period_bytes_min =     64,
+       .period_bytes_max =     (128*1024),
+       .periods_min =          1,
+       .periods_max =          1024,
+};
+
+static int snd_waveartist_playback_open(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       chip->playback_substream = substream;
+       substream->runtime->hw = snd_waveartist_playback;
+
+       snd_pcm_limit_isa_dma_size(chip->dma_playback,
+                                  &substream->runtime->hw.buffer_bytes_max);
+       snd_pcm_limit_isa_dma_size(chip->dma_playback,
+                                  &substream->runtime->hw.period_bytes_max);
+
+       return 0;
+}
+
+static int snd_waveartist_capture_open(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       chip->capture_substream = substream;
+       substream->runtime->hw = snd_waveartist_capture;
+
+       snd_pcm_limit_isa_dma_size(chip->dma_capture,
+                                  &substream->runtime->hw.buffer_bytes_max);
+       snd_pcm_limit_isa_dma_size(chip->dma_capture,
+                                  &substream->runtime->hw.period_bytes_max);
+
+       return 0;
+}
+
+static int snd_waveartist_playback_close(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       chip->playback_substream = NULL;
+       snd_pcm_lib_free_pages(substream);
+
+       return 0;
+}
+
+static int snd_waveartist_capture_close(struct snd_pcm_substream *substream)
+{
+       struct snd_waveartist *chip = snd_pcm_substream_chip(substream);
+
+       chip->capture_substream = NULL;
+       snd_pcm_lib_free_pages(substream);
+
+       return 0;
+}
+
+static struct snd_pcm_ops snd_waveartist_playback_ops = {
+       .open =         snd_waveartist_playback_open,
+       .close =        snd_waveartist_playback_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_waveartist_playback_hw_params,
+       .hw_free =      snd_waveartist_pcm_hw_free,
+       .prepare =      snd_waveartist_playback_prepare,
+       .trigger =      snd_waveartist_playback_trigger,
+       .pointer =      snd_waveartist_playback_pointer,
+};
+
+static struct snd_pcm_ops snd_waveartist_capture_ops = {
+       .open =         snd_waveartist_capture_open,
+       .close =        snd_waveartist_capture_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    snd_waveartist_capture_hw_params,
+       .hw_free =      snd_waveartist_pcm_hw_free,
+       .prepare =      snd_waveartist_capture_prepare,
+       .trigger =      snd_waveartist_capture_trigger,
+       .pointer =      snd_waveartist_capture_pointer,
+};
+
+static int snd_waveartist_pcm(struct snd_card *card)
+{
+       struct snd_waveartist *chip = card->private_data;
+       struct snd_pcm *pcm;
+       int err;
+
+       err = snd_pcm_new(card, "WaveArtist", 0, 1, 1, &pcm);
+       if (err < 0)
+               return err;
+
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+                       &snd_waveartist_playback_ops);
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+                       &snd_waveartist_capture_ops);
+
+       /* global setup */
+       pcm->private_data = chip;
+       pcm->info_flags = 0;
+       if (chip->dma_playback == chip->dma_capture)
+               pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX;
+       strcpy(pcm->name, card->shortname);
+       chip->pcm = pcm;
+
+       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+                     snd_dma_isa_data(), 64 * 1024,
+                     chip->dma_playback > 3 || chip->dma_capture > 3 ?
+                     128 * 1024 : 64 * 1024);
+
+       return 0;
+}
+
+static void snd_waveartist_free(struct snd_card *card)
+{
+       struct snd_waveartist *chip = card->private_data;
+
+       release_and_free_resource(chip->res_port);
+       if (chip->irq >= 0)
+               free_irq(chip->irq, card);
+       if (chip->dma_playback >= 0) {
+               disable_dma(chip->dma_playback);
+               free_dma(chip->dma_playback);
+       }
+       if (chip->dma_capture >= 0 && chip->dma_capture != chip->dma_playback) {
+               disable_dma(chip->dma_capture);
+               free_dma(chip->dma_capture);
+       }
+}
+
+static int snd_waveartist_dev_free(struct snd_device *device)
+{
+       snd_waveartist_free(device->card);
+
+       return 0;
+}
+
+static int snd_waveartist_init(struct snd_waveartist *chip)
+{
+       if (waveartist_reset(chip))
+               return -ENODEV;
+       dev_dbg(chip->card->dev, "chip rev. 0x%x\n", waveartist_getrev(chip));
+
+       waveartist_cmd1(chip, WACMD_RST_MIXER); /* reset mixer */
+       waveartist_iack(chip);  /* clear any pending interrupt */
+       waveartist_set_ctlr(chip, 0, DMA1_IE | DMA0_IE); /* enable DMA IRQs */
+       waveartist_iack(chip);  /* clear any pending interrupt */
+
+       return 0;
+}
+
+static int snd_waveartist_new_device(struct snd_card *card,
+                                    unsigned long port,
+                                    unsigned long mpu_port,
+                                    unsigned long fm_port,
+                                    int irq, int dma1, int dma2)
+{
+       struct snd_waveartist *chip = card->private_data;
+       static struct snd_device_ops ops = {
+               .dev_free =     snd_waveartist_dev_free,
+       };
+       int err;
+
+       spin_lock_init(&chip->reg_lock);
+       chip->card = card;
+       chip->port = port;
+       chip->irq = -1;
+       chip->dma_playback = -1;
+       chip->dma_capture = -1;
+
+       chip->res_port = request_region(port, 16, "WaveArtist");
+       if (!chip->res_port) {
+               snd_waveartist_free(card);
+               dev_err(chip->card->dev, "unable to grab ports 0x%lx-0x%lx\n",
+                       port, port + 16 - 1);
+               return -EBUSY;
+       }
+
+       if (snd_waveartist_init(chip) < 0) {
+               snd_waveartist_free(card);
+               return -ENODEV;
+       }
+
+       if (request_irq(irq, snd_waveartist_interrupt, 0, "WaveArtist", card)) {
+               snd_waveartist_free(card);
+               dev_err(chip->card->dev, "unable to grab IRQ %d\n", irq);
+               return -EBUSY;
+       }
+       chip->irq = irq;
+
+       if (dma1 == dma2 || dma1 == SNDRV_AUTO_DMA || dma2 == SNDRV_AUTO_DMA) {
+               /* we have only one DMA channel */
+               if (dma1 == SNDRV_AUTO_DMA)
+                       dma1 = dma2;
+               if (request_dma(dma1, "WaveArtist")) {
+                       snd_waveartist_free(card);
+                       dev_err(chip->card->dev, "unable to grab DMA %d\n",
+                               dma1);
+                       return -EBUSY;
+               }
+               chip->dma_playback = chip->dma_capture = dma1;
+       } else {
+               /*
+                * 2 channels: use 16-bit for playback and 8-bit for capture as
+                * full-duplex works better this way. However, the chip seems to
+                * have some band-width limit so full-duplex at 44kHz/16-bit/2ch
+                * is not possible - some frames are dropped during capture,
+                * resulting in too-fast recording. If capture is done using
+                * lower rate or 8-bit or mono, everything is fine.
+                */
+               if (dma2 > 3)
+                       swap(dma1, dma2);
+
+               if (request_dma(dma1, "WaveArtist playback")) {
+                       snd_waveartist_free(card);
+                       dev_err(chip->card->dev, "unable to grab playback DMA 
%d\n",
+                               dma1);
+                       return -EBUSY;
+               }
+               chip->dma_playback = dma1;
+
+               if (dma2 != dma1 && request_dma(dma2, "WaveArtist capture")) {
+                       snd_waveartist_free(card);
+                       dev_err(chip->card->dev, "unable to grab capture DMA 
%d\n",
+                               dma2);
+                       return -EBUSY;
+               }
+               chip->dma_capture = dma2;
+       }
+
+       err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+       if (err < 0) {
+               snd_waveartist_free(card);
+               return err;
+       }
+
+       return 0;
+}
+
+static int snd_waveartist_card_new(struct device *pdev, int dev,
+                               struct snd_card **cardp)
+{
+       return snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
+                           sizeof(struct snd_waveartist), cardp);
+}
+
+static int snd_waveartist_info_single(struct snd_kcontrol *kcontrol,
+                                     struct snd_ctl_elem_info *uinfo)
+{
+       int mask = (kcontrol->private_value >> 16) & 0xff;
+
+       uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
+                                 SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = mask;
+
+       return 0;
+}
+
+static int snd_waveartist_get_single(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       int reg = kcontrol->private_value & 0xff;
+       int shift = (kcontrol->private_value >> 8) & 0xff;
+       int mask = (kcontrol->private_value >> 16) & 0xff;
+       u16 val;
+
+       val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | reg << 8);
+       ucontrol->value.integer.value[0] = (val >> shift) & mask;
+
+       return 0;
+}
+
+static int snd_waveartist_put_single(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       int reg = kcontrol->private_value & 0xff;
+       int shift = (kcontrol->private_value >> 8) & 0xff;
+       int mask = (kcontrol->private_value >> 16) & 0xff;
+       u16 val, old_val, new_val;
+
+       val = (ucontrol->value.integer.value[0] & mask);
+       mask <<= shift;
+       val <<= shift;
+
+       old_val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | reg << 8);
+       new_val = (old_val & ~mask) | (val & mask);
+
+       if (new_val == old_val)
+               return 0;
+
+       /* new_val already contains register number (bits 12..15), bit 11 set */
+       waveartist_cmd3(chip, WACMD_SET_MIXER, new_val, new_val);
+
+       return 1;
+}
+
+static int snd_waveartist_info_double(struct snd_kcontrol *kcontrol,
+                                     struct snd_ctl_elem_info *uinfo)
+{
+       int mask = (kcontrol->private_value >> 24) & 0xff;
+
+       uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
+                                 SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = mask ? mask : 0x7fff;
+
+       return 0;
+}
+
+static int snd_waveartist_get_double(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       int left_reg = kcontrol->private_value & 0xff;
+       int right_reg = (kcontrol->private_value >> 8) & 0xff;
+       int shift_left = (kcontrol->private_value >> 16) & 0x0f;
+       int shift_right = (kcontrol->private_value >> 20) & 0x0f;
+       int mask = (kcontrol->private_value >> 24) & 0xff;
+       u16 left, right;
+
+       if (mask == 0)
+               mask = 0x7fff;
+
+       left = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | left_reg << 8);
+       if (left_reg != right_reg)
+               right = waveartist_cmd1_r(chip, WACMD_GET_LEVEL |
+                                               right_reg << 8);
+       else
+               right = left;
+
+       ucontrol->value.integer.value[0] = (left >> shift_left) & mask;
+       ucontrol->value.integer.value[1] = (right >> shift_right) & mask;
+
+       return 0;
+}
+
+static int snd_waveartist_put_double(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       int left_reg = kcontrol->private_value & 0xff;
+       int right_reg = (kcontrol->private_value >> 8) & 0xff;
+       int shift_left = (kcontrol->private_value >> 16) & 0x0f;
+       int shift_right = (kcontrol->private_value >> 20) & 0x0f;
+       int mask = (kcontrol->private_value >> 24) & 0xff;
+       u16 val1, val2, mask1, mask2;
+       u16 old_left, old_right, new_left, new_right;
+
+       if (mask == 0)
+               mask = 0x7fff;
+
+       val1 = ucontrol->value.integer.value[0] & mask;
+       val2 = ucontrol->value.integer.value[1] & mask;
+       val1 <<= shift_left;
+       val2 <<= shift_right;
+       mask1 = mask << shift_left;
+       mask2 = mask << shift_right;
+
+       old_left = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | left_reg << 8);
+       if (left_reg != right_reg)
+               old_right = waveartist_cmd1_r(chip, WACMD_GET_LEVEL |
+                                                   right_reg << 8);
+       else
+               old_right = old_left;
+
+       new_left = (old_left & ~mask1) | (val1 & mask1);
+       new_right = (old_right & ~mask2) | (val2 & mask2);
+
+       if (new_left == old_left && new_right == old_right)
+               return 0;
+
+       if (left_reg < 10)
+               /* new_* already contain reg. num. (bits 12..15), bit 11 set */
+               waveartist_cmd3(chip, WACMD_SET_MIXER, new_left, new_right);
+       else
+               waveartist_cmd3(chip, WACMD_SET_LEVEL |
+                                     ((left_reg - 10) / 2) << 8,
+                                     new_left, new_right);
+
+       return 1;
+}
+
+static int snd_waveartist_info_mux(struct snd_kcontrol *kcontrol,
+                                  struct snd_ctl_elem_info *uinfo)
+{
+       static const char * const mux_texts[] = {
+               "None", "Mix", "Line", "Phone", "CD", "Mic"
+       };
+
+       return snd_ctl_enum_info(uinfo, 2, ARRAY_SIZE(mux_texts), mux_texts);
+}
+
+static int snd_waveartist_get_mux(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       int mux = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | 0x08 << 8);
+
+       ucontrol->value.enumerated.item[0] = mux & 0x07;
+       ucontrol->value.enumerated.item[1] = (mux >> 3) & 0x07;
+
+       return 0;
+}
+
+static int snd_waveartist_put_mux(struct snd_kcontrol *kcontrol,
+                                 struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_waveartist *chip = snd_kcontrol_chip(kcontrol);
+       u16 old_val, new_val;
+       u16 val1 = ucontrol->value.enumerated.item[0];
+       u16 val2 = ucontrol->value.enumerated.item[1];
+
+       old_val = waveartist_cmd1_r(chip, WACMD_GET_LEVEL | 0x08 << 8);
+       new_val = (old_val & ~0x3f) | (val1 & 0x07) | ((val2 & 0x07) << 3);
+       if (new_val == old_val)
+               return 0;
+
+       /* new_val already contains register number (bits 12..15), bit 11 set */
+       waveartist_cmd3(chip, WACMD_SET_MIXER, new_val, new_val);
+
+       return 1;
+}
+
+#define WAVEARTIST_SINGLE(xname, reg, shift, mask) \
+{ \
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = snd_waveartist_info_single, \
+       .get = snd_waveartist_get_single, .put = snd_waveartist_put_single, \
+       .private_value = reg | (shift << 8) | (mask << 16) \
+}
+
+#define WAVEARTIST_DOUBLE(xname, left_reg, right_reg, shift_left, shift_right, 
\
+                         mask) \
+{ \
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .info = snd_waveartist_info_double, \
+       .get = snd_waveartist_get_double, .put = snd_waveartist_put_double, \
+       .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | \
+                        (shift_right << 20) | (mask << 24) \
+}
+
+static struct snd_kcontrol_new snd_waveartist_controls[] = {
+WAVEARTIST_DOUBLE("Master Playback Volume", 2, 6, 1, 1, 0x07),
+WAVEARTIST_DOUBLE("Master Playback Switch", 0, 4, 0, 0, 1),
+WAVEARTIST_DOUBLE("PCM Playback Volume", 10, 11, 0, 0, 0x00),/* 0x00 = 0x7fff 
*/
+WAVEARTIST_DOUBLE("FM Playback Volume", 12, 13, 0, 0, 0x00),/* 0x00 = 0x7fff */
+/* WAVEARTIST_DOUBLE("Wavetable? Playback Volume", 14, 15, 0, 0, 0x00), */
+/* WAVEARTIST_DOUBLE("? Playback Volume", 16, 17, 0, 0, 0x00), */
+/* WAVEARTIST_DOUBLE("? Playback Volume", 18, 19, 0, 0, 0x00), */
+WAVEARTIST_DOUBLE("Digital Playback Switch", 3, 7, 10, 10, 1), /* PCM + FM */
+WAVEARTIST_DOUBLE("CD Playback Volume", 0, 4, 1, 1, 0x1f),
+WAVEARTIST_DOUBLE("CD Playback Switch", 3, 7, 6, 6, 1),
+WAVEARTIST_DOUBLE("Line Playback Volume", 0, 4, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Line Playback Switch", 3, 7, 4, 4, 1),
+WAVEARTIST_DOUBLE("Phone Playback Volume", 1, 5, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Phone Playback Switch", 3, 7, 5, 5, 1),
+WAVEARTIST_SINGLE("Mono Playback Volume", 8, 5, 0x1f),
+WAVEARTIST_DOUBLE("Mono Playback Switch", 3, 7, 9, 9, 1),
+WAVEARTIST_DOUBLE("Mic Playback Volume", 2, 6, 6, 6, 0x1f),
+WAVEARTIST_DOUBLE("Mic 2 Playback Volume", 1, 5, 1, 1, 0x1f),
+WAVEARTIST_DOUBLE("Mic Playback Switch", 3, 7, 7, 7, 1),
+WAVEARTIST_DOUBLE("Mic 2 Playback Switch", 3, 7, 8, 8, 1),
+WAVEARTIST_DOUBLE("Mic Gain", 2, 6, 4, 4, 0x03),
+WAVEARTIST_DOUBLE("Capture Volume", 3, 7, 0, 0, 0x0f),
+WAVEARTIST_SINGLE("Mono Output Playback Switch", 1, 0, 1),
+{
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "Capture Source",
+       .info = snd_waveartist_info_mux,
+       .get = snd_waveartist_get_mux,
+       .put = snd_waveartist_put_mux,
+}
+};
+
+static int snd_waveartist_mixer(struct snd_card *card)
+{
+       struct snd_waveartist *chip = card->private_data;
+       int err;
+       unsigned int idx;
+
+       strcpy(card->mixername, chip->pcm->name);
+
+       for (idx = 0; idx < ARRAY_SIZE(snd_waveartist_controls); idx++) {
+               struct snd_kcontrol *kctl;
+
+               kctl = snd_ctl_new1(&snd_waveartist_controls[idx], chip);
+               err = snd_ctl_add(card, kctl);
+               if (err < 0)
+                       return err;
+       }
+
+       return 0;
+}
+
+static int snd_waveartist_probe(struct snd_card *card, int dev)
+{
+       struct snd_waveartist *chip = card->private_data;
+       struct snd_opl3 *opl3;
+       int err;
+
+       err = snd_waveartist_new_device(card,
+                                       port[dev], midi_port[dev], fm_port[dev],
+                                       irq[dev], dma1[dev], dma2[dev]);
+       if (err < 0)
+               return err;
+
+       strcpy(card->driver, "WaveArtist");
+       strcpy(card->shortname, "Rockwell WaveArtist RWA010");
+       sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d",
+               card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]);
+
+       err = snd_waveartist_pcm(card);
+       if (err < 0)
+               return err;
+       err = snd_waveartist_mixer(card);
+       if (err < 0)
+               return err;
+
+       if (fm_port[dev] >= 0x388 && fm_port[dev] < 0x3f0) {
+               err = snd_opl3_create(card, fm_port[dev], fm_port[dev] + 2,
+                                     OPL3_HW_OPL3, 0, &opl3);
+               if (err < 0)
+                       return err;
+               err = snd_opl3_timer_new(opl3, 1, 2);
+               if (err < 0)
+                       return err;
+               err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth);
+               if (err < 0)
+                       return err;
+       }
+       if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x3f0) {
+               err = snd_mpu401_uart_new(card, 0, MPU401_HW_WAVEARTIST,
+                                         midi_port[dev], MPU401_INFO_IRQ_HOOK,
+                                         -1, &chip->rmidi);
+               if (err < 0)
+                       return err;
+       }
+
+       return snd_card_register(card);
+}
+
+static int snd_waveartist_isa_match(struct device *pdev,
+                                   unsigned int dev)
+{
+       if (!enable[dev])
+               return 0;
+#ifdef CONFIG_PNP
+       if (isapnp[dev])
+               return 0;
+#endif
+       if (port[dev] == SNDRV_AUTO_PORT) {
+               dev_err(pdev, "specify port\n");
+               return 0;
+       }
+       if (irq[dev] == SNDRV_AUTO_IRQ) {
+               dev_err(pdev, "specify irq\n");
+               return 0;
+       }
+       if (dma1[dev] == SNDRV_AUTO_DMA) {
+               dev_err(pdev, "specify dma1\n");
+               return 0;
+       }
+
+       return 1;
+}
+
+static int snd_waveartist_isa_probe(struct device *pdev,
+                                   unsigned int dev)
+{
+       struct snd_card *card;
+       int err;
+
+       err = snd_waveartist_card_new(pdev, dev, &card);
+       if (err < 0)
+               return err;
+       err = snd_waveartist_probe(card, dev);
+       if (err < 0) {
+               snd_card_free(card);
+               return err;
+       }
+       dev_set_drvdata(pdev, card);
+
+       return 0;
+}
+
+static int snd_waveartist_isa_remove(struct device *devptr,
+                                    unsigned int dev)
+{
+       snd_card_free(dev_get_drvdata(devptr));
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_isa_suspend(struct device *dev, unsigned int n,
+                                     pm_message_t state)
+{
+       return snd_waveartist_suspend(dev_get_drvdata(dev), state);
+}
+
+static int snd_waveartist_isa_resume(struct device *dev, unsigned int n)
+{
+       return snd_waveartist_resume(dev_get_drvdata(dev));
+}
+#endif
+
+static struct isa_driver waveartist_isa_driver = {
+       .match          = snd_waveartist_isa_match,
+       .probe          = snd_waveartist_isa_probe,
+       .remove         = snd_waveartist_isa_remove,
+#ifdef CONFIG_PM
+       .suspend        = snd_waveartist_isa_suspend,
+       .resume         = snd_waveartist_isa_resume,
+#endif
+       .driver         = { .name = "waveartist" },
+};
+
+#ifdef CONFIG_PNP
+static int snd_waveartist_pnp_detect(struct pnp_card_link *pcard,
+                                    const struct pnp_card_device_id *pid)
+{
+       static int dev;
+       int err;
+       struct snd_card *card;
+
+       for (; dev < SNDRV_CARDS; dev++) {
+               if (enable[dev] && isapnp[dev])
+                       break;
+       }
+       if (dev >= SNDRV_CARDS)
+               return -ENODEV;
+
+       err = snd_waveartist_card_new(&pcard->card->dev, dev, &card);
+       if (err < 0)
+               return err;
+       err = snd_waveartist_pnp(dev, card->private_data, pcard, pid);
+       if (err < 0) {
+               dev_err(&pcard->card->dev, "PnP detection failed\n");
+               snd_card_free(card);
+               return err;
+       }
+       err = snd_waveartist_probe(card, dev);
+       if (err < 0) {
+               snd_card_free(card);
+               return err;
+       }
+       pnp_set_card_drvdata(pcard, card);
+       dev++;
+
+       return 0;
+}
+
+static void snd_waveartist_pnp_remove(struct pnp_card_link *pcard)
+{
+       struct snd_card *card = pnp_get_card_drvdata(pcard);
+       struct snd_waveartist *chip = card->private_data;
+
+       /* disable forced IRQs */
+       snd_waveartist_set_irq(chip->wa, 0);
+       snd_waveartist_set_irq(chip->mpu, 0);
+
+       snd_card_free(pnp_get_card_drvdata(pcard));
+       pnp_set_card_drvdata(pcard, NULL);
+}
+
+#ifdef CONFIG_PM
+static int snd_waveartist_pnp_suspend(struct pnp_card_link *pcard,
+                                     pm_message_t state)
+{
+       struct snd_card *card = pnp_get_card_drvdata(pcard);
+       struct snd_waveartist *chip = card->private_data;
+
+       snd_waveartist_suspend(card, state);
+
+       /* disable forced IRQs to prevent opps */
+       snd_waveartist_set_irq(chip->wa, 0);
+       snd_waveartist_set_irq(chip->mpu, 0);
+
+       return 0;
+}
+static int snd_waveartist_pnp_resume(struct pnp_card_link *pcard)
+{
+       struct snd_card *card = pnp_get_card_drvdata(pcard);
+       struct snd_waveartist *chip = card->private_data;
+
+       /* re-enable forced IRQs */
+       snd_waveartist_set_irq(chip->wa, chip->irq);
+       snd_waveartist_set_irq(chip->mpu, chip->irq);
+
+       return snd_waveartist_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
+static struct pnp_card_driver waveartist_pnpc_driver = {
+       .flags          = PNP_DRIVER_RES_DISABLE,
+       .name           = "waveartist",
+       .id_table       = snd_waveartist_pnpids,
+       .probe          = snd_waveartist_pnp_detect,
+       .remove         = snd_waveartist_pnp_remove,
+#ifdef CONFIG_PM
+       .suspend        = snd_waveartist_pnp_suspend,
+       .resume         = snd_waveartist_pnp_resume,
+#endif
+};
+#endif /* CONFIG_PNP */
+
+static int __init alsa_card_waveartist_init(void)
+{
+       int err;
+
+       err = isa_register_driver(&waveartist_isa_driver, SNDRV_CARDS);
+#ifdef CONFIG_PNP
+       if (!err)
+               isa_registered = 1;
+
+       err = pnp_register_card_driver(&waveartist_pnpc_driver);
+       if (!err)
+               pnp_registered = 1;
+
+       if (isa_registered)
+               err = 0;
+#endif
+       return err;
+}
+
+static void __exit alsa_card_waveartist_exit(void)
+{
+#ifdef CONFIG_PNP
+       if (pnp_registered)
+               pnp_unregister_card_driver(&waveartist_pnpc_driver);
+
+       if (isa_registered)
+#endif
+               isa_unregister_driver(&waveartist_isa_driver);
+}
+
+module_init(alsa_card_waveartist_init)
+module_exit(alsa_card_waveartist_exit)
diff --git a/sound/isa/waveartist.h b/sound/isa/waveartist.h
new file mode 100644
index 0000000..72254cf
--- /dev/null
+++ b/sound/isa/waveartist.h
@@ -0,0 +1,74 @@
+/*
+ *  Rockwell WaveArtist RWA010 chip register definitions
+ *
+ *  Based on OSS WaveArtist driver by Hannu Savolainen
+ */
+
+/* registers */
+#define CMDR   0       /* command (16-bit) */
+#define DATR   2       /* data (for PIO?) (16-bit) */
+#define CTLR   4       /* control */
+#define        STATR   5       /* status */
+#define EXPCR1 6       /* expansion control 1 */
+#define EXPCR2 7       /* expansion control 2 */
+#define EXPDAT1 8      /* expansion data 1 (16-bit) */
+#define EXPDAT2 10     /* expansion data 2 (16-bit) */
+#define        IRQSTAT 12      /* IRQ status */
+
+/* STATR register bit definitions */
+#define        CMD_WE  BIT(7)  /* CMDR write empty (ready for write) */
+#define        CMD_RF  BIT(6)  /* CMDR read full (ready for read) */
+#define        DAT_WE  BIT(5)  /* DATR write empty (ready for write) */
+#define        DAT_RF  BIT(4)  /* DATR read full (ready for read) */
+#define        IRQ_REQ BIT(3)  /* IRQ was requested */
+#define        DMA1    BIT(2)  /* DMA1 IRQ was requested */
+#define        DMA0    BIT(1)  /* DMA0 IRQ was requested */
+
+/* CTLR register bit definitions */
+#define        CMD_WEIE        BIT(7)  /* CMDR write empty interrupt enable */
+#define        CMD_RFIE        BIT(6)  /* CMDR read full interrupt enable */
+#define        DAT_WEIE        BIT(5)  /* DATR write empty interrupt enable */
+#define        DAT_RFIE        BIT(4)  /* DATR read full interrupt enable */
+#define        RESET           BIT(3)  /* chip reset */
+#define        DMA1_IE         BIT(2)  /* DMA1 interrupt enable */
+#define        DMA0_IE         BIT(1)  /* DMA0 interrupt enable */
+#define        IRQ_ACK         BIT(0)  /* IRQ acknowlege */
+
+/* IRQSTAT register bit definitions */
+#define IRQ_MPU                BIT(2)  /* MPU-401 */
+#define IRQ_SB         BIT(1)  /* SB emulation */
+#define IRQ_PCM                BIT(0)  /* native PCM */
+
+/* commands */
+#define WACMD_GETREV           0x00
+
+#define        WACMD_INPUTFORMAT       0x10    /* 0=S8, 1=S16, 2=U8 */
+#define        WACMD_INPUTCHANNELS     0x11    /* 1=mono, 2=Stereo */
+#define        WACMD_INPUTSPEED        0x12    /* sampling rate */
+#define        WACMD_INPUTDMA          0x13    /* 0=8bit, 1=16bit, 2=PIO */
+#define        WACMD_INPUTSIZE         0x14    /* samples to interrupt */
+#define        WACMD_INPUTSTART        0x15    /* start ADC */
+#define        WACMD_INPUTPAUSE        0x16    /* pause ADC */
+#define        WACMD_INPUTSTOP         0x17    /* stop ADC */
+#define        WACMD_INPUTRESUME       0x18    /* resume ADC */
+#define        WACMD_INPUTPIO          0x19    /* PIO ADC */
+
+#define        WACMD_OUTPUTFORMAT      0x20    /* 0=S8, 1=S16, 2=U8 */
+#define        WACMD_OUTPUTCHANNELS    0x21    /* 1=mono, 2=stereo */
+#define        WACMD_OUTPUTSPEED       0x22    /* sampling rate */
+#define        WACMD_OUTPUTDMA         0x23    /* 0=8bit, 1=16bit, 2=PIO */
+#define        WACMD_OUTPUTSIZE        0x24    /* samples to interrupt */
+#define        WACMD_OUTPUTSTART       0x25    /* start DAC */
+#define        WACMD_OUTPUTPAUSE       0x26    /* pause DAC */
+#define        WACMD_OUTPUTSTOP        0x27    /* stop DAC */
+#define        WACMD_OUTPUTRESUME      0x28    /* resume DAC */
+#define        WACMD_OUTPUTPIO         0x29    /* PIO DAC */
+
+#define        WACMD_GET_LEVEL         0x30    /* read mixer reg */
+#define        WACMD_SET_LEVEL         0x31    /* set mixer regs (10..19) */
+#define        WACMD_SET_MIXER         0x32    /* set mixer regs (0..9) */
+#define        WACMD_RST_MIXER         0x33    /* mixer reset */
+#define        WACMD_SET_MONO          0x34    /* set mono mode (|0x000=L, 
|0x100=R) */
+
+enum wa_format { WA_FMT_S8 = 0, WA_FMT_S16, WA_FMT_U8 };
+enum wa_dma { WA_DMA_8BIT = 0, WA_DMA_16BIT, WA_DMA_PIO };
-- 
Ondrej Zary

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to