[PATCH 1/2] [resend #2] snd-waveartist: Introduce Rockwell WaveArtist RWA010 driver

From: Ondrej Zary
Date: Mon Aug 24 2015 - 15:50:51 EST


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 <linux@xxxxxxxxxxxxxxxxxxxx>
---
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/