![David Howells](/assets/img/avatar_default.png)
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2035 lines
46 KiB
C
2035 lines
46 KiB
C
/*
|
|
* linux/sound/oss/waveartist.c
|
|
*
|
|
* The low level driver for the RWA010 Rockwell Wave Artist
|
|
* codec chip used in the Rebel.com NetWinder.
|
|
*
|
|
* Cleaned up and integrated into 2.1 by Russell King (rmk@arm.linux.org.uk)
|
|
* and Pat Beirne (patb@corel.ca)
|
|
*
|
|
*
|
|
* Copyright (C) by Rebel.com 1998-1999
|
|
*
|
|
* RWA010 specs received under NDA from Rockwell
|
|
*
|
|
* Copyright (C) by Hannu Savolainen 1993-1997
|
|
*
|
|
* OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
|
|
* Version 2 (June 1991). See the "COPYING" file distributed with this software
|
|
* for more info.
|
|
*
|
|
* Changes:
|
|
* 11-10-2000 Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
|
|
* Added __init to waveartist_init()
|
|
*/
|
|
|
|
/* Debugging */
|
|
#define DEBUG_CMD 1
|
|
#define DEBUG_OUT 2
|
|
#define DEBUG_IN 4
|
|
#define DEBUG_INTR 8
|
|
#define DEBUG_MIXER 16
|
|
#define DEBUG_TRIGGER 32
|
|
|
|
#define debug_flg (0)
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include "sound_config.h"
|
|
#include "waveartist.h"
|
|
|
|
#ifdef CONFIG_ARM
|
|
#include <asm/hardware.h>
|
|
#include <asm/mach-types.h>
|
|
#endif
|
|
|
|
#ifndef NO_DMA
|
|
#define NO_DMA 255
|
|
#endif
|
|
|
|
#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH |\
|
|
SOUND_MASK_PCM |\
|
|
SOUND_MASK_LINE |\
|
|
SOUND_MASK_MIC |\
|
|
SOUND_MASK_LINE1 |\
|
|
SOUND_MASK_RECLEV |\
|
|
SOUND_MASK_VOLUME |\
|
|
SOUND_MASK_IMIX)
|
|
|
|
static unsigned short levels[SOUND_MIXER_NRDEVICES] = {
|
|
0x5555, /* Master Volume */
|
|
0x0000, /* Bass */
|
|
0x0000, /* Treble */
|
|
0x2323, /* Synth (FM) */
|
|
0x4b4b, /* PCM */
|
|
0x6464, /* PC Speaker */
|
|
0x0000, /* Ext Line */
|
|
0x0000, /* Mic */
|
|
0x0000, /* CD */
|
|
0x6464, /* Recording monitor */
|
|
0x0000, /* SB PCM (ALT PCM) */
|
|
0x0000, /* Recording level */
|
|
0x6464, /* Input gain */
|
|
0x6464, /* Output gain */
|
|
0x0000, /* Line1 (Aux1) */
|
|
0x0000, /* Line2 (Aux2) */
|
|
0x0000, /* Line3 (Aux3) */
|
|
0x0000, /* Digital1 */
|
|
0x0000, /* Digital2 */
|
|
0x0000, /* Digital3 */
|
|
0x0000, /* Phone In */
|
|
0x6464, /* Phone Out */
|
|
0x0000, /* Video */
|
|
0x0000, /* Radio */
|
|
0x0000 /* Monitor */
|
|
};
|
|
|
|
typedef struct {
|
|
struct address_info hw; /* hardware */
|
|
char *chip_name;
|
|
|
|
int xfer_count;
|
|
int audio_mode;
|
|
int open_mode;
|
|
int audio_flags;
|
|
int record_dev;
|
|
int playback_dev;
|
|
int dev_no;
|
|
|
|
/* Mixer parameters */
|
|
const struct waveartist_mixer_info *mix;
|
|
|
|
unsigned short *levels; /* cache of volume settings */
|
|
int recmask; /* currently enabled recording device! */
|
|
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
signed int slider_vol; /* hardware slider volume */
|
|
unsigned int handset_detect :1;
|
|
unsigned int telephone_detect:1;
|
|
unsigned int no_autoselect :1;/* handset/telephone autoselects a path */
|
|
unsigned int spkr_mute_state :1;/* set by ioctl or autoselect */
|
|
unsigned int line_mute_state :1;/* set by ioctl or autoselect */
|
|
unsigned int use_slider :1;/* use slider setting for o/p vol */
|
|
#endif
|
|
} wavnc_info;
|
|
|
|
/*
|
|
* This is the implementation specific mixer information.
|
|
*/
|
|
struct waveartist_mixer_info {
|
|
unsigned int supported_devs; /* Supported devices */
|
|
unsigned int recording_devs; /* Recordable devies */
|
|
unsigned int stereo_devs; /* Stereo devices */
|
|
|
|
unsigned int (*select_input)(wavnc_info *, unsigned int,
|
|
unsigned char *, unsigned char *);
|
|
int (*decode_mixer)(wavnc_info *, int,
|
|
unsigned char, unsigned char);
|
|
int (*get_mixer)(wavnc_info *, int);
|
|
};
|
|
|
|
typedef struct wavnc_port_info {
|
|
int open_mode;
|
|
int speed;
|
|
int channels;
|
|
int audio_format;
|
|
} wavnc_port_info;
|
|
|
|
static int nr_waveartist_devs;
|
|
static wavnc_info adev_info[MAX_AUDIO_DEV];
|
|
static DEFINE_SPINLOCK(waveartist_lock);
|
|
|
|
#ifndef CONFIG_ARCH_NETWINDER
|
|
#define machine_is_netwinder() 0
|
|
#else
|
|
static struct timer_list vnc_timer;
|
|
static void vnc_configure_mixer(wavnc_info *devc, unsigned int input_mask);
|
|
static int vnc_private_ioctl(int dev, unsigned int cmd, int __user *arg);
|
|
static void vnc_slider_tick(unsigned long data);
|
|
#endif
|
|
|
|
static inline void
|
|
waveartist_set_ctlr(struct address_info *hw, unsigned char clear, unsigned char set)
|
|
{
|
|
unsigned int ctlr_port = hw->io_base + CTLR;
|
|
|
|
clear = ~clear & inb(ctlr_port);
|
|
|
|
outb(clear | set, ctlr_port);
|
|
}
|
|
|
|
/* Toggle IRQ acknowledge line
|
|
*/
|
|
static inline void
|
|
waveartist_iack(wavnc_info *devc)
|
|
{
|
|
unsigned int ctlr_port = devc->hw.io_base + CTLR;
|
|
int old_ctlr;
|
|
|
|
old_ctlr = inb(ctlr_port) & ~IRQ_ACK;
|
|
|
|
outb(old_ctlr | IRQ_ACK, ctlr_port);
|
|
outb(old_ctlr, ctlr_port);
|
|
}
|
|
|
|
static inline int
|
|
waveartist_sleep(int timeout_ms)
|
|
{
|
|
unsigned int timeout = timeout_ms * 10 * HZ / 100;
|
|
|
|
do {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
timeout = schedule_timeout(timeout);
|
|
} while (timeout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
waveartist_reset(wavnc_info *devc)
|
|
{
|
|
struct address_info *hw = &devc->hw;
|
|
unsigned int timeout, res = -1;
|
|
|
|
waveartist_set_ctlr(hw, -1, RESET);
|
|
waveartist_sleep(2);
|
|
waveartist_set_ctlr(hw, RESET, 0);
|
|
|
|
timeout = 500;
|
|
do {
|
|
mdelay(2);
|
|
|
|
if (inb(hw->io_base + STATR) & CMD_RF) {
|
|
res = inw(hw->io_base + CMDR);
|
|
if (res == 0x55aa)
|
|
break;
|
|
}
|
|
} while (--timeout);
|
|
|
|
if (timeout == 0) {
|
|
printk(KERN_WARNING "WaveArtist: reset timeout ");
|
|
if (res != (unsigned int)-1)
|
|
printk("(res=%04X)", res);
|
|
printk("\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Helper function to send and receive words
|
|
* from WaveArtist. It handles all the handshaking
|
|
* and can send or receive multiple words.
|
|
*/
|
|
static int
|
|
waveartist_cmd(wavnc_info *devc,
|
|
int nr_cmd, unsigned int *cmd,
|
|
int nr_resp, unsigned int *resp)
|
|
{
|
|
unsigned int io_base = devc->hw.io_base;
|
|
unsigned int timed_out = 0;
|
|
unsigned int i;
|
|
|
|
if (debug_flg & DEBUG_CMD) {
|
|
printk("waveartist_cmd: cmd=");
|
|
|
|
for (i = 0; i < nr_cmd; i++)
|
|
printk("%04X ", cmd[i]);
|
|
|
|
printk("\n");
|
|
}
|
|
|
|
if (inb(io_base + STATR) & CMD_RF) {
|
|
int old_data;
|
|
|
|
/* flush the port
|
|
*/
|
|
|
|
old_data = inw(io_base + CMDR);
|
|
|
|
if (debug_flg & DEBUG_CMD)
|
|
printk("flushed %04X...", old_data);
|
|
|
|
udelay(10);
|
|
}
|
|
|
|
for (i = 0; !timed_out && i < nr_cmd; i++) {
|
|
int count;
|
|
|
|
for (count = 5000; count; count--)
|
|
if (inb(io_base + STATR) & CMD_WE)
|
|
break;
|
|
|
|
if (!count)
|
|
timed_out = 1;
|
|
else
|
|
outw(cmd[i], io_base + CMDR);
|
|
}
|
|
|
|
for (i = 0; !timed_out && i < nr_resp; i++) {
|
|
int count;
|
|
|
|
for (count = 5000; count; count--)
|
|
if (inb(io_base + STATR) & CMD_RF)
|
|
break;
|
|
|
|
if (!count)
|
|
timed_out = 1;
|
|
else
|
|
resp[i] = inw(io_base + CMDR);
|
|
}
|
|
|
|
if (debug_flg & DEBUG_CMD) {
|
|
if (!timed_out) {
|
|
printk("waveartist_cmd: resp=");
|
|
|
|
for (i = 0; i < nr_resp; i++)
|
|
printk("%04X ", resp[i]);
|
|
|
|
printk("\n");
|
|
} else
|
|
printk("waveartist_cmd: timed out\n");
|
|
}
|
|
|
|
return timed_out ? 1 : 0;
|
|
}
|
|
|
|
/*
|
|
* Send one command word
|
|
*/
|
|
static inline int
|
|
waveartist_cmd1(wavnc_info *devc, unsigned int cmd)
|
|
{
|
|
return waveartist_cmd(devc, 1, &cmd, 0, NULL);
|
|
}
|
|
|
|
/*
|
|
* Send one command, receive one word
|
|
*/
|
|
static inline unsigned int
|
|
waveartist_cmd1_r(wavnc_info *devc, unsigned int cmd)
|
|
{
|
|
unsigned int ret;
|
|
|
|
waveartist_cmd(devc, 1, &cmd, 1, &ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Send a double command, receive one
|
|
* word (and throw it away)
|
|
*/
|
|
static inline int
|
|
waveartist_cmd2(wavnc_info *devc, unsigned int cmd, unsigned int arg)
|
|
{
|
|
unsigned int vals[2];
|
|
|
|
vals[0] = cmd;
|
|
vals[1] = arg;
|
|
|
|
return waveartist_cmd(devc, 2, vals, 1, vals);
|
|
}
|
|
|
|
/*
|
|
* Send a triple command
|
|
*/
|
|
static inline int
|
|
waveartist_cmd3(wavnc_info *devc, unsigned int cmd,
|
|
unsigned int arg1, unsigned int arg2)
|
|
{
|
|
unsigned int vals[3];
|
|
|
|
vals[0] = cmd;
|
|
vals[1] = arg1;
|
|
vals[2] = arg2;
|
|
|
|
return waveartist_cmd(devc, 3, vals, 0, NULL);
|
|
}
|
|
|
|
static int
|
|
waveartist_getrev(wavnc_info *devc, char *rev)
|
|
{
|
|
unsigned int temp[2];
|
|
unsigned int cmd = WACMD_GETREV;
|
|
|
|
waveartist_cmd(devc, 1, &cmd, 2, temp);
|
|
|
|
rev[0] = temp[0] >> 8;
|
|
rev[1] = temp[0] & 255;
|
|
rev[2] = '\0';
|
|
|
|
return temp[0];
|
|
}
|
|
|
|
static void waveartist_halt_output(int dev);
|
|
static void waveartist_halt_input(int dev);
|
|
static void waveartist_halt(int dev);
|
|
static void waveartist_trigger(int dev, int state);
|
|
|
|
static int
|
|
waveartist_open(int dev, int mode)
|
|
{
|
|
wavnc_info *devc;
|
|
wavnc_port_info *portc;
|
|
unsigned long flags;
|
|
|
|
if (dev < 0 || dev >= num_audiodevs)
|
|
return -ENXIO;
|
|
|
|
devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
if (portc->open_mode || (devc->open_mode & mode)) {
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
return -EBUSY;
|
|
}
|
|
|
|
devc->audio_mode = 0;
|
|
devc->open_mode |= mode;
|
|
portc->open_mode = mode;
|
|
waveartist_trigger(dev, 0);
|
|
|
|
if (mode & OPEN_READ)
|
|
devc->record_dev = dev;
|
|
if (mode & OPEN_WRITE)
|
|
devc->playback_dev = dev;
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
waveartist_close(int dev)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
waveartist_halt(dev);
|
|
|
|
devc->audio_mode = 0;
|
|
devc->open_mode &= ~portc->open_mode;
|
|
portc->open_mode = 0;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static void
|
|
waveartist_output_block(int dev, unsigned long buf, int __count, int intrflag)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
unsigned long flags;
|
|
unsigned int count = __count;
|
|
|
|
if (debug_flg & DEBUG_OUT)
|
|
printk("waveartist: output block, buf=0x%lx, count=0x%x...\n",
|
|
buf, count);
|
|
/*
|
|
* 16 bit data
|
|
*/
|
|
if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE))
|
|
count >>= 1;
|
|
|
|
if (portc->channels > 1)
|
|
count >>= 1;
|
|
|
|
count -= 1;
|
|
|
|
if (devc->audio_mode & PCM_ENABLE_OUTPUT &&
|
|
audio_devs[dev]->flags & DMA_AUTOMODE &&
|
|
intrflag &&
|
|
count == devc->xfer_count) {
|
|
devc->audio_mode |= PCM_ENABLE_OUTPUT;
|
|
return; /*
|
|
* Auto DMA mode on. No need to react
|
|
*/
|
|
}
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
/*
|
|
* set sample count
|
|
*/
|
|
waveartist_cmd2(devc, WACMD_OUTPUTSIZE, count);
|
|
|
|
devc->xfer_count = count;
|
|
devc->audio_mode |= PCM_ENABLE_OUTPUT;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static void
|
|
waveartist_start_input(int dev, unsigned long buf, int __count, int intrflag)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
unsigned long flags;
|
|
unsigned int count = __count;
|
|
|
|
if (debug_flg & DEBUG_IN)
|
|
printk("waveartist: start input, buf=0x%lx, count=0x%x...\n",
|
|
buf, count);
|
|
|
|
if (portc->audio_format & (AFMT_S16_LE | AFMT_S16_BE)) /* 16 bit data */
|
|
count >>= 1;
|
|
|
|
if (portc->channels > 1)
|
|
count >>= 1;
|
|
|
|
count -= 1;
|
|
|
|
if (devc->audio_mode & PCM_ENABLE_INPUT &&
|
|
audio_devs[dev]->flags & DMA_AUTOMODE &&
|
|
intrflag &&
|
|
count == devc->xfer_count) {
|
|
devc->audio_mode |= PCM_ENABLE_INPUT;
|
|
return; /*
|
|
* Auto DMA mode on. No need to react
|
|
*/
|
|
}
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
/*
|
|
* set sample count
|
|
*/
|
|
waveartist_cmd2(devc, WACMD_INPUTSIZE, count);
|
|
|
|
devc->xfer_count = count;
|
|
devc->audio_mode |= PCM_ENABLE_INPUT;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static int
|
|
waveartist_ioctl(int dev, unsigned int cmd, void __user * arg)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
static unsigned int
|
|
waveartist_get_speed(wavnc_port_info *portc)
|
|
{
|
|
unsigned int speed;
|
|
|
|
/*
|
|
* program the speed, channels, bits
|
|
*/
|
|
if (portc->speed == 8000)
|
|
speed = 0x2E71;
|
|
else if (portc->speed == 11025)
|
|
speed = 0x4000;
|
|
else if (portc->speed == 22050)
|
|
speed = 0x8000;
|
|
else if (portc->speed == 44100)
|
|
speed = 0x0;
|
|
else {
|
|
/*
|
|
* non-standard - just calculate
|
|
*/
|
|
speed = portc->speed << 16;
|
|
|
|
speed = (speed / 44100) & 65535;
|
|
}
|
|
|
|
return speed;
|
|
}
|
|
|
|
static unsigned int
|
|
waveartist_get_bits(wavnc_port_info *portc)
|
|
{
|
|
unsigned int bits;
|
|
|
|
if (portc->audio_format == AFMT_S16_LE)
|
|
bits = 1;
|
|
else if (portc->audio_format == AFMT_S8)
|
|
bits = 0;
|
|
else
|
|
bits = 2; //default AFMT_U8
|
|
|
|
return bits;
|
|
}
|
|
|
|
static int
|
|
waveartist_prepare_for_input(int dev, int bsize, int bcount)
|
|
{
|
|
unsigned long flags;
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
unsigned int speed, bits;
|
|
|
|
if (devc->audio_mode)
|
|
return 0;
|
|
|
|
speed = waveartist_get_speed(portc);
|
|
bits = waveartist_get_bits(portc);
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
|
|
printk(KERN_WARNING "waveartist: error setting the "
|
|
"record format to %d\n", portc->audio_format);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_INPUTCHANNELS, portc->channels))
|
|
printk(KERN_WARNING "waveartist: error setting record "
|
|
"to %d channels\n", portc->channels);
|
|
|
|
/*
|
|
* write cmd SetSampleSpeedTimeConstant
|
|
*/
|
|
if (waveartist_cmd2(devc, WACMD_INPUTSPEED, speed))
|
|
printk(KERN_WARNING "waveartist: error setting the record "
|
|
"speed to %dHz.\n", portc->speed);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_INPUTDMA, 1))
|
|
printk(KERN_WARNING "waveartist: error setting the record "
|
|
"data path to 0x%X\n", 1);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_INPUTFORMAT, bits))
|
|
printk(KERN_WARNING "waveartist: error setting the record "
|
|
"format to %d\n", portc->audio_format);
|
|
|
|
devc->xfer_count = 0;
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
waveartist_halt_input(dev);
|
|
|
|
if (debug_flg & DEBUG_INTR) {
|
|
printk("WA CTLR reg: 0x%02X.\n",
|
|
inb(devc->hw.io_base + CTLR));
|
|
printk("WA STAT reg: 0x%02X.\n",
|
|
inb(devc->hw.io_base + STATR));
|
|
printk("WA IRQS reg: 0x%02X.\n",
|
|
inb(devc->hw.io_base + IRQSTAT));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
waveartist_prepare_for_output(int dev, int bsize, int bcount)
|
|
{
|
|
unsigned long flags;
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
unsigned int speed, bits;
|
|
|
|
/*
|
|
* program the speed, channels, bits
|
|
*/
|
|
speed = waveartist_get_speed(portc);
|
|
bits = waveartist_get_bits(portc);
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed) &&
|
|
waveartist_cmd2(devc, WACMD_OUTPUTSPEED, speed))
|
|
printk(KERN_WARNING "waveartist: error setting the playback "
|
|
"speed to %dHz.\n", portc->speed);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_OUTPUTCHANNELS, portc->channels))
|
|
printk(KERN_WARNING "waveartist: error setting the playback "
|
|
"to %d channels\n", portc->channels);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_OUTPUTDMA, 0))
|
|
printk(KERN_WARNING "waveartist: error setting the playback "
|
|
"data path to 0x%X\n", 0);
|
|
|
|
if (waveartist_cmd2(devc, WACMD_OUTPUTFORMAT, bits))
|
|
printk(KERN_WARNING "waveartist: error setting the playback "
|
|
"format to %d\n", portc->audio_format);
|
|
|
|
devc->xfer_count = 0;
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
waveartist_halt_output(dev);
|
|
|
|
if (debug_flg & DEBUG_INTR) {
|
|
printk("WA CTLR reg: 0x%02X.\n",inb(devc->hw.io_base + CTLR));
|
|
printk("WA STAT reg: 0x%02X.\n",inb(devc->hw.io_base + STATR));
|
|
printk("WA IRQS reg: 0x%02X.\n",inb(devc->hw.io_base + IRQSTAT));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
waveartist_halt(int dev)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
wavnc_info *devc;
|
|
|
|
if (portc->open_mode & OPEN_WRITE)
|
|
waveartist_halt_output(dev);
|
|
|
|
if (portc->open_mode & OPEN_READ)
|
|
waveartist_halt_input(dev);
|
|
|
|
devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
devc->audio_mode = 0;
|
|
}
|
|
|
|
static void
|
|
waveartist_halt_input(int dev)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
/*
|
|
* Stop capture
|
|
*/
|
|
waveartist_cmd1(devc, WACMD_INPUTSTOP);
|
|
|
|
devc->audio_mode &= ~PCM_ENABLE_INPUT;
|
|
|
|
/*
|
|
* Clear interrupt by toggling
|
|
* the IRQ_ACK bit in CTRL
|
|
*/
|
|
if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
|
|
waveartist_iack(devc);
|
|
|
|
// devc->audio_mode &= ~PCM_ENABLE_INPUT;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static void
|
|
waveartist_halt_output(int dev)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
waveartist_cmd1(devc, WACMD_OUTPUTSTOP);
|
|
|
|
devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
|
|
|
|
/*
|
|
* Clear interrupt by toggling
|
|
* the IRQ_ACK bit in CTRL
|
|
*/
|
|
if (inb(devc->hw.io_base + STATR) & IRQ_REQ)
|
|
waveartist_iack(devc);
|
|
|
|
// devc->audio_mode &= ~PCM_ENABLE_OUTPUT;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static void
|
|
waveartist_trigger(int dev, int state)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *) audio_devs[dev]->devc;
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
unsigned long flags;
|
|
|
|
if (debug_flg & DEBUG_TRIGGER) {
|
|
printk("wavnc: audio trigger ");
|
|
if (state & PCM_ENABLE_INPUT)
|
|
printk("in ");
|
|
if (state & PCM_ENABLE_OUTPUT)
|
|
printk("out");
|
|
printk("\n");
|
|
}
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
state &= devc->audio_mode;
|
|
|
|
if (portc->open_mode & OPEN_READ &&
|
|
state & PCM_ENABLE_INPUT)
|
|
/*
|
|
* enable ADC Data Transfer to PC
|
|
*/
|
|
waveartist_cmd1(devc, WACMD_INPUTSTART);
|
|
|
|
if (portc->open_mode & OPEN_WRITE &&
|
|
state & PCM_ENABLE_OUTPUT)
|
|
/*
|
|
* enable DAC data transfer from PC
|
|
*/
|
|
waveartist_cmd1(devc, WACMD_OUTPUTSTART);
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
}
|
|
|
|
static int
|
|
waveartist_set_speed(int dev, int arg)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
|
|
if (arg <= 0)
|
|
return portc->speed;
|
|
|
|
if (arg < 5000)
|
|
arg = 5000;
|
|
if (arg > 44100)
|
|
arg = 44100;
|
|
|
|
portc->speed = arg;
|
|
return portc->speed;
|
|
|
|
}
|
|
|
|
static short
|
|
waveartist_set_channels(int dev, short arg)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
|
|
if (arg != 1 && arg != 2)
|
|
return portc->channels;
|
|
|
|
portc->channels = arg;
|
|
return arg;
|
|
}
|
|
|
|
static unsigned int
|
|
waveartist_set_bits(int dev, unsigned int arg)
|
|
{
|
|
wavnc_port_info *portc = (wavnc_port_info *) audio_devs[dev]->portc;
|
|
|
|
if (arg == 0)
|
|
return portc->audio_format;
|
|
|
|
if ((arg != AFMT_U8) && (arg != AFMT_S16_LE) && (arg != AFMT_S8))
|
|
arg = AFMT_U8;
|
|
|
|
portc->audio_format = arg;
|
|
|
|
return arg;
|
|
}
|
|
|
|
static struct audio_driver waveartist_audio_driver = {
|
|
.owner = THIS_MODULE,
|
|
.open = waveartist_open,
|
|
.close = waveartist_close,
|
|
.output_block = waveartist_output_block,
|
|
.start_input = waveartist_start_input,
|
|
.ioctl = waveartist_ioctl,
|
|
.prepare_for_input = waveartist_prepare_for_input,
|
|
.prepare_for_output = waveartist_prepare_for_output,
|
|
.halt_io = waveartist_halt,
|
|
.halt_input = waveartist_halt_input,
|
|
.halt_output = waveartist_halt_output,
|
|
.trigger = waveartist_trigger,
|
|
.set_speed = waveartist_set_speed,
|
|
.set_bits = waveartist_set_bits,
|
|
.set_channels = waveartist_set_channels
|
|
};
|
|
|
|
|
|
static irqreturn_t
|
|
waveartist_intr(int irq, void *dev_id)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *)dev_id;
|
|
int irqstatus, status;
|
|
|
|
spin_lock(&waveartist_lock);
|
|
irqstatus = inb(devc->hw.io_base + IRQSTAT);
|
|
status = inb(devc->hw.io_base + STATR);
|
|
|
|
if (debug_flg & DEBUG_INTR)
|
|
printk("waveartist_intr: stat=%02x, irqstat=%02x\n",
|
|
status, irqstatus);
|
|
|
|
if (status & IRQ_REQ) /* Clear interrupt */
|
|
waveartist_iack(devc);
|
|
else
|
|
printk(KERN_WARNING "waveartist: unexpected interrupt\n");
|
|
|
|
if (irqstatus & 0x01) {
|
|
int temp = 1;
|
|
|
|
/* PCM buffer done
|
|
*/
|
|
if ((status & DMA0) && (devc->audio_mode & PCM_ENABLE_OUTPUT)) {
|
|
DMAbuf_outputintr(devc->playback_dev, 1);
|
|
temp = 0;
|
|
}
|
|
if ((status & DMA1) && (devc->audio_mode & PCM_ENABLE_INPUT)) {
|
|
DMAbuf_inputintr(devc->record_dev);
|
|
temp = 0;
|
|
}
|
|
if (temp) //default:
|
|
printk(KERN_WARNING "waveartist: Unknown interrupt\n");
|
|
}
|
|
if (irqstatus & 0x2)
|
|
// We do not use SB mode natively...
|
|
printk(KERN_WARNING "waveartist: Unexpected SB interrupt...\n");
|
|
spin_unlock(&waveartist_lock);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Mixer stuff
|
|
*/
|
|
struct mix_ent {
|
|
unsigned char reg_l;
|
|
unsigned char reg_r;
|
|
unsigned char shift;
|
|
unsigned char max;
|
|
};
|
|
|
|
static const struct mix_ent mix_devs[SOUND_MIXER_NRDEVICES] = {
|
|
{ 2, 6, 1, 7 }, /* SOUND_MIXER_VOLUME */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_BASS */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_TREBLE */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_SYNTH */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_PCM */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_SPEAKER */
|
|
{ 0, 4, 6, 31 }, /* SOUND_MIXER_LINE */
|
|
{ 2, 6, 4, 3 }, /* SOUND_MIXER_MIC */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_CD */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_IMIX */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_ALTPCM */
|
|
#if 0
|
|
{ 3, 7, 0, 10 }, /* SOUND_MIXER_RECLEV */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_IGAIN */
|
|
#else
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_RECLEV */
|
|
{ 3, 7, 0, 7 }, /* SOUND_MIXER_IGAIN */
|
|
#endif
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_OGAIN */
|
|
{ 0, 4, 1, 31 }, /* SOUND_MIXER_LINE1 */
|
|
{ 1, 5, 6, 31 }, /* SOUND_MIXER_LINE2 */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_LINE3 */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL1 */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL2 */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_DIGITAL3 */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_PHONEIN */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_PHONEOUT */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_VIDEO */
|
|
{ 0, 0, 0, 0 }, /* SOUND_MIXER_RADIO */
|
|
{ 0, 0, 0, 0 } /* SOUND_MIXER_MONITOR */
|
|
};
|
|
|
|
static void
|
|
waveartist_mixer_update(wavnc_info *devc, int whichDev)
|
|
{
|
|
unsigned int lev_left, lev_right;
|
|
|
|
lev_left = devc->levels[whichDev] & 0xff;
|
|
lev_right = devc->levels[whichDev] >> 8;
|
|
|
|
if (lev_left > 100)
|
|
lev_left = 100;
|
|
if (lev_right > 100)
|
|
lev_right = 100;
|
|
|
|
#define SCALE(lev,max) ((lev) * (max) / 100)
|
|
|
|
if (machine_is_netwinder() && whichDev == SOUND_MIXER_PHONEOUT)
|
|
whichDev = SOUND_MIXER_VOLUME;
|
|
|
|
if (mix_devs[whichDev].reg_l || mix_devs[whichDev].reg_r) {
|
|
const struct mix_ent *mix = mix_devs + whichDev;
|
|
unsigned int mask, left, right;
|
|
|
|
mask = mix->max << mix->shift;
|
|
lev_left = SCALE(lev_left, mix->max) << mix->shift;
|
|
lev_right = SCALE(lev_right, mix->max) << mix->shift;
|
|
|
|
/* read left setting */
|
|
left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
|
|
mix->reg_l << 8);
|
|
|
|
/* read right setting */
|
|
right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
|
|
mix->reg_r << 8);
|
|
|
|
left = (left & ~mask) | (lev_left & mask);
|
|
right = (right & ~mask) | (lev_right & mask);
|
|
|
|
/* write left,right back */
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
|
|
} else {
|
|
switch(whichDev) {
|
|
case SOUND_MIXER_PCM:
|
|
waveartist_cmd3(devc, WACMD_SET_LEVEL,
|
|
SCALE(lev_left, 32767),
|
|
SCALE(lev_right, 32767));
|
|
break;
|
|
|
|
case SOUND_MIXER_SYNTH:
|
|
waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL,
|
|
SCALE(lev_left, 32767),
|
|
SCALE(lev_right, 32767));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the ADC MUX to the specified values. We do NOT do any
|
|
* checking of the values passed, since we assume that the
|
|
* relevant *_select_input function has done that for us.
|
|
*/
|
|
static void
|
|
waveartist_set_adc_mux(wavnc_info *devc, char left_dev, char right_dev)
|
|
{
|
|
unsigned int reg_08, reg_09;
|
|
|
|
reg_08 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0800);
|
|
reg_09 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0900);
|
|
|
|
reg_08 = (reg_08 & ~0x3f) | right_dev << 3 | left_dev;
|
|
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, reg_08, reg_09);
|
|
}
|
|
|
|
/*
|
|
* Decode a recording mask into a mixer selection as follows:
|
|
*
|
|
* OSS Source WA Source Actual source
|
|
* SOUND_MASK_IMIX Mixer Mixer output (same as AD1848)
|
|
* SOUND_MASK_LINE Line Line in
|
|
* SOUND_MASK_LINE1 Aux 1 Aux 1 in
|
|
* SOUND_MASK_LINE2 Aux 2 Aux 2 in
|
|
* SOUND_MASK_MIC Mic Microphone
|
|
*/
|
|
static unsigned int
|
|
waveartist_select_input(wavnc_info *devc, unsigned int recmask,
|
|
unsigned char *dev_l, unsigned char *dev_r)
|
|
{
|
|
unsigned int recdev = ADC_MUX_NONE;
|
|
|
|
if (recmask & SOUND_MASK_IMIX) {
|
|
recmask = SOUND_MASK_IMIX;
|
|
recdev = ADC_MUX_MIXER;
|
|
} else if (recmask & SOUND_MASK_LINE2) {
|
|
recmask = SOUND_MASK_LINE2;
|
|
recdev = ADC_MUX_AUX2;
|
|
} else if (recmask & SOUND_MASK_LINE1) {
|
|
recmask = SOUND_MASK_LINE1;
|
|
recdev = ADC_MUX_AUX1;
|
|
} else if (recmask & SOUND_MASK_LINE) {
|
|
recmask = SOUND_MASK_LINE;
|
|
recdev = ADC_MUX_LINE;
|
|
} else if (recmask & SOUND_MASK_MIC) {
|
|
recmask = SOUND_MASK_MIC;
|
|
recdev = ADC_MUX_MIC;
|
|
}
|
|
|
|
*dev_l = *dev_r = recdev;
|
|
|
|
return recmask;
|
|
}
|
|
|
|
static int
|
|
waveartist_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
|
|
unsigned char lev_r)
|
|
{
|
|
switch (dev) {
|
|
case SOUND_MIXER_VOLUME:
|
|
case SOUND_MIXER_SYNTH:
|
|
case SOUND_MIXER_PCM:
|
|
case SOUND_MIXER_LINE:
|
|
case SOUND_MIXER_MIC:
|
|
case SOUND_MIXER_IGAIN:
|
|
case SOUND_MIXER_LINE1:
|
|
case SOUND_MIXER_LINE2:
|
|
devc->levels[dev] = lev_l | lev_r << 8;
|
|
break;
|
|
|
|
case SOUND_MIXER_IMIX:
|
|
break;
|
|
|
|
default:
|
|
dev = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return dev;
|
|
}
|
|
|
|
static int waveartist_get_mixer(wavnc_info *devc, int dev)
|
|
{
|
|
return devc->levels[dev];
|
|
}
|
|
|
|
static const struct waveartist_mixer_info waveartist_mixer = {
|
|
.supported_devs = SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN,
|
|
.recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
|
|
SOUND_MASK_LINE1 | SOUND_MASK_LINE2 |
|
|
SOUND_MASK_IMIX,
|
|
.stereo_devs = (SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN) & ~
|
|
(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX),
|
|
.select_input = waveartist_select_input,
|
|
.decode_mixer = waveartist_decode_mixer,
|
|
.get_mixer = waveartist_get_mixer,
|
|
};
|
|
|
|
static void
|
|
waveartist_set_recmask(wavnc_info *devc, unsigned int recmask)
|
|
{
|
|
unsigned char dev_l, dev_r;
|
|
|
|
recmask &= devc->mix->recording_devs;
|
|
|
|
/*
|
|
* If more than one recording device selected,
|
|
* disable the device that is currently in use.
|
|
*/
|
|
if (hweight32(recmask) > 1)
|
|
recmask &= ~devc->recmask;
|
|
|
|
/*
|
|
* Translate the recording device mask into
|
|
* the ADC multiplexer settings.
|
|
*/
|
|
devc->recmask = devc->mix->select_input(devc, recmask,
|
|
&dev_l, &dev_r);
|
|
|
|
waveartist_set_adc_mux(devc, dev_l, dev_r);
|
|
}
|
|
|
|
static int
|
|
waveartist_set_mixer(wavnc_info *devc, int dev, unsigned int level)
|
|
{
|
|
unsigned int lev_left = level & 0x00ff;
|
|
unsigned int lev_right = (level & 0xff00) >> 8;
|
|
|
|
if (lev_left > 100)
|
|
lev_left = 100;
|
|
if (lev_right > 100)
|
|
lev_right = 100;
|
|
|
|
/*
|
|
* Mono devices have their right volume forced to their
|
|
* left volume. (from ALSA driver OSS emulation).
|
|
*/
|
|
if (!(devc->mix->stereo_devs & (1 << dev)))
|
|
lev_right = lev_left;
|
|
|
|
dev = devc->mix->decode_mixer(devc, dev, lev_left, lev_right);
|
|
|
|
if (dev >= 0)
|
|
waveartist_mixer_update(devc, dev);
|
|
|
|
return dev < 0 ? dev : 0;
|
|
}
|
|
|
|
static int
|
|
waveartist_mixer_ioctl(int dev, unsigned int cmd, void __user * arg)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
|
|
int ret = 0, val, nr;
|
|
|
|
/*
|
|
* All SOUND_MIXER_* ioctls use type 'M'
|
|
*/
|
|
if (((cmd >> 8) & 255) != 'M')
|
|
return -ENOIOCTLCMD;
|
|
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
if (machine_is_netwinder()) {
|
|
ret = vnc_private_ioctl(dev, cmd, arg);
|
|
if (ret != -ENOIOCTLCMD)
|
|
return ret;
|
|
else
|
|
ret = 0;
|
|
}
|
|
#endif
|
|
|
|
nr = cmd & 0xff;
|
|
|
|
if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
|
|
if (get_user(val, (int __user *)arg))
|
|
return -EFAULT;
|
|
|
|
switch (nr) {
|
|
case SOUND_MIXER_RECSRC:
|
|
waveartist_set_recmask(devc, val);
|
|
break;
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
if (nr < SOUND_MIXER_NRDEVICES &&
|
|
devc->mix->supported_devs & (1 << nr))
|
|
ret = waveartist_set_mixer(devc, nr, val);
|
|
}
|
|
}
|
|
|
|
if (ret == 0 && _SIOC_DIR(cmd) & _SIOC_READ) {
|
|
ret = -EINVAL;
|
|
|
|
switch (nr) {
|
|
case SOUND_MIXER_RECSRC:
|
|
ret = devc->recmask;
|
|
break;
|
|
|
|
case SOUND_MIXER_DEVMASK:
|
|
ret = devc->mix->supported_devs;
|
|
break;
|
|
|
|
case SOUND_MIXER_STEREODEVS:
|
|
ret = devc->mix->stereo_devs;
|
|
break;
|
|
|
|
case SOUND_MIXER_RECMASK:
|
|
ret = devc->mix->recording_devs;
|
|
break;
|
|
|
|
case SOUND_MIXER_CAPS:
|
|
ret = SOUND_CAP_EXCL_INPUT;
|
|
break;
|
|
|
|
default:
|
|
if (nr < SOUND_MIXER_NRDEVICES)
|
|
ret = devc->mix->get_mixer(devc, nr);
|
|
break;
|
|
}
|
|
|
|
if (ret >= 0)
|
|
ret = put_user(ret, (int __user *)arg) ? -EFAULT : 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct mixer_operations waveartist_mixer_operations =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
.id = "WaveArtist",
|
|
.name = "WaveArtist",
|
|
.ioctl = waveartist_mixer_ioctl
|
|
};
|
|
|
|
static void
|
|
waveartist_mixer_reset(wavnc_info *devc)
|
|
{
|
|
int i;
|
|
|
|
if (debug_flg & DEBUG_MIXER)
|
|
printk("%s: mixer_reset\n", devc->hw.name);
|
|
|
|
/*
|
|
* reset mixer cmd
|
|
*/
|
|
waveartist_cmd1(devc, WACMD_RST_MIXER);
|
|
|
|
/*
|
|
* set input for ADC to come from 'quiet'
|
|
* turn on default modes
|
|
*/
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, 0x9800, 0xa836);
|
|
|
|
/*
|
|
* set mixer input select to none, RX filter gains 0 dB
|
|
*/
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, 0x4c00, 0x8c00);
|
|
|
|
/*
|
|
* set bit 0 reg 2 to 1 - unmute MonoOut
|
|
*/
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, 0x2801, 0x6800);
|
|
|
|
/* set default input device = internal mic
|
|
* current recording device = none
|
|
*/
|
|
waveartist_set_recmask(devc, 0);
|
|
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
|
|
waveartist_mixer_update(devc, i);
|
|
}
|
|
|
|
static int __init waveartist_init(wavnc_info *devc)
|
|
{
|
|
wavnc_port_info *portc;
|
|
char rev[3], dev_name[64];
|
|
int my_dev;
|
|
|
|
if (waveartist_reset(devc))
|
|
return -ENODEV;
|
|
|
|
sprintf(dev_name, "%s (%s", devc->hw.name, devc->chip_name);
|
|
|
|
if (waveartist_getrev(devc, rev)) {
|
|
strcat(dev_name, " rev. ");
|
|
strcat(dev_name, rev);
|
|
}
|
|
strcat(dev_name, ")");
|
|
|
|
conf_printf2(dev_name, devc->hw.io_base, devc->hw.irq,
|
|
devc->hw.dma, devc->hw.dma2);
|
|
|
|
portc = (wavnc_port_info *)kmalloc(sizeof(wavnc_port_info), GFP_KERNEL);
|
|
if (portc == NULL)
|
|
goto nomem;
|
|
|
|
memset(portc, 0, sizeof(wavnc_port_info));
|
|
|
|
my_dev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, dev_name,
|
|
&waveartist_audio_driver, sizeof(struct audio_driver),
|
|
devc->audio_flags, AFMT_U8 | AFMT_S16_LE | AFMT_S8,
|
|
devc, devc->hw.dma, devc->hw.dma2);
|
|
|
|
if (my_dev < 0)
|
|
goto free;
|
|
|
|
audio_devs[my_dev]->portc = portc;
|
|
|
|
waveartist_mixer_reset(devc);
|
|
|
|
/*
|
|
* clear any pending interrupt
|
|
*/
|
|
waveartist_iack(devc);
|
|
|
|
if (request_irq(devc->hw.irq, waveartist_intr, 0, devc->hw.name, devc) < 0) {
|
|
printk(KERN_ERR "%s: IRQ %d in use\n",
|
|
devc->hw.name, devc->hw.irq);
|
|
goto uninstall;
|
|
}
|
|
|
|
if (sound_alloc_dma(devc->hw.dma, devc->hw.name)) {
|
|
printk(KERN_ERR "%s: Can't allocate DMA%d\n",
|
|
devc->hw.name, devc->hw.dma);
|
|
goto uninstall_irq;
|
|
}
|
|
|
|
if (devc->hw.dma != devc->hw.dma2 && devc->hw.dma2 != NO_DMA)
|
|
if (sound_alloc_dma(devc->hw.dma2, devc->hw.name)) {
|
|
printk(KERN_ERR "%s: can't allocate DMA%d\n",
|
|
devc->hw.name, devc->hw.dma2);
|
|
goto uninstall_dma;
|
|
}
|
|
|
|
waveartist_set_ctlr(&devc->hw, 0, DMA1_IE | DMA0_IE);
|
|
|
|
audio_devs[my_dev]->mixer_dev =
|
|
sound_install_mixer(MIXER_DRIVER_VERSION,
|
|
dev_name,
|
|
&waveartist_mixer_operations,
|
|
sizeof(struct mixer_operations),
|
|
devc);
|
|
|
|
return my_dev;
|
|
|
|
uninstall_dma:
|
|
sound_free_dma(devc->hw.dma);
|
|
|
|
uninstall_irq:
|
|
free_irq(devc->hw.irq, devc);
|
|
|
|
uninstall:
|
|
sound_unload_audiodev(my_dev);
|
|
|
|
free:
|
|
kfree(portc);
|
|
|
|
nomem:
|
|
return -1;
|
|
}
|
|
|
|
static int __init probe_waveartist(struct address_info *hw_config)
|
|
{
|
|
wavnc_info *devc = &adev_info[nr_waveartist_devs];
|
|
|
|
if (nr_waveartist_devs >= MAX_AUDIO_DEV) {
|
|
printk(KERN_WARNING "waveartist: too many audio devices\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!request_region(hw_config->io_base, 15, hw_config->name)) {
|
|
printk(KERN_WARNING "WaveArtist: I/O port conflict\n");
|
|
return 0;
|
|
}
|
|
|
|
if (hw_config->irq > 15 || hw_config->irq < 0) {
|
|
release_region(hw_config->io_base, 15);
|
|
printk(KERN_WARNING "WaveArtist: Bad IRQ %d\n",
|
|
hw_config->irq);
|
|
return 0;
|
|
}
|
|
|
|
if (hw_config->dma != 3) {
|
|
release_region(hw_config->io_base, 15);
|
|
printk(KERN_WARNING "WaveArtist: Bad DMA %d\n",
|
|
hw_config->dma);
|
|
return 0;
|
|
}
|
|
|
|
hw_config->name = "WaveArtist";
|
|
devc->hw = *hw_config;
|
|
devc->open_mode = 0;
|
|
devc->chip_name = "RWA-010";
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void __init
|
|
attach_waveartist(struct address_info *hw, const struct waveartist_mixer_info *mix)
|
|
{
|
|
wavnc_info *devc = &adev_info[nr_waveartist_devs];
|
|
|
|
/*
|
|
* NOTE! If irq < 0, there is another driver which has allocated the
|
|
* IRQ so that this driver doesn't need to allocate/deallocate it.
|
|
* The actually used IRQ is ABS(irq).
|
|
*/
|
|
devc->hw = *hw;
|
|
devc->hw.irq = (hw->irq > 0) ? hw->irq : 0;
|
|
devc->open_mode = 0;
|
|
devc->playback_dev = 0;
|
|
devc->record_dev = 0;
|
|
devc->audio_flags = DMA_AUTOMODE;
|
|
devc->levels = levels;
|
|
|
|
if (hw->dma != hw->dma2 && hw->dma2 != NO_DMA)
|
|
devc->audio_flags |= DMA_DUPLEX;
|
|
|
|
devc->mix = mix;
|
|
devc->dev_no = waveartist_init(devc);
|
|
|
|
if (devc->dev_no < 0)
|
|
release_region(hw->io_base, 15);
|
|
else {
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
if (machine_is_netwinder()) {
|
|
init_timer(&vnc_timer);
|
|
vnc_timer.function = vnc_slider_tick;
|
|
vnc_timer.expires = jiffies;
|
|
vnc_timer.data = nr_waveartist_devs;
|
|
add_timer(&vnc_timer);
|
|
|
|
vnc_configure_mixer(devc, 0);
|
|
|
|
devc->no_autoselect = 1;
|
|
}
|
|
#endif
|
|
nr_waveartist_devs += 1;
|
|
}
|
|
}
|
|
|
|
static void __exit unload_waveartist(struct address_info *hw)
|
|
{
|
|
wavnc_info *devc = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < nr_waveartist_devs; i++)
|
|
if (hw->io_base == adev_info[i].hw.io_base) {
|
|
devc = adev_info + i;
|
|
break;
|
|
}
|
|
|
|
if (devc != NULL) {
|
|
int mixer;
|
|
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
if (machine_is_netwinder())
|
|
del_timer(&vnc_timer);
|
|
#endif
|
|
|
|
release_region(devc->hw.io_base, 15);
|
|
|
|
waveartist_set_ctlr(&devc->hw, DMA1_IE|DMA0_IE, 0);
|
|
|
|
if (devc->hw.irq >= 0)
|
|
free_irq(devc->hw.irq, devc);
|
|
|
|
sound_free_dma(devc->hw.dma);
|
|
|
|
if (devc->hw.dma != devc->hw.dma2 &&
|
|
devc->hw.dma2 != NO_DMA)
|
|
sound_free_dma(devc->hw.dma2);
|
|
|
|
mixer = audio_devs[devc->dev_no]->mixer_dev;
|
|
|
|
if (mixer >= 0)
|
|
sound_unload_mixerdev(mixer);
|
|
|
|
if (devc->dev_no >= 0)
|
|
sound_unload_audiodev(devc->dev_no);
|
|
|
|
nr_waveartist_devs -= 1;
|
|
|
|
for (; i < nr_waveartist_devs; i++)
|
|
adev_info[i] = adev_info[i + 1];
|
|
} else
|
|
printk(KERN_WARNING "waveartist: can't find device "
|
|
"to unload\n");
|
|
}
|
|
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
|
|
/*
|
|
* Rebel.com Netwinder specifics...
|
|
*/
|
|
|
|
#include <asm/hardware/dec21285.h>
|
|
|
|
#define VNC_TIMER_PERIOD (HZ/4) //check slider 4 times/sec
|
|
|
|
#define MIXER_PRIVATE3_RESET 0x53570000
|
|
#define MIXER_PRIVATE3_READ 0x53570001
|
|
#define MIXER_PRIVATE3_WRITE 0x53570002
|
|
|
|
#define VNC_MUTE_INTERNAL_SPKR 0x01 //the sw mute on/off control bit
|
|
#define VNC_MUTE_LINE_OUT 0x10
|
|
#define VNC_PHONE_DETECT 0x20
|
|
#define VNC_HANDSET_DETECT 0x40
|
|
#define VNC_DISABLE_AUTOSWITCH 0x80
|
|
|
|
extern spinlock_t gpio_lock;
|
|
|
|
static inline void
|
|
vnc_mute_spkr(wavnc_info *devc)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&gpio_lock, flags);
|
|
cpld_modify(CPLD_UNMUTE, devc->spkr_mute_state ? 0 : CPLD_UNMUTE);
|
|
spin_unlock_irqrestore(&gpio_lock, flags);
|
|
}
|
|
|
|
static void
|
|
vnc_mute_lout(wavnc_info *devc)
|
|
{
|
|
unsigned int left, right;
|
|
|
|
left = waveartist_cmd1_r(devc, WACMD_GET_LEVEL);
|
|
right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x400);
|
|
|
|
if (devc->line_mute_state) {
|
|
left &= ~1;
|
|
right &= ~1;
|
|
} else {
|
|
left |= 1;
|
|
right |= 1;
|
|
}
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
|
|
|
|
}
|
|
|
|
static int
|
|
vnc_volume_slider(wavnc_info *devc)
|
|
{
|
|
static signed int old_slider_volume;
|
|
unsigned long flags;
|
|
signed int volume = 255;
|
|
|
|
*CSR_TIMER1_LOAD = 0x00ffffff;
|
|
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
outb(0xFF, 0x201);
|
|
*CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV1;
|
|
|
|
while (volume && (inb(0x201) & 0x01))
|
|
volume--;
|
|
|
|
*CSR_TIMER1_CNTL = 0;
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock,flags);
|
|
|
|
volume = 0x00ffffff - *CSR_TIMER1_VALUE;
|
|
|
|
|
|
#ifndef REVERSE
|
|
volume = 150 - (volume >> 5);
|
|
#else
|
|
volume = (volume >> 6) - 25;
|
|
#endif
|
|
|
|
if (volume < 0)
|
|
volume = 0;
|
|
|
|
if (volume > 100)
|
|
volume = 100;
|
|
|
|
/*
|
|
* slider quite often reads +-8, so debounce this random noise
|
|
*/
|
|
if (abs(volume - old_slider_volume) > 7) {
|
|
old_slider_volume = volume;
|
|
|
|
if (debug_flg & DEBUG_MIXER)
|
|
printk(KERN_DEBUG "Slider volume: %d.\n", volume);
|
|
}
|
|
|
|
return old_slider_volume;
|
|
}
|
|
|
|
/*
|
|
* Decode a recording mask into a mixer selection on the NetWinder
|
|
* as follows:
|
|
*
|
|
* OSS Source WA Source Actual source
|
|
* SOUND_MASK_IMIX Mixer Mixer output (same as AD1848)
|
|
* SOUND_MASK_LINE Line Line in
|
|
* SOUND_MASK_LINE1 Left Mic Handset
|
|
* SOUND_MASK_PHONEIN Left Aux Telephone microphone
|
|
* SOUND_MASK_MIC Right Mic Builtin microphone
|
|
*/
|
|
static unsigned int
|
|
netwinder_select_input(wavnc_info *devc, unsigned int recmask,
|
|
unsigned char *dev_l, unsigned char *dev_r)
|
|
{
|
|
unsigned int recdev_l = ADC_MUX_NONE, recdev_r = ADC_MUX_NONE;
|
|
|
|
if (recmask & SOUND_MASK_IMIX) {
|
|
recmask = SOUND_MASK_IMIX;
|
|
recdev_l = ADC_MUX_MIXER;
|
|
recdev_r = ADC_MUX_MIXER;
|
|
} else if (recmask & SOUND_MASK_LINE) {
|
|
recmask = SOUND_MASK_LINE;
|
|
recdev_l = ADC_MUX_LINE;
|
|
recdev_r = ADC_MUX_LINE;
|
|
} else if (recmask & SOUND_MASK_LINE1) {
|
|
recmask = SOUND_MASK_LINE1;
|
|
waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
|
|
recdev_l = ADC_MUX_MIC;
|
|
recdev_r = ADC_MUX_NONE;
|
|
} else if (recmask & SOUND_MASK_PHONEIN) {
|
|
recmask = SOUND_MASK_PHONEIN;
|
|
waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
|
|
recdev_l = ADC_MUX_AUX1;
|
|
recdev_r = ADC_MUX_NONE;
|
|
} else if (recmask & SOUND_MASK_MIC) {
|
|
recmask = SOUND_MASK_MIC;
|
|
waveartist_cmd1(devc, WACMD_SET_MONO | 0x100); /* right */
|
|
recdev_l = ADC_MUX_NONE;
|
|
recdev_r = ADC_MUX_MIC;
|
|
}
|
|
|
|
*dev_l = recdev_l;
|
|
*dev_r = recdev_r;
|
|
|
|
return recmask;
|
|
}
|
|
|
|
static int
|
|
netwinder_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
|
|
unsigned char lev_r)
|
|
{
|
|
switch (dev) {
|
|
case SOUND_MIXER_VOLUME:
|
|
case SOUND_MIXER_SYNTH:
|
|
case SOUND_MIXER_PCM:
|
|
case SOUND_MIXER_LINE:
|
|
case SOUND_MIXER_IGAIN:
|
|
devc->levels[dev] = lev_l | lev_r << 8;
|
|
break;
|
|
|
|
case SOUND_MIXER_MIC: /* right mic only */
|
|
devc->levels[SOUND_MIXER_MIC] &= 0xff;
|
|
devc->levels[SOUND_MIXER_MIC] |= lev_l << 8;
|
|
break;
|
|
|
|
case SOUND_MIXER_LINE1: /* left mic only */
|
|
devc->levels[SOUND_MIXER_MIC] &= 0xff00;
|
|
devc->levels[SOUND_MIXER_MIC] |= lev_l;
|
|
dev = SOUND_MIXER_MIC;
|
|
break;
|
|
|
|
case SOUND_MIXER_PHONEIN: /* left aux only */
|
|
devc->levels[SOUND_MIXER_LINE1] = lev_l;
|
|
dev = SOUND_MIXER_LINE1;
|
|
break;
|
|
|
|
case SOUND_MIXER_IMIX:
|
|
case SOUND_MIXER_PHONEOUT:
|
|
break;
|
|
|
|
default:
|
|
dev = -EINVAL;
|
|
break;
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
static int netwinder_get_mixer(wavnc_info *devc, int dev)
|
|
{
|
|
int levels;
|
|
|
|
switch (dev) {
|
|
case SOUND_MIXER_VOLUME:
|
|
case SOUND_MIXER_SYNTH:
|
|
case SOUND_MIXER_PCM:
|
|
case SOUND_MIXER_LINE:
|
|
case SOUND_MIXER_IGAIN:
|
|
levels = devc->levels[dev];
|
|
break;
|
|
|
|
case SOUND_MIXER_MIC: /* builtin mic: right mic only */
|
|
levels = devc->levels[SOUND_MIXER_MIC] >> 8;
|
|
levels |= levels << 8;
|
|
break;
|
|
|
|
case SOUND_MIXER_LINE1: /* handset mic: left mic only */
|
|
levels = devc->levels[SOUND_MIXER_MIC] & 0xff;
|
|
levels |= levels << 8;
|
|
break;
|
|
|
|
case SOUND_MIXER_PHONEIN: /* phone mic: left aux1 only */
|
|
levels = devc->levels[SOUND_MIXER_LINE1] & 0xff;
|
|
levels |= levels << 8;
|
|
break;
|
|
|
|
default:
|
|
levels = 0;
|
|
}
|
|
|
|
return levels;
|
|
}
|
|
|
|
/*
|
|
* Waveartist specific mixer information.
|
|
*/
|
|
static const struct waveartist_mixer_info netwinder_mixer = {
|
|
.supported_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
|
|
SOUND_MASK_PCM | SOUND_MASK_SPEAKER |
|
|
SOUND_MASK_LINE | SOUND_MASK_MIC |
|
|
SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
|
|
SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT|
|
|
SOUND_MASK_IGAIN,
|
|
|
|
.recording_devs = SOUND_MASK_LINE | SOUND_MASK_MIC |
|
|
SOUND_MASK_IMIX | SOUND_MASK_LINE1 |
|
|
SOUND_MASK_PHONEIN,
|
|
|
|
.stereo_devs = SOUND_MASK_VOLUME | SOUND_MASK_SYNTH |
|
|
SOUND_MASK_PCM | SOUND_MASK_LINE |
|
|
SOUND_MASK_IMIX | SOUND_MASK_IGAIN,
|
|
|
|
.select_input = netwinder_select_input,
|
|
.decode_mixer = netwinder_decode_mixer,
|
|
.get_mixer = netwinder_get_mixer,
|
|
};
|
|
|
|
static void
|
|
vnc_configure_mixer(wavnc_info *devc, unsigned int recmask)
|
|
{
|
|
if (!devc->no_autoselect) {
|
|
if (devc->handset_detect) {
|
|
recmask = SOUND_MASK_LINE1;
|
|
devc->spkr_mute_state = devc->line_mute_state = 1;
|
|
} else if (devc->telephone_detect) {
|
|
recmask = SOUND_MASK_PHONEIN;
|
|
devc->spkr_mute_state = devc->line_mute_state = 1;
|
|
} else {
|
|
/* unless someone has asked for LINE-IN,
|
|
* we default to MIC
|
|
*/
|
|
if ((devc->recmask & SOUND_MASK_LINE) == 0)
|
|
devc->recmask = SOUND_MASK_MIC;
|
|
devc->spkr_mute_state = devc->line_mute_state = 0;
|
|
}
|
|
vnc_mute_spkr(devc);
|
|
vnc_mute_lout(devc);
|
|
|
|
if (recmask != devc->recmask)
|
|
waveartist_set_recmask(devc, recmask);
|
|
}
|
|
}
|
|
|
|
static int
|
|
vnc_slider(wavnc_info *devc)
|
|
{
|
|
signed int slider_volume;
|
|
unsigned int temp, old_hs, old_td;
|
|
|
|
/*
|
|
* read the "buttons" state.
|
|
* Bit 4 = 0 means handset present
|
|
* Bit 5 = 1 means phone offhook
|
|
*/
|
|
temp = inb(0x201);
|
|
|
|
old_hs = devc->handset_detect;
|
|
old_td = devc->telephone_detect;
|
|
|
|
devc->handset_detect = !(temp & 0x10);
|
|
devc->telephone_detect = !!(temp & 0x20);
|
|
|
|
if (!devc->no_autoselect &&
|
|
(old_hs != devc->handset_detect ||
|
|
old_td != devc->telephone_detect))
|
|
vnc_configure_mixer(devc, devc->recmask);
|
|
|
|
slider_volume = vnc_volume_slider(devc);
|
|
|
|
/*
|
|
* If we're using software controlled volume, and
|
|
* the slider moves by more than 20%, then we
|
|
* switch back to slider controlled volume.
|
|
*/
|
|
if (abs(devc->slider_vol - slider_volume) > 20)
|
|
devc->use_slider = 1;
|
|
|
|
/*
|
|
* use only left channel
|
|
*/
|
|
temp = levels[SOUND_MIXER_VOLUME] & 0xFF;
|
|
|
|
if (slider_volume != temp && devc->use_slider) {
|
|
devc->slider_vol = slider_volume;
|
|
|
|
waveartist_set_mixer(devc, SOUND_MIXER_VOLUME,
|
|
slider_volume | slider_volume << 8);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
vnc_slider_tick(unsigned long data)
|
|
{
|
|
int next_timeout;
|
|
|
|
if (vnc_slider(adev_info + data))
|
|
next_timeout = 5; // mixer reported change
|
|
else
|
|
next_timeout = VNC_TIMER_PERIOD;
|
|
|
|
mod_timer(&vnc_timer, jiffies + next_timeout);
|
|
}
|
|
|
|
static int
|
|
vnc_private_ioctl(int dev, unsigned int cmd, int __user * arg)
|
|
{
|
|
wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
|
|
int val;
|
|
|
|
switch (cmd) {
|
|
case SOUND_MIXER_PRIVATE1:
|
|
{
|
|
u_int prev_spkr_mute, prev_line_mute, prev_auto_state;
|
|
int val;
|
|
|
|
if (get_user(val, arg))
|
|
return -EFAULT;
|
|
|
|
/* check if parameter is logical */
|
|
if (val & ~(VNC_MUTE_INTERNAL_SPKR |
|
|
VNC_MUTE_LINE_OUT |
|
|
VNC_DISABLE_AUTOSWITCH))
|
|
return -EINVAL;
|
|
|
|
prev_auto_state = devc->no_autoselect;
|
|
prev_spkr_mute = devc->spkr_mute_state;
|
|
prev_line_mute = devc->line_mute_state;
|
|
|
|
devc->no_autoselect = (val & VNC_DISABLE_AUTOSWITCH) ? 1 : 0;
|
|
devc->spkr_mute_state = (val & VNC_MUTE_INTERNAL_SPKR) ? 1 : 0;
|
|
devc->line_mute_state = (val & VNC_MUTE_LINE_OUT) ? 1 : 0;
|
|
|
|
if (prev_spkr_mute != devc->spkr_mute_state)
|
|
vnc_mute_spkr(devc);
|
|
|
|
if (prev_line_mute != devc->line_mute_state)
|
|
vnc_mute_lout(devc);
|
|
|
|
if (prev_auto_state != devc->no_autoselect)
|
|
vnc_configure_mixer(devc, devc->recmask);
|
|
|
|
return 0;
|
|
}
|
|
|
|
case SOUND_MIXER_PRIVATE2:
|
|
if (get_user(val, arg))
|
|
return -EFAULT;
|
|
|
|
switch (val) {
|
|
#define VNC_SOUND_PAUSE 0x53 //to pause the DSP
|
|
#define VNC_SOUND_RESUME 0x57 //to unpause the DSP
|
|
case VNC_SOUND_PAUSE:
|
|
waveartist_cmd1(devc, 0x16);
|
|
break;
|
|
|
|
case VNC_SOUND_RESUME:
|
|
waveartist_cmd1(devc, 0x18);
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
|
|
/* private ioctl to allow bulk access to waveartist */
|
|
case SOUND_MIXER_PRIVATE3:
|
|
{
|
|
unsigned long flags;
|
|
int mixer_reg[15], i, val;
|
|
|
|
if (get_user(val, arg))
|
|
return -EFAULT;
|
|
if (copy_from_user(mixer_reg, (void *)val, sizeof(mixer_reg)))
|
|
return -EFAULT;
|
|
|
|
switch (mixer_reg[14]) {
|
|
case MIXER_PRIVATE3_RESET:
|
|
waveartist_mixer_reset(devc);
|
|
break;
|
|
|
|
case MIXER_PRIVATE3_WRITE:
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[0], mixer_reg[4]);
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[1], mixer_reg[5]);
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[2], mixer_reg[6]);
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[3], mixer_reg[7]);
|
|
waveartist_cmd3(devc, WACMD_SET_MIXER, mixer_reg[8], mixer_reg[9]);
|
|
|
|
waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[10], mixer_reg[11]);
|
|
waveartist_cmd3(devc, WACMD_SET_LEVEL, mixer_reg[12], mixer_reg[13]);
|
|
break;
|
|
|
|
case MIXER_PRIVATE3_READ:
|
|
spin_lock_irqsave(&waveartist_lock, flags);
|
|
|
|
for (i = 0x30; i < 14 << 8; i += 1 << 8)
|
|
waveartist_cmd(devc, 1, &i, 1, mixer_reg + (i >> 8));
|
|
|
|
spin_unlock_irqrestore(&waveartist_lock, flags);
|
|
|
|
if (copy_to_user((void *)val, mixer_reg, sizeof(mixer_reg)))
|
|
return -EFAULT;
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* read back the state from PRIVATE1 */
|
|
case SOUND_MIXER_PRIVATE4:
|
|
val = (devc->spkr_mute_state ? VNC_MUTE_INTERNAL_SPKR : 0) |
|
|
(devc->line_mute_state ? VNC_MUTE_LINE_OUT : 0) |
|
|
(devc->handset_detect ? VNC_HANDSET_DETECT : 0) |
|
|
(devc->telephone_detect ? VNC_PHONE_DETECT : 0) |
|
|
(devc->no_autoselect ? VNC_DISABLE_AUTOSWITCH : 0);
|
|
|
|
return put_user(val, arg) ? -EFAULT : 0;
|
|
}
|
|
|
|
if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
|
|
/*
|
|
* special case for master volume: if we
|
|
* received this call - switch from hw
|
|
* volume control to a software volume
|
|
* control, till the hw volume is modified
|
|
* to signal that user wants to be back in
|
|
* hardware...
|
|
*/
|
|
if ((cmd & 0xff) == SOUND_MIXER_VOLUME)
|
|
devc->use_slider = 0;
|
|
|
|
/* speaker output */
|
|
if ((cmd & 0xff) == SOUND_MIXER_SPEAKER) {
|
|
unsigned int val, l, r;
|
|
|
|
if (get_user(val, arg))
|
|
return -EFAULT;
|
|
|
|
l = val & 0x7f;
|
|
r = (val & 0x7f00) >> 8;
|
|
val = (l + r) / 2;
|
|
devc->levels[SOUND_MIXER_SPEAKER] = val | (val << 8);
|
|
devc->spkr_mute_state = (val <= 50);
|
|
vnc_mute_spkr(devc);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOIOCTLCMD;
|
|
}
|
|
|
|
#endif
|
|
|
|
static struct address_info cfg;
|
|
|
|
static int attached;
|
|
|
|
static int __initdata io = 0;
|
|
static int __initdata irq = 0;
|
|
static int __initdata dma = 0;
|
|
static int __initdata dma2 = 0;
|
|
|
|
|
|
static int __init init_waveartist(void)
|
|
{
|
|
const struct waveartist_mixer_info *mix;
|
|
|
|
if (!io && machine_is_netwinder()) {
|
|
/*
|
|
* The NetWinder WaveArtist is at a fixed address.
|
|
* If the user does not supply an address, use the
|
|
* well-known parameters.
|
|
*/
|
|
io = 0x250;
|
|
irq = 12;
|
|
dma = 3;
|
|
dma2 = 7;
|
|
}
|
|
|
|
mix = &waveartist_mixer;
|
|
#ifdef CONFIG_ARCH_NETWINDER
|
|
if (machine_is_netwinder())
|
|
mix = &netwinder_mixer;
|
|
#endif
|
|
|
|
cfg.io_base = io;
|
|
cfg.irq = irq;
|
|
cfg.dma = dma;
|
|
cfg.dma2 = dma2;
|
|
|
|
if (!probe_waveartist(&cfg))
|
|
return -ENODEV;
|
|
|
|
attach_waveartist(&cfg, mix);
|
|
attached = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit cleanup_waveartist(void)
|
|
{
|
|
if (attached)
|
|
unload_waveartist(&cfg);
|
|
}
|
|
|
|
module_init(init_waveartist);
|
|
module_exit(cleanup_waveartist);
|
|
|
|
#ifndef MODULE
|
|
static int __init setup_waveartist(char *str)
|
|
{
|
|
/* io, irq, dma, dma2 */
|
|
int ints[5];
|
|
|
|
str = get_options(str, ARRAY_SIZE(ints), ints);
|
|
|
|
io = ints[1];
|
|
irq = ints[2];
|
|
dma = ints[3];
|
|
dma2 = ints[4];
|
|
|
|
return 1;
|
|
}
|
|
__setup("waveartist=", setup_waveartist);
|
|
#endif
|
|
|
|
MODULE_DESCRIPTION("Rockwell WaveArtist RWA-010 sound driver");
|
|
module_param(io, int, 0); /* IO base */
|
|
module_param(irq, int, 0); /* IRQ */
|
|
module_param(dma, int, 0); /* DMA */
|
|
module_param(dma2, int, 0); /* DMA2 */
|
|
MODULE_LICENSE("GPL");
|