2015-03-08

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@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);

+ /* 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 = 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 = 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, 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, 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@vger.kernel.org

More majordomo info at http://vger.kernel.org/majordomo-info.html

Please read the FAQ at http://www.tux.org/lkml/

Show more