Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754182AbbHXTuj (ORCPT ); Mon, 24 Aug 2015 15:50:39 -0400 Received: from ns.gsystem.sk ([62.176.172.50]:60938 "EHLO gsystem.sk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751179AbbHXTu3 (ORCPT ); Mon, 24 Aug 2015 15:50:29 -0400 From: Ondrej Zary To: alsa-devel@alsa-project.org Cc: Takashi Iwai , Kernel development list Subject: [PATCH 1/2] [resend #2] snd-waveartist: Introduce Rockwell WaveArtist RWA010 driver Date: Mon, 24 Aug 2015 21:50:15 +0200 Message-Id: <1440445816-28593-1-git-send-email-linux@rainbow-software.org> X-Mailer: git-send-email 1.7.10.4 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 46850 Lines: 1571 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 --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/