diff options
author | Ian C <ianc@noddybox.co.uk> | 2007-07-05 00:17:20 +0000 |
---|---|---|
committer | Ian C <ianc@noddybox.co.uk> | 2007-07-05 00:17:20 +0000 |
commit | 2fc4bfec8dacfbf83aac65a36a5cae60ab994b4a (patch) | |
tree | 8f2d94ee2be8f24e0d4b67d1caa0137bb4b5e695 /hardsid.c | |
parent | dbde49327daa8759b87a8c3e0bfebf8b8bb8b3b7 (diff) |
which included commits to RCS files with non-trunk default branches.
Diffstat (limited to 'hardsid.c')
-rw-r--r-- | hardsid.c | 1840 |
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(¤t->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"); + |