⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 als300.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* *  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 + -