mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-29 13:00:35 +00:00
232b0b0829
When the kernel is running in secure boot mode, we lock down the kernel to prevent userspace from modifying the running kernel image. Whilst this includes prohibiting access to things like /dev/mem, it must also prevent access by means of configuring driver modules in such a way as to cause a device to access or modify the kernel image. To this end, annotate module_param* statements that refer to hardware configuration and indicate for future reference what type of parameter they specify. The parameter parser in the core sees this information and can skip such parameters with an error message if the kernel is locked down. The module initialisation then runs as normal, but just sees whatever the default values for those parameters is. Note that we do still need to do the module initialisation because some drivers have viable defaults set in case parameters aren't specified and some drivers support automatic configuration (e.g. PNP or PCI) in addition to manually coded parameters. This patch annotates drivers in sound/oss/. Suggested-by: Alan Cox <gnomes@lxorguk.ukuu.org.uk> Signed-off-by: David Howells <dhowells@redhat.com> cc: Jaroslav Kysela <perex@perex.cz> cc: Takashi Iwai <tiwai@suse.com> cc: Andrew Veliath <andrewtv@usa.net> cc: alsa-devel@alsa-project.org
2044 lines
46 KiB
C
2044 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/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/bitops.h>
|
|
|
|
|
|
#include "sound_config.h"
|
|
#include "waveartist.h"
|
|
|
|
#ifdef CONFIG_ARM
|
|
#include <mach/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 */
|
|
};
|
|
|
|
struct wavnc_info {
|
|
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
|
|
};
|
|
|
|
/*
|
|
* 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)(struct wavnc_info *, unsigned int,
|
|
unsigned char *, unsigned char *);
|
|
int (*decode_mixer)(struct wavnc_info *, int,
|
|
unsigned char, unsigned char);
|
|
int (*get_mixer)(struct wavnc_info *, int);
|
|
};
|
|
|
|
struct wavnc_port_info {
|
|
int open_mode;
|
|
int speed;
|
|
int channels;
|
|
int audio_format;
|
|
};
|
|
|
|
static int nr_waveartist_devs;
|
|
static struct 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(struct 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(struct 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 = msecs_to_jiffies(timeout_ms*100);
|
|
return schedule_timeout_interruptible(timeout);
|
|
}
|
|
|
|
static int
|
|
waveartist_reset(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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)
|
|
{
|
|
struct wavnc_info *devc;
|
|
struct wavnc_port_info *portc;
|
|
unsigned long flags;
|
|
|
|
if (dev < 0 || dev >= num_audiodevs)
|
|
return -ENXIO;
|
|
|
|
devc = (struct wavnc_info *) audio_devs[dev]->devc;
|
|
portc = (struct 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)
|
|
{
|
|
struct wavnc_info *devc = (struct wavnc_info *)
|
|
audio_devs[dev]->devc;
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct wavnc_port_info *)
|
|
audio_devs[dev]->portc;
|
|
struct wavnc_info *devc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct wavnc_port_info *)
|
|
audio_devs[dev]->portc;
|
|
struct wavnc_info *devc = (struct 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(struct 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(struct 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;
|
|
struct wavnc_info *devc = (struct wavnc_info *)
|
|
audio_devs[dev]->devc;
|
|
struct wavnc_port_info *portc = (struct 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;
|
|
struct wavnc_info *devc = (struct wavnc_info *)
|
|
audio_devs[dev]->devc;
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct wavnc_port_info *)
|
|
audio_devs[dev]->portc;
|
|
struct wavnc_info *devc;
|
|
|
|
if (portc->open_mode & OPEN_WRITE)
|
|
waveartist_halt_output(dev);
|
|
|
|
if (portc->open_mode & OPEN_READ)
|
|
waveartist_halt_input(dev);
|
|
|
|
devc = (struct wavnc_info *) audio_devs[dev]->devc;
|
|
devc->audio_mode = 0;
|
|
}
|
|
|
|
static void
|
|
waveartist_halt_input(int dev)
|
|
{
|
|
struct wavnc_info *devc = (struct 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)
|
|
{
|
|
struct wavnc_info *devc = (struct 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)
|
|
{
|
|
struct wavnc_info *devc = (struct wavnc_info *)
|
|
audio_devs[dev]->devc;
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_port_info *portc = (struct 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)
|
|
{
|
|
struct wavnc_info *devc = 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(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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)
|
|
{
|
|
struct wavnc_info *devc = (struct 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(struct 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(struct wavnc_info *devc)
|
|
{
|
|
struct 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 = kzalloc(sizeof(struct wavnc_port_info), GFP_KERNEL);
|
|
if (portc == NULL)
|
|
goto nomem;
|
|
|
|
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)
|
|
{
|
|
struct 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)
|
|
{
|
|
struct 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()) {
|
|
setup_timer(&vnc_timer, vnc_slider_tick,
|
|
nr_waveartist_devs);
|
|
mod_timer(&vnc_timer, jiffies);
|
|
|
|
vnc_configure_mixer(devc, 0);
|
|
|
|
devc->no_autoselect = 1;
|
|
}
|
|
#endif
|
|
nr_waveartist_devs += 1;
|
|
}
|
|
}
|
|
|
|
static void __exit unload_waveartist(struct address_info *hw)
|
|
{
|
|
struct 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
|
|
|
|
static inline void
|
|
vnc_mute_spkr(struct wavnc_info *devc)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&nw_gpio_lock, flags);
|
|
nw_cpld_modify(CPLD_UNMUTE, devc->spkr_mute_state ? 0 : CPLD_UNMUTE);
|
|
raw_spin_unlock_irqrestore(&nw_gpio_lock, flags);
|
|
}
|
|
|
|
static void
|
|
vnc_mute_lout(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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(struct 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)
|
|
{
|
|
struct wavnc_info *devc = (struct 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_hw(io, int, ioport, 0); /* IO base */
|
|
module_param_hw(irq, int, irq, 0); /* IRQ */
|
|
module_param_hw(dma, int, dma, 0); /* DMA */
|
|
module_param_hw(dma2, int, dma, 0); /* DMA2 */
|
|
MODULE_LICENSE("GPL");
|