summaryrefslogtreecommitdiff
path: root/hardsid.c
diff options
context:
space:
mode:
Diffstat (limited to 'hardsid.c')
-rw-r--r--hardsid.c1840
1 files changed, 1840 insertions, 0 deletions
diff --git a/hardsid.c b/hardsid.c
new file mode 100644
index 0000000..66c945f
--- /dev/null
+++ b/hardsid.c
@@ -0,0 +1,1840 @@
+/*
+ *
+ * Linux driver for the HardSID cards by Hard Software
+ * and Catweasel MK3 by Individual Computers
+ *
+ * Copyright (C) 2000-2003 Jarno Paananen <jpaana@s2.org>
+ * Copyright (C) 2001-2003 Simon White <s_a_white@email.com>
+ *
+ * Based on the Linux kernel RTC driver
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+/*
+ * hardsid.c,v
+ * Revision 1.48 2003/12/27 19:27:17 jpaana
+ * 0.16 release
+ *
+ * Revision 1.47 2003/11/18 13:08:54 jpaana
+ * Fixes for devfs in 2.6 kernels
+ *
+ * Revision 1.46 2003/09/24 20:22:51 s_a_white
+ * Fixed assignment of pci hardsid card type to be after the sid structure
+ * is cleared. Prevent hardsid module causing a segmentation fault when
+ * loaded with no hardsid card available (don't call pci_unregister_driver
+ * if pci_module_init failed).
+ *
+ * Revision 1.45 2003/09/08 05:05:27 jpaana
+ * New minor handling for 2.6.0-test4 or so
+ *
+ * Revision 1.44 2003/04/17 17:41:09 jpaana
+ * Probe also other ISA ports than the first one
+ *
+ * Revision 1.43 2003/04/07 00:19:38 jpaana
+ * Some init and remove cleanups, fix for preemptible and later 2.5 kernels
+ *
+ * Revision 1.42 2003/03/13 22:35:47 s_a_white
+ * Make temp sid object used for probing global to prevent stack overflows
+ * (fixes crash on insmod).
+ *
+ * Revision 1.41 2003/03/13 10:06:43 s_a_white
+ * Add PCI hardsid support. Attempt to make different card types live
+ * together and support potential PNP hotswapping. Code currently takes
+ * the kernel out during insmod!
+ *
+ * Revision 1.40 2003/03/13 09:57:46 s_a_white
+ * Adjust peek/poke functions to be part of the sid object. This way we
+ * only need to work out the functions to use once, removing various
+ * switches/if's from the code. The peek/poke functions also have direct
+ * access to the sid objects data to support PCI hardsid differences.
+ *
+ * Revision 1.39 2003/02/10 05:23:20 jpaana
+ * First version of Catweasel MK3 support
+ *
+ * Revision 1.38 2002/07/28 17:54:35 jpaana
+ * 2.4.19 apparently uses the same renice mechanism as 2.5
+ *
+ * Revision 1.37 2002/02/01 02:11:17 jpaana
+ * Another 2.5 kernel fix
+ *
+ * Revision 1.36 2002/01/30 05:28:26 jpaana
+ * reparent_to_init is not present nor needed in 2.2 apparently
+ *
+ * Revision 1.35 2002/01/30 05:00:37 jpaana
+ * reparent_to_init to avoid zombies
+ *
+ * Revision 1.34 2002/01/25 23:27:14 jpaana
+ * Fixed read ioctl, 0.15a release
+ *
+ * Revision 1.33 2002/01/25 04:49:35 jpaana
+ * 0.15 release
+ *
+ * Revision 1.32 2002/01/15 16:29:11 jpaana
+ * Another change for 2.5 kernels
+ *
+ * Revision 1.31 2002/01/07 13:48:39 jpaana
+ * Fixed to compile on newer 2.5.2-pre versions (check is for 2.5.1 as I use 2.5.1-dj series at the moment...)
+ *
+ * Revision 1.30 2001/11/09 01:35:52 jpaana
+ * Add renice value to module options
+ *
+ * Revision 1.29 2001/11/08 21:05:25 s_a_white
+ * File tidy. Fixed resid style faked reads to decay properly.
+ * Added modversions.h to remove unresolved symbols.
+ *
+ * Revision 1.28 2001/11/07 21:57:51 jpaana
+ * - reset all SIDs when closing and using the Quattro hack
+ * - merged Simon's changes including:
+ * - faked reads of write only registers
+ * (speeds up Fred Gray's tunes for example)
+ * - write clean up (writes to command buffer in one place only)
+ * - separated ioctl definitions to a header file for use with user land tools
+ * - added read and delay ioctls
+ * - fixed devfs (my bad...)
+ *
+ * Revision 1.27 2001/09/30 02:26:58 jpaana
+ * Changed config #defines to real module options
+ *
+ * Revision 1.26 2001/09/30 01:44:47 jpaana
+ * Added MODULE_LICENSE tag
+ *
+ * Revision 1.25 2001/09/04 20:01:24 jpaana
+ * - removed unnecessary #ifdefs from the devfs-support
+ * - added my Quattro hack
+ * - renice the play thread a bit as the realtime-stuff doesn't seem to work
+ *
+ * Revision 1.24 2001/08/23 01:36:16 jpaana
+ * Fix for kernels > 2.4.8 and some cosmetic stuff
+ *
+ * Revision 1.23 2001/03/31 11:45:20 jpaana
+ * Added flush ioctl
+ *
+ * Revision 1.22 2001/03/15 05:03:21 jpaana
+ * Fix oops with multiple chips
+ *
+ * Revision 1.21 2001/03/03 23:33:44 s_a_white
+ * Reduce speaker pops and clicks by modifing reset ioctl.
+ *
+ * Revision 1.20 2001/02/28 20:55:11 s_a_white
+ * Added ability to disable filters (swhite), usefull for debugging. /proc/hardsid
+ * now returns mute and filter states.
+ *
+ * Revision 1.19 2001/02/28 09:49:19 jpaana
+ * Remove a debug printk from mute ioctl
+ *
+ * Revision 1.18 2001/02/27 05:48:03 jpaana
+ * Added /proc support for 2.2 kernels
+ *
+ * Revision 1.17 2001/02/23 17:32:21 jpaana
+ * 0.14 release
+ *
+ * Revision 1.16 2001/02/11 17:07:38 jpaana
+ * Unused variables cleaned up
+ *
+ * Revision 1.15 2001/02/02 12:14:58 jpaana
+ * Added mute support
+ *
+ * Revision 1.14 2001/01/31 15:43:21 jpaana
+ * - DEVFS support
+ * - detection override hack for testing
+ *
+ * Revision 1.13 2001/01/27 06:11:03 jpaana
+ * 0.13 release
+ *
+ * Revision 1.12 2001/01/27 06:08:20 jpaana
+ * duh... fixed oops on trying to open unexisting device
+ *
+ * Revision 1.11 2001/01/26 22:38:45 jpaana
+ * 0.12 release
+ * - removed test hacks left from previous commit
+ *
+ * Revision 1.10 2001/01/26 22:15:01 jpaana
+ * Fixed 2.2 semaphore problem
+ *
+ * Revision 1.9 2001/01/25 03:38:26 jpaana
+ * - multiple SID support, major changes nearly everywhere
+ * - device number changed from misc devices to own major (60 for now)
+ * - tested to work on Alpha
+ * - fixed a bug with reads and Quattro cards
+ * - simplified reset ioctl
+ *
+ * Revision 1.8 2001/01/24 01:22:52 jpaana
+ * Forgot slow IO access on
+ *
+ * Revision 1.7 2001/01/24 01:19:24 jpaana
+ * - cleanup
+ * - initial HardSID Quattro support
+ * - added ioctl to query card type
+ *
+ * Revision 1.6 2001/01/14 23:32:09 jpaana
+ * 0.09 release
+ *
+ * Revision 1.5 2001/01/14 23:30:46 jpaana
+ * Printk cleanup and added support for dummy writes for delays longer than 0xffff
+ *
+ * Revision 1.4 2001/01/14 23:14:03 jpaana
+ * Fixed reset
+ *
+ * Revision 1.3 2001/01/14 22:50:49 jpaana
+ * Fixed RCSID
+ *
+ * Revision 1.2 2001/01/14 22:49:37 jpaana
+ * Added CVS tags
+ *
+ */
+
+
+#define HSID_VERSION "0.16"
+
+const char rcsid[] = "hardsid.c,v 1.48 2003/12/27 19:27:17 jpaana Exp";
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,10)
+#include <linux/modversions.h>
+#endif
+/*#include <linux/config.h>*/
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/version.h>
+#include <linux/delay.h>
+
+#include <linux/pci.h>
+
+#include <asm/io.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
+# define KERNEL_2_2
+# define __exit
+#endif
+
+#define HSID_MAX_CARDS 4
+#define HSID_MAX_SIDS_PER_CARD 4
+#define HSID_MAX_SIDS (HSID_MAX_CARDS * HSID_MAX_SIDS_PER_CARD)
+
+/* DEVFS */
+#ifdef CONFIG_DEVFS_FS
+#include <linux/devfs_fs_kernel.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+static devfs_handle_t hsid_handles[HSID_MAX_SIDS];
+#endif
+#endif
+
+#include "hardsid.h"
+
+
+/*
+ All of these are module options, no need to hardcode them here unless your
+ kernel is ancient and doesn't support MODULE_PARM stuff
+ */
+
+/*
+ If for some reason the autodetection fails, you can try fiddling with the
+ following variables, but that should not be necessary.
+ */
+static int io = 0x300;
+static int ioextent = 8;
+/* The major device number we use */
+static int major = 60;
+/* If 16-bit access doesn't work for you for some reason, set this to 1 */
+static int slowaccess = 0;
+/* Set this to 1 if you want to be able to load this module even
+ without a SID, used mainly for debugging */
+static int detecthack = 0;
+/* Set this to 1 if you want to play the first SID stuff with all
+ chips in a Quattro. This is a major hack, but works for me(tm) */
+static int quattrohack = 0;
+/* Set the kernel thread renice value */
+static int renice = -5;
+
+/* Nothing configurable found below */
+
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+#define GETMINOR(file) (iminor((file)->f_dentry->d_inode))
+#else
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,1)
+#define GETMINOR(file) (minor((file)->f_dentry->d_inode->i_rdev))
+#else
+#define GETMINOR(file) (MINOR((file)->f_dentry->d_inode->i_rdev))
+#endif
+#endif
+
+/* Bits in sid_d.status. */
+#define HSID_IS_OPEN 0x01 /* means /dev/sidX is in use */
+#define HSID_IS_REGISTERED 0x02 /* device registered */
+
+/* These IDs are not registered and may belong to others */
+#define PCI_VENDOR_ID_INDIVIDUAL 0xe159
+#define PCI_DEVICE_ID_INDIVIDUAL_CWMK3 0x0001
+#define PCI_SUBSYSTEM_VENDOR_ID_INDIVIDUAL 0x1212
+#define PCI_SUBSYSTEM_DEVICE_ID_INDIVIDUAL_CWMK3 0x0002
+#define PCI_VENDOR_ID_HARDSOFTWARE 0x6581
+#define PCI_DEVICE_ID_HARDSOFTWARE_HSID 0x8580
+
+static __initdata struct pci_device_id id_table[] = {
+ { PCI_VENDOR_ID_INDIVIDUAL, PCI_DEVICE_ID_INDIVIDUAL_CWMK3,
+ PCI_SUBSYSTEM_VENDOR_ID_INDIVIDUAL,
+ PCI_SUBSYSTEM_DEVICE_ID_INDIVIDUAL_CWMK3, 0, 0, 0 },
+ /* @FIXME@ PCI_ANY_ID will work for now, probably best to
+ * insert the correct values here when known */
+ { PCI_VENDOR_ID_HARDSOFTWARE, PCI_DEVICE_ID_HARDSOFTWARE_HSID,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0, }
+};
+
+
+
+/* Sid type enum */
+typedef enum
+{
+ SID_NONE = 0,
+ SID_6581,
+ SID_8580
+} sid_type;
+
+/* Card type enum */
+typedef enum
+{
+ SID_CARD_NONE = 0,
+ SID_CARD_HARDSID,
+ SID_CARD_QUATTRO,
+ SID_CARD_CWMK3,
+ SID_CARD_PCI_HARDSID,
+ SID_CARD_PCI_QUATTRO
+} sid_card_type;
+
+static const char * const sid_card[] =
+{
+ "",
+ "HardSID",
+ "HardSID Quattro",
+ "Catweasel MK3",
+ "HardSID PCI",
+ "HardSID PCI Quattro"
+};
+
+
+/* Per sid card data */
+#define HSID_BUFFER_SIZE 8192
+typedef struct sid_d
+{
+ unsigned short port;
+ unsigned short port2;
+ unsigned char chip;
+ sid_type type;
+ sid_card_type card;
+ struct pci_dev* pcidev;
+ int curCommand;
+ int lastCommand;
+ struct semaphore bufferSem;
+ struct semaphore todoSem;
+ struct timeval tv;
+ int cycles;
+ int status;
+ __u32 buffer[HSID_BUFFER_SIZE];
+ int mute;
+ int filterEnabled;
+ unsigned char filterReg; /* Used to restore value */
+/* Statistics stuff */
+ int maxDelay;
+ int minDelay;
+ int writes;
+ int reads;
+ int seconds;
+ int longDelay;
+ int longDelays;
+ int shortDelay;
+ int shortDelays;
+ int noDelay;
+ int noDelays;
+ int jitter;
+
+/* ReSID style faked reads */
+ unsigned char fakedRead;
+ int fakedDecay;
+
+/* read write callbacks */
+ void (*poke) (struct sid_d *sid, unsigned char reg,
+ unsigned char value);
+ unsigned char (*peek) (struct sid_d *sid, unsigned char reg);
+} sid_d;
+
+
+/* Function prototypes */
+static loff_t hsid_llseek (struct file *file, loff_t offset,
+ int origin);
+static ssize_t hsid_read (struct file *file, char *buf,
+ size_t count, loff_t *ppos);
+static int hsid_ioctl (struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static void hsid_write_pr (sid_d *sid, __u32 cmd);
+#ifdef KERNEL_2_2
+static int hsid_read_proc (char *page, char **start, off_t off,
+ int count, int unused);
+#else
+static int hsid_read_proc (char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+#endif
+
+
+/* Global data */
+#ifdef KERNEL_2_2
+static struct proc_dir_entry hsid_proc_entry =
+{
+ 0, /* low_ino: inode is dynamic */
+ 7, "hardsid", /* length of name and name */
+ S_IFREG | S_IRUGO, /* mode */
+ 1, 0, 0, /* nlinks, owner, group */
+ 0, /* size -- not used */
+ NULL, /* operations -- use default */
+ &hsid_read_proc, /* function used to read data */
+ /* nothing more */
+};
+#endif /* KERNEL_2_2 */
+
+static DECLARE_WAIT_QUEUE_HEAD(hsid_wait);
+static spinlock_t hsid_lock;
+static spinlock_t hsid_reg_lock;
+static struct fasync_struct *hsid_async_queue;
+sid_d *sid_data[HSID_MAX_SIDS];
+static struct task_struct *thread;
+static struct semaphore *notify;
+static int active,rmmod;
+static struct semaphore todoSem;
+static int sid_numSIDs;
+static int sid_open;
+static sid_d sid_setup;
+static int isa_allocated;
+static int pci_allocated;
+/* End of Globals */
+
+
+static void hsid_poke_isa_slowaccess (sid_d *sid, unsigned char reg,
+ unsigned char value)
+{
+ unsigned short port = sid->port;
+ outb(value, port);
+ outb(reg | (sid->chip << 6), port+1);
+}
+
+static void hsid_poke_isa (sid_d *sid, unsigned char reg,
+ unsigned char value)
+{
+ outw( (sid->chip <<14) | (reg << 8) | value, sid->port);
+}
+
+static void hsid_poke_cwmk3 (sid_d *sid, unsigned char reg,
+ unsigned char value)
+{
+ unsigned short port = sid->port;
+ outb(value, port + 0xd8);
+ outb(reg, port + 0xdc);
+}
+
+static void hsid_poke_pci (sid_d *sid, unsigned char reg,
+ unsigned char value)
+{
+ outw( (sid->chip <<14) | (reg << 8) | value, sid->port+3);
+}
+
+/* When reading a register OR 0x20 to the register value */
+static unsigned char hsid_peek_isa (sid_d *sid, unsigned char reg)
+{
+ unsigned short port = sid->port;
+ outb( reg | 0x20 | (sid->chip << 6), port+1);
+ udelay(2);
+ return inb(port);
+}
+
+static unsigned char hsid_peek_cwmk3 (sid_d *sid, unsigned char reg)
+{
+ unsigned short port = sid->port;
+ outb( reg | 0x20, port + 0xdc);
+ udelay(2);
+ return inb(port + 0xd8);
+}
+
+/* When reading a register OR 0x20 to the register value */
+static unsigned char hsid_peek_pci (sid_d *sid, unsigned char reg)
+{
+ unsigned short port = sid->port2 + 2;
+ unsigned char ret;
+ outb (reg | 0x20 | (sid->chip << 6), sid->port+4);
+ udelay (2);
+ outb (0x20, port);
+ ret = inb (sid->port);
+ outb (0x80, port);
+ return ret;
+}
+
+static void hsid_delay (sid_d *sid, __u32 delay)
+{
+ hsid_write_pr (sid, (delay << 16) | 0x1f00);
+
+ { /* Support ReSID style faked reads */
+ __u32 decay = sid->fakedDecay;
+ sid->fakedDecay -= delay;
+ if (delay > decay)
+ {
+ sid->fakedDecay = 0;
+ sid->fakedRead = 0;
+ }
+ }
+}
+
+
+/* Reset the SID chip */
+static void hsid_reset(sid_d *sid, unsigned char vol)
+{
+ int i;
+ for (i = 0; i < 0x18; i++ )
+ {
+ sid->poke(sid, i, 0);
+ udelay(2);
+ }
+ /* Set the volume */
+ sid->poke(sid, i, vol);
+ udelay(2);
+}
+
+/* The main worker thread, also known as ksidd */
+static int hsid_thread(void* data)
+{
+ sid_d** sids = (sid_d**)data;
+ sid_d* sid;
+ __u32 cmd, delay;
+ int clocks;
+ struct timeval tv;
+ int delayed;
+ int next, nextTime, wait, i;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+ daemonize();
+#ifdef KERNEL_2_4
+ reparent_to_init();
+#endif
+ sigfillset(&current->blocked);
+ strcpy(current->comm, "ksidd");
+#else
+ daemonize("ksidd");
+#endif
+ thread = current;
+
+ /* We need high priority, so we go to real-time priority */
+ thread->policy = SCHED_FIFO;
+ thread->rt_priority = 1;
+#ifdef KERNEL_2_2
+ thread->priority = renice;
+#else
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,1)) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,19))
+ set_user_nice(current, renice);
+#else
+ thread->nice = renice;
+#endif
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,3)
+ current->need_resched = 1;
+#endif
+
+ /* Notify the parent */
+ if(notify != NULL)
+ up(notify);
+
+ active = 1;
+ for(;;)
+ {
+ if (rmmod || signal_pending(current))
+ break;
+
+ /* We sit here waiting for something to do */
+ down_interruptible(&todoSem);
+
+ if (rmmod || signal_pending(current))
+ break;
+
+ /* Find the next write we should do */
+ next = -1;
+ nextTime = 0;
+
+ do_gettimeofday(&tv);
+
+ for ( i = 0; i < sid_numSIDs; i++ )
+ {
+ sid = sids[i];
+ if ( sid->status & HSID_IS_OPEN )
+ {
+ clocks = (tv.tv_sec - sid->tv.tv_sec) * 1000000
+ + ( tv.tv_usec - sid->tv.tv_usec);
+
+ cmd = sid->buffer[sid->curCommand];
+ delay = cmd >> 16;
+ wait = (sid->cycles + delay) - clocks;
+
+ if ( (atomic_read(&sid->todoSem.count) > 0) &&
+ ( next == -1 || wait < nextTime ) )
+ {
+ next = i;
+ nextTime = wait;
+ }
+ }
+ }
+
+ if ( next == -1 )
+ {
+ /* False alarm, possibly reset */
+ continue;
+ }
+
+ sid = sids[next];
+
+ down(&sid->todoSem);
+ cmd = sid->buffer[sid->curCommand];
+ sid->curCommand++;
+ sid->curCommand &= HSID_BUFFER_SIZE - 1;
+
+ delay = cmd >> 16;
+ sid->cycles += delay;
+
+ if (((sid->minDelay == -1) || (delay < sid->minDelay)) && (delay != 0))
+ sid->minDelay = delay;
+ if ( sid->maxDelay == -1 || delay > sid->maxDelay )
+ sid->maxDelay = delay;
+ sid->writes++;
+
+ /* We make a brute approximation of SID clock as
+ 1 MHz (which aligns nicely with 1 usec resolution
+ of gettimeofday */
+
+ delayed = 0;
+
+ /* Check how much time has passed since previous write */
+ clocks = (tv.tv_sec - sid->tv.tv_sec) * 1000000
+ + ( tv.tv_usec - sid->tv.tv_usec);
+
+ memcpy(&sid->tv, &tv, sizeof(tv));
+
+ sid->cycles -= clocks;
+
+ while ( sid->cycles > 1000000 / HZ )
+ {
+ /* Long wait, schedule */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(sid->cycles / 1000000);
+ /* Now we should only have to delay a short while if at all */
+ do_gettimeofday(&tv);
+
+ /* Update cycle status */
+ clocks = (tv.tv_sec - sid->tv.tv_sec) * 1000000
+ + ( tv.tv_usec - sid->tv.tv_usec);
+
+ memcpy(&sid->tv, &tv, sizeof(tv));
+ sid->longDelay++;
+ sid->longDelays += sid->cycles;
+ sid->cycles -= clocks;
+ delayed = 1;
+ }
+
+ if ( sid->cycles > 4 )
+ {
+ /* Short delay */
+ udelay(sid->cycles);
+ sid->shortDelay++;
+ sid->shortDelays += sid->cycles;
+ }
+ else
+ {
+ if ( !delayed )
+ {
+ /* Always at least a small delay (4 cycles here )
+ so SID can manage it */
+ udelay(4);
+ sid->noDelay++;
+ sid->noDelays += sid->cycles;
+ }
+ }
+
+ switch( (cmd >> 8) & 0x1f )
+ {
+ case 4:
+ cmd &= ~( sid->mute & 1);
+ break;
+ case 0xb:
+ cmd &= ~(( sid->mute >> 1) & 1);
+ break;
+ case 0x12:
+ cmd &= ~(( sid->mute >> 2) & 1);
+ break;
+ case 0x17:
+ sid->filterReg = cmd & 0xff;
+ if (!sid->filterEnabled)
+ cmd &= ~((__u32) 0x0f);
+ default:
+ break;
+ }
+
+ /* Ignore registers greater than 0x18 as they are either read only
+ * or not used
+ */
+ if ( (cmd & 0x1f00) <= 0x1800 )
+ {
+ unsigned char reg = (unsigned char) (cmd >> 8),
+ data = (unsigned char) cmd & 0xff;
+
+ spin_lock(&hsid_reg_lock);
+ switch (sid->card)
+ {
+ case SID_CARD_QUATTRO:
+ case SID_CARD_PCI_QUATTRO:
+ if (quattrohack)
+ {
+ int chip = sid->chip;
+ sid->chip = 0;
+ sid->poke (sid, reg, data);
+ sid->chip = 1;
+ sid->poke (sid, reg, data);
+ sid->chip = 2;
+ sid->poke (sid, reg, data);
+ sid->chip = 3;
+ sid->poke (sid, reg, data);
+ sid->chip = chip;
+ break;
+ }
+ default:
+ sid->poke (sid, reg, data);
+ }
+ spin_unlock(&hsid_reg_lock);
+ }
+ do_gettimeofday(&tv);
+
+ /* Update cycle status */
+ clocks = (tv.tv_sec - sid->tv.tv_sec) * 1000000
+ + ( tv.tv_usec - sid->tv.tv_usec);
+
+ memcpy(&sid->tv, &tv, sizeof(tv));
+ sid->cycles -= clocks;
+
+ sid->jitter += sid->cycles;
+ if ( sid->cycles < 0 )
+ sid->cycles = 0;
+
+ /* Free one buffer item */
+ up(&sid->bufferSem);
+ }
+
+ /* Off we go */
+ active = 0;
+ thread = NULL;
+
+ if(notify != NULL)
+ up(notify);
+
+ return 0;
+}
+
+/*
+ * Now all the various file operations that we export.
+ */
+
+static loff_t hsid_llseek(struct file *file, loff_t offset, int origin)
+{
+ switch(origin)
+ {
+ case 0:
+ file->f_pos = offset;
+ return file->f_pos;
+ case 1:
+ file->f_pos += offset;
+ return file->f_pos;
+ default:
+ return -EINVAL;
+ }
+}
+
+
+static void hsid_write_pr(sid_d *sid, __u32 cmd)
+{
+ down(&sid->bufferSem);
+ sid->buffer[sid->lastCommand] = cmd;
+ sid->lastCommand++;
+ sid->lastCommand &= HSID_BUFFER_SIZE - 1;
+ up(&sid->todoSem);
+ up(&todoSem);
+}
+
+
+static ssize_t hsid_write(struct file * file, const char * buffer,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0;
+ size_t bytes;
+ __u32 buf;
+ const char *p = buffer;
+ size_t c = count;
+ sid_d* sid;
+
+ if ( GETMINOR(file) >= sid_numSIDs )
+ return -EFAULT;
+
+ sid = sid_data[GETMINOR(file)];
+
+ while (c > 0)
+ {
+ bytes = MIN(c, sizeof(buf));
+
+ bytes -= copy_from_user(&buf, p, bytes);
+ if (!bytes)
+ {
+ ret = -EFAULT;
+ break;
+ }
+ c -= bytes;
+ p += bytes;
+
+ /* We want multiples of 4 bytes here */
+ if ( bytes != 4 )
+ break;
+
+ /* Support ReSID based faked reads */
+ sid->fakedRead = (unsigned char) (buf & 0xff);
+ sid->fakedDecay = 0x2000; /* Clock cycles */
+
+ /* Command structure:
+ bits 31-16: 16 bit delay timer value in C64 clock cycles
+ bits 15-13: reserved, keep zero
+ bits 12-8: SID register number
+ bits 7-0: Data
+ */
+ hsid_write_pr(sid, buf);
+ }
+
+ if (p == buffer)
+ {
+ return (ssize_t)ret;
+ }
+ else
+ {
+ file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
+ mark_inode_dirty(file->f_dentry->d_inode);
+ return (ssize_t)(p - buffer);
+ }
+}
+
+
+static unsigned char hsid_read_pr(sid_d *sid, __u32 cmd)
+{
+ unsigned char t;
+
+ sid->reads++;
+
+ if (cmd & 0xFFFF0000)
+ { /* Perform delay */
+ hsid_delay (sid, cmd >> 16);
+ }
+
+ if ( (cmd & 0x1f00) < 0x1900 ||
+ (cmd & 0x1f00) > 0x1c00 )
+ { /* ReSID style faked read */
+ return sid->fakedRead;
+ }
+
+ /* Wait until all writes are done */
+ while ( atomic_read(&sid->todoSem.count) > 0 )
+ {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+ }
+
+ udelay(2);
+
+ spin_lock(&hsid_reg_lock);
+ t = sid->peek(sid, (cmd >> 8) & 0x1f);
+ spin_unlock(&hsid_reg_lock);
+ return t;
+}
+
+
+static ssize_t hsid_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long i = *ppos;
+ sid_d* sid;
+
+ if ( GETMINOR(file) >= sid_numSIDs )
+ return -EFAULT;
+
+ sid = sid_data[GETMINOR(file)];
+
+ /* We want only 1 byte reads */
+ if ( count != 1 )
+ return -EFAULT;
+
+ if ( i > 0x1f )
+ return -EFAULT;
+
+ /*
+ if (verify_area(VERIFY_WRITE,buf,count))
+ return -EFAULT;
+ */
+
+ if (__put_user( hsid_read_pr(sid, i << 8), buf) < 0)
+ return -EFAULT;
+
+ *ppos = i + 1;
+ return 1;
+}
+
+static int hsid_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ sid_d* sid;
+ int t;
+
+ uint32_t _minor;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+ _minor = iminor(inode);
+#else
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,1)
+ _minor = minor(inode->i_rdev);
+#else
+ _minor = MINOR(inode->i_rdev);
+#endif
+#endif
+
+ if ( _minor >= sid_numSIDs )
+ return 0;
+
+ sid = sid_data[_minor];
+
+ switch(cmd)
+ {
+ case HSID_IOCTL_RESET:
+ sid->curCommand = 0;
+ sid->lastCommand = 0;
+ sid->cycles = 0;
+ sid->filterReg = 0;
+
+ sid->reads = 0;
+ sid->writes = 0;
+ sid->minDelay = -1;
+ sid->maxDelay = -1;
+ sid->longDelay = 0;
+ sid->shortDelay = 0;
+ sid->noDelay = 0;
+ sid->longDelays = 0;
+ sid->shortDelays = 0;
+ sid->noDelays = 0;
+ sid->jitter = 0;
+
+ sid->fakedRead = 0;
+ sid->fakedDecay = 0;
+
+ do_gettimeofday(&sid->tv);
+ sid->seconds = sid->tv.tv_sec;
+
+ t = atomic_read(&todoSem.count);
+ t -= atomic_read(&sid->todoSem.count);
+ atomic_set(&todoSem.count, t);
+#ifdef KERNEL_2_2
+ init_MUTEX(&sid->bufferSem);
+ init_MUTEX(&sid->todoSem);
+#endif
+ sema_init(&sid->bufferSem, HSID_BUFFER_SIZE);
+ sema_init(&sid->todoSem, 0);
+
+ spin_lock(&hsid_reg_lock);
+ hsid_reset(sid, arg & 0x0f);
+ spin_unlock(&hsid_reg_lock);
+ break;
+
+ case HSID_IOCTL_FIFOSIZE:
+ return put_user(HSID_BUFFER_SIZE, (int*)arg);
+
+ case HSID_IOCTL_FIFOFREE:
+ t = atomic_read(&sid->bufferSem.count);
+ return put_user(t, (int*)arg);
+
+ case HSID_IOCTL_SIDTYPE:
+ return put_user(sid->type, (int*)arg);
+
+ case HSID_IOCTL_CARDTYPE:
+ return put_user(sid->card, (int*)arg);
+
+ case HSID_IOCTL_MUTE:
+ sid->mute = arg & 0x7;
+ break;
+
+ case HSID_IOCTL_NOFILTER:
+ arg = (arg != 0);
+ if (arg == sid->filterEnabled)
+ break;
+ sid->filterEnabled = arg;
+ if (arg) /* Enabled, schedule restore of filters */
+ hsid_write_pr(sid, sid->filterReg | 0x1700);
+ break;
+
+ case HSID_IOCTL_FLUSH:
+ /* Wait until all writes are done */
+ while ( atomic_read(&sid->todoSem.count) > 0 )
+ {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+ }
+ break;
+
+ case HSID_IOCTL_DELAY:
+ hsid_delay (sid, (__u32) arg);
+ break;
+
+ case HSID_IOCTL_READ:
+ {
+ uint32_t parameter;
+ if ( get_user( parameter, (int*)arg) )
+ return -EFAULT;
+ return put_user( hsid_read_pr(sid, parameter), (int*) arg );
+ }
+ default:
+ printk(KERN_ERR "hardsid: unknown ioctl %x\n", cmd);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous data on an open, and clean up things on
+ * a close.
+ */
+
+/* We use hsid_lock to protect against concurrent opens. So the BKL is not
+ * needed here. Or anywhere else in this driver. */
+static int hsid_open(struct inode *inode, struct file *file)
+{
+ DECLARE_MUTEX_LOCKED(sem);
+ sid_d* sid;
+ uint32_t _minor;
+
+ spin_lock(&hsid_lock);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+ _minor = iminor(inode);
+#else
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,1)
+ _minor = minor(inode->i_rdev);
+#else
+ _minor = MINOR(inode->i_rdev);
+#endif
+#endif
+
+ if ( _minor >= sid_numSIDs )
+ goto out_busy;
+
+ sid = sid_data[_minor];
+ if(sid->status & HSID_IS_OPEN)
+ goto out_busy;
+
+ sid->status |= HSID_IS_OPEN;
+ sid->curCommand = 0;
+ sid->lastCommand = 0;
+ sid->cycles = 0;
+ sid->mute = 0; /* All 3 voices on */
+ sid->filterEnabled = 1;
+ sid->filterReg = 0;
+
+ sid->reads = 0;
+ sid->writes = 0;
+ sid->minDelay = -1;
+ sid->maxDelay = -1;
+ sid->longDelay = 0;
+ sid->shortDelay = 0;
+ sid->noDelay = 0;
+ sid->longDelays = 0;
+ sid->shortDelays = 0;
+ sid->noDelays = 0;
+ sid->jitter = 0;
+ do_gettimeofday(&sid->tv);
+ sid->seconds = sid->tv.tv_sec;
+
+#ifdef KERNEL_2_2
+ init_MUTEX(&sid->bufferSem);
+ init_MUTEX(&sid->todoSem);
+#endif
+ sema_init(&sid->bufferSem, HSID_BUFFER_SIZE);
+ sema_init(&sid->todoSem, 0);
+
+ sid_open++;
+
+ spin_unlock(&hsid_lock);
+
+ if ( thread == NULL )
+ {
+#ifdef KERNEL_2_2
+ init_MUTEX(&todoSem);
+#endif
+ sema_init(&todoSem, 0);
+
+ rmmod = 0;
+ notify = &sem;
+ kernel_thread(hsid_thread, (void *)sid_data, 0);
+ down(&sem);
+ notify = NULL;
+ }
+
+ return 0;
+
+ out_busy:
+ spin_unlock(&hsid_lock);
+ return -EBUSY;
+}
+
+static int hsid_fasync (int fd, struct file *filp, int on)
+
+{
+ return fasync_helper (fd, filp, on, &hsid_async_queue);
+}
+
+static int hsid_release(struct inode *inode, struct file *file)
+{
+ DECLARE_MUTEX_LOCKED(sem);
+ sid_d* sid;
+ uint32_t _minor;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
+ _minor = iminor(inode);
+#else
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,1)
+ _minor = minor(inode->i_rdev);
+#else
+ _minor = MINOR(inode->i_rdev);
+#endif
+#endif
+
+ if ( _minor >= sid_numSIDs )
+ return 0;
+
+ sid = sid_data[_minor];
+ if( !(sid->status & HSID_IS_OPEN) )
+ return 0;
+
+ sid_open--;
+
+ if (thread != 0 && sid_open == 0)
+ {
+ notify = &sem;
+ rmmod = 1;
+ up(&todoSem);
+ down(&sem);
+ notify = NULL;
+ rmmod = 0;
+ }
+
+ /* Reset the chip for returning to the sid pool */
+ switch (sid->card)
+ {
+ case SID_CARD_QUATTRO:
+ case SID_CARD_PCI_QUATTRO:
+ if (quattrohack)
+ {
+ int chip = sid->chip;
+ sid->chip = 0;
+ hsid_reset(sid, 0);
+ sid->chip = 1;
+ hsid_reset(sid, 0);
+ sid->chip = 2;
+ hsid_reset(sid, 0);
+ sid->chip = 3;
+ hsid_reset(sid, 0);
+ sid->chip = chip;
+ break;
+ }
+ default:
+ hsid_reset(sid, 0);
+ }
+
+ /* No need for locking -- nobody else can do anything until this rmw is
+ * committed */
+ sid->status &= ~HSID_IS_OPEN;
+
+ return 0;
+}
+
+static unsigned int hsid_poll(struct file *file, poll_table *wait)
+{
+ unsigned long l = 0;
+
+ poll_wait(file, &hsid_wait, wait);
+
+ spin_lock(&hsid_lock);
+
+/* TODO */
+
+ spin_unlock(&hsid_lock);
+
+ if (l != 0)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations hsid_fops =
+{
+#ifndef KERNEL_2_2
+ owner: THIS_MODULE,
+#endif
+ llseek: hsid_llseek,
+ read: hsid_read,
+ write: hsid_write,
+ poll: hsid_poll,
+ ioctl: hsid_ioctl,
+ open: hsid_open,
+ release: hsid_release,
+ fasync: hsid_fasync,
+};
+
+
+static sid_type hsid_detect(sid_d *sid)
+{
+ int i, val;
+
+ /* Reset the chip */
+ hsid_reset(sid, 0);
+
+ /* Set frequency */
+ sid->poke(sid, 0xf, 0xff);
+ udelay(4);
+
+ /* Set TEST bit to reset the noise generator */
+ sid->poke(sid, 0x12, 0x88);
+ udelay(10);
+
+ /* Clear TEST */
+ sid->poke(sid, 0x12, 0x80);
+ udelay(50);
+
+ /* Read the current oscillator 3 output */
+ val = sid->peek(sid, 0x1b);
+ for ( i = 0; i < 0xffff; i++ )
+ {
+ /* If the value changes, we might have a SID here */
+ if ( sid->peek(sid, 0x1b) != val )
+ break;
+ }
+ /* If we looped all the way, there is no SID */
+ if ( i == 0xffff )
+ return SID_NONE;
+
+ /* Reset the chip */
+ hsid_reset(sid, 0);
+
+ sid->poke(sid, 0xf, 0xff);
+ udelay(4);
+
+ /* Set combined waveform which doesn't work on 6581 */
+ sid->poke(sid, 0x12, 0x30);
+ udelay(50);
+
+ for ( i = 0; i < 0xffff; i++ )
+ {
+ if ( ( sid->peek(sid, 0x1b) & 0x80) != 0 )
+ break;
+ }
+
+ hsid_reset(sid, 0);
+
+ /* If the previous loop didn't finish, we have a 8580, otherwise 6581 */
+ if ( i == 0xffff )
+ return SID_6581;
+ else
+ return SID_8580;
+}
+
+static int hsid_detect_chips (sid_d *sid, int chips)
+{
+ int i, detected = 0;
+ sid_d *newsid;
+ for ( i = 0; i < chips; i++ )
+ {
+ sid->chip = (unsigned char) i;
+ sid->type = hsid_detect(sid);
+ if (sid->type == SID_NONE)
+ {
+ if ( detecthack )
+ sid->type = SID_6581;
+ else
+ continue;
+ }
+
+ newsid = sid_data[sid_numSIDs] =
+ kmalloc(sizeof(sid_d), GFP_KERNEL);
+ memcpy (newsid, sid, sizeof (sid_d));
+ sid_numSIDs++;
+ printk(KERN_INFO "%s card with %s as chip %d "
+ "detected @ %#x\n", sid_card[sid->card], sid->type == SID_6581? "6581":"8580",
+ i, sid->port);
+ detected = 1;
+ }
+ return detected;
+}
+
+static void hsid_register(void)
+{
+#ifdef CONFIG_DEVFS_FS
+ char device_name[16];
+ int i;
+ for (i = 0; i < sid_numSIDs; i++)
+ {
+ sid_d *sid = sid_data[i];
+ if ( !(sid->status & HSID_IS_REGISTERED) )
+ {
+ sprintf(device_name, "sid%d", i);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+ hsid_handles[i] = devfs_register(NULL, device_name, DEVFS_FL_DEFAULT,
+ major, i,
+ S_IFCHR | S_IRUGO | S_IWUGO,
+ &hsid_fops, NULL);
+#else
+ devfs_mk_cdev(MKDEV(major, i), S_IFCHR | S_IRUGO | S_IWUGO,
+ device_name, i);
+#endif
+ sid->status |= HSID_IS_REGISTERED;
+ }
+ }
+#endif
+}
+
+static int hsid_probe_isa (sid_d *sid)
+{
+ int i, val;
+
+ /* Reset the first two chips (doesn't matter if there isn't any as
+ chip 1) */
+ sid->chip = 0;
+ hsid_reset(sid, 0);
+ sid->chip = 1;
+ hsid_reset(sid, 0);
+ sid->chip = 0;
+
+ /* Program chip 0 for noise */
+
+ /* Set frequency */
+ sid->poke (sid, 0xf, 0xff);
+ udelay(4);
+
+ /* Set TEST bit to reset the noise generator */
+ sid->poke (sid, 0x12, 0x88);
+ udelay(10);
+
+ /* Clear TEST */
+ sid->poke (sid, 0x12, 0x80);
+ udelay(50);
+
+
+ /* Read the current oscillator 3 output from chip 0 */
+ val = sid->peek (sid, 0x1b);
+ for ( i = 0; i < 0xffff; i++ )
+ {
+ /* If the value changes, we have have a SID here */
+ if ( sid->peek (sid, 0x1b) != val )
+ break;
+ }
+
+ /* If we looped all the way, there is no SID here */
+ if ( i == 0xffff )
+ return SID_CARD_NONE;
+
+ /* Read the current oscillator 3 output from possible chip 1 */
+ sid->chip = 1;
+ val = sid->peek (sid, 0x1b);
+ for ( i = 0; i < 0xffff; i++ )
+ {
+ /* If the value changes, we have a regular HardSID */
+ if ( sid->peek (sid, 0x1b) != val )
+ break;
+ }
+ /* Reset the chip */
+ sid->chip = 0;
+ hsid_reset(sid, 0);
+
+ /* If we looped all the way, it is a Quattro */
+ if ( i == 0xffff )
+ return SID_CARD_QUATTRO;
+ return SID_CARD_HARDSID;
+}
+
+static int hsid_probe_pci(struct pci_dev *pcidev,
+ const struct pci_device_id *pciid)
+{
+ int iobase1, iobase2;
+ sid_d *sid = &sid_setup;
+ int err;
+ u8 rev;
+ sid_card_type card;
+
+ /* This need testing */
+ pci_read_config_byte (pcidev, PCI_REVISION_ID, &rev);
+ switch (rev)
+ {
+ case 1:
+ card = SID_CARD_PCI_HARDSID;
+ break;
+ case 2:
+ card = SID_CARD_PCI_QUATTRO;
+ break;
+ default:
+ /* The card is not ours or is a new model ... */
+ return 0;
+ }
+
+ if(( err = pci_enable_device(pcidev) ))
+ {
+ printk(KERN_ERR "hardsid: could not enable device\n");
+ return err;
+ }
+
+ /* Allocate all card resources */
+ if ( (err = pci_request_regions(pcidev, "hardsid")) )
+ return err;
+
+ /* get the io addresses */
+ iobase1 = pci_resource_start(pcidev, 0);
+ if (pci_resource_len(pcidev, 1))
+ iobase2 = pci_resource_start(pcidev, 1);
+ else
+ iobase2 = iobase1 + 0x0400;
+
+ // Initialize PCI controller
+ outb(0xff, iobase1 + 0x0);
+ outb(0x80, iobase2 + 0x2);
+ outb(0x00, iobase1 + 0x2);
+ udelay (100);
+ outb(0x24, iobase1 + 0x2);
+
+ memset (sid, 0, sizeof (sid_d));
+ sid->card = card;
+ sid->peek = hsid_peek_pci;
+ sid->poke = hsid_poke_pci;
+ sid->port = (unsigned short) iobase1;
+ sid->port2 = (unsigned short) iobase2;
+ sid->pcidev = pcidev;
+
+ if (sid->card == SID_CARD_PCI_QUATTRO)
+ err = hsid_detect_chips(sid, HSID_MAX_SIDS_PER_CARD);
+ else
+ err = hsid_detect_chips(sid, 1);
+
+ if (err == 0)
+ {
+ printk(KERN_ERR "hardsid: no SID detected on card in 0x%04x\n",
+ sid->port);
+ pci_release_regions(pcidev);
+ sid->pcidev = 0;
+ return -EIO; /* Correct for multiple cards? */
+ }
+
+ hsid_register ();
+ return 0;
+}
+
+static int hsid_probe_cmk3(struct pci_dev *pcidev,
+ const struct pci_device_id *pciid)
+{
+ int cw_iobase;
+ sid_d *sid = &sid_setup;
+ int err;
+
+ if(( err = pci_enable_device(pcidev) ))
+ {
+ printk(KERN_ERR "hardsid: could not enable device\n");
+ return err;
+ }
+
+ cw_iobase = pci_resource_start(pcidev, 0);
+
+ if(!request_region(cw_iobase, 256, "hardsid"))
+ {
+ printk(KERN_ERR "hardsid: IO-ports 0x%04x-0x%04x in use\n", cw_iobase, cw_iobase+255);
+ return -EBUSY;
+ }
+
+ // Initialize PCI controller
+ outb(0xf1, cw_iobase + 0x0);
+ outb(0x00, cw_iobase + 0x1);
+ outb(0x00, cw_iobase + 0x2);
+ outb(0x00, cw_iobase + 0x4);
+ outb(0x00, cw_iobase + 0x5);
+ outb(0x00, cw_iobase + 0x29);
+ outb(0x00, cw_iobase + 0x2b);
+
+ // Init the rest
+ memset (sid, 0, sizeof (sid_d));
+ sid->peek = hsid_peek_cwmk3;
+ sid->poke = hsid_poke_cwmk3;
+ sid->port = (unsigned short) cw_iobase;
+ sid->card = SID_CARD_CWMK3;
+ sid->type = hsid_detect(sid);
+ sid->pcidev = pcidev;
+
+ if ( !hsid_detect_chips (sid, 1) )
+ {
+ printk(KERN_ERR "hardsid: no SID detected on card in 0x%04x\n",
+ cw_iobase);
+ release_region(cw_iobase, 256);
+ sid->port = 0;
+ sid->pcidev = 0;
+ return -EIO; /* Correct for multiple cards? */
+ }
+
+ hsid_register ();
+ return 0;
+}
+
+static int __init hsid_probe(struct pci_dev *pcidev,
+ const struct pci_device_id *pciid)
+{
+ switch (pcidev->vendor)
+ {
+ case PCI_VENDOR_ID_INDIVIDUAL:
+ return hsid_probe_cmk3 (pcidev, pciid);
+ case PCI_VENDOR_ID_HARDSOFTWARE:
+ return hsid_probe_pci (pcidev, pciid);
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+MODULE_DEVICE_TABLE(pci, id_table);
+
+/* @FIXME@ Need more ?? PCI devices are hotswapable */
+static struct pci_driver hsid_driver =
+{
+ name: "hardsid",
+ id_table: id_table,
+ probe: hsid_probe,
+};
+
+static void __exit hsid_exit (void);
+
+static int __init hsid_init(void)
+{
+ int i, ret;
+ sid_d *sid = &sid_setup;
+ int gotmajor;
+
+ printk(KERN_INFO "HardSID Driver v" HSID_VERSION "\n");
+ sid_numSIDs = 0;
+ sid_open = 0;
+ isa_allocated = 0;
+ pci_allocated = 0;
+ spin_lock_init(&hsid_lock);
+ spin_lock_init(&hsid_reg_lock);
+
+ /* Once registered always leave them that way till module is unloaded.
+ * This allows support for PCI hotswappable support */
+ gotmajor = register_chrdev(major, "hardsid", &hsid_fops);
+ if ( gotmajor < 0 )
+ {
+ printk(KERN_ERR "hardsid: could not register major number %d.\n",
+ major);
+ return -EIO;
+ }
+ /* If major was 0, we asked for a dynamic major number, use it */
+ if ( major == 0 )
+ {
+ major = gotmajor;
+ printk(KERN_INFO "Using major number %d.\n", major);
+ }
+
+#ifdef CONFIG_DEVFS_FS
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+ if (devfs_register_chrdev(major, "hardsid", &hsid_fops))
+ {
+ printk(KERN_ERR "hardsid: could not register major number %d.\n",
+ major);
+ return -EIO;
+ }
+#endif
+#endif
+
+#ifdef CONFIG_PROC_FS
+#ifndef KERNEL_2_2
+ create_proc_read_entry ("hardsid", 0, 0, hsid_read_proc, NULL);
+#else
+ proc_register(&proc_root, &hsid_proc_entry);
+#endif
+#endif
+
+ do
+ {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,55)
+ if (check_region (io, ioextent))
+ {
+ printk(KERN_ERR "hardsid: I/O port #%x is not free.\n", io);
+ break;
+ }
+#endif
+ if(!request_region(io, ioextent, "hardsid"))
+ {
+ printk(KERN_ERR "hardsid: I/O port #%x is not free.\n", io);
+ break;
+ }
+ isa_allocated = 1;
+
+ memset (sid, 0, sizeof (sid_d));
+ sid->peek = hsid_peek_isa;
+ sid->poke = hsid_poke_isa;
+ if (slowaccess)
+ sid->poke = hsid_poke_isa_slowaccess;
+
+ for ( i = io; i < io + ioextent; i += 2)
+ {
+ sid->port = (unsigned short) i;
+ sid->card = hsid_probe_isa(sid);
+
+ if ( detecthack)
+ if ( i == io ) sid->card = SID_CARD_HARDSID;
+
+ switch(sid->card)
+ {
+ case SID_CARD_QUATTRO:
+ (void) hsid_detect_chips (sid, HSID_MAX_SIDS_PER_CARD);
+ break;
+
+ case SID_CARD_HARDSID:
+ (void) hsid_detect_chips (sid, 1);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if ( sid_numSIDs )
+ hsid_register ();
+ else
+ {
+ release_region(io, ioextent);
+ isa_allocated = 0;
+ }
+ } while (0);
+
+
+ ret = pci_module_init(&hsid_driver);
+ pci_allocated = (ret == 0);
+ if (sid_numSIDs || pci_allocated)
+ return 0;
+
+ printk(KERN_ERR "hardsid: could not find any HardSID cards.\n");
+ /* Clean up */
+ hsid_exit ();
+ return ret;
+}
+
+static void __exit hsid_exit (void)
+{
+ int i;
+
+ if ( isa_allocated )
+ release_region(io, ioextent);
+
+ if ( pci_allocated )
+ pci_unregister_driver(&hsid_driver);
+
+ for ( i = 0; i < sid_numSIDs; i++ )
+ {
+ switch( sid_data[i]->card )
+ {
+ case SID_CARD_CWMK3:
+ if ( sid_data[i]->port )
+ release_region(sid_data[i]->port, 256);
+ break;
+ case SID_CARD_PCI_HARDSID:
+ case SID_CARD_PCI_QUATTRO:
+ if ( sid_data[i]->pcidev )
+ pci_release_regions(sid_data[i]->pcidev);
+ break;
+ default:
+ break;
+ }
+ kfree(sid_data[i]);
+ }
+#ifdef CONFIG_PROC_FS
+#ifndef KERNEL_2_2
+ remove_proc_entry ("hardsid", NULL);
+#else
+ proc_unregister(&proc_root, hsid_proc_entry.low_ino);
+#endif
+#endif
+
+ unregister_chrdev(major, "hardsid");
+#ifdef CONFIG_DEVFS_FS
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+ devfs_unregister_chrdev(major, "hardsid");
+#endif
+ for ( i = 0; i < sid_numSIDs; i++ )
+ {
+ sid_d *sid = sid_data[i];
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+ if ( hsid_handles[i] )
+ devfs_unregister(hsid_handles[i]);
+#else
+ char device_name[16];
+ sprintf(device_name, "sid%d", i);
+ devfs_remove(device_name, i);
+#endif
+ sid->status &= ~HSID_IS_REGISTERED;
+ }
+#endif
+}
+
+
+/*
+ * Info exported via "/proc/driver/hardsid".
+ */
+
+static int hsid_proc_output (char *buf)
+{
+ char *p;
+ struct timeval tv;
+ int rps, wps, lps, sps, nps;
+ int ald, asd, and, jpa;
+ sid_d* sid;
+ int i;
+
+ do_gettimeofday(&tv);
+
+ p = buf;
+
+ for ( i = 0; i < sid_numSIDs; i++ )
+ {
+ sid = sid_data[i];
+
+ rps = 0; wps = 0; lps = 0; sps = 0; nps = 0;
+ ald = 0; asd = 0; and = 0; jpa = 0;
+
+ if ( tv.tv_sec > sid->seconds )
+ {
+ rps = sid->reads / (tv.tv_sec - sid->seconds);
+ wps = sid->writes / (tv.tv_sec - sid->seconds);
+ lps = sid->longDelay / (tv.tv_sec - sid->seconds);
+ sps = sid->shortDelay / (tv.tv_sec - sid->seconds);
+ nps = sid->noDelay / (tv.tv_sec - sid->seconds);
+ }
+ if ( sid->longDelay )
+ ald = sid->longDelays / sid->longDelay;
+ if ( sid->shortDelay )
+ asd = sid->shortDelays / sid->shortDelay;
+ if ( sid->noDelay )
+ and = sid->noDelays / sid->noDelay;
+
+ if ( sid->writes + sid->reads != 0 )
+ jpa = sid->jitter / ( sid->writes + sid->reads);
+
+ p += sprintf(p, "%s configured for port %#x\n",
+ sid_card[sid->card], sid->port);
+ p += sprintf(p, "SID type %s\n",
+ sid->type == SID_6581? "6581": "8580");
+ p += sprintf(p, "Muted: ");
+
+ if (sid->mute)
+ {
+ int j;
+ for (j = 0; j < 3; j++)
+ {
+ if ((sid->mute >> j) & 1)
+ p += sprintf(p, "%d ", j + 1);
+ }
+ }
+ else
+ p += sprintf(p, "None");
+
+ p += sprintf(p, "\nFilter: %s\n", sid->filterEnabled ? "Enabled" : "Disabled");
+ p += sprintf(p, "Writes: %8d Reads: %8d\n", sid->writes, sid->reads);
+ p += sprintf(p, "Per/s: %8d %8d\n", wps, rps);
+ p += sprintf(p, "Delays: %8d - %8d\n", sid->minDelay,
+ sid->maxDelay);
+ p += sprintf(p, "Long: %8d avg. %8d %8d/s\n", sid->longDelay, ald,
+ lps);
+ p += sprintf(p, "Short: %8d avg. %8d %8d/s\n", sid->shortDelay, asd,
+ sps);
+ p += sprintf(p, "None: %8d avg. %8d %8d/s\n", sid->noDelay, and,
+ nps);
+ p += sprintf(p, "Jitter: %8d avg. %8d\n", sid->jitter, jpa);
+ }
+ return p - buf;
+}
+
+
+#ifndef KERNEL_2_2
+static int hsid_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+#else
+static int hsid_read_proc(char *page, char **start, off_t off,
+ int count, int unused)
+#endif
+{
+ int len = hsid_proc_output (page);
+#ifndef KERNEL_2_2
+ if (len <= off+count) *eof = 1;
+#endif
+ *start = page + off;
+ len -= off;
+ if (len > count) len = count;
+ if (len < 0) len = 0;
+ return len;
+}
+
+
+module_init(hsid_init);
+module_exit(hsid_exit);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,50)
+EXPORT_NO_SYMBOLS;
+#endif
+MODULE_AUTHOR("Jarno Paananen");
+MODULE_DESCRIPTION("Driver for HardSID card");
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("GPL");
+#endif
+
+#define MODULE_PARM(x,y) module_param(x,uint,0)
+
+MODULE_PARM(io, "i");
+MODULE_PARM_DESC(io, "Base I/O-port (default=0x300), use only if"
+ "autodetection fails");
+MODULE_PARM(ioextent, "i");
+MODULE_PARM_DESC(ioextent, "Number of addresses to reserve (default=8), use"
+ "only if autodetection fails");
+MODULE_PARM(major, "i");
+MODULE_PARM_DESC(major, "Device major number to use (default=60)");
+MODULE_PARM(slowaccess, "i");
+MODULE_PARM_DESC(slowaccess, "Force use 8-bit I/O if the default 16-bit I/O"
+ "doesn't work");
+MODULE_PARM(detecthack, "i");
+MODULE_PARM_DESC(detecthack, "Force loading of the driver without a card,"
+ "fakes a regular HardSID with 6581 at 0x300");
+MODULE_PARM(quattrohack, "i");
+MODULE_PARM_DESC(quattrohack, "Play the first SID data on all chips in a"
+ "Quattro");
+MODULE_PARM(renice, "i");
+MODULE_PARM_DESC(renice, "Set the kernel thread renice value");
+