/* * * Linux driver for the HardSID cards by Hard Software * and Catweasel MK3 by Individual Computers * * Copyright (C) 2000-2003 Jarno Paananen * Copyright (C) 2001-2003 Simon White * * 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 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,10) #include #endif /*#include */ #include #include #include #include #include #include #include #include #include #include #include #include #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 #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");