📄 als300.c
字号:
/* * als300.c - driver for Avance Logic ALS300/ALS300+ soundcards. * Copyright (C) 2005 by Ash Willis <ashwillis@programmer.net> * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * TODO * 4 channel playback for ALS300+ * gameport * mpu401 * opl3 * * NOTES * The BLOCK_COUNTER registers for the ALS300(+) return a figure related to * the position in the current period, NOT the whole buffer. It is important * to know which period we are in so we can calculate the correct pointer. * This is why we always use 2 periods. We can then use a flip-flop variable * to keep track of what period we are in. */#include <sound/driver.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/moduleparam.h>#include <linux/pci.h>#include <linux/dma-mapping.h>#include <linux/interrupt.h>#include <linux/slab.h>#include <asm/io.h>#include <sound/core.h>#include <sound/control.h>#include <sound/initval.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/ac97_codec.h>#include <sound/opl3.h>/* snd_als300_set_irq_flag */#define IRQ_DISABLE 0#define IRQ_ENABLE 1/* I/O port layout */#define AC97_ACCESS 0x00#define AC97_READ 0x04#define AC97_STATUS 0x06#define AC97_DATA_AVAIL (1<<6)#define AC97_BUSY (1<<7)#define ALS300_IRQ_STATUS 0x07 /* ALS300 Only */#define IRQ_PLAYBACK (1<<3)#define IRQ_CAPTURE (1<<2)#define GCR_DATA 0x08#define GCR_INDEX 0x0C#define ALS300P_DRAM_IRQ_STATUS 0x0D /* ALS300+ Only */#define MPU_IRQ_STATUS 0x0E /* ALS300 Rev. E+, ALS300+ */#define ALS300P_IRQ_STATUS 0x0F /* ALS300+ Only *//* General Control Registers */#define PLAYBACK_START 0x80#define PLAYBACK_END 0x81#define PLAYBACK_CONTROL 0x82#define TRANSFER_START (1<<16)#define FIFO_PAUSE (1<<17)#define RECORD_START 0x83#define RECORD_END 0x84#define RECORD_CONTROL 0x85#define DRAM_WRITE_CONTROL 0x8B#define WRITE_TRANS_START (1<<16)#define DRAM_MODE_2 (1<<17)#define MISC_CONTROL 0x8C#define IRQ_SET_BIT (1<<15)#define VMUTE_NORMAL (1<<20)#define MMUTE_NORMAL (1<<21)#define MUS_VOC_VOL 0x8E#define PLAYBACK_BLOCK_COUNTER 0x9A#define RECORD_BLOCK_COUNTER 0x9B#define DEBUG_CALLS 0#define DEBUG_PLAY_REC 0#if DEBUG_CALLS#define snd_als300_dbgcalls(format, args...) printk(format, ##args)#define snd_als300_dbgcallenter() printk(KERN_ERR "--> %s\n", __FUNCTION__)#define snd_als300_dbgcallleave() printk(KERN_ERR "<-- %s\n", __FUNCTION__)#else#define snd_als300_dbgcalls(format, args...)#define snd_als300_dbgcallenter()#define snd_als300_dbgcallleave()#endif#if DEBUG_PLAY_REC#define snd_als300_dbgplay(format, args...) printk(KERN_ERR format, ##args)#else#define snd_als300_dbgplay(format, args...)#endif enum {DEVICE_ALS300, DEVICE_ALS300_PLUS};MODULE_AUTHOR("Ash Willis <ashwillis@programmer.net>");MODULE_DESCRIPTION("Avance Logic ALS300");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS300},{Avance Logic,ALS300+}}");static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;struct snd_als300 { unsigned long port; spinlock_t reg_lock; struct snd_card *card; struct pci_dev *pci; struct snd_pcm *pcm; struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *capture_substream; struct snd_ac97 *ac97; struct snd_opl3 *opl3; struct resource *res_port; int irq; int chip_type; /* ALS300 or ALS300+ */ char revision; };struct snd_als300_substream_data { int period_flipflop; int control_register; int block_counter_register;};static struct pci_device_id snd_als300_ids[] = { { 0x4005, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300 }, { 0x4005, 0x0308, PCI_ANY_ID, PCI_ANY_ID, 0, 0, DEVICE_ALS300_PLUS }, { 0, }};MODULE_DEVICE_TABLE(pci, snd_als300_ids);static inline u32 snd_als300_gcr_read(unsigned long port, unsigned short reg){ outb(reg, port+GCR_INDEX); return inl(port+GCR_DATA);}static inline void snd_als300_gcr_write(unsigned long port, unsigned short reg, u32 val){ outb(reg, port+GCR_INDEX); outl(val, port+GCR_DATA);}/* Enable/Disable Interrupts */static void snd_als300_set_irq_flag(struct snd_als300 *chip, int cmd){ u32 tmp = snd_als300_gcr_read(chip->port, MISC_CONTROL); snd_als300_dbgcallenter(); /* boolean XOR check, since old vs. new hardware have directly reversed bit setting for ENABLE and DISABLE. ALS300+ acts like newer versions of ALS300 */ if (((chip->revision > 5 || chip->chip_type == DEVICE_ALS300_PLUS) ^ (cmd == IRQ_ENABLE)) == 0) tmp |= IRQ_SET_BIT; else tmp &= ~IRQ_SET_BIT; snd_als300_gcr_write(chip->port, MISC_CONTROL, tmp); snd_als300_dbgcallleave();}static int snd_als300_free(struct snd_als300 *chip){ snd_als300_dbgcallenter(); snd_als300_set_irq_flag(chip, IRQ_DISABLE); if (chip->irq >= 0) free_irq(chip->irq, chip); pci_release_regions(chip->pci); pci_disable_device(chip->pci); kfree(chip); snd_als300_dbgcallleave(); return 0;}static int snd_als300_dev_free(struct snd_device *device){ struct snd_als300 *chip = device->device_data; return snd_als300_free(chip);}static irqreturn_t snd_als300_interrupt(int irq, void *dev_id){ u8 status; struct snd_als300 *chip = dev_id; struct snd_als300_substream_data *data; status = inb(chip->port+ALS300_IRQ_STATUS); if (!status) /* shared IRQ, for different device?? Exit ASAP! */ return IRQ_NONE; /* ACK everything ASAP */ outb(status, chip->port+ALS300_IRQ_STATUS); if (status & IRQ_PLAYBACK) { if (chip->pcm && chip->playback_substream) { data = chip->playback_substream->runtime->private_data; data->period_flipflop ^= 1; snd_pcm_period_elapsed(chip->playback_substream); snd_als300_dbgplay("IRQ_PLAYBACK\n"); } } if (status & IRQ_CAPTURE) { if (chip->pcm && chip->capture_substream) { data = chip->capture_substream->runtime->private_data; data->period_flipflop ^= 1; snd_pcm_period_elapsed(chip->capture_substream); snd_als300_dbgplay("IRQ_CAPTURE\n"); } } return IRQ_HANDLED;}static irqreturn_t snd_als300plus_interrupt(int irq, void *dev_id){ u8 general, mpu, dram; struct snd_als300 *chip = dev_id; struct snd_als300_substream_data *data; general = inb(chip->port+ALS300P_IRQ_STATUS); mpu = inb(chip->port+MPU_IRQ_STATUS); dram = inb(chip->port+ALS300P_DRAM_IRQ_STATUS); /* shared IRQ, for different device?? Exit ASAP! */ if ((general == 0) && ((mpu & 0x80) == 0) && ((dram & 0x01) == 0)) return IRQ_NONE; if (general & IRQ_PLAYBACK) { if (chip->pcm && chip->playback_substream) { outb(IRQ_PLAYBACK, chip->port+ALS300P_IRQ_STATUS); data = chip->playback_substream->runtime->private_data; data->period_flipflop ^= 1; snd_pcm_period_elapsed(chip->playback_substream); snd_als300_dbgplay("IRQ_PLAYBACK\n"); } } if (general & IRQ_CAPTURE) { if (chip->pcm && chip->capture_substream) { outb(IRQ_CAPTURE, chip->port+ALS300P_IRQ_STATUS); data = chip->capture_substream->runtime->private_data; data->period_flipflop ^= 1; snd_pcm_period_elapsed(chip->capture_substream); snd_als300_dbgplay("IRQ_CAPTURE\n"); } } /* FIXME: Ack other interrupt types. Not important right now as * those other devices aren't enabled. */ return IRQ_HANDLED;}static void __devexit snd_als300_remove(struct pci_dev *pci){ snd_als300_dbgcallenter(); snd_card_free(pci_get_drvdata(pci)); pci_set_drvdata(pci, NULL); snd_als300_dbgcallleave();}static unsigned short snd_als300_ac97_read(struct snd_ac97 *ac97, unsigned short reg){ int i; struct snd_als300 *chip = ac97->private_data; for (i = 0; i < 1000; i++) { if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) break; udelay(10); } outl((reg << 24) | (1 << 31), chip->port+AC97_ACCESS); for (i = 0; i < 1000; i++) { if ((inb(chip->port+AC97_STATUS) & (AC97_DATA_AVAIL)) != 0) break; udelay(10); } return inw(chip->port+AC97_READ);}static void snd_als300_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val){ int i; struct snd_als300 *chip = ac97->private_data; for (i = 0; i < 1000; i++) { if ((inb(chip->port+AC97_STATUS) & (AC97_BUSY)) == 0) break; udelay(10); } outl((reg << 24) | val, chip->port+AC97_ACCESS);}static int snd_als300_ac97(struct snd_als300 *chip){ struct snd_ac97_bus *bus; struct snd_ac97_template ac97; int err; static struct snd_ac97_bus_ops ops = { .write = snd_als300_ac97_write, .read = snd_als300_ac97_read, }; snd_als300_dbgcallenter(); if ((err = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus)) < 0) return err; memset(&ac97, 0, sizeof(ac97)); ac97.private_data = chip; snd_als300_dbgcallleave(); return snd_ac97_mixer(bus, &ac97, &chip->ac97);}/* hardware definition * * In AC97 mode, we always use 48k/16bit/stereo. * Any request to change data type is ignored by * the card when it is running outside of legacy * mode. */static struct snd_pcm_hardware snd_als300_playback_hw ={ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16, .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 64, .period_bytes_max = 32 * 1024, .periods_min = 2, .periods_max = 2,};static struct snd_pcm_hardware snd_als300_capture_hw ={ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16, .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 64, .period_bytes_max = 32 * 1024, .periods_min = 2, .periods_max = 2,};static int snd_als300_playback_open(struct snd_pcm_substream *substream){ struct snd_als300 *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_als300_substream_data *data = kzalloc(sizeof(*data), GFP_KERNEL); snd_als300_dbgcallenter(); chip->playback_substream = substream; runtime->hw = snd_als300_playback_hw; runtime->private_data = data; data->control_register = PLAYBACK_CONTROL; data->block_counter_register = PLAYBACK_BLOCK_COUNTER; snd_als300_dbgcallleave(); return 0;}static int snd_als300_playback_close(struct snd_pcm_substream *substream){ struct snd_als300 *chip = snd_pcm_substream_chip(substream); struct snd_als300_substream_data *data; data = substream->runtime->private_data; snd_als300_dbgcallenter(); kfree(data); chip->playback_substream = NULL; snd_pcm_lib_free_pages(substream); snd_als300_dbgcallleave(); return 0;}static int snd_als300_capture_open(struct snd_pcm_substream *substream){ struct snd_als300 *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_als300_substream_data *data = kzalloc(sizeof(*data), GFP_KERNEL); snd_als300_dbgcallenter(); chip->capture_substream = substream; runtime->hw = snd_als300_capture_hw; runtime->private_data = data; data->control_register = RECORD_CONTROL; data->block_counter_register = RECORD_BLOCK_COUNTER; snd_als300_dbgcallleave(); return 0;}static int snd_als300_capture_close(struct snd_pcm_substream *substream){ struct snd_als300 *chip = snd_pcm_substream_chip(substream); struct snd_als300_substream_data *data;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -