/*
* sound/maui.c
*
* The low level driver for Turtle Beach Maui and Tropez.
*/
/*
* Copyright (C) by Hannu Savolainen 1993-1996
*
* USS/Lite 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.
*/
#include <linux/config.h>
#define USE_SEQ_MACROS
#define USE_SIMPLE_MACROS
#include "sound_config.h"
#if defined(CONFIG_MAUI)
static int maui_base = 0x330;
static volatile int irq_ok = 0;
static int *maui_osp;
#define HOST_DATA_PORT (maui_base + 2)
#define HOST_STAT_PORT (maui_base + 3)
#define HOST_CTRL_PORT (maui_base + 3)
#define STAT_TX_INTR 0x40
#define STAT_TX_AVAIL 0x20
#define STAT_TX_IENA 0x10
#define STAT_RX_INTR 0x04
#define STAT_RX_AVAIL 0x02
#define STAT_RX_IENA 0x01
static int (*orig_load_patch) (int dev, int format, const char *addr,
int offs, int count, int pmgr_flag) = NULL;
#ifdef HAVE_MAUI_BOOT
#include "maui_boot.h"
#else
static unsigned char *maui_os = NULL;
static int maui_osLen = 0;
#endif
static wait_handle *maui_sleeper = NULL;
static volatile struct snd_wait maui_sleep_flag =
{0};
static int
maui_wait (int mask)
{
int i;
/*
* Perform a short initial wait without sleeping
*/
for (i = 0; i < 100; i++)
{
if (inb (HOST_STAT_PORT) & mask)
{
return 1;
}
}
/*
* Wait up to 15 seconds with sleeping
*/
for (i = 0; i < 150; i++)
{
if (inb (HOST_STAT_PORT) & mask)
{
return 1;
}
{
unsigned long tlimit;
if (HZ / 10)
current_set_timeout (tlimit = jiffies + (HZ / 10));
else
tlimit = (unsigned long) -1;
maui_sleep_flag.flags = WK_SLEEP;
module_interruptible_sleep_on (&maui_sleeper);
if (!(maui_sleep_flag.flags & WK_WAKEUP))
{
if (jiffies >= tlimit)
maui_sleep_flag.flags |= WK_TIMEOUT;
}
maui_sleep_flag.flags &= ~WK_SLEEP;
};
if (current_got_fatal_signal ())
return 0;
}
return 0;
}
static int
maui_read (void)
{
if (maui_wait (STAT_RX_AVAIL))
return inb (HOST_DATA_PORT);
return -1;
}
static int
maui_write (unsigned char data)
{
if (maui_wait (STAT_TX_AVAIL))
{
outb (data, HOST_DATA_PORT);
return 1;
}
printk ("Maui: Write timeout\n");
return 0;
}
void
mauiintr (int irq, void *dev_id, struct pt_regs *dummy)
{
irq_ok = 1;
}
static int
download_code (void)
{
int i, lines = 0;
int eol_seen = 0, done = 0;
int skip = 1;
printk ("Code download (%d bytes): ", maui_osLen);
for (i = 0; i < maui_osLen; i++)
{
if (maui_os[i] != '\r')
if (!skip || (maui_os[i] == 'S' && (i == 0 || maui_os[i - 1] == '\n')))
{
skip = 0;
if (maui_os[i] == '\n')
eol_seen = skip = 1;
else if (maui_os[i] == 'S')
{
if (maui_os[i + 1] == '8')
done = 1;
if (!maui_write (0xF1))
goto failure;
if (!maui_write ('S'))
goto failure;
}
else
{
if (!maui_write (maui_os[i]))
goto failure;
}
if (eol_seen)
{
int c = 0;
int n;
eol_seen = 0;
for (n = 0; n < 2; n++)
if (maui_wait (STAT_RX_AVAIL))
{
c = inb (HOST_DATA_PORT);
break;
}
if (c != 0x80)
{
printk ("Download not acknowledged\n");
return 0;
}
else if (!(lines++ % 10))
printk (".");
if (done)
{
printk ("\nDownload complete\n");
return 1;
}
}
}
}
failure:
printk ("\nDownload failed!!!\n");
return 0;
}
static int
maui_init (int irq)
{
int i;
unsigned char bits;
switch (irq)
{
case 9:
bits = 0x00;
break;
case 5:
bits = 0x08;
break;
case 12:
bits = 0x10;
break;
case 15:
bits = 0x18;
break;
default:
printk ("Maui: Invalid IRQ %d\n", irq);
return 0;
}
outb (0x00, HOST_CTRL_PORT); /* Reset */
outb (bits, HOST_DATA_PORT); /* Set the IRQ bits */
outb (bits | 0x80, HOST_DATA_PORT); /* Set the IRQ bits again? */
outb (0x80, HOST_CTRL_PORT); /* Leave reset */
outb (0x80, HOST_CTRL_PORT); /* Leave reset */
outb (0xD0, HOST_CTRL_PORT); /* Cause interrupt */
for (i = 0; i < 1000000 && !irq_ok; i++);
if (!irq_ok)
return 0;
outb (0x80, HOST_CTRL_PORT); /* Leave reset */
printk ("Turtle Beach Maui initialization\n");
if (!download_code ())
return 0;
outb (0xE0, HOST_CTRL_PORT); /* Normal operation */
/* Select mpu401 mode */
maui_write (0xf0);
maui_write (1);
if (maui_read () != 0x80)
{
maui_write (0xf0);
maui_write (1);
if (maui_read () != 0x80)
printk ("Maui didn't acknowledge set HW mode command\n");
}
printk ("Maui initialized OK\n");
return 1;
}
static int
maui_short_wait (int mask)
{
int i;
for (i = 0; i < 1000; i++)
{
if (inb (HOST_STAT_PORT) & mask)
{
return 1;
}
}
return 0;
}
int
maui_load_patch (int dev, int format, const char *addr,
int offs, int count, int pmgr_flag)
{
struct sysex_info header;
unsigned long left, src_offs;
int hdr_size = (unsigned long) &header.data[0] - (unsigned long) &header;
int i;
if (format == SYSEX_PATCH) /* Handled by midi_synth.c */
return orig_load_patch (dev, format, addr, offs, count, pmgr_flag);
if (format != MAUI_PATCH)
{
printk ("Maui: Unknown patch format\n");
}
if (count < hdr_size)
{
printk ("Maui error: Patch header too short\n");
return -(EINVAL);
}
count -= hdr_size;
/*
* Copy the header from user space but ignore the first bytes which have
* been transferred already.
*/
memcpy_fromfs (&((char *) &header)[offs], &(addr)[offs], hdr_size - offs);
if (count < header.len)
{
printk ("Maui warning: Host command record too short (%d<%d)\n",
count, (int) header.len);
header.len = count;
}
left = header.len;
src_offs = 0;
for (i = 0; i < left; i++)
{
unsigned char data;
data = get_fs_byte (&((addr)[hdr_size + i]));
if (i == 0 && !(data & 0x80))
return -(EINVAL);
if (maui_write (data) == -1)
return -(EIO);
}
if ((i = maui_read ()) != 0x80)
{
if (i != -1)
printk ("Maui: Error status %02x\n", i);
return -(EIO);
}
return 0;
}
int
probe_maui (struct address_info *hw_config)
{
int i;
int tmp1, tmp2, ret;
if (check_region (hw_config->io_base, 8))
return 0;
maui_base = hw_config->io_base;
maui_osp = hw_config->osp;
if (snd_set_irq_handler (hw_config->irq, mauiintr, "Maui", maui_osp) < 0)
return 0;
maui_sleep_flag.flags = WK_NONE;
/*
* Initialize the processor if necessary
*/
if (maui_osLen > 0)
{
if (!(inb (HOST_STAT_PORT) & STAT_TX_AVAIL) ||
!maui_write (0x9F) || /* Report firmware version */
!maui_short_wait (STAT_RX_AVAIL) ||
maui_read () == -1 || maui_read () == -1)
if (!maui_init (hw_config->irq))
{
snd_release_irq (hw_config->irq);
return 0;
}
}
if (!maui_write (0xCF)) /* Report hardware version */
{
printk ("No WaveFront firmware detected (card uninitialized?)\n");
snd_release_irq (hw_config->irq);
return 0;
}
if ((tmp1 = maui_read ()) == -1 || (tmp2 = maui_read ()) == -1)
{
printk ("No WaveFront firmware detected (card uninitialized?)\n");
snd_release_irq (hw_config->irq);
return 0;
}
if (tmp1 == 0xff || tmp2 == 0xff)
{
snd_release_irq (hw_config->irq);
return 0;
}
if (trace_init)
printk ("WaveFront hardware version %d.%d\n", tmp1, tmp2);
if (!maui_write (0x9F)) /* Report firmware version */
return 0;
if ((tmp1 = maui_read ()) == -1 || (tmp2 = maui_read ()) == -1)
return 0;
if (trace_init)
printk ("WaveFront firmware version %d.%d\n", tmp1, tmp2);
if (!maui_write (0x85)) /* Report free DRAM */
return 0;
tmp1 = 0;
for (i = 0; i < 4; i++)
{
tmp1 |= maui_read () << (7 * i);
}
if (trace_init)
printk ("Available DRAM %dk\n", tmp1 / 1024);
for (i = 0; i < 1000; i++)
if (probe_mpu401 (hw_config))
break;
ret = probe_mpu401 (hw_config);
if (ret)
request_region (hw_config->io_base + 2, 6, "Maui");
return ret;
}
void
attach_maui (struct address_info *hw_config)
{
int this_dev = num_midis;
conf_printf ("Maui", hw_config);
hw_config->irq *= -1;
hw_config->name = "Maui";
attach_mpu401 (hw_config);
if (num_midis > this_dev) /* The MPU401 driver installed itself */
{
struct synth_operations *synth;
/*
* Intercept patch loading calls so that they can be handled
* by the Maui driver.
*/
synth = midi_devs[this_dev]->converter;
if (synth != NULL)
{
orig_load_patch = synth->load_patch;
synth->load_patch = &maui_load_patch;
}
else
printk ("Maui: Can't install patch loader\n");
}
}
void
unload_maui (struct address_info *hw_config)
{
int irq = hw_config->irq;
release_region (hw_config->io_base + 2, 6);
unload_mpu401 (hw_config);
if (irq < 0)
irq = -irq;
if (irq > 0)
snd_release_irq (irq);
}
#endif