mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 01:34:14 +08:00
e992ef5705
When the kernel is running in secure boot mode, we lock down the kernel to prevent userspace from modifying the running kernel image. Whilst this includes prohibiting access to things like /dev/mem, it must also prevent access by means of configuring driver modules in such a way as to cause a device to access or modify the kernel image. To this end, annotate module_param* statements that refer to hardware configuration and indicate for future reference what type of parameter they specify. The parameter parser in the core sees this information and can skip such parameters with an error message if the kernel is locked down. The module initialisation then runs as normal, but just sees whatever the default values for those parameters is. Note that we do still need to do the module initialisation because some drivers have viable defaults set in case parameters aren't specified and some drivers support automatic configuration (e.g. PNP or PCI) in addition to manually coded parameters. This patch annotates drivers in sound/isa/. Suggested-by: Alan Cox <gnomes@lxorguk.ukuu.org.uk> Signed-off-by: David Howells <dhowells@redhat.com> cc: Jaroslav Kysela <perex@perex.cz> cc: Takashi Iwai <tiwai@suse.com> cc: alsa-devel@alsa-project.org
1358 lines
33 KiB
C
1358 lines
33 KiB
C
/*
|
|
* Low-level ALSA driver for the ENSONIQ SoundScape
|
|
* Copyright (c) by Chris Rankin
|
|
*
|
|
* This driver was written in part using information obtained from
|
|
* the OSS/Free SoundScape driver, written by Hannu Savolainen.
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/isa.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/pnp.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/module.h>
|
|
#include <asm/dma.h>
|
|
#include <sound/core.h>
|
|
#include <sound/wss.h>
|
|
#include <sound/mpu401.h>
|
|
#include <sound/initval.h>
|
|
|
|
|
|
MODULE_AUTHOR("Chris Rankin");
|
|
MODULE_DESCRIPTION("ENSONIQ SoundScape driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_FIRMWARE("sndscape.co0");
|
|
MODULE_FIRMWARE("sndscape.co1");
|
|
MODULE_FIRMWARE("sndscape.co2");
|
|
MODULE_FIRMWARE("sndscape.co3");
|
|
MODULE_FIRMWARE("sndscape.co4");
|
|
MODULE_FIRMWARE("scope.cod");
|
|
|
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
|
|
static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
|
|
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
|
static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
|
|
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
|
|
static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
|
|
static int dma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
|
|
static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA;
|
|
static bool joystick[SNDRV_CARDS];
|
|
|
|
module_param_array(index, int, NULL, 0444);
|
|
MODULE_PARM_DESC(index, "Index number for SoundScape soundcard");
|
|
|
|
module_param_array(id, charp, NULL, 0444);
|
|
MODULE_PARM_DESC(id, "Description for SoundScape card");
|
|
|
|
module_param_hw_array(port, long, ioport, NULL, 0444);
|
|
MODULE_PARM_DESC(port, "Port # for SoundScape driver.");
|
|
|
|
module_param_hw_array(wss_port, long, ioport, NULL, 0444);
|
|
MODULE_PARM_DESC(wss_port, "WSS Port # for SoundScape driver.");
|
|
|
|
module_param_hw_array(irq, int, irq, NULL, 0444);
|
|
MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver.");
|
|
|
|
module_param_hw_array(mpu_irq, int, irq, NULL, 0444);
|
|
MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver.");
|
|
|
|
module_param_hw_array(dma, int, dma, NULL, 0444);
|
|
MODULE_PARM_DESC(dma, "DMA # for SoundScape driver.");
|
|
|
|
module_param_hw_array(dma2, int, dma, NULL, 0444);
|
|
MODULE_PARM_DESC(dma2, "DMA2 # for SoundScape driver.");
|
|
|
|
module_param_array(joystick, bool, NULL, 0444);
|
|
MODULE_PARM_DESC(joystick, "Enable gameport.");
|
|
|
|
#ifdef CONFIG_PNP
|
|
static int isa_registered;
|
|
static int pnp_registered;
|
|
|
|
static struct pnp_card_device_id sscape_pnpids[] = {
|
|
{ .id = "ENS3081", .devs = { { "ENS0000" } } }, /* Soundscape PnP */
|
|
{ .id = "ENS4081", .devs = { { "ENS1011" } } }, /* VIVO90 */
|
|
{ .id = "" } /* end */
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids);
|
|
#endif
|
|
|
|
|
|
#define HOST_CTRL_IO(i) ((i) + 2)
|
|
#define HOST_DATA_IO(i) ((i) + 3)
|
|
#define ODIE_ADDR_IO(i) ((i) + 4)
|
|
#define ODIE_DATA_IO(i) ((i) + 5)
|
|
#define CODEC_IO(i) ((i) + 8)
|
|
|
|
#define IC_ODIE 1
|
|
#define IC_OPUS 2
|
|
|
|
#define RX_READY 0x01
|
|
#define TX_READY 0x02
|
|
|
|
#define CMD_ACK 0x80
|
|
#define CMD_SET_MIDI_VOL 0x84
|
|
#define CMD_GET_MIDI_VOL 0x85
|
|
#define CMD_XXX_MIDI_VOL 0x86
|
|
#define CMD_SET_EXTMIDI 0x8a
|
|
#define CMD_GET_EXTMIDI 0x8b
|
|
#define CMD_SET_MT32 0x8c
|
|
#define CMD_GET_MT32 0x8d
|
|
|
|
enum GA_REG {
|
|
GA_INTSTAT_REG = 0,
|
|
GA_INTENA_REG,
|
|
GA_DMAA_REG,
|
|
GA_DMAB_REG,
|
|
GA_INTCFG_REG,
|
|
GA_DMACFG_REG,
|
|
GA_CDCFG_REG,
|
|
GA_SMCFGA_REG,
|
|
GA_SMCFGB_REG,
|
|
GA_HMCTL_REG
|
|
};
|
|
|
|
#define DMA_8BIT 0x80
|
|
|
|
|
|
enum card_type {
|
|
MEDIA_FX, /* Sequoia S-1000 */
|
|
SSCAPE, /* Sequoia S-2000 */
|
|
SSCAPE_PNP,
|
|
SSCAPE_VIVO,
|
|
};
|
|
|
|
struct soundscape {
|
|
spinlock_t lock;
|
|
unsigned io_base;
|
|
int ic_type;
|
|
enum card_type type;
|
|
struct resource *io_res;
|
|
struct resource *wss_res;
|
|
struct snd_wss *chip;
|
|
|
|
unsigned char midi_vol;
|
|
};
|
|
|
|
#define INVALID_IRQ ((unsigned)-1)
|
|
|
|
|
|
static inline struct soundscape *get_card_soundscape(struct snd_card *c)
|
|
{
|
|
return (struct soundscape *) (c->private_data);
|
|
}
|
|
|
|
/*
|
|
* Allocates some kernel memory that we can use for DMA.
|
|
* I think this means that the memory has to map to
|
|
* contiguous pages of physical memory.
|
|
*/
|
|
static struct snd_dma_buffer *get_dmabuf(struct snd_dma_buffer *buf,
|
|
unsigned long size)
|
|
{
|
|
if (buf) {
|
|
if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV,
|
|
snd_dma_isa_data(),
|
|
size, buf) < 0) {
|
|
snd_printk(KERN_ERR "sscape: Failed to allocate "
|
|
"%lu bytes for DMA\n",
|
|
size);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Release the DMA-able kernel memory ...
|
|
*/
|
|
static void free_dmabuf(struct snd_dma_buffer *buf)
|
|
{
|
|
if (buf && buf->area)
|
|
snd_dma_free_pages(buf);
|
|
}
|
|
|
|
/*
|
|
* This function writes to the SoundScape's control registers,
|
|
* but doesn't do any locking. It's up to the caller to do that.
|
|
* This is why this function is "unsafe" ...
|
|
*/
|
|
static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg,
|
|
unsigned char val)
|
|
{
|
|
outb(reg, ODIE_ADDR_IO(io_base));
|
|
outb(val, ODIE_DATA_IO(io_base));
|
|
}
|
|
|
|
/*
|
|
* Write to the SoundScape's control registers, and do the
|
|
* necessary locking ...
|
|
*/
|
|
static void sscape_write(struct soundscape *s, enum GA_REG reg,
|
|
unsigned char val)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
sscape_write_unsafe(s->io_base, reg, val);
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
}
|
|
|
|
/*
|
|
* Read from the SoundScape's control registers, but leave any
|
|
* locking to the caller. This is why the function is "unsafe" ...
|
|
*/
|
|
static inline unsigned char sscape_read_unsafe(unsigned io_base,
|
|
enum GA_REG reg)
|
|
{
|
|
outb(reg, ODIE_ADDR_IO(io_base));
|
|
return inb(ODIE_DATA_IO(io_base));
|
|
}
|
|
|
|
/*
|
|
* Puts the SoundScape into "host" mode, as compared to "MIDI" mode
|
|
*/
|
|
static inline void set_host_mode_unsafe(unsigned io_base)
|
|
{
|
|
outb(0x0, HOST_CTRL_IO(io_base));
|
|
}
|
|
|
|
/*
|
|
* Puts the SoundScape into "MIDI" mode, as compared to "host" mode
|
|
*/
|
|
static inline void set_midi_mode_unsafe(unsigned io_base)
|
|
{
|
|
outb(0x3, HOST_CTRL_IO(io_base));
|
|
}
|
|
|
|
/*
|
|
* Read the SoundScape's host-mode control register, but leave
|
|
* any locking issues to the caller ...
|
|
*/
|
|
static inline int host_read_unsafe(unsigned io_base)
|
|
{
|
|
int data = -1;
|
|
if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0)
|
|
data = inb(HOST_DATA_IO(io_base));
|
|
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* Read the SoundScape's host-mode control register, performing
|
|
* a limited amount of busy-waiting if the register isn't ready.
|
|
* Also leaves all locking-issues to the caller ...
|
|
*/
|
|
static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout)
|
|
{
|
|
int data;
|
|
|
|
while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) {
|
|
udelay(100);
|
|
--timeout;
|
|
} /* while */
|
|
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
* Write to the SoundScape's host-mode control registers, but
|
|
* leave any locking issues to the caller ...
|
|
*/
|
|
static inline int host_write_unsafe(unsigned io_base, unsigned char data)
|
|
{
|
|
if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) {
|
|
outb(data, HOST_DATA_IO(io_base));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Write to the SoundScape's host-mode control registers, performing
|
|
* a limited amount of busy-waiting if the register isn't ready.
|
|
* Also leaves all locking-issues to the caller ...
|
|
*/
|
|
static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data,
|
|
unsigned timeout)
|
|
{
|
|
int err;
|
|
|
|
while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) {
|
|
udelay(100);
|
|
--timeout;
|
|
} /* while */
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check that the MIDI subsystem is operational. If it isn't,
|
|
* then we will hang the computer if we try to use it ...
|
|
*
|
|
* NOTE: This check is based upon observation, not documentation.
|
|
*/
|
|
static inline int verify_mpu401(const struct snd_mpu401 *mpu)
|
|
{
|
|
return ((inb(MPU401C(mpu)) & 0xc0) == 0x80);
|
|
}
|
|
|
|
/*
|
|
* This is apparently the standard way to initailise an MPU-401
|
|
*/
|
|
static inline void initialise_mpu401(const struct snd_mpu401 *mpu)
|
|
{
|
|
outb(0, MPU401D(mpu));
|
|
}
|
|
|
|
/*
|
|
* Tell the SoundScape to activate the AD1845 chip (I think).
|
|
* The AD1845 detection fails if we *don't* do this, so I
|
|
* think that this is a good idea ...
|
|
*/
|
|
static void activate_ad1845_unsafe(unsigned io_base)
|
|
{
|
|
unsigned char val = sscape_read_unsafe(io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(io_base, GA_HMCTL_REG, (val & 0xcf) | 0x10);
|
|
sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80);
|
|
}
|
|
|
|
/*
|
|
* Do the necessary ALSA-level cleanup to deallocate our driver ...
|
|
*/
|
|
static void soundscape_free(struct snd_card *c)
|
|
{
|
|
struct soundscape *sscape = get_card_soundscape(c);
|
|
release_and_free_resource(sscape->io_res);
|
|
release_and_free_resource(sscape->wss_res);
|
|
free_dma(sscape->chip->dma1);
|
|
}
|
|
|
|
/*
|
|
* Tell the SoundScape to begin a DMA tranfer using the given channel.
|
|
* All locking issues are left to the caller.
|
|
*/
|
|
static void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg)
|
|
{
|
|
sscape_write_unsafe(io_base, reg,
|
|
sscape_read_unsafe(io_base, reg) | 0x01);
|
|
sscape_write_unsafe(io_base, reg,
|
|
sscape_read_unsafe(io_base, reg) & 0xfe);
|
|
}
|
|
|
|
/*
|
|
* Wait for a DMA transfer to complete. This is a "limited busy-wait",
|
|
* and all locking issues are left to the caller.
|
|
*/
|
|
static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg,
|
|
unsigned timeout)
|
|
{
|
|
while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) {
|
|
udelay(100);
|
|
--timeout;
|
|
} /* while */
|
|
|
|
return sscape_read_unsafe(io_base, reg) & 0x01;
|
|
}
|
|
|
|
/*
|
|
* Wait for the On-Board Processor to return its start-up
|
|
* acknowledgement sequence. This wait is too long for
|
|
* us to perform "busy-waiting", and so we must sleep.
|
|
* This in turn means that we must not be holding any
|
|
* spinlocks when we call this function.
|
|
*/
|
|
static int obp_startup_ack(struct soundscape *s, unsigned timeout)
|
|
{
|
|
unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
|
|
|
|
do {
|
|
unsigned long flags;
|
|
int x;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
x = host_read_unsafe(s->io_base);
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
if (x == 0xfe || x == 0xff)
|
|
return 1;
|
|
|
|
msleep(10);
|
|
} while (time_before(jiffies, end_time));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wait for the host to return its start-up acknowledgement
|
|
* sequence. This wait is too long for us to perform
|
|
* "busy-waiting", and so we must sleep. This in turn means
|
|
* that we must not be holding any spinlocks when we call
|
|
* this function.
|
|
*/
|
|
static int host_startup_ack(struct soundscape *s, unsigned timeout)
|
|
{
|
|
unsigned long end_time = jiffies + msecs_to_jiffies(timeout);
|
|
|
|
do {
|
|
unsigned long flags;
|
|
int x;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
x = host_read_unsafe(s->io_base);
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
if (x == 0xfe)
|
|
return 1;
|
|
|
|
msleep(10);
|
|
} while (time_before(jiffies, end_time));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Upload a byte-stream into the SoundScape using DMA channel A.
|
|
*/
|
|
static int upload_dma_data(struct soundscape *s, const unsigned char *data,
|
|
size_t size)
|
|
{
|
|
unsigned long flags;
|
|
struct snd_dma_buffer dma;
|
|
int ret;
|
|
unsigned char val;
|
|
|
|
if (!get_dmabuf(&dma, PAGE_ALIGN(32 * 1024)))
|
|
return -ENOMEM;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
|
|
/*
|
|
* Reset the board ...
|
|
*/
|
|
val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val & 0x3f);
|
|
|
|
/*
|
|
* Enable the DMA channels and configure them ...
|
|
*/
|
|
val = (s->chip->dma1 << 4) | DMA_8BIT;
|
|
sscape_write_unsafe(s->io_base, GA_DMAA_REG, val);
|
|
sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20);
|
|
|
|
/*
|
|
* Take the board out of reset ...
|
|
*/
|
|
val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x80);
|
|
|
|
/*
|
|
* Upload the firmware to the SoundScape
|
|
* board through the DMA channel ...
|
|
*/
|
|
while (size != 0) {
|
|
unsigned long len;
|
|
|
|
len = min(size, dma.bytes);
|
|
memcpy(dma.area, data, len);
|
|
data += len;
|
|
size -= len;
|
|
|
|
snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE);
|
|
sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG);
|
|
if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) {
|
|
/*
|
|
* Don't forget to release this spinlock we're holding
|
|
*/
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
|
|
snd_printk(KERN_ERR
|
|
"sscape: DMA upload has timed out\n");
|
|
ret = -EAGAIN;
|
|
goto _release_dma;
|
|
}
|
|
} /* while */
|
|
|
|
set_host_mode_unsafe(s->io_base);
|
|
outb(0x0, s->io_base);
|
|
|
|
/*
|
|
* Boot the board ... (I think)
|
|
*/
|
|
val = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, val | 0x40);
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
|
|
/*
|
|
* If all has gone well, then the board should acknowledge
|
|
* the new upload and tell us that it has rebooted OK. We
|
|
* give it 5 seconds (max) ...
|
|
*/
|
|
ret = 0;
|
|
if (!obp_startup_ack(s, 5000)) {
|
|
snd_printk(KERN_ERR "sscape: No response "
|
|
"from on-board processor after upload\n");
|
|
ret = -EAGAIN;
|
|
} else if (!host_startup_ack(s, 5000)) {
|
|
snd_printk(KERN_ERR
|
|
"sscape: SoundScape failed to initialise\n");
|
|
ret = -EAGAIN;
|
|
}
|
|
|
|
_release_dma:
|
|
/*
|
|
* NOTE!!! We are NOT holding any spinlocks at this point !!!
|
|
*/
|
|
sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_OPUS ? 0x40 : 0x70));
|
|
free_dmabuf(&dma);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Upload the bootblock(?) into the SoundScape. The only
|
|
* purpose of this block of code seems to be to tell
|
|
* us which version of the microcode we should be using.
|
|
*/
|
|
static int sscape_upload_bootblock(struct snd_card *card)
|
|
{
|
|
struct soundscape *sscape = get_card_soundscape(card);
|
|
unsigned long flags;
|
|
const struct firmware *init_fw = NULL;
|
|
int data = 0;
|
|
int ret;
|
|
|
|
ret = request_firmware(&init_fw, "scope.cod", card->dev);
|
|
if (ret < 0) {
|
|
snd_printk(KERN_ERR "sscape: Error loading scope.cod");
|
|
return ret;
|
|
}
|
|
ret = upload_dma_data(sscape, init_fw->data, init_fw->size);
|
|
|
|
release_firmware(init_fw);
|
|
|
|
spin_lock_irqsave(&sscape->lock, flags);
|
|
if (ret == 0)
|
|
data = host_read_ctrl_unsafe(sscape->io_base, 100);
|
|
|
|
if (data & 0x10)
|
|
sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2f);
|
|
|
|
spin_unlock_irqrestore(&sscape->lock, flags);
|
|
|
|
data &= 0xf;
|
|
if (ret == 0 && data > 7) {
|
|
snd_printk(KERN_ERR
|
|
"sscape: timeout reading firmware version\n");
|
|
ret = -EAGAIN;
|
|
}
|
|
|
|
return (ret == 0) ? data : ret;
|
|
}
|
|
|
|
/*
|
|
* Upload the microcode into the SoundScape.
|
|
*/
|
|
static int sscape_upload_microcode(struct snd_card *card, int version)
|
|
{
|
|
struct soundscape *sscape = get_card_soundscape(card);
|
|
const struct firmware *init_fw = NULL;
|
|
char name[14];
|
|
int err;
|
|
|
|
snprintf(name, sizeof(name), "sndscape.co%d", version);
|
|
|
|
err = request_firmware(&init_fw, name, card->dev);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: Error loading sndscape.co%d",
|
|
version);
|
|
return err;
|
|
}
|
|
err = upload_dma_data(sscape, init_fw->data, init_fw->size);
|
|
if (err == 0)
|
|
snd_printk(KERN_INFO "sscape: MIDI firmware loaded %zu KBs\n",
|
|
init_fw->size >> 10);
|
|
|
|
release_firmware(init_fw);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Mixer control for the SoundScape's MIDI device.
|
|
*/
|
|
static int sscape_midi_info(struct snd_kcontrol *ctl,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = 127;
|
|
return 0;
|
|
}
|
|
|
|
static int sscape_midi_get(struct snd_kcontrol *kctl,
|
|
struct snd_ctl_elem_value *uctl)
|
|
{
|
|
struct snd_wss *chip = snd_kcontrol_chip(kctl);
|
|
struct snd_card *card = chip->card;
|
|
register struct soundscape *s = get_card_soundscape(card);
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
uctl->value.integer.value[0] = s->midi_vol;
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
static int sscape_midi_put(struct snd_kcontrol *kctl,
|
|
struct snd_ctl_elem_value *uctl)
|
|
{
|
|
struct snd_wss *chip = snd_kcontrol_chip(kctl);
|
|
struct snd_card *card = chip->card;
|
|
struct soundscape *s = get_card_soundscape(card);
|
|
unsigned long flags;
|
|
int change;
|
|
unsigned char new_val;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
|
|
new_val = uctl->value.integer.value[0] & 127;
|
|
/*
|
|
* We need to put the board into HOST mode before we
|
|
* can send any volume-changing HOST commands ...
|
|
*/
|
|
set_host_mode_unsafe(s->io_base);
|
|
|
|
/*
|
|
* To successfully change the MIDI volume setting, you seem to
|
|
* have to write a volume command, write the new volume value,
|
|
* and then perform another volume-related command. Perhaps the
|
|
* first command is an "open" and the second command is a "close"?
|
|
*/
|
|
if (s->midi_vol == new_val) {
|
|
change = 0;
|
|
goto __skip_change;
|
|
}
|
|
change = host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100)
|
|
&& host_write_ctrl_unsafe(s->io_base, new_val, 100)
|
|
&& host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100)
|
|
&& host_write_ctrl_unsafe(s->io_base, new_val, 100);
|
|
s->midi_vol = new_val;
|
|
__skip_change:
|
|
|
|
/*
|
|
* Take the board out of HOST mode and back into MIDI mode ...
|
|
*/
|
|
set_midi_mode_unsafe(s->io_base);
|
|
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
return change;
|
|
}
|
|
|
|
static struct snd_kcontrol_new midi_mixer_ctl = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = "MIDI",
|
|
.info = sscape_midi_info,
|
|
.get = sscape_midi_get,
|
|
.put = sscape_midi_put
|
|
};
|
|
|
|
/*
|
|
* The SoundScape can use two IRQs from a possible set of four.
|
|
* These IRQs are encoded as bit patterns so that they can be
|
|
* written to the control registers.
|
|
*/
|
|
static unsigned get_irq_config(int sscape_type, int irq)
|
|
{
|
|
static const int valid_irq[] = { 9, 5, 7, 10 };
|
|
static const int old_irq[] = { 9, 7, 5, 15 };
|
|
unsigned cfg;
|
|
|
|
if (sscape_type == MEDIA_FX) {
|
|
for (cfg = 0; cfg < ARRAY_SIZE(old_irq); ++cfg)
|
|
if (irq == old_irq[cfg])
|
|
return cfg;
|
|
} else {
|
|
for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg)
|
|
if (irq == valid_irq[cfg])
|
|
return cfg;
|
|
}
|
|
|
|
return INVALID_IRQ;
|
|
}
|
|
|
|
/*
|
|
* Perform certain arcane port-checks to see whether there
|
|
* is a SoundScape board lurking behind the given ports.
|
|
*/
|
|
static int detect_sscape(struct soundscape *s, long wss_io)
|
|
{
|
|
unsigned long flags;
|
|
unsigned d;
|
|
int retval = 0;
|
|
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
|
|
/*
|
|
* The following code is lifted from the original OSS driver,
|
|
* and as I don't have a datasheet I cannot really comment
|
|
* on what it is doing...
|
|
*/
|
|
if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0)
|
|
goto _done;
|
|
|
|
d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0;
|
|
if ((d & 0x80) != 0)
|
|
goto _done;
|
|
|
|
if (d == 0)
|
|
s->ic_type = IC_ODIE;
|
|
else if ((d & 0x60) != 0)
|
|
s->ic_type = IC_OPUS;
|
|
else
|
|
goto _done;
|
|
|
|
outb(0xfa, ODIE_ADDR_IO(s->io_base));
|
|
if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a)
|
|
goto _done;
|
|
|
|
outb(0xfe, ODIE_ADDR_IO(s->io_base));
|
|
if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e)
|
|
goto _done;
|
|
|
|
outb(0xfe, ODIE_ADDR_IO(s->io_base));
|
|
d = inb(ODIE_DATA_IO(s->io_base));
|
|
if (s->type != SSCAPE_VIVO && (d & 0x9f) != 0x0e)
|
|
goto _done;
|
|
|
|
if (s->ic_type == IC_OPUS)
|
|
activate_ad1845_unsafe(s->io_base);
|
|
|
|
if (s->type == SSCAPE_VIVO)
|
|
wss_io += 4;
|
|
|
|
d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
|
|
|
|
/* wait for WSS codec */
|
|
for (d = 0; d < 500; d++) {
|
|
if ((inb(wss_io) & 0x80) == 0)
|
|
break;
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
msleep(1);
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
}
|
|
|
|
if ((inb(wss_io) & 0x80) != 0)
|
|
goto _done;
|
|
|
|
if (inb(wss_io + 2) == 0xff)
|
|
goto _done;
|
|
|
|
d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f;
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d);
|
|
|
|
if ((inb(wss_io) & 0x80) != 0)
|
|
s->type = MEDIA_FX;
|
|
|
|
d = sscape_read_unsafe(s->io_base, GA_HMCTL_REG);
|
|
sscape_write_unsafe(s->io_base, GA_HMCTL_REG, d | 0xc0);
|
|
/* wait for WSS codec */
|
|
for (d = 0; d < 500; d++) {
|
|
if ((inb(wss_io) & 0x80) == 0)
|
|
break;
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
msleep(1);
|
|
spin_lock_irqsave(&s->lock, flags);
|
|
}
|
|
|
|
/*
|
|
* SoundScape successfully detected!
|
|
*/
|
|
retval = 1;
|
|
|
|
_done:
|
|
spin_unlock_irqrestore(&s->lock, flags);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* ALSA callback function, called when attempting to open the MIDI device.
|
|
* Check that the MIDI firmware has been loaded, because we don't want
|
|
* to crash the machine. Also check that someone isn't using the hardware
|
|
* IOCTL device.
|
|
*/
|
|
static int mpu401_open(struct snd_mpu401 *mpu)
|
|
{
|
|
if (!verify_mpu401(mpu)) {
|
|
snd_printk(KERN_ERR "sscape: MIDI disabled, "
|
|
"please load firmware\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialse an MPU-401 subdevice for MIDI support on the SoundScape.
|
|
*/
|
|
static int create_mpu401(struct snd_card *card, int devnum,
|
|
unsigned long port, int irq)
|
|
{
|
|
struct soundscape *sscape = get_card_soundscape(card);
|
|
struct snd_rawmidi *rawmidi;
|
|
int err;
|
|
|
|
err = snd_mpu401_uart_new(card, devnum, MPU401_HW_MPU401, port,
|
|
MPU401_INFO_INTEGRATED, irq, &rawmidi);
|
|
if (err == 0) {
|
|
struct snd_mpu401 *mpu = rawmidi->private_data;
|
|
mpu->open_input = mpu401_open;
|
|
mpu->open_output = mpu401_open;
|
|
mpu->private_data = sscape;
|
|
|
|
initialise_mpu401(mpu);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create an AD1845 PCM subdevice on the SoundScape. The AD1845
|
|
* is very much like a CS4231, with a few extra bits. We will
|
|
* try to support at least some of the extra bits by overriding
|
|
* some of the CS4231 callback.
|
|
*/
|
|
static int create_ad1845(struct snd_card *card, unsigned port,
|
|
int irq, int dma1, int dma2)
|
|
{
|
|
register struct soundscape *sscape = get_card_soundscape(card);
|
|
struct snd_wss *chip;
|
|
int err;
|
|
int codec_type = WSS_HW_DETECT;
|
|
|
|
switch (sscape->type) {
|
|
case MEDIA_FX:
|
|
case SSCAPE:
|
|
/*
|
|
* There are some freak examples of early Soundscape cards
|
|
* with CS4231 instead of AD1848/CS4248. Unfortunately, the
|
|
* CS4231 works only in CS4248 compatibility mode on
|
|
* these cards so force it.
|
|
*/
|
|
if (sscape->ic_type != IC_OPUS)
|
|
codec_type = WSS_HW_AD1848;
|
|
break;
|
|
|
|
case SSCAPE_VIVO:
|
|
port += 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
err = snd_wss_create(card, port, -1, irq, dma1, dma2,
|
|
codec_type, WSS_HWSHARE_DMA1, &chip);
|
|
if (!err) {
|
|
unsigned long flags;
|
|
|
|
if (sscape->type != SSCAPE_VIVO) {
|
|
/*
|
|
* The input clock frequency on the SoundScape must
|
|
* be 14.31818 MHz, because we must set this register
|
|
* to get the playback to sound correct ...
|
|
*/
|
|
snd_wss_mce_up(chip);
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
snd_wss_out(chip, AD1845_CLOCK, 0x20);
|
|
spin_unlock_irqrestore(&chip->reg_lock, flags);
|
|
snd_wss_mce_down(chip);
|
|
|
|
}
|
|
|
|
err = snd_wss_pcm(chip, 0);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: No PCM device "
|
|
"for AD1845 chip\n");
|
|
goto _error;
|
|
}
|
|
|
|
err = snd_wss_mixer(chip);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: No mixer device "
|
|
"for AD1845 chip\n");
|
|
goto _error;
|
|
}
|
|
if (chip->hardware != WSS_HW_AD1848) {
|
|
err = snd_wss_timer(chip, 0);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: No timer device "
|
|
"for AD1845 chip\n");
|
|
goto _error;
|
|
}
|
|
}
|
|
|
|
if (sscape->type != SSCAPE_VIVO) {
|
|
err = snd_ctl_add(card,
|
|
snd_ctl_new1(&midi_mixer_ctl, chip));
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: Could not create "
|
|
"MIDI mixer control\n");
|
|
goto _error;
|
|
}
|
|
}
|
|
|
|
sscape->chip = chip;
|
|
}
|
|
|
|
_error:
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* Create an ALSA soundcard entry for the SoundScape, using
|
|
* the given list of port, IRQ and DMA resources.
|
|
*/
|
|
static int create_sscape(int dev, struct snd_card *card)
|
|
{
|
|
struct soundscape *sscape = get_card_soundscape(card);
|
|
unsigned dma_cfg;
|
|
unsigned irq_cfg;
|
|
unsigned mpu_irq_cfg;
|
|
struct resource *io_res;
|
|
struct resource *wss_res;
|
|
unsigned long flags;
|
|
int err;
|
|
int val;
|
|
const char *name;
|
|
|
|
/*
|
|
* Grab IO ports that we will need to probe so that we
|
|
* can detect and control this hardware ...
|
|
*/
|
|
io_res = request_region(port[dev], 8, "SoundScape");
|
|
if (!io_res) {
|
|
snd_printk(KERN_ERR
|
|
"sscape: can't grab port 0x%lx\n", port[dev]);
|
|
return -EBUSY;
|
|
}
|
|
wss_res = NULL;
|
|
if (sscape->type == SSCAPE_VIVO) {
|
|
wss_res = request_region(wss_port[dev], 4, "SoundScape");
|
|
if (!wss_res) {
|
|
snd_printk(KERN_ERR "sscape: can't grab port 0x%lx\n",
|
|
wss_port[dev]);
|
|
err = -EBUSY;
|
|
goto _release_region;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Grab one DMA channel ...
|
|
*/
|
|
err = request_dma(dma[dev], "SoundScape");
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", dma[dev]);
|
|
goto _release_region;
|
|
}
|
|
|
|
spin_lock_init(&sscape->lock);
|
|
sscape->io_res = io_res;
|
|
sscape->wss_res = wss_res;
|
|
sscape->io_base = port[dev];
|
|
|
|
if (!detect_sscape(sscape, wss_port[dev])) {
|
|
printk(KERN_ERR "sscape: hardware not detected at 0x%x\n",
|
|
sscape->io_base);
|
|
err = -ENODEV;
|
|
goto _release_dma;
|
|
}
|
|
|
|
switch (sscape->type) {
|
|
case MEDIA_FX:
|
|
name = "MediaFX/SoundFX";
|
|
break;
|
|
case SSCAPE:
|
|
name = "Soundscape";
|
|
break;
|
|
case SSCAPE_PNP:
|
|
name = "Soundscape PnP";
|
|
break;
|
|
case SSCAPE_VIVO:
|
|
name = "Soundscape VIVO";
|
|
break;
|
|
default:
|
|
name = "unknown Soundscape";
|
|
break;
|
|
}
|
|
|
|
printk(KERN_INFO "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
|
|
name, sscape->io_base, irq[dev], dma[dev]);
|
|
|
|
/*
|
|
* Check that the user didn't pass us garbage data ...
|
|
*/
|
|
irq_cfg = get_irq_config(sscape->type, irq[dev]);
|
|
if (irq_cfg == INVALID_IRQ) {
|
|
snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", irq[dev]);
|
|
err = -ENXIO;
|
|
goto _release_dma;
|
|
}
|
|
|
|
mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
|
|
if (mpu_irq_cfg == INVALID_IRQ) {
|
|
snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
|
|
err = -ENXIO;
|
|
goto _release_dma;
|
|
}
|
|
|
|
/*
|
|
* Tell the on-board devices where their resources are (I think -
|
|
* I can't be sure without a datasheet ... So many magic values!)
|
|
*/
|
|
spin_lock_irqsave(&sscape->lock, flags);
|
|
|
|
sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
|
|
sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
|
|
|
|
/*
|
|
* Enable and configure the DMA channels ...
|
|
*/
|
|
sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
|
|
dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
|
|
sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
|
|
sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
|
|
|
|
mpu_irq_cfg |= mpu_irq_cfg << 2;
|
|
val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
|
|
if (joystick[dev])
|
|
val |= 8;
|
|
sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
|
|
sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
|
|
sscape_write_unsafe(sscape->io_base,
|
|
GA_CDCFG_REG, 0x09 | DMA_8BIT
|
|
| (dma[dev] << 4) | (irq_cfg << 1));
|
|
/*
|
|
* Enable the master IRQ ...
|
|
*/
|
|
sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
|
|
|
|
spin_unlock_irqrestore(&sscape->lock, flags);
|
|
|
|
/*
|
|
* We have now enabled the codec chip, and so we should
|
|
* detect the AD1845 device ...
|
|
*/
|
|
err = create_ad1845(card, wss_port[dev], irq[dev],
|
|
dma[dev], dma2[dev]);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR
|
|
"sscape: No AD1845 device at 0x%lx, IRQ %d\n",
|
|
wss_port[dev], irq[dev]);
|
|
goto _release_dma;
|
|
}
|
|
strcpy(card->driver, "SoundScape");
|
|
strcpy(card->shortname, name);
|
|
snprintf(card->longname, sizeof(card->longname),
|
|
"%s at 0x%lx, IRQ %d, DMA1 %d, DMA2 %d\n",
|
|
name, sscape->chip->port, sscape->chip->irq,
|
|
sscape->chip->dma1, sscape->chip->dma2);
|
|
|
|
#define MIDI_DEVNUM 0
|
|
if (sscape->type != SSCAPE_VIVO) {
|
|
err = sscape_upload_bootblock(card);
|
|
if (err >= 0)
|
|
err = sscape_upload_microcode(card, err);
|
|
|
|
if (err == 0) {
|
|
err = create_mpu401(card, MIDI_DEVNUM, port[dev],
|
|
mpu_irq[dev]);
|
|
if (err < 0) {
|
|
snd_printk(KERN_ERR "sscape: Failed to create "
|
|
"MPU-401 device at 0x%lx\n",
|
|
port[dev]);
|
|
goto _release_dma;
|
|
}
|
|
|
|
/*
|
|
* Initialize mixer
|
|
*/
|
|
spin_lock_irqsave(&sscape->lock, flags);
|
|
sscape->midi_vol = 0;
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
CMD_SET_MIDI_VOL, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
sscape->midi_vol, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
CMD_XXX_MIDI_VOL, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
sscape->midi_vol, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
CMD_SET_EXTMIDI, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base,
|
|
0, 100);
|
|
host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
|
|
|
|
set_midi_mode_unsafe(sscape->io_base);
|
|
spin_unlock_irqrestore(&sscape->lock, flags);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Now that we have successfully created this sound card,
|
|
* it is safe to store the pointer.
|
|
* NOTE: we only register the sound card's "destructor"
|
|
* function now that our "constructor" has completed.
|
|
*/
|
|
card->private_free = soundscape_free;
|
|
|
|
return 0;
|
|
|
|
_release_dma:
|
|
free_dma(dma[dev]);
|
|
|
|
_release_region:
|
|
release_and_free_resource(wss_res);
|
|
release_and_free_resource(io_res);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static int snd_sscape_match(struct device *pdev, unsigned int i)
|
|
{
|
|
/*
|
|
* Make sure we were given ALL of the other parameters.
|
|
*/
|
|
if (port[i] == SNDRV_AUTO_PORT)
|
|
return 0;
|
|
|
|
if (irq[i] == SNDRV_AUTO_IRQ ||
|
|
mpu_irq[i] == SNDRV_AUTO_IRQ ||
|
|
dma[i] == SNDRV_AUTO_DMA) {
|
|
printk(KERN_INFO
|
|
"sscape: insufficient parameters, "
|
|
"need IO, IRQ, MPU-IRQ and DMA\n");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int snd_sscape_probe(struct device *pdev, unsigned int dev)
|
|
{
|
|
struct snd_card *card;
|
|
struct soundscape *sscape;
|
|
int ret;
|
|
|
|
ret = snd_card_new(pdev, index[dev], id[dev], THIS_MODULE,
|
|
sizeof(struct soundscape), &card);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sscape = get_card_soundscape(card);
|
|
sscape->type = SSCAPE;
|
|
|
|
dma[dev] &= 0x03;
|
|
|
|
ret = create_sscape(dev, card);
|
|
if (ret < 0)
|
|
goto _release_card;
|
|
|
|
ret = snd_card_register(card);
|
|
if (ret < 0) {
|
|
snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
|
|
goto _release_card;
|
|
}
|
|
dev_set_drvdata(pdev, card);
|
|
return 0;
|
|
|
|
_release_card:
|
|
snd_card_free(card);
|
|
return ret;
|
|
}
|
|
|
|
static int snd_sscape_remove(struct device *devptr, unsigned int dev)
|
|
{
|
|
snd_card_free(dev_get_drvdata(devptr));
|
|
return 0;
|
|
}
|
|
|
|
#define DEV_NAME "sscape"
|
|
|
|
static struct isa_driver snd_sscape_driver = {
|
|
.match = snd_sscape_match,
|
|
.probe = snd_sscape_probe,
|
|
.remove = snd_sscape_remove,
|
|
/* FIXME: suspend/resume */
|
|
.driver = {
|
|
.name = DEV_NAME
|
|
},
|
|
};
|
|
|
|
#ifdef CONFIG_PNP
|
|
static inline int get_next_autoindex(int i)
|
|
{
|
|
while (i < SNDRV_CARDS && port[i] != SNDRV_AUTO_PORT)
|
|
++i;
|
|
return i;
|
|
}
|
|
|
|
|
|
static int sscape_pnp_detect(struct pnp_card_link *pcard,
|
|
const struct pnp_card_device_id *pid)
|
|
{
|
|
static int idx = 0;
|
|
struct pnp_dev *dev;
|
|
struct snd_card *card;
|
|
struct soundscape *sscape;
|
|
int ret;
|
|
|
|
/*
|
|
* Allow this function to fail *quietly* if all the ISA PnP
|
|
* devices were configured using module parameters instead.
|
|
*/
|
|
idx = get_next_autoindex(idx);
|
|
if (idx >= SNDRV_CARDS)
|
|
return -ENOSPC;
|
|
|
|
/*
|
|
* Check that we still have room for another sound card ...
|
|
*/
|
|
dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL);
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
if (!pnp_is_active(dev)) {
|
|
if (pnp_activate_dev(dev) < 0) {
|
|
snd_printk(KERN_INFO "sscape: device is inactive\n");
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a new ALSA sound card entry, in anticipation
|
|
* of detecting our hardware ...
|
|
*/
|
|
ret = snd_card_new(&pcard->card->dev,
|
|
index[idx], id[idx], THIS_MODULE,
|
|
sizeof(struct soundscape), &card);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
sscape = get_card_soundscape(card);
|
|
|
|
/*
|
|
* Identify card model ...
|
|
*/
|
|
if (!strncmp("ENS4081", pid->id, 7))
|
|
sscape->type = SSCAPE_VIVO;
|
|
else
|
|
sscape->type = SSCAPE_PNP;
|
|
|
|
/*
|
|
* Read the correct parameters off the ISA PnP bus ...
|
|
*/
|
|
port[idx] = pnp_port_start(dev, 0);
|
|
irq[idx] = pnp_irq(dev, 0);
|
|
mpu_irq[idx] = pnp_irq(dev, 1);
|
|
dma[idx] = pnp_dma(dev, 0) & 0x03;
|
|
if (sscape->type == SSCAPE_PNP) {
|
|
dma2[idx] = dma[idx];
|
|
wss_port[idx] = CODEC_IO(port[idx]);
|
|
} else {
|
|
wss_port[idx] = pnp_port_start(dev, 1);
|
|
dma2[idx] = pnp_dma(dev, 1);
|
|
}
|
|
|
|
ret = create_sscape(idx, card);
|
|
if (ret < 0)
|
|
goto _release_card;
|
|
|
|
ret = snd_card_register(card);
|
|
if (ret < 0) {
|
|
snd_printk(KERN_ERR "sscape: Failed to register sound card\n");
|
|
goto _release_card;
|
|
}
|
|
|
|
pnp_set_card_drvdata(pcard, card);
|
|
++idx;
|
|
return 0;
|
|
|
|
_release_card:
|
|
snd_card_free(card);
|
|
return ret;
|
|
}
|
|
|
|
static void sscape_pnp_remove(struct pnp_card_link *pcard)
|
|
{
|
|
snd_card_free(pnp_get_card_drvdata(pcard));
|
|
pnp_set_card_drvdata(pcard, NULL);
|
|
}
|
|
|
|
static struct pnp_card_driver sscape_pnpc_driver = {
|
|
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
|
|
.name = "sscape",
|
|
.id_table = sscape_pnpids,
|
|
.probe = sscape_pnp_detect,
|
|
.remove = sscape_pnp_remove,
|
|
};
|
|
|
|
#endif /* CONFIG_PNP */
|
|
|
|
static int __init sscape_init(void)
|
|
{
|
|
int err;
|
|
|
|
err = isa_register_driver(&snd_sscape_driver, SNDRV_CARDS);
|
|
#ifdef CONFIG_PNP
|
|
if (!err)
|
|
isa_registered = 1;
|
|
|
|
err = pnp_register_card_driver(&sscape_pnpc_driver);
|
|
if (!err)
|
|
pnp_registered = 1;
|
|
|
|
if (isa_registered)
|
|
err = 0;
|
|
#endif
|
|
return err;
|
|
}
|
|
|
|
static void __exit sscape_exit(void)
|
|
{
|
|
#ifdef CONFIG_PNP
|
|
if (pnp_registered)
|
|
pnp_unregister_card_driver(&sscape_pnpc_driver);
|
|
if (isa_registered)
|
|
#endif
|
|
isa_unregister_driver(&snd_sscape_driver);
|
|
}
|
|
|
|
module_init(sscape_init);
|
|
module_exit(sscape_exit);
|