2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* forte.c - ForteMedia FM801 OSS Driver
|
|
|
|
*
|
|
|
|
* Written by Martin K. Petersen <mkp@mkp.net>
|
|
|
|
* Copyright (C) 2002 Hewlett-Packard Company
|
|
|
|
* Portions Copyright (C) 2003 Martin K. Petersen
|
|
|
|
*
|
|
|
|
* Latest version: http://mkp.net/forte/
|
|
|
|
*
|
|
|
|
* Based upon the ALSA FM801 driver by Jaroslav Kysela and OSS drivers
|
|
|
|
* by Thomas Sailer, Alan Cox, Zach Brown, and Jeff Garzik. Thanks
|
|
|
|
* guys!
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License version
|
|
|
|
* 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* 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/config.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/poll.h>
|
|
|
|
|
|
|
|
#include <linux/sound.h>
|
|
|
|
#include <linux/ac97_codec.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
|
|
|
|
#include <linux/proc_fs.h>
|
2006-03-23 11:00:39 +00:00
|
|
|
#include <linux/mutex.h>
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
|
|
|
|
#define DRIVER_NAME "forte"
|
|
|
|
#define DRIVER_VERSION "$Id: forte.c,v 1.63 2003/03/01 05:32:42 mkp Exp $"
|
|
|
|
#define PFX DRIVER_NAME ": "
|
|
|
|
|
|
|
|
#undef M_DEBUG
|
|
|
|
|
|
|
|
#ifdef M_DEBUG
|
|
|
|
#define DPRINTK(args...) printk(KERN_WARNING args)
|
|
|
|
#else
|
|
|
|
#define DPRINTK(args...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Card capabilities */
|
|
|
|
#define FORTE_CAPS (DSP_CAP_MMAP | DSP_CAP_TRIGGER)
|
|
|
|
|
|
|
|
/* Supported audio formats */
|
|
|
|
#define FORTE_FMTS (AFMT_U8 | AFMT_S16_LE)
|
|
|
|
|
|
|
|
/* Buffers */
|
|
|
|
#define FORTE_MIN_FRAG_SIZE 256
|
|
|
|
#define FORTE_MAX_FRAG_SIZE PAGE_SIZE
|
|
|
|
#define FORTE_DEF_FRAG_SIZE 256
|
|
|
|
#define FORTE_MIN_FRAGMENTS 2
|
|
|
|
#define FORTE_MAX_FRAGMENTS 256
|
|
|
|
#define FORTE_DEF_FRAGMENTS 2
|
|
|
|
#define FORTE_MIN_BUF_MSECS 500
|
|
|
|
#define FORTE_MAX_BUF_MSECS 1000
|
|
|
|
|
|
|
|
/* PCI BARs */
|
|
|
|
#define FORTE_PCM_VOL 0x00 /* PCM Output Volume */
|
|
|
|
#define FORTE_FM_VOL 0x02 /* FM Output Volume */
|
|
|
|
#define FORTE_I2S_VOL 0x04 /* I2S Volume */
|
|
|
|
#define FORTE_REC_SRC 0x06 /* Record Source */
|
|
|
|
#define FORTE_PLY_CTRL 0x08 /* Playback Control */
|
|
|
|
#define FORTE_PLY_COUNT 0x0a /* Playback Count */
|
|
|
|
#define FORTE_PLY_BUF1 0x0c /* Playback Buffer I */
|
|
|
|
#define FORTE_PLY_BUF2 0x10 /* Playback Buffer II */
|
|
|
|
#define FORTE_CAP_CTRL 0x14 /* Capture Control */
|
|
|
|
#define FORTE_CAP_COUNT 0x16 /* Capture Count */
|
|
|
|
#define FORTE_CAP_BUF1 0x18 /* Capture Buffer I */
|
|
|
|
#define FORTE_CAP_BUF2 0x1c /* Capture Buffer II */
|
|
|
|
#define FORTE_CODEC_CTRL 0x22 /* Codec Control */
|
|
|
|
#define FORTE_I2S_MODE 0x24 /* I2S Mode Control */
|
|
|
|
#define FORTE_VOLUME 0x26 /* Volume Up/Down/Mute Status */
|
|
|
|
#define FORTE_I2C_CTRL 0x29 /* I2C Control */
|
|
|
|
#define FORTE_AC97_CMD 0x2a /* AC'97 Command */
|
|
|
|
#define FORTE_AC97_DATA 0x2c /* AC'97 Data */
|
|
|
|
#define FORTE_MPU401_DATA 0x30 /* MPU401 Data */
|
|
|
|
#define FORTE_MPU401_CMD 0x31 /* MPU401 Command */
|
|
|
|
#define FORTE_GPIO_CTRL 0x52 /* General Purpose I/O Control */
|
|
|
|
#define FORTE_GEN_CTRL 0x54 /* General Control */
|
|
|
|
#define FORTE_IRQ_MASK 0x56 /* Interrupt Mask */
|
|
|
|
#define FORTE_IRQ_STATUS 0x5a /* Interrupt Status */
|
|
|
|
#define FORTE_OPL3_BANK0 0x68 /* OPL3 Status Read / Bank 0 Write */
|
|
|
|
#define FORTE_OPL3_DATA0 0x69 /* OPL3 Data 0 Write */
|
|
|
|
#define FORTE_OPL3_BANK1 0x6a /* OPL3 Bank 1 Write */
|
|
|
|
#define FORTE_OPL3_DATA1 0x6b /* OPL3 Bank 1 Write */
|
|
|
|
#define FORTE_POWERDOWN 0x70 /* Blocks Power Down Control */
|
|
|
|
|
|
|
|
#define FORTE_CAP_OFFSET FORTE_CAP_CTRL - FORTE_PLY_CTRL
|
|
|
|
|
|
|
|
#define FORTE_AC97_ADDR_SHIFT 10
|
|
|
|
|
|
|
|
/* Playback and record control register bits */
|
|
|
|
#define FORTE_BUF1_LAST (1<<1)
|
|
|
|
#define FORTE_BUF2_LAST (1<<2)
|
|
|
|
#define FORTE_START (1<<5)
|
|
|
|
#define FORTE_PAUSE (1<<6)
|
|
|
|
#define FORTE_IMMED_STOP (1<<7)
|
|
|
|
#define FORTE_RATE_SHIFT 8
|
|
|
|
#define FORTE_RATE_MASK (15 << FORTE_RATE_SHIFT)
|
|
|
|
#define FORTE_CHANNELS_4 (1<<12) /* Playback only */
|
|
|
|
#define FORTE_CHANNELS_6 (2<<12) /* Playback only */
|
|
|
|
#define FORTE_CHANNELS_6MS (3<<12) /* Playback only */
|
|
|
|
#define FORTE_CHANNELS_MASK (3<<12)
|
|
|
|
#define FORTE_16BIT (1<<14)
|
|
|
|
#define FORTE_STEREO (1<<15)
|
|
|
|
|
|
|
|
/* IRQ status bits */
|
|
|
|
#define FORTE_IRQ_PLAYBACK (1<<8)
|
|
|
|
#define FORTE_IRQ_CAPTURE (1<<9)
|
|
|
|
#define FORTE_IRQ_VOLUME (1<<14)
|
|
|
|
#define FORTE_IRQ_MPU (1<<15)
|
|
|
|
|
|
|
|
/* CODEC control */
|
|
|
|
#define FORTE_CC_CODEC_RESET (1<<5)
|
|
|
|
#define FORTE_CC_AC97_RESET (1<<6)
|
|
|
|
|
|
|
|
/* AC97 cmd */
|
|
|
|
#define FORTE_AC97_WRITE (0<<7)
|
|
|
|
#define FORTE_AC97_READ (1<<7)
|
|
|
|
#define FORTE_AC97_DP_INVALID (0<<8)
|
|
|
|
#define FORTE_AC97_DP_VALID (1<<8)
|
|
|
|
#define FORTE_AC97_PORT_RDY (0<<9)
|
|
|
|
#define FORTE_AC97_PORT_BSY (1<<9)
|
|
|
|
|
|
|
|
|
|
|
|
struct forte_channel {
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
unsigned short ctrl; /* Ctrl BAR contents */
|
|
|
|
unsigned long iobase; /* Ctrl BAR address */
|
|
|
|
|
|
|
|
wait_queue_head_t wait;
|
|
|
|
|
|
|
|
void *buf; /* Buffer */
|
|
|
|
dma_addr_t buf_handle; /* Buffer handle */
|
|
|
|
|
|
|
|
unsigned int record;
|
|
|
|
unsigned int format;
|
|
|
|
unsigned int rate;
|
|
|
|
unsigned int stereo;
|
|
|
|
|
|
|
|
unsigned int frag_sz; /* Current fragment size */
|
|
|
|
unsigned int frag_num; /* Current # of fragments */
|
|
|
|
unsigned int frag_msecs; /* Milliseconds per frag */
|
|
|
|
unsigned int buf_sz; /* Current buffer size */
|
|
|
|
|
|
|
|
unsigned int hwptr; /* Tail */
|
|
|
|
unsigned int swptr; /* Head */
|
|
|
|
unsigned int filled_frags; /* Fragments currently full */
|
|
|
|
unsigned int next_buf; /* Index of next buffer */
|
|
|
|
|
|
|
|
unsigned int active; /* Channel currently in use */
|
|
|
|
unsigned int mapped; /* mmap */
|
|
|
|
|
|
|
|
unsigned int buf_pages; /* Real size of buffer */
|
|
|
|
unsigned int nr_irqs; /* Number of interrupts */
|
|
|
|
unsigned int bytes; /* Total bytes */
|
|
|
|
unsigned int residue; /* Partial fragment */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct forte_chip {
|
|
|
|
struct pci_dev *pci_dev;
|
|
|
|
unsigned long iobase;
|
|
|
|
int irq;
|
|
|
|
|
2006-03-23 11:00:39 +00:00
|
|
|
struct mutex open_mutex; /* Device access */
|
2005-04-16 22:20:36 +00:00
|
|
|
spinlock_t lock; /* State */
|
|
|
|
|
|
|
|
spinlock_t ac97_lock;
|
|
|
|
struct ac97_codec *ac97;
|
|
|
|
|
|
|
|
int multichannel;
|
|
|
|
int dsp; /* OSS handle */
|
|
|
|
int trigger; /* mmap I/O trigger */
|
|
|
|
|
|
|
|
struct forte_channel play;
|
|
|
|
struct forte_channel rec;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int channels[] = { 2, 4, 6, };
|
|
|
|
static int rates[] = { 5500, 8000, 9600, 11025, 16000, 19200,
|
|
|
|
22050, 32000, 38400, 44100, 48000, };
|
|
|
|
|
|
|
|
static struct forte_chip *forte;
|
|
|
|
static int found;
|
|
|
|
|
|
|
|
|
|
|
|
/* AC97 Codec -------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_ac97_wait:
|
|
|
|
* @chip: fm801 instance whose AC97 codec to wait on
|
|
|
|
*
|
|
|
|
* FIXME:
|
|
|
|
* Stop busy-waiting
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
forte_ac97_wait (struct forte_chip *chip)
|
|
|
|
{
|
|
|
|
int i = 10000;
|
|
|
|
|
|
|
|
while ( (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_PORT_BSY)
|
|
|
|
&& i-- )
|
|
|
|
cpu_relax();
|
|
|
|
|
|
|
|
return i == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_ac97_read:
|
|
|
|
* @codec: AC97 codec to read from
|
|
|
|
* @reg: register to read
|
|
|
|
*/
|
|
|
|
|
|
|
|
static u16
|
|
|
|
forte_ac97_read (struct ac97_codec *codec, u8 reg)
|
|
|
|
{
|
|
|
|
u16 ret = 0;
|
|
|
|
struct forte_chip *chip = codec->private_data;
|
|
|
|
|
|
|
|
spin_lock (&chip->ac97_lock);
|
|
|
|
|
|
|
|
/* Knock, knock */
|
|
|
|
if (forte_ac97_wait (chip)) {
|
|
|
|
printk (KERN_ERR PFX "ac97_read: Serial bus busy\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Send read command */
|
|
|
|
outw (reg | (1<<7), chip->iobase + FORTE_AC97_CMD);
|
|
|
|
|
|
|
|
if (forte_ac97_wait (chip)) {
|
|
|
|
printk (KERN_ERR PFX "ac97_read: Bus busy reading reg 0x%x\n",
|
|
|
|
reg);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sanity checking */
|
|
|
|
if (inw (chip->iobase + FORTE_AC97_CMD) & FORTE_AC97_DP_INVALID) {
|
|
|
|
printk (KERN_ERR PFX "ac97_read: Invalid data port");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fetch result */
|
|
|
|
ret = inw (chip->iobase + FORTE_AC97_DATA);
|
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock (&chip->ac97_lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_ac97_write:
|
|
|
|
* @codec: AC97 codec to send command to
|
|
|
|
* @reg: register to write
|
|
|
|
* @val: value to write
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_ac97_write (struct ac97_codec *codec, u8 reg, u16 val)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = codec->private_data;
|
|
|
|
|
|
|
|
spin_lock (&chip->ac97_lock);
|
|
|
|
|
|
|
|
/* Knock, knock */
|
|
|
|
if (forte_ac97_wait (chip)) {
|
|
|
|
printk (KERN_ERR PFX "ac97_write: Serial bus busy\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
outw (val, chip->iobase + FORTE_AC97_DATA);
|
|
|
|
outb (reg | FORTE_AC97_WRITE, chip->iobase + FORTE_AC97_CMD);
|
|
|
|
|
|
|
|
/* Wait for completion */
|
|
|
|
if (forte_ac97_wait (chip)) {
|
|
|
|
printk (KERN_ERR PFX "ac97_write: Bus busy after write\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock (&chip->ac97_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Mixer ------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_mixer_open:
|
|
|
|
* @inode:
|
|
|
|
* @file:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_mixer_open (struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = forte;
|
|
|
|
file->private_data = chip->ac97;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_mixer_release:
|
|
|
|
* @inode:
|
|
|
|
* @file:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_mixer_release (struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
/* We will welease Wodewick */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_mixer_ioctl:
|
|
|
|
* @inode:
|
|
|
|
* @file:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_mixer_ioctl (struct inode *inode, struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
|
|
|
struct ac97_codec *codec = (struct ac97_codec *) file->private_data;
|
|
|
|
|
|
|
|
return codec->mixer_ioctl (codec, cmd, arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct file_operations forte_mixer_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.llseek = no_llseek,
|
|
|
|
.ioctl = forte_mixer_ioctl,
|
|
|
|
.open = forte_mixer_open,
|
|
|
|
.release = forte_mixer_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Channel ----------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_reset:
|
|
|
|
* @channel: Channel to reset
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_channel_reset (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return;
|
|
|
|
|
|
|
|
DPRINTK ("%s: channel = %s\n", __FUNCTION__, channel->name);
|
|
|
|
|
|
|
|
channel->ctrl &= ~FORTE_START;
|
|
|
|
outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
|
|
|
|
|
|
|
|
/* We always play at least two fragments, hence these defaults */
|
|
|
|
channel->hwptr = channel->frag_sz;
|
|
|
|
channel->next_buf = 1;
|
|
|
|
channel->swptr = 0;
|
|
|
|
channel->filled_frags = 0;
|
|
|
|
channel->active = 0;
|
|
|
|
channel->bytes = 0;
|
|
|
|
channel->nr_irqs = 0;
|
|
|
|
channel->mapped = 0;
|
|
|
|
channel->residue = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_start:
|
|
|
|
* @channel: Channel to start (record/playback)
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void inline
|
|
|
|
forte_channel_start (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
if (!channel || !channel->iobase || channel->active)
|
|
|
|
return;
|
|
|
|
|
|
|
|
channel->ctrl &= ~(FORTE_PAUSE | FORTE_BUF1_LAST | FORTE_BUF2_LAST
|
|
|
|
| FORTE_IMMED_STOP);
|
|
|
|
channel->ctrl |= FORTE_START;
|
|
|
|
channel->active = 1;
|
|
|
|
outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_stop:
|
|
|
|
* @channel: Channel to stop
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void inline
|
|
|
|
forte_channel_stop (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return;
|
|
|
|
|
|
|
|
channel->ctrl &= ~(FORTE_START | FORTE_PAUSE);
|
|
|
|
channel->ctrl |= FORTE_IMMED_STOP;
|
|
|
|
|
|
|
|
channel->active = 0;
|
|
|
|
outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_pause:
|
|
|
|
* @channel: Channel to pause
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void inline
|
|
|
|
forte_channel_pause (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return;
|
|
|
|
|
|
|
|
channel->ctrl |= FORTE_PAUSE;
|
|
|
|
|
|
|
|
channel->active = 0;
|
|
|
|
outw (channel->ctrl, channel->iobase + FORTE_PLY_CTRL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_rate:
|
|
|
|
* @channel: Channel whose rate to set. Playback and record are
|
|
|
|
* independent.
|
|
|
|
* @rate: Channel rate in Hz
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_channel_rate (struct forte_channel *channel, unsigned int rate)
|
|
|
|
{
|
|
|
|
int new_rate;
|
|
|
|
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* The FM801 only supports a handful of fixed frequencies.
|
|
|
|
* We find the value closest to what userland requested.
|
|
|
|
*/
|
|
|
|
if (rate <= 6250) { rate = 5500; new_rate = 0; }
|
|
|
|
else if (rate <= 8800) { rate = 8000; new_rate = 1; }
|
|
|
|
else if (rate <= 10312) { rate = 9600; new_rate = 2; }
|
|
|
|
else if (rate <= 13512) { rate = 11025; new_rate = 3; }
|
|
|
|
else if (rate <= 17600) { rate = 16000; new_rate = 4; }
|
|
|
|
else if (rate <= 20625) { rate = 19200; new_rate = 5; }
|
|
|
|
else if (rate <= 27025) { rate = 22050; new_rate = 6; }
|
|
|
|
else if (rate <= 35200) { rate = 32000; new_rate = 7; }
|
|
|
|
else if (rate <= 41250) { rate = 38400; new_rate = 8; }
|
|
|
|
else if (rate <= 46050) { rate = 44100; new_rate = 9; }
|
|
|
|
else { rate = 48000; new_rate = 10; }
|
|
|
|
|
|
|
|
channel->ctrl &= ~FORTE_RATE_MASK;
|
|
|
|
channel->ctrl |= new_rate << FORTE_RATE_SHIFT;
|
|
|
|
channel->rate = rate;
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s rate = %d\n", __FUNCTION__, channel->name, rate);
|
|
|
|
|
|
|
|
return rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_format:
|
|
|
|
* @channel: Channel whose audio format to set
|
|
|
|
* @format: OSS format ID
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_channel_format (struct forte_channel *channel, int format)
|
|
|
|
{
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
|
|
|
|
case AFMT_QUERY:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AFMT_U8:
|
|
|
|
channel->ctrl &= ~FORTE_16BIT;
|
|
|
|
channel->format = AFMT_U8;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AFMT_S16_LE:
|
|
|
|
default:
|
|
|
|
channel->ctrl |= FORTE_16BIT;
|
|
|
|
channel->format = AFMT_S16_LE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s want %d format, got %d\n", __FUNCTION__, channel->name,
|
|
|
|
format, channel->format);
|
|
|
|
|
|
|
|
return channel->format;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_stereo:
|
|
|
|
* @channel: Channel to toggle
|
|
|
|
* @stereo: 0 for Mono, 1 for Stereo
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_channel_stereo (struct forte_channel *channel, unsigned int stereo)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!channel || !channel->iobase)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s stereo = %d\n", __FUNCTION__, channel->name, stereo);
|
|
|
|
|
|
|
|
switch (stereo) {
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
channel->ctrl &= ~(FORTE_STEREO | FORTE_CHANNELS_MASK);
|
|
|
|
channel-> stereo = stereo;
|
|
|
|
ret = stereo;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
channel->ctrl &= ~FORTE_CHANNELS_MASK;
|
|
|
|
channel->ctrl |= FORTE_STEREO;
|
|
|
|
channel-> stereo = stereo;
|
|
|
|
ret = stereo;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTK ("Unsupported channel format");
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_buffer:
|
|
|
|
* @channel: Channel whose buffer to set up
|
|
|
|
*
|
|
|
|
* Locking: Must be called with lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_channel_buffer (struct forte_channel *channel, int sz, int num)
|
|
|
|
{
|
|
|
|
unsigned int msecs, shift;
|
|
|
|
|
|
|
|
/* Go away, I'm busy */
|
|
|
|
if (channel->filled_frags || channel->bytes)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Fragment size must be a power of 2 */
|
|
|
|
shift = 0; sz++;
|
|
|
|
while (sz >>= 1)
|
|
|
|
shift++;
|
|
|
|
channel->frag_sz = 1 << shift;
|
|
|
|
|
|
|
|
/* Round fragment size to something reasonable */
|
|
|
|
if (channel->frag_sz < FORTE_MIN_FRAG_SIZE)
|
|
|
|
channel->frag_sz = FORTE_MIN_FRAG_SIZE;
|
|
|
|
|
|
|
|
if (channel->frag_sz > FORTE_MAX_FRAG_SIZE)
|
|
|
|
channel->frag_sz = FORTE_MAX_FRAG_SIZE;
|
|
|
|
|
|
|
|
/* Find fragment length in milliseconds */
|
|
|
|
msecs = channel->frag_sz /
|
|
|
|
(channel->format == AFMT_S16_LE ? 2 : 1) /
|
|
|
|
(channel->stereo ? 2 : 1) /
|
|
|
|
(channel->rate / 1000);
|
|
|
|
|
|
|
|
channel->frag_msecs = msecs;
|
|
|
|
|
|
|
|
/* Pick a suitable number of fragments */
|
|
|
|
if (msecs * num < FORTE_MIN_BUF_MSECS)
|
|
|
|
num = FORTE_MIN_BUF_MSECS / msecs;
|
|
|
|
|
|
|
|
if (msecs * num > FORTE_MAX_BUF_MSECS)
|
|
|
|
num = FORTE_MAX_BUF_MSECS / msecs;
|
|
|
|
|
|
|
|
/* Fragment number must be a power of 2 */
|
|
|
|
shift = 0;
|
|
|
|
while (num >>= 1)
|
|
|
|
shift++;
|
|
|
|
channel->frag_num = 1 << (shift + 1);
|
|
|
|
|
|
|
|
/* Round fragment number to something reasonable */
|
|
|
|
if (channel->frag_num < FORTE_MIN_FRAGMENTS)
|
|
|
|
channel->frag_num = FORTE_MIN_FRAGMENTS;
|
|
|
|
|
|
|
|
if (channel->frag_num > FORTE_MAX_FRAGMENTS)
|
|
|
|
channel->frag_num = FORTE_MAX_FRAGMENTS;
|
|
|
|
|
|
|
|
channel->buf_sz = channel->frag_sz * channel->frag_num;
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d\n",
|
|
|
|
__FUNCTION__, channel->name, channel->frag_sz,
|
|
|
|
channel->frag_num, channel->buf_sz);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_prep:
|
|
|
|
* @channel: Channel whose buffer to prepare
|
|
|
|
*
|
|
|
|
* Locking: Lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_channel_prep (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
struct page *page;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (channel->buf)
|
|
|
|
return;
|
|
|
|
|
|
|
|
forte_channel_buffer (channel, channel->frag_sz, channel->frag_num);
|
|
|
|
channel->buf_pages = channel->buf_sz >> PAGE_SHIFT;
|
|
|
|
|
|
|
|
if (channel->buf_sz % PAGE_SIZE)
|
|
|
|
channel->buf_pages++;
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s frag_sz = %d, frag_num = %d, buf_sz = %d, pg = %d\n",
|
|
|
|
__FUNCTION__, channel->name, channel->frag_sz,
|
|
|
|
channel->frag_num, channel->buf_sz, channel->buf_pages);
|
|
|
|
|
|
|
|
/* DMA buffer */
|
|
|
|
channel->buf = pci_alloc_consistent (forte->pci_dev,
|
|
|
|
channel->buf_pages * PAGE_SIZE,
|
|
|
|
&channel->buf_handle);
|
|
|
|
|
|
|
|
if (!channel->buf || !channel->buf_handle)
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
page = virt_to_page (channel->buf);
|
|
|
|
|
|
|
|
/* FIXME: can this go away ? */
|
|
|
|
for (i = 0 ; i < channel->buf_pages ; i++)
|
|
|
|
SetPageReserved(page++);
|
|
|
|
|
|
|
|
/* Prep buffer registers */
|
|
|
|
outw (channel->frag_sz - 1, channel->iobase + FORTE_PLY_COUNT);
|
|
|
|
outl (channel->buf_handle, channel->iobase + FORTE_PLY_BUF1);
|
|
|
|
outl (channel->buf_handle + channel->frag_sz,
|
|
|
|
channel->iobase + FORTE_PLY_BUF2);
|
|
|
|
|
|
|
|
/* Reset hwptr */
|
|
|
|
channel->hwptr = channel->frag_sz;
|
|
|
|
channel->next_buf = 1;
|
|
|
|
|
|
|
|
DPRINTK ("%s: %s buffer @ %p (%p)\n", __FUNCTION__, channel->name,
|
|
|
|
channel->buf, channel->buf_handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_drain:
|
|
|
|
* @chip:
|
|
|
|
* @channel:
|
|
|
|
*
|
|
|
|
* Locking: Don't hold the lock.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
forte_channel_drain (struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
DECLARE_WAITQUEUE (wait, current);
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
DPRINTK ("%s\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (channel->mapped) {
|
|
|
|
spin_lock_irqsave (&forte->lock, flags);
|
|
|
|
forte_channel_stop (channel);
|
|
|
|
spin_unlock_irqrestore (&forte->lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave (&forte->lock, flags);
|
|
|
|
add_wait_queue (&channel->wait, &wait);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (channel->active == 0 || channel->filled_frags == 1)
|
|
|
|
break;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&forte->lock, flags);
|
|
|
|
|
|
|
|
__set_current_state (TASK_INTERRUPTIBLE);
|
|
|
|
schedule();
|
|
|
|
|
|
|
|
spin_lock_irqsave (&forte->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
forte_channel_stop (channel);
|
|
|
|
forte_channel_reset (channel);
|
|
|
|
set_current_state (TASK_RUNNING);
|
|
|
|
remove_wait_queue (&channel->wait, &wait);
|
|
|
|
spin_unlock_irqrestore (&forte->lock, flags);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_init:
|
|
|
|
* @chip: Forte chip instance the channel hangs off
|
|
|
|
* @channel: Channel to initialize
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Initializes a channel, sets defaults, and allocates
|
|
|
|
* buffers.
|
|
|
|
*
|
|
|
|
* Locking: No lock held.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_channel_init (struct forte_chip *chip, struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
DPRINTK ("%s: chip iobase @ %p\n", __FUNCTION__, (void *)chip->iobase);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
memset (channel, 0x0, sizeof (*channel));
|
|
|
|
|
|
|
|
if (channel == &chip->play) {
|
|
|
|
channel->name = "PCM_OUT";
|
|
|
|
channel->iobase = chip->iobase;
|
|
|
|
DPRINTK ("%s: PCM-OUT iobase @ %p\n", __FUNCTION__,
|
|
|
|
(void *) channel->iobase);
|
|
|
|
}
|
|
|
|
else if (channel == &chip->rec) {
|
|
|
|
channel->name = "PCM_IN";
|
|
|
|
channel->iobase = chip->iobase + FORTE_CAP_OFFSET;
|
|
|
|
channel->record = 1;
|
|
|
|
DPRINTK ("%s: PCM-IN iobase @ %p\n", __FUNCTION__,
|
|
|
|
(void *) channel->iobase);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
init_waitqueue_head (&channel->wait);
|
|
|
|
|
|
|
|
/* Defaults: 48kHz, 16-bit, stereo */
|
|
|
|
channel->ctrl = inw (channel->iobase + FORTE_PLY_CTRL);
|
|
|
|
forte_channel_reset (channel);
|
|
|
|
forte_channel_stereo (channel, 1);
|
|
|
|
forte_channel_format (channel, AFMT_S16_LE);
|
|
|
|
forte_channel_rate (channel, 48000);
|
|
|
|
channel->frag_sz = FORTE_DEF_FRAG_SIZE;
|
|
|
|
channel->frag_num = FORTE_DEF_FRAGMENTS;
|
|
|
|
|
|
|
|
chip->trigger = 0;
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_channel_free:
|
|
|
|
* @chip: Chip this channel hangs off
|
|
|
|
* @channel: Channel to nuke
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Resets channel and frees buffers.
|
|
|
|
*
|
|
|
|
* Locking: Hold your horses.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_channel_free (struct forte_chip *chip, struct forte_channel *channel)
|
|
|
|
{
|
|
|
|
DPRINTK ("%s: %s\n", __FUNCTION__, channel->name);
|
|
|
|
|
|
|
|
if (!channel->buf_handle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pci_free_consistent (chip->pci_dev, channel->buf_pages * PAGE_SIZE,
|
|
|
|
channel->buf, channel->buf_handle);
|
|
|
|
|
|
|
|
memset (channel, 0x0, sizeof (*channel));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* DSP --------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_ioctl:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_dsp_ioctl (struct inode *inode, struct file *file, unsigned int cmd,
|
|
|
|
unsigned long arg)
|
|
|
|
{
|
|
|
|
int ival=0, ret, rval=0, rd, wr, count;
|
|
|
|
struct forte_chip *chip;
|
|
|
|
struct audio_buf_info abi;
|
|
|
|
struct count_info cinfo;
|
|
|
|
void __user *argp = (void __user *)arg;
|
|
|
|
int __user *p = argp;
|
|
|
|
|
|
|
|
chip = file->private_data;
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE)
|
|
|
|
wr = 1;
|
|
|
|
else
|
|
|
|
wr = 0;
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_READ)
|
|
|
|
rd = 1;
|
|
|
|
else
|
|
|
|
rd = 0;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
|
|
|
|
case OSS_GETVERSION:
|
|
|
|
return put_user (SOUND_VERSION, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETCAPS:
|
|
|
|
DPRINTK ("%s: GETCAPS\n", __FUNCTION__);
|
|
|
|
|
|
|
|
ival = FORTE_CAPS; /* DUPLEX */
|
|
|
|
return put_user (ival, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETFMTS:
|
|
|
|
DPRINTK ("%s: GETFMTS\n", __FUNCTION__);
|
|
|
|
|
|
|
|
ival = FORTE_FMTS; /* U8, 16LE */
|
|
|
|
return put_user (ival, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SETFMT: /* U8, 16LE */
|
|
|
|
DPRINTK ("%s: SETFMT\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_format (&chip->rec, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_format (&chip->play, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (rval, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_STEREO: /* 0 - mono, 1 - stereo */
|
|
|
|
DPRINTK ("%s: STEREO\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_stereo (&chip->rec, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_stereo (&chip->play, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (rval, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_CHANNELS: /* 1 - mono, 2 - stereo */
|
|
|
|
DPRINTK ("%s: CHANNELS\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_stereo (&chip->rec, ival-1) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
forte_channel_stop (&chip->play);
|
|
|
|
rval = forte_channel_stereo (&chip->play, ival-1) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (rval, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SPEED:
|
|
|
|
DPRINTK ("%s: SPEED\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd) {
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
rval = forte_channel_rate (&chip->rec, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
forte_channel_stop (&chip->play);
|
|
|
|
rval = forte_channel_rate (&chip->play, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user(rval, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETBLKSIZE:
|
|
|
|
DPRINTK ("%s: GETBLKSIZE\n", __FUNCTION__);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd)
|
|
|
|
ival = chip->rec.frag_sz;
|
|
|
|
|
|
|
|
if (wr)
|
|
|
|
ival = chip->play.frag_sz;
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (ival, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_RESET:
|
|
|
|
DPRINTK ("%s: RESET\n", __FUNCTION__);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd)
|
|
|
|
forte_channel_reset (&chip->rec);
|
|
|
|
|
|
|
|
if (wr)
|
|
|
|
forte_channel_reset (&chip->play);
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SYNC:
|
|
|
|
DPRINTK ("%s: SYNC\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (wr)
|
|
|
|
ret = forte_channel_drain (&chip->play);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_POST:
|
|
|
|
DPRINTK ("%s: POST\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (chip->play.filled_frags)
|
|
|
|
forte_channel_start (&chip->play);
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SETFRAGMENT:
|
|
|
|
DPRINTK ("%s: SETFRAGMENT\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (rd) {
|
|
|
|
forte_channel_buffer (&chip->rec, ival & 0xffff,
|
|
|
|
(ival >> 16) & 0xffff);
|
|
|
|
ival = (chip->rec.frag_num << 16) + chip->rec.frag_sz;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
forte_channel_buffer (&chip->play, ival & 0xffff,
|
|
|
|
(ival >> 16) & 0xffff);
|
|
|
|
ival = (chip->play.frag_num << 16) +chip->play.frag_sz;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (ival, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETISPACE:
|
|
|
|
DPRINTK ("%s: GETISPACE\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (!rd)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
abi.fragstotal = chip->rec.frag_num;
|
|
|
|
abi.fragsize = chip->rec.frag_sz;
|
|
|
|
|
|
|
|
if (chip->rec.mapped) {
|
|
|
|
abi.fragments = chip->rec.frag_num - 2;
|
|
|
|
abi.bytes = abi.fragments * abi.fragsize;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
abi.fragments = chip->rec.filled_frags;
|
|
|
|
abi.bytes = abi.fragments * abi.fragsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return copy_to_user (argp, &abi, sizeof (abi)) ? -EFAULT : 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETIPTR:
|
|
|
|
DPRINTK ("%s: GETIPTR\n", __FUNCTION__);
|
|
|
|
|
|
|
|
if (!rd)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (chip->rec.active)
|
|
|
|
cinfo.ptr = chip->rec.hwptr;
|
|
|
|
else
|
|
|
|
cinfo.ptr = 0;
|
|
|
|
|
|
|
|
cinfo.bytes = chip->rec.bytes;
|
|
|
|
cinfo.blocks = chip->rec.nr_irqs;
|
|
|
|
chip->rec.nr_irqs = 0;
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return copy_to_user (argp, &cinfo, sizeof (cinfo)) ? -EFAULT : 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETOSPACE:
|
|
|
|
if (!wr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
abi.fragstotal = chip->play.frag_num;
|
|
|
|
abi.fragsize = chip->play.frag_sz;
|
|
|
|
|
|
|
|
if (chip->play.mapped) {
|
|
|
|
abi.fragments = chip->play.frag_num - 2;
|
|
|
|
abi.bytes = chip->play.buf_sz;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
abi.fragments = chip->play.frag_num -
|
|
|
|
chip->play.filled_frags;
|
|
|
|
|
|
|
|
if (chip->play.residue)
|
|
|
|
abi.fragments--;
|
|
|
|
|
|
|
|
abi.bytes = abi.fragments * abi.fragsize +
|
|
|
|
chip->play.residue;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return copy_to_user (argp, &abi, sizeof (abi)) ? -EFAULT : 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETOPTR:
|
|
|
|
if (!wr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (chip->play.active)
|
|
|
|
cinfo.ptr = chip->play.hwptr;
|
|
|
|
else
|
|
|
|
cinfo.ptr = 0;
|
|
|
|
|
|
|
|
cinfo.bytes = chip->play.bytes;
|
|
|
|
cinfo.blocks = chip->play.nr_irqs;
|
|
|
|
chip->play.nr_irqs = 0;
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return copy_to_user (argp, &cinfo, sizeof (cinfo)) ? -EFAULT : 0;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETODELAY:
|
|
|
|
if (!wr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (!chip->play.active) {
|
|
|
|
ival = 0;
|
|
|
|
}
|
|
|
|
else if (chip->play.mapped) {
|
|
|
|
count = inw (chip->play.iobase + FORTE_PLY_COUNT) + 1;
|
|
|
|
ival = chip->play.frag_sz - count;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ival = chip->play.filled_frags * chip->play.frag_sz;
|
|
|
|
|
|
|
|
if (chip->play.residue)
|
|
|
|
ival += chip->play.frag_sz - chip->play.residue;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
|
|
|
|
return put_user (ival, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SETDUPLEX:
|
|
|
|
DPRINTK ("%s: SETDUPLEX\n", __FUNCTION__);
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
case SNDCTL_DSP_GETTRIGGER:
|
|
|
|
DPRINTK ("%s: GETTRIGGER\n", __FUNCTION__);
|
|
|
|
|
|
|
|
return put_user (chip->trigger, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_SETTRIGGER:
|
|
|
|
|
|
|
|
if (get_user (ival, p))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
DPRINTK ("%s: SETTRIGGER %d\n", __FUNCTION__, ival);
|
|
|
|
|
|
|
|
if (wr) {
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (ival & PCM_ENABLE_OUTPUT)
|
|
|
|
forte_channel_start (&chip->play);
|
|
|
|
else {
|
|
|
|
chip->trigger = 1;
|
|
|
|
forte_channel_prep (&chip->play);
|
|
|
|
forte_channel_stop (&chip->play);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
else if (rd) {
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (ival & PCM_ENABLE_INPUT)
|
|
|
|
forte_channel_start (&chip->rec);
|
|
|
|
else {
|
|
|
|
chip->trigger = 1;
|
|
|
|
forte_channel_prep (&chip->rec);
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case SOUND_PCM_READ_RATE:
|
|
|
|
DPRINTK ("%s: PCM_READ_RATE\n", __FUNCTION__);
|
|
|
|
return put_user (chip->play.rate, p);
|
|
|
|
|
|
|
|
case SOUND_PCM_READ_CHANNELS:
|
|
|
|
DPRINTK ("%s: PCM_READ_CHANNELS\n", __FUNCTION__);
|
|
|
|
return put_user (chip->play.stereo, p);
|
|
|
|
|
|
|
|
case SOUND_PCM_READ_BITS:
|
|
|
|
DPRINTK ("%s: PCM_READ_BITS\n", __FUNCTION__);
|
|
|
|
return put_user (chip->play.format, p);
|
|
|
|
|
|
|
|
case SNDCTL_DSP_NONBLOCK:
|
|
|
|
DPRINTK ("%s: DSP_NONBLOCK\n", __FUNCTION__);
|
|
|
|
file->f_flags |= O_NONBLOCK;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DPRINTK ("Unsupported ioctl: %x (%p)\n", cmd, argp);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_open:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_dsp_open (struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = forte; /* FIXME: HACK FROM HELL! */
|
|
|
|
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
2006-03-23 11:00:39 +00:00
|
|
|
if (!mutex_trylock(&chip->open_mutex)) {
|
2005-04-16 22:20:36 +00:00
|
|
|
DPRINTK ("%s: returning -EAGAIN\n", __FUNCTION__);
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2006-03-23 11:00:39 +00:00
|
|
|
if (mutex_lock_interruptible(&chip->open_mutex)) {
|
2005-04-16 22:20:36 +00:00
|
|
|
DPRINTK ("%s: returning -ERESTARTSYS\n", __FUNCTION__);
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file->private_data = forte;
|
|
|
|
|
|
|
|
DPRINTK ("%s: dsp opened by %d\n", __FUNCTION__, current->pid);
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE)
|
|
|
|
forte_channel_init (forte, &forte->play);
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_READ)
|
|
|
|
forte_channel_init (forte, &forte->rec);
|
|
|
|
|
|
|
|
return nonseekable_open(inode, file);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_release:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_dsp_release (struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = file->private_data;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
DPRINTK ("%s: chip @ %p\n", __FUNCTION__, chip);
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE) {
|
|
|
|
forte_channel_drain (&chip->play);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
forte_channel_free (chip, &chip->play);
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_READ) {
|
|
|
|
while (chip->rec.filled_frags > 0)
|
|
|
|
interruptible_sleep_on (&chip->rec.wait);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
forte_channel_stop (&chip->rec);
|
|
|
|
forte_channel_free (chip, &chip->rec);
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
2006-03-23 11:00:39 +00:00
|
|
|
mutex_unlock(&chip->open_mutex);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_poll:
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned int
|
|
|
|
forte_dsp_poll (struct file *file, struct poll_table_struct *wait)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip;
|
|
|
|
struct forte_channel *channel;
|
|
|
|
unsigned int mask = 0;
|
|
|
|
|
|
|
|
chip = file->private_data;
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE) {
|
|
|
|
channel = &chip->play;
|
|
|
|
|
|
|
|
if (channel->active)
|
|
|
|
poll_wait (file, &channel->wait, wait);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (channel->frag_num - channel->filled_frags > 0)
|
|
|
|
mask |= POLLOUT | POLLWRNORM;
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_READ) {
|
|
|
|
channel = &chip->rec;
|
|
|
|
|
|
|
|
if (channel->active)
|
|
|
|
poll_wait (file, &channel->wait, wait);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (channel->filled_frags > 0)
|
|
|
|
mask |= POLLIN | POLLRDNORM;
|
|
|
|
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_mmap:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_dsp_mmap (struct file *file, struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip;
|
|
|
|
struct forte_channel *channel;
|
|
|
|
unsigned long size;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
chip = file->private_data;
|
|
|
|
|
|
|
|
DPRINTK ("%s: start %lXh, size %ld, pgoff %ld\n", __FUNCTION__,
|
|
|
|
vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_pgoff);
|
|
|
|
|
|
|
|
spin_lock_irq (&chip->lock);
|
|
|
|
|
|
|
|
if (vma->vm_flags & VM_WRITE && chip->play.active) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vma->vm_flags & VM_READ && chip->rec.active) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file->f_mode & FMODE_WRITE)
|
|
|
|
channel = &chip->play;
|
|
|
|
else if (file->f_mode & FMODE_READ)
|
|
|
|
channel = &chip->rec;
|
|
|
|
else {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
forte_channel_prep (channel);
|
|
|
|
channel->mapped = 1;
|
|
|
|
|
|
|
|
if (vma->vm_pgoff != 0) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
size = vma->vm_end - vma->vm_start;
|
|
|
|
|
|
|
|
if (size > channel->buf_pages * PAGE_SIZE) {
|
|
|
|
DPRINTK ("%s: size (%ld) > buf_sz (%d) \n", __FUNCTION__,
|
|
|
|
size, channel->buf_sz);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remap_pfn_range(vma, vma->vm_start,
|
|
|
|
virt_to_phys(channel->buf) >> PAGE_SHIFT,
|
|
|
|
size, vma->vm_page_prot)) {
|
|
|
|
DPRINTK ("%s: remap el a no worko\n", __FUNCTION__);
|
|
|
|
ret = -EAGAIN;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out:
|
|
|
|
spin_unlock_irq (&chip->lock);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_write:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
forte_dsp_write (struct file *file, const char __user *buffer, size_t bytes,
|
|
|
|
loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip;
|
|
|
|
struct forte_channel *channel;
|
|
|
|
unsigned int i = bytes, sz = 0;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (!access_ok (VERIFY_READ, buffer, bytes))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
chip = (struct forte_chip *) file->private_data;
|
|
|
|
|
|
|
|
if (!chip)
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
channel = &chip->play;
|
|
|
|
|
|
|
|
if (!channel)
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
|
|
|
|
/* Set up buffers with the right fragment size */
|
|
|
|
forte_channel_prep (channel);
|
|
|
|
|
|
|
|
while (i) {
|
|
|
|
/* All fragment buffers in use -> wait */
|
|
|
|
if (channel->frag_num - channel->filled_frags == 0) {
|
|
|
|
DECLARE_WAITQUEUE (wait, current);
|
|
|
|
|
|
|
|
/* For trigger or non-blocking operation, get out */
|
|
|
|
if (chip->trigger || file->f_flags & O_NONBLOCK) {
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise wait for buffers */
|
|
|
|
add_wait_queue (&channel->wait, &wait);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
set_current_state (TASK_INTERRUPTIBLE);
|
|
|
|
schedule();
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
|
|
|
|
if (channel->frag_num - channel->filled_frags)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
remove_wait_queue (&channel->wait, &wait);
|
|
|
|
set_current_state (TASK_RUNNING);
|
|
|
|
|
|
|
|
if (signal_pending (current)) {
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channel->residue)
|
|
|
|
sz = channel->residue;
|
|
|
|
else if (i > channel->frag_sz)
|
|
|
|
sz = channel->frag_sz;
|
|
|
|
else
|
|
|
|
sz = i;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
if (copy_from_user ((void *) channel->buf + channel->swptr, buffer, sz))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
|
|
|
|
/* Advance software pointer */
|
|
|
|
buffer += sz;
|
|
|
|
channel->swptr += sz;
|
|
|
|
channel->swptr %= channel->buf_sz;
|
|
|
|
i -= sz;
|
|
|
|
|
|
|
|
/* Only bump filled_frags if a full fragment has been written */
|
|
|
|
if (channel->swptr % channel->frag_sz == 0) {
|
|
|
|
channel->filled_frags++;
|
|
|
|
channel->residue = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
channel->residue = channel->frag_sz - sz;
|
|
|
|
|
|
|
|
/* If playback isn't active, start it */
|
|
|
|
if (channel->active == 0 && chip->trigger == 0)
|
|
|
|
forte_channel_start (channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
return bytes - i;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_dsp_read:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
forte_dsp_read (struct file *file, char __user *buffer, size_t bytes,
|
|
|
|
loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip;
|
|
|
|
struct forte_channel *channel;
|
|
|
|
unsigned int i = bytes, sz;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (!access_ok (VERIFY_WRITE, buffer, bytes))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
chip = (struct forte_chip *) file->private_data;
|
|
|
|
|
|
|
|
if (!chip)
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
channel = &chip->rec;
|
|
|
|
|
|
|
|
if (!channel)
|
|
|
|
BUG();
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
|
|
|
|
/* Set up buffers with the right fragment size */
|
|
|
|
forte_channel_prep (channel);
|
|
|
|
|
|
|
|
/* Start recording */
|
|
|
|
if (!chip->trigger)
|
|
|
|
forte_channel_start (channel);
|
|
|
|
|
|
|
|
while (i) {
|
|
|
|
/* No fragment buffers in use -> wait */
|
|
|
|
if (channel->filled_frags == 0) {
|
|
|
|
DECLARE_WAITQUEUE (wait, current);
|
|
|
|
|
|
|
|
/* For trigger mode operation, get out */
|
|
|
|
if (chip->trigger) {
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
add_wait_queue (&channel->wait, &wait);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (channel->active == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (channel->filled_frags)
|
|
|
|
break;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
set_current_state (TASK_INTERRUPTIBLE);
|
|
|
|
schedule();
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
set_current_state (TASK_RUNNING);
|
|
|
|
remove_wait_queue (&channel->wait, &wait);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i > channel->frag_sz)
|
|
|
|
sz = channel->frag_sz;
|
|
|
|
else
|
|
|
|
sz = i;
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
if (copy_to_user (buffer, (void *)channel->buf+channel->swptr, sz)) {
|
|
|
|
DPRINTK ("%s: copy_to_user failed\n", __FUNCTION__);
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave (&chip->lock, flags);
|
|
|
|
|
|
|
|
/* Advance software pointer */
|
|
|
|
buffer += sz;
|
|
|
|
if (channel->filled_frags > 0)
|
|
|
|
channel->filled_frags--;
|
|
|
|
channel->swptr += channel->frag_sz;
|
|
|
|
channel->swptr %= channel->buf_sz;
|
|
|
|
i -= sz;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock_irqrestore (&chip->lock, flags);
|
|
|
|
|
|
|
|
return bytes - i;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct file_operations forte_dsp_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.llseek = &no_llseek,
|
|
|
|
.read = &forte_dsp_read,
|
|
|
|
.write = &forte_dsp_write,
|
|
|
|
.poll = &forte_dsp_poll,
|
|
|
|
.ioctl = &forte_dsp_ioctl,
|
|
|
|
.open = &forte_dsp_open,
|
|
|
|
.release = &forte_dsp_release,
|
|
|
|
.mmap = &forte_dsp_mmap,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* Common ------------------------------------------------------------------ */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_interrupt:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static irqreturn_t
|
|
|
|
forte_interrupt (int irq, void *dev_id, struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = dev_id;
|
|
|
|
struct forte_channel *channel = NULL;
|
|
|
|
u16 status, count;
|
|
|
|
|
|
|
|
status = inw (chip->iobase + FORTE_IRQ_STATUS);
|
|
|
|
|
|
|
|
/* If this is not for us, get outta here ASAP */
|
|
|
|
if ((status & (FORTE_IRQ_PLAYBACK | FORTE_IRQ_CAPTURE)) == 0)
|
|
|
|
return IRQ_NONE;
|
|
|
|
|
|
|
|
if (status & FORTE_IRQ_PLAYBACK) {
|
|
|
|
channel = &chip->play;
|
|
|
|
|
|
|
|
spin_lock (&chip->lock);
|
|
|
|
|
|
|
|
if (channel->frag_sz == 0)
|
|
|
|
goto pack;
|
|
|
|
|
|
|
|
/* Declare a fragment done */
|
|
|
|
if (channel->filled_frags > 0)
|
|
|
|
channel->filled_frags--;
|
|
|
|
channel->bytes += channel->frag_sz;
|
|
|
|
channel->nr_irqs++;
|
|
|
|
|
|
|
|
/* Flip-flop between buffer I and II */
|
|
|
|
channel->next_buf ^= 1;
|
|
|
|
|
|
|
|
/* Advance hardware pointer by fragment size and wrap around */
|
|
|
|
channel->hwptr += channel->frag_sz;
|
|
|
|
channel->hwptr %= channel->buf_sz;
|
|
|
|
|
|
|
|
/* Buffer I or buffer II BAR */
|
|
|
|
outl (channel->buf_handle + channel->hwptr,
|
|
|
|
channel->next_buf == 0 ?
|
|
|
|
channel->iobase + FORTE_PLY_BUF1 :
|
|
|
|
channel->iobase + FORTE_PLY_BUF2);
|
|
|
|
|
|
|
|
/* If the currently playing fragment is last, schedule pause */
|
|
|
|
if (channel->filled_frags == 1)
|
|
|
|
forte_channel_pause (channel);
|
|
|
|
|
|
|
|
pack:
|
|
|
|
/* Acknowledge interrupt */
|
|
|
|
outw (FORTE_IRQ_PLAYBACK, chip->iobase + FORTE_IRQ_STATUS);
|
|
|
|
|
|
|
|
if (waitqueue_active (&channel->wait))
|
|
|
|
wake_up_all (&channel->wait);
|
|
|
|
|
|
|
|
spin_unlock (&chip->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status & FORTE_IRQ_CAPTURE) {
|
|
|
|
channel = &chip->rec;
|
|
|
|
spin_lock (&chip->lock);
|
|
|
|
|
|
|
|
/* One fragment filled */
|
|
|
|
channel->filled_frags++;
|
|
|
|
|
|
|
|
/* Get # of completed bytes */
|
|
|
|
count = inw (channel->iobase + FORTE_PLY_COUNT) + 1;
|
|
|
|
|
|
|
|
if (count == 0) {
|
|
|
|
DPRINTK ("%s: last, filled_frags = %d\n", __FUNCTION__,
|
|
|
|
channel->filled_frags);
|
|
|
|
channel->filled_frags = 0;
|
|
|
|
goto rack;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Buffer I or buffer II BAR */
|
|
|
|
outl (channel->buf_handle + channel->hwptr,
|
|
|
|
channel->next_buf == 0 ?
|
|
|
|
channel->iobase + FORTE_PLY_BUF1 :
|
|
|
|
channel->iobase + FORTE_PLY_BUF2);
|
|
|
|
|
|
|
|
/* Flip-flop between buffer I and II */
|
|
|
|
channel->next_buf ^= 1;
|
|
|
|
|
|
|
|
/* Advance hardware pointer by fragment size and wrap around */
|
|
|
|
channel->hwptr += channel->frag_sz;
|
|
|
|
channel->hwptr %= channel->buf_sz;
|
|
|
|
|
|
|
|
/* Out of buffers */
|
|
|
|
if (channel->filled_frags == channel->frag_num - 1)
|
|
|
|
forte_channel_stop (channel);
|
|
|
|
rack:
|
|
|
|
/* Acknowledge interrupt */
|
|
|
|
outw (FORTE_IRQ_CAPTURE, chip->iobase + FORTE_IRQ_STATUS);
|
|
|
|
|
|
|
|
spin_unlock (&chip->lock);
|
|
|
|
|
|
|
|
if (waitqueue_active (&channel->wait))
|
|
|
|
wake_up_all (&channel->wait);
|
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_proc_read:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
forte_proc_read (char *page, char **start, off_t off, int count,
|
|
|
|
int *eof, void *data)
|
|
|
|
{
|
|
|
|
int i = 0, p_rate, p_chan, r_rate;
|
|
|
|
unsigned short p_reg, r_reg;
|
|
|
|
|
|
|
|
i += sprintf (page, "ForteMedia FM801 OSS Lite driver\n%s\n \n",
|
|
|
|
DRIVER_VERSION);
|
|
|
|
|
|
|
|
if (!forte->iobase)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
p_rate = p_chan = -1;
|
|
|
|
p_reg = inw (forte->iobase + FORTE_PLY_CTRL);
|
|
|
|
p_rate = (p_reg >> 8) & 15;
|
|
|
|
p_chan = (p_reg >> 12) & 3;
|
|
|
|
|
|
|
|
if (p_rate >= 0 || p_rate <= 10)
|
|
|
|
p_rate = rates[p_rate];
|
|
|
|
|
|
|
|
if (p_chan >= 0 || p_chan <= 2)
|
|
|
|
p_chan = channels[p_chan];
|
|
|
|
|
|
|
|
r_rate = -1;
|
|
|
|
r_reg = inw (forte->iobase + FORTE_CAP_CTRL);
|
|
|
|
r_rate = (r_reg >> 8) & 15;
|
|
|
|
|
|
|
|
if (r_rate >= 0 || r_rate <= 10)
|
|
|
|
r_rate = rates[r_rate];
|
|
|
|
|
|
|
|
i += sprintf (page + i,
|
|
|
|
" Playback Capture\n"
|
|
|
|
"FIFO empty : %-3s %-3s\n"
|
|
|
|
"Buf1 Last : %-3s %-3s\n"
|
|
|
|
"Buf2 Last : %-3s %-3s\n"
|
|
|
|
"Started : %-3s %-3s\n"
|
|
|
|
"Paused : %-3s %-3s\n"
|
|
|
|
"Immed Stop : %-3s %-3s\n"
|
|
|
|
"Rate : %-5d %-5d\n"
|
|
|
|
"Channels : %-5d -\n"
|
|
|
|
"16-bit : %-3s %-3s\n"
|
|
|
|
"Stereo : %-3s %-3s\n"
|
|
|
|
" \n"
|
|
|
|
"Buffer Sz : %-6d %-6d\n"
|
|
|
|
"Frag Sz : %-6d %-6d\n"
|
|
|
|
"Frag Num : %-6d %-6d\n"
|
|
|
|
"Frag msecs : %-6d %-6d\n"
|
|
|
|
"Used Frags : %-6d %-6d\n"
|
|
|
|
"Mapped : %-3s %-3s\n",
|
|
|
|
p_reg & 1<<0 ? "yes" : "no",
|
|
|
|
r_reg & 1<<0 ? "yes" : "no",
|
|
|
|
p_reg & 1<<1 ? "yes" : "no",
|
|
|
|
r_reg & 1<<1 ? "yes" : "no",
|
|
|
|
p_reg & 1<<2 ? "yes" : "no",
|
|
|
|
r_reg & 1<<2 ? "yes" : "no",
|
|
|
|
p_reg & 1<<5 ? "yes" : "no",
|
|
|
|
r_reg & 1<<5 ? "yes" : "no",
|
|
|
|
p_reg & 1<<6 ? "yes" : "no",
|
|
|
|
r_reg & 1<<6 ? "yes" : "no",
|
|
|
|
p_reg & 1<<7 ? "yes" : "no",
|
|
|
|
r_reg & 1<<7 ? "yes" : "no",
|
|
|
|
p_rate, r_rate,
|
|
|
|
p_chan,
|
|
|
|
p_reg & 1<<14 ? "yes" : "no",
|
|
|
|
r_reg & 1<<14 ? "yes" : "no",
|
|
|
|
p_reg & 1<<15 ? "yes" : "no",
|
|
|
|
r_reg & 1<<15 ? "yes" : "no",
|
|
|
|
forte->play.buf_sz, forte->rec.buf_sz,
|
|
|
|
forte->play.frag_sz, forte->rec.frag_sz,
|
|
|
|
forte->play.frag_num, forte->rec.frag_num,
|
|
|
|
forte->play.frag_msecs, forte->rec.frag_msecs,
|
|
|
|
forte->play.filled_frags, forte->rec.filled_frags,
|
|
|
|
forte->play.mapped ? "yes" : "no",
|
|
|
|
forte->rec.mapped ? "yes" : "no"
|
|
|
|
);
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_proc_init:
|
|
|
|
*
|
|
|
|
* Creates driver info entries in /proc
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __init
|
|
|
|
forte_proc_init (void)
|
|
|
|
{
|
|
|
|
if (!proc_mkdir ("driver/forte", NULL))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
if (!create_proc_read_entry ("driver/forte/chip", 0, NULL, forte_proc_read, forte)) {
|
|
|
|
remove_proc_entry ("driver/forte", NULL);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!create_proc_read_entry("driver/forte/ac97", 0, NULL, ac97_read_proc, forte->ac97)) {
|
|
|
|
remove_proc_entry ("driver/forte/chip", NULL);
|
|
|
|
remove_proc_entry ("driver/forte", NULL);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_proc_remove:
|
|
|
|
*
|
|
|
|
* Removes driver info entries in /proc
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_proc_remove (void)
|
|
|
|
{
|
|
|
|
remove_proc_entry ("driver/forte/ac97", NULL);
|
|
|
|
remove_proc_entry ("driver/forte/chip", NULL);
|
|
|
|
remove_proc_entry ("driver/forte", NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_chip_init:
|
|
|
|
* @chip: Chip instance to initialize
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Resets chip, configures codec and registers the driver with
|
|
|
|
* the sound subsystem.
|
|
|
|
*
|
|
|
|
* Press and hold Start for 8 secs, then switch on Run
|
|
|
|
* and hold for 4 seconds. Let go of Start. Numbers
|
|
|
|
* assume a properly oiled TWG.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __devinit
|
|
|
|
forte_chip_init (struct forte_chip *chip)
|
|
|
|
{
|
|
|
|
u8 revision;
|
|
|
|
u16 cmdw;
|
|
|
|
struct ac97_codec *codec;
|
|
|
|
|
|
|
|
pci_read_config_byte (chip->pci_dev, PCI_REVISION_ID, &revision);
|
|
|
|
|
|
|
|
if (revision >= 0xB1) {
|
|
|
|
chip->multichannel = 1;
|
|
|
|
printk (KERN_INFO PFX "Multi-channel device detected.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Reset chip */
|
|
|
|
outw (FORTE_CC_CODEC_RESET | FORTE_CC_AC97_RESET,
|
|
|
|
chip->iobase + FORTE_CODEC_CTRL);
|
|
|
|
udelay(100);
|
|
|
|
outw (0, chip->iobase + FORTE_CODEC_CTRL);
|
|
|
|
|
|
|
|
/* Request read from AC97 */
|
|
|
|
outw (FORTE_AC97_READ | (0 << FORTE_AC97_ADDR_SHIFT),
|
|
|
|
chip->iobase + FORTE_AC97_CMD);
|
|
|
|
mdelay(750);
|
|
|
|
|
|
|
|
if ((inw (chip->iobase + FORTE_AC97_CMD) & (3<<8)) != (1<<8)) {
|
|
|
|
printk (KERN_INFO PFX "AC97 codec not responding");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Init volume */
|
|
|
|
outw (0x0808, chip->iobase + FORTE_PCM_VOL);
|
|
|
|
outw (0x9f1f, chip->iobase + FORTE_FM_VOL);
|
|
|
|
outw (0x8808, chip->iobase + FORTE_I2S_VOL);
|
|
|
|
|
|
|
|
/* I2S control - I2S mode */
|
|
|
|
outw (0x0003, chip->iobase + FORTE_I2S_MODE);
|
|
|
|
|
|
|
|
/* Interrupt setup - unmask PLAYBACK & CAPTURE */
|
|
|
|
cmdw = inw (chip->iobase + FORTE_IRQ_MASK);
|
|
|
|
cmdw &= ~0x0003;
|
|
|
|
outw (cmdw, chip->iobase + FORTE_IRQ_MASK);
|
|
|
|
|
|
|
|
/* Interrupt clear */
|
|
|
|
outw (FORTE_IRQ_PLAYBACK|FORTE_IRQ_CAPTURE,
|
|
|
|
chip->iobase + FORTE_IRQ_STATUS);
|
|
|
|
|
|
|
|
/* Set up the AC97 codec */
|
|
|
|
if ((codec = ac97_alloc_codec()) == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
codec->private_data = chip;
|
|
|
|
codec->codec_read = forte_ac97_read;
|
|
|
|
codec->codec_write = forte_ac97_write;
|
|
|
|
codec->id = 0;
|
|
|
|
|
|
|
|
if (ac97_probe_codec (codec) == 0) {
|
|
|
|
printk (KERN_ERR PFX "codec probe failed\n");
|
|
|
|
ac97_release_codec(codec);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register mixer */
|
|
|
|
if ((codec->dev_mixer =
|
|
|
|
register_sound_mixer (&forte_mixer_fops, -1)) < 0) {
|
|
|
|
printk (KERN_ERR PFX "couldn't register mixer!\n");
|
|
|
|
ac97_release_codec(codec);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
chip->ac97 = codec;
|
|
|
|
|
|
|
|
/* Register DSP */
|
|
|
|
if ((chip->dsp = register_sound_dsp (&forte_dsp_fops, -1) ) < 0) {
|
|
|
|
printk (KERN_ERR PFX "couldn't register dsp!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register with /proc */
|
|
|
|
if (forte_proc_init()) {
|
|
|
|
printk (KERN_ERR PFX "couldn't add entries to /proc!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_probe:
|
|
|
|
* @pci_dev: PCI struct for probed device
|
|
|
|
* @pci_id:
|
|
|
|
*
|
|
|
|
* Description:
|
|
|
|
* Allocates chip instance, I/O region, and IRQ
|
|
|
|
*/
|
|
|
|
static int __init
|
|
|
|
forte_probe (struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* FIXME: Support more than one chip */
|
|
|
|
if (found++)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
/* Ignition */
|
|
|
|
if (pci_enable_device (pci_dev))
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
pci_set_master (pci_dev);
|
|
|
|
|
|
|
|
/* Allocate chip instance and configure */
|
|
|
|
forte = (struct forte_chip *)
|
|
|
|
kmalloc (sizeof (struct forte_chip), GFP_KERNEL);
|
|
|
|
chip = forte;
|
|
|
|
|
|
|
|
if (chip == NULL) {
|
|
|
|
printk (KERN_WARNING PFX "Out of memory");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset (chip, 0, sizeof (struct forte_chip));
|
|
|
|
chip->pci_dev = pci_dev;
|
|
|
|
|
2006-03-23 11:00:39 +00:00
|
|
|
mutex_init(&chip->open_mutex);
|
2005-04-16 22:20:36 +00:00
|
|
|
spin_lock_init (&chip->lock);
|
|
|
|
spin_lock_init (&chip->ac97_lock);
|
|
|
|
|
|
|
|
if (! request_region (pci_resource_start (pci_dev, 0),
|
|
|
|
pci_resource_len (pci_dev, 0), DRIVER_NAME)) {
|
|
|
|
printk (KERN_WARNING PFX "Unable to reserve I/O space");
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
chip->iobase = pci_resource_start (pci_dev, 0);
|
|
|
|
chip->irq = pci_dev->irq;
|
|
|
|
|
|
|
|
if (request_irq (chip->irq, forte_interrupt, SA_SHIRQ, DRIVER_NAME,
|
|
|
|
chip)) {
|
|
|
|
printk (KERN_WARNING PFX "Unable to reserve IRQ");
|
|
|
|
ret = -EIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
pci_set_drvdata (pci_dev, chip);
|
|
|
|
|
|
|
|
printk (KERN_INFO PFX "FM801 chip found at 0x%04lX-0x%04lX IRQ %u\n",
|
|
|
|
chip->iobase, pci_resource_end (pci_dev, 0), chip->irq);
|
|
|
|
|
|
|
|
/* Power it up */
|
|
|
|
if ((ret = forte_chip_init (chip)) == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (chip->irq)
|
|
|
|
free_irq (chip->irq, chip);
|
|
|
|
|
|
|
|
if (chip->iobase)
|
|
|
|
release_region (pci_resource_start (pci_dev, 0),
|
|
|
|
pci_resource_len (pci_dev, 0));
|
|
|
|
|
|
|
|
kfree (chip);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_remove:
|
|
|
|
* @pci_dev: PCI device to unclaim
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
forte_remove (struct pci_dev *pci_dev)
|
|
|
|
{
|
|
|
|
struct forte_chip *chip = pci_get_drvdata (pci_dev);
|
|
|
|
|
|
|
|
if (chip == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Turn volume down to avoid popping */
|
|
|
|
outw (0x1f1f, chip->iobase + FORTE_PCM_VOL);
|
|
|
|
outw (0x1f1f, chip->iobase + FORTE_FM_VOL);
|
|
|
|
outw (0x1f1f, chip->iobase + FORTE_I2S_VOL);
|
|
|
|
|
|
|
|
forte_proc_remove();
|
|
|
|
free_irq (chip->irq, chip);
|
|
|
|
release_region (chip->iobase, pci_resource_len (pci_dev, 0));
|
|
|
|
|
|
|
|
unregister_sound_dsp (chip->dsp);
|
|
|
|
unregister_sound_mixer (chip->ac97->dev_mixer);
|
|
|
|
ac97_release_codec(chip->ac97);
|
|
|
|
kfree (chip);
|
|
|
|
|
|
|
|
printk (KERN_INFO PFX "driver released\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct pci_device_id forte_pci_ids[] = {
|
|
|
|
{ 0x1319, 0x0801, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
|
|
|
|
{ 0, }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static struct pci_driver forte_pci_driver = {
|
|
|
|
.name = DRIVER_NAME,
|
|
|
|
.id_table = forte_pci_ids,
|
|
|
|
.probe = forte_probe,
|
|
|
|
.remove = forte_remove,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_init_module:
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __init
|
|
|
|
forte_init_module (void)
|
|
|
|
{
|
|
|
|
printk (KERN_INFO PFX DRIVER_VERSION "\n");
|
|
|
|
|
|
|
|
return pci_register_driver (&forte_pci_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* forte_cleanup_module:
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void __exit
|
|
|
|
forte_cleanup_module (void)
|
|
|
|
{
|
|
|
|
pci_unregister_driver (&forte_pci_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module_init(forte_init_module);
|
|
|
|
module_exit(forte_cleanup_module);
|
|
|
|
|
|
|
|
MODULE_AUTHOR("Martin K. Petersen <mkp@mkp.net>");
|
|
|
|
MODULE_DESCRIPTION("ForteMedia FM801 OSS Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DEVICE_TABLE (pci, forte_pci_ids);
|