📄 esssolo1.c
字号:
/****************************************************************************//* * esssolo1.c -- ESS Technology Solo1 (ES1946) audio driver. * * Copyright (C) 1998-2001 Thomas Sailer (t.sailer@alumni.ethz.ch) * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Module command line parameters: * none so far * * Supported devices: * /dev/dsp standard /dev/dsp device, (mostly) OSS compatible * /dev/mixer standard /dev/mixer device, (mostly) OSS compatible * /dev/midi simple MIDI UART interface, no ioctl * * Revision history * 10.11.1998 0.1 Initial release (without any hardware) * 22.03.1999 0.2 cinfo.blocks should be reset after GETxPTR ioctl. * reported by Johan Maes <joma@telindus.be> * return EAGAIN instead of EBUSY when O_NONBLOCK * read/write cannot be executed * 07.04.1999 0.3 implemented the following ioctl's: SOUND_PCM_READ_RATE, * SOUND_PCM_READ_CHANNELS, SOUND_PCM_READ_BITS; * Alpha fixes reported by Peter Jones <pjones@redhat.com> * 15.06.1999 0.4 Fix bad allocation bug. * Thanks to Deti Fliegl <fliegl@in.tum.de> * 28.06.1999 0.5 Add pci_set_master * 12.08.1999 0.6 Fix MIDI UART crashing the driver * Changed mixer semantics from OSS documented * behaviour to OSS "code behaviour". * Recording might actually work now. * The real DDMA controller address register is at PCI config * 0x60, while the register at 0x18 is used as a placeholder * register for BIOS address allocation. This register * is supposed to be copied into 0x60, according * to the Solo1 datasheet. When I do that, I can access * the DDMA registers except the mask bit, which * is stuck at 1. When I copy the contents of 0x18 +0x10 * to the DDMA base register, everything seems to work. * The fun part is that the Windows Solo1 driver doesn't * seem to do these tricks. * Bugs remaining: plops and clicks when starting/stopping playback * 31.08.1999 0.7 add spin_lock_init * replaced current->state = x with set_current_state(x) * 03.09.1999 0.8 change read semantics for MIDI to match * OSS more closely; remove possible wakeup race * 07.10.1999 0.9 Fix initialization; complain if sequencer writes time out * Revised resource grabbing for the FM synthesizer * 28.10.1999 0.10 More waitqueue races fixed * 09.12.1999 0.11 Work around stupid Alpha port issue (virt_to_bus(kmalloc(GFP_DMA)) > 16M) * Disabling recording on Alpha * 12.01.2000 0.12 Prevent some ioctl's from returning bad count values on underrun/overrun; * Tim Janik's BSE (Bedevilled Sound Engine) found this * Integrated (aka redid 8-)) APM support patch by Zach Brown * 07.02.2000 0.13 Use pci_alloc_consistent and pci_register_driver * 19.02.2000 0.14 Use pci_dma_supported to determine if recording should be disabled * 13.03.2000 0.15 Reintroduce initialization of a couple of PCI config space registers * 21.11.2000 0.16 Initialize dma buffers in poll, otherwise poll may return a bogus mask * 12.12.2000 0.17 More dma buffer initializations, patch from * Tjeerd Mulder <tjeerd.mulder@fujitsu-siemens.com> * 31.01.2001 0.18 Register/Unregister gameport, original patch from * Nathaniel Daw <daw@cs.cmu.edu> * Fix SETTRIGGER non OSS API conformity * 10.03.2001 provide abs function, prevent picking up a bogus kernel macro * for abs. Bug report by Andrew Morton <andrewm@uow.edu.au> * 15.05.2001 pci_enable_device moved, return values in probe cleaned * up. Marcus Meissner <mm@caldera.de> * 22.05.2001 0.19 more cleanups, changed PM to PCI 2.4 style, got rid * of global list of devices, using pci device data. * Marcus Meissner <mm@caldera.de> *//*****************************************************************************/ #include <linux/version.h>#include <linux/module.h>#include <linux/string.h>#include <linux/ioport.h>#include <linux/sched.h>#include <linux/delay.h>#include <linux/sound.h>#include <linux/slab.h>#include <linux/soundcard.h>#include <linux/pci.h>#include <linux/bitops.h>#include <asm/io.h>#include <asm/dma.h>#include <linux/init.h>#include <linux/poll.h>#include <linux/spinlock.h>#include <linux/smp_lock.h>#include <linux/wrapper.h>#include <asm/uaccess.h>#include <asm/hardirq.h>#include <linux/gameport.h>#include "dm.h"/* --------------------------------------------------------------------- */#undef OSS_DOCUMENTED_MIXER_SEMANTICS/* --------------------------------------------------------------------- */#ifndef PCI_VENDOR_ID_ESS#define PCI_VENDOR_ID_ESS 0x125d#endif#ifndef PCI_DEVICE_ID_ESS_SOLO1#define PCI_DEVICE_ID_ESS_SOLO1 0x1969#endif#define SOLO1_MAGIC ((PCI_VENDOR_ID_ESS<<16)|PCI_DEVICE_ID_ESS_SOLO1)#define DDMABASE_OFFSET 0 /* chip bug workaround kludge */#define DDMABASE_EXTENT 16#define IOBASE_EXTENT 16#define SBBASE_EXTENT 16#define VCBASE_EXTENT (DDMABASE_EXTENT+DDMABASE_OFFSET)#define MPUBASE_EXTENT 4#define GPBASE_EXTENT 4#define GAMEPORT_EXTENT 4#define FMSYNTH_EXTENT 4/* MIDI buffer sizes */#define MIDIINBUF 256#define MIDIOUTBUF 256#define FMODE_MIDI_SHIFT 3#define FMODE_MIDI_READ (FMODE_READ << FMODE_MIDI_SHIFT)#define FMODE_MIDI_WRITE (FMODE_WRITE << FMODE_MIDI_SHIFT)#define FMODE_DMFM 0x10static struct pci_driver solo1_driver;/* --------------------------------------------------------------------- */struct solo1_state { /* magic */ unsigned int magic; /* the corresponding pci_dev structure */ struct pci_dev *dev; /* soundcore stuff */ int dev_audio; int dev_mixer; int dev_midi; int dev_dmfm; /* hardware resources */ unsigned long iobase, sbbase, vcbase, ddmabase, mpubase; /* long for SPARC */ unsigned int irq; /* mixer registers */ struct { unsigned short vol[10]; unsigned int recsrc; unsigned int modcnt; unsigned short micpreamp; } mix; /* wave stuff */ unsigned fmt; unsigned channels; unsigned rate; unsigned char clkdiv; unsigned ena; spinlock_t lock; struct semaphore open_sem; mode_t open_mode; wait_queue_head_t open_wait; struct dmabuf { void *rawbuf; dma_addr_t dmaaddr; unsigned buforder; unsigned numfrag; unsigned fragshift; unsigned hwptr, swptr; unsigned total_bytes; int count; unsigned error; /* over/underrun */ wait_queue_head_t wait; /* redundant, but makes calculations easier */ unsigned fragsize; unsigned dmasize; unsigned fragsamples; /* OSS stuff */ unsigned mapped:1; unsigned ready:1; unsigned endcleared:1; unsigned enabled:1; unsigned ossfragshift; int ossmaxfrags; unsigned subdivision; } dma_dac, dma_adc; /* midi stuff */ struct { unsigned ird, iwr, icnt; unsigned ord, owr, ocnt; wait_queue_head_t iwait; wait_queue_head_t owait; struct timer_list timer; unsigned char ibuf[MIDIINBUF]; unsigned char obuf[MIDIOUTBUF]; } midi; struct gameport gameport;};/* --------------------------------------------------------------------- */static inline void write_seq(struct solo1_state *s, unsigned char data){ int i; unsigned long flags; /* the __cli stunt is to send the data within the command window */ for (i = 0; i < 0xffff; i++) { __save_flags(flags); __cli(); if (!(inb(s->sbbase+0xc) & 0x80)) { outb(data, s->sbbase+0xc); __restore_flags(flags); return; } __restore_flags(flags); } printk(KERN_ERR "esssolo1: write_seq timeout\n"); outb(data, s->sbbase+0xc);}static inline int read_seq(struct solo1_state *s, unsigned char *data){ int i; if (!data) return 0; for (i = 0; i < 0xffff; i++) if (inb(s->sbbase+0xe) & 0x80) { *data = inb(s->sbbase+0xa); return 1; } printk(KERN_ERR "esssolo1: read_seq timeout\n"); return 0;}static int inline reset_ctrl(struct solo1_state *s){ int i; outb(3, s->sbbase+6); /* clear sequencer and FIFO */ udelay(10); outb(0, s->sbbase+6); for (i = 0; i < 0xffff; i++) if (inb(s->sbbase+0xe) & 0x80) if (inb(s->sbbase+0xa) == 0xaa) { write_seq(s, 0xc6); /* enter enhanced mode */ return 1; } return 0;}static void write_ctrl(struct solo1_state *s, unsigned char reg, unsigned char data){ write_seq(s, reg); write_seq(s, data);}#if 0 /* unused */static unsigned char read_ctrl(struct solo1_state *s, unsigned char reg){ unsigned char r; write_seq(s, 0xc0); write_seq(s, reg); read_seq(s, &r); return r;}#endif /* unused */static void write_mixer(struct solo1_state *s, unsigned char reg, unsigned char data){ outb(reg, s->sbbase+4); outb(data, s->sbbase+5);}static unsigned char read_mixer(struct solo1_state *s, unsigned char reg){ outb(reg, s->sbbase+4); return inb(s->sbbase+5);}/* --------------------------------------------------------------------- */static inline unsigned ld2(unsigned int x){ unsigned r = 0; if (x >= 0x10000) { x >>= 16; r += 16; } if (x >= 0x100) { x >>= 8; r += 8; } if (x >= 0x10) { x >>= 4; r += 4; } if (x >= 4) { x >>= 2; r += 2; } if (x >= 2) r++; return r;}/* --------------------------------------------------------------------- */static inline void stop_dac(struct solo1_state *s){ unsigned long flags; spin_lock_irqsave(&s->lock, flags); s->ena &= ~FMODE_WRITE; write_mixer(s, 0x78, 0x10); spin_unlock_irqrestore(&s->lock, flags);}static void start_dac(struct solo1_state *s){ unsigned long flags; spin_lock_irqsave(&s->lock, flags); if (!(s->ena & FMODE_WRITE) && (s->dma_dac.mapped || s->dma_dac.count > 0) && s->dma_dac.ready) { s->ena |= FMODE_WRITE; write_mixer(s, 0x78, 0x12); udelay(10); write_mixer(s, 0x78, 0x13); } spin_unlock_irqrestore(&s->lock, flags);} static inline void stop_adc(struct solo1_state *s){ unsigned long flags; spin_lock_irqsave(&s->lock, flags); s->ena &= ~FMODE_READ; write_ctrl(s, 0xb8, 0xe); spin_unlock_irqrestore(&s->lock, flags);}static void start_adc(struct solo1_state *s){ unsigned long flags; spin_lock_irqsave(&s->lock, flags); if (!(s->ena & FMODE_READ) && (s->dma_adc.mapped || s->dma_adc.count < (signed)(s->dma_adc.dmasize - 2*s->dma_adc.fragsize)) && s->dma_adc.ready) { s->ena |= FMODE_READ; write_ctrl(s, 0xb8, 0xf);#if 0 printk(KERN_DEBUG "solo1: DMAbuffer: 0x%08lx\n", (long)s->dma_adc.rawbuf); printk(KERN_DEBUG "solo1: DMA: mask: 0x%02x cnt: 0x%04x addr: 0x%08x stat: 0x%02x\n", inb(s->ddmabase+0xf), inw(s->ddmabase+4), inl(s->ddmabase), inb(s->ddmabase+8));#endif outb(0, s->ddmabase+0xd); /* master reset */ outb(1, s->ddmabase+0xf); /* mask */ outb(0x54/*0x14*/, s->ddmabase+0xb); /* DMA_MODE_READ | DMA_MODE_AUTOINIT */ outl(virt_to_bus(s->dma_adc.rawbuf), s->ddmabase); outw(s->dma_adc.dmasize-1, s->ddmabase+4); outb(0, s->ddmabase+0xf); } spin_unlock_irqrestore(&s->lock, flags);#if 0 printk(KERN_DEBUG "solo1: start DMA: reg B8: 0x%02x SBstat: 0x%02x\n" KERN_DEBUG "solo1: DMA: stat: 0x%02x cnt: 0x%04x mask: 0x%02x\n", read_ctrl(s, 0xb8), inb(s->sbbase+0xc), inb(s->ddmabase+8), inw(s->ddmabase+4), inb(s->ddmabase+0xf)); printk(KERN_DEBUG "solo1: A1: 0x%02x A2: 0x%02x A4: 0x%02x A5: 0x%02x A8: 0x%02x\n" KERN_DEBUG "solo1: B1: 0x%02x B2: 0x%02x B4: 0x%02x B7: 0x%02x B8: 0x%02x B9: 0x%02x\n", read_ctrl(s, 0xa1), read_ctrl(s, 0xa2), read_ctrl(s, 0xa4), read_ctrl(s, 0xa5), read_ctrl(s, 0xa8), read_ctrl(s, 0xb1), read_ctrl(s, 0xb2), read_ctrl(s, 0xb4), read_ctrl(s, 0xb7), read_ctrl(s, 0xb8), read_ctrl(s, 0xb9));#endif}/* --------------------------------------------------------------------- */#define DMABUF_DEFAULTORDER (15-PAGE_SHIFT)#define DMABUF_MINORDER 1static inline void dealloc_dmabuf(struct solo1_state *s, struct dmabuf *db){ struct page *page, *pend; if (db->rawbuf) { /* undo marking the pages as reserved */ pend = virt_to_page(db->rawbuf + (PAGE_SIZE << db->buforder) - 1); for (page = virt_to_page(db->rawbuf); page <= pend; page++) mem_map_unreserve(page); pci_free_consistent(s->dev, PAGE_SIZE << db->buforder, db->rawbuf, db->dmaaddr); } db->rawbuf = NULL; db->mapped = db->ready = 0;}static int prog_dmabuf(struct solo1_state *s, struct dmabuf *db){ int order; unsigned bytespersec; unsigned bufs, sample_shift = 0; struct page *page, *pend; db->hwptr = db->swptr = db->total_bytes = db->count = db->error = db->endcleared = 0; if (!db->rawbuf) { db->ready = db->mapped = 0; for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) if ((db->rawbuf = pci_alloc_consistent(s->dev, PAGE_SIZE << order, &db->dmaaddr))) break; if (!db->rawbuf)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -