📄 atiixp.c
字号:
/* * ALSA driver for ATI IXP 150/200/250/300 AC97 controllers * * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de> * * 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 * */#include <sound/driver.h>#include <asm/io.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/init.h>#include <linux/pci.h>#include <linux/slab.h>#include <linux/moduleparam.h>#include <linux/mutex.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/info.h>#include <sound/ac97_codec.h>#include <sound/initval.h>MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");MODULE_DESCRIPTION("ATI IXP AC97 controller");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250/300/400}}");static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */static int ac97_clock = 48000;static char *ac97_quirk;static int spdif_aclink = 1;static int ac97_codec = -1;module_param(index, int, 0444);MODULE_PARM_DESC(index, "Index value for ATI IXP controller.");module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for ATI IXP controller.");module_param(ac97_clock, int, 0444);MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");module_param(ac97_quirk, charp, 0444);MODULE_PARM_DESC(ac97_quirk, "AC'97 workaround for strange hardware.");module_param(ac97_codec, int, 0444);MODULE_PARM_DESC(ac97_codec, "Specify codec instead of probing.");module_param(spdif_aclink, bool, 0444);MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link.");/* just for backward compatibility */static int enable;module_param(enable, bool, 0444);/* */#define ATI_REG_ISR 0x00 /* interrupt source */#define ATI_REG_ISR_IN_XRUN (1U<<0)#define ATI_REG_ISR_IN_STATUS (1U<<1)#define ATI_REG_ISR_OUT_XRUN (1U<<2)#define ATI_REG_ISR_OUT_STATUS (1U<<3)#define ATI_REG_ISR_SPDF_XRUN (1U<<4)#define ATI_REG_ISR_SPDF_STATUS (1U<<5)#define ATI_REG_ISR_PHYS_INTR (1U<<8)#define ATI_REG_ISR_PHYS_MISMATCH (1U<<9)#define ATI_REG_ISR_CODEC0_NOT_READY (1U<<10)#define ATI_REG_ISR_CODEC1_NOT_READY (1U<<11)#define ATI_REG_ISR_CODEC2_NOT_READY (1U<<12)#define ATI_REG_ISR_NEW_FRAME (1U<<13)#define ATI_REG_IER 0x04 /* interrupt enable */#define ATI_REG_IER_IN_XRUN_EN (1U<<0)#define ATI_REG_IER_IO_STATUS_EN (1U<<1)#define ATI_REG_IER_OUT_XRUN_EN (1U<<2)#define ATI_REG_IER_OUT_XRUN_COND (1U<<3)#define ATI_REG_IER_SPDF_XRUN_EN (1U<<4)#define ATI_REG_IER_SPDF_STATUS_EN (1U<<5)#define ATI_REG_IER_PHYS_INTR_EN (1U<<8)#define ATI_REG_IER_PHYS_MISMATCH_EN (1U<<9)#define ATI_REG_IER_CODEC0_INTR_EN (1U<<10)#define ATI_REG_IER_CODEC1_INTR_EN (1U<<11)#define ATI_REG_IER_CODEC2_INTR_EN (1U<<12)#define ATI_REG_IER_NEW_FRAME_EN (1U<<13) /* (RO */#define ATI_REG_IER_SET_BUS_BUSY (1U<<14) /* (WO) audio is running */#define ATI_REG_CMD 0x08 /* command */#define ATI_REG_CMD_POWERDOWN (1U<<0)#define ATI_REG_CMD_RECEIVE_EN (1U<<1)#define ATI_REG_CMD_SEND_EN (1U<<2)#define ATI_REG_CMD_STATUS_MEM (1U<<3)#define ATI_REG_CMD_SPDF_OUT_EN (1U<<4)#define ATI_REG_CMD_SPDF_STATUS_MEM (1U<<5)#define ATI_REG_CMD_SPDF_THRESHOLD (3U<<6)#define ATI_REG_CMD_SPDF_THRESHOLD_SHIFT 6#define ATI_REG_CMD_IN_DMA_EN (1U<<8)#define ATI_REG_CMD_OUT_DMA_EN (1U<<9)#define ATI_REG_CMD_SPDF_DMA_EN (1U<<10)#define ATI_REG_CMD_SPDF_OUT_STOPPED (1U<<11)#define ATI_REG_CMD_SPDF_CONFIG_MASK (7U<<12)#define ATI_REG_CMD_SPDF_CONFIG_34 (1U<<12)#define ATI_REG_CMD_SPDF_CONFIG_78 (2U<<12)#define ATI_REG_CMD_SPDF_CONFIG_69 (3U<<12)#define ATI_REG_CMD_SPDF_CONFIG_01 (4U<<12)#define ATI_REG_CMD_INTERLEAVE_SPDF (1U<<16)#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20)#define ATI_REG_CMD_INTERLEAVE_IN (1U<<21)#define ATI_REG_CMD_INTERLEAVE_OUT (1U<<22)#define ATI_REG_CMD_LOOPBACK_EN (1U<<23)#define ATI_REG_CMD_PACKED_DIS (1U<<24)#define ATI_REG_CMD_BURST_EN (1U<<25)#define ATI_REG_CMD_PANIC_EN (1U<<26)#define ATI_REG_CMD_MODEM_PRESENT (1U<<27)#define ATI_REG_CMD_ACLINK_ACTIVE (1U<<28)#define ATI_REG_CMD_AC_SOFT_RESET (1U<<29)#define ATI_REG_CMD_AC_SYNC (1U<<30)#define ATI_REG_CMD_AC_RESET (1U<<31)#define ATI_REG_PHYS_OUT_ADDR 0x0c#define ATI_REG_PHYS_OUT_CODEC_MASK (3U<<0)#define ATI_REG_PHYS_OUT_RW (1U<<2)#define ATI_REG_PHYS_OUT_ADDR_EN (1U<<8)#define ATI_REG_PHYS_OUT_ADDR_SHIFT 9#define ATI_REG_PHYS_OUT_DATA_SHIFT 16#define ATI_REG_PHYS_IN_ADDR 0x10#define ATI_REG_PHYS_IN_READ_FLAG (1U<<8)#define ATI_REG_PHYS_IN_ADDR_SHIFT 9#define ATI_REG_PHYS_IN_DATA_SHIFT 16#define ATI_REG_SLOTREQ 0x14#define ATI_REG_COUNTER 0x18#define ATI_REG_COUNTER_SLOT (3U<<0) /* slot # */#define ATI_REG_COUNTER_BITCLOCK (31U<<8)#define ATI_REG_IN_FIFO_THRESHOLD 0x1c#define ATI_REG_IN_DMA_LINKPTR 0x20#define ATI_REG_IN_DMA_DT_START 0x24 /* RO */#define ATI_REG_IN_DMA_DT_NEXT 0x28 /* RO */#define ATI_REG_IN_DMA_DT_CUR 0x2c /* RO */#define ATI_REG_IN_DMA_DT_SIZE 0x30#define ATI_REG_OUT_DMA_SLOT 0x34#define ATI_REG_OUT_DMA_SLOT_BIT(x) (1U << ((x) - 3))#define ATI_REG_OUT_DMA_SLOT_MASK 0x1ff#define ATI_REG_OUT_DMA_THRESHOLD_MASK 0xf800#define ATI_REG_OUT_DMA_THRESHOLD_SHIFT 11#define ATI_REG_OUT_DMA_LINKPTR 0x38#define ATI_REG_OUT_DMA_DT_START 0x3c /* RO */#define ATI_REG_OUT_DMA_DT_NEXT 0x40 /* RO */#define ATI_REG_OUT_DMA_DT_CUR 0x44 /* RO */#define ATI_REG_OUT_DMA_DT_SIZE 0x48#define ATI_REG_SPDF_CMD 0x4c#define ATI_REG_SPDF_CMD_LFSR (1U<<4)#define ATI_REG_SPDF_CMD_SINGLE_CH (1U<<5)#define ATI_REG_SPDF_CMD_LFSR_ACC (0xff<<8) /* RO */#define ATI_REG_SPDF_DMA_LINKPTR 0x50#define ATI_REG_SPDF_DMA_DT_START 0x54 /* RO */#define ATI_REG_SPDF_DMA_DT_NEXT 0x58 /* RO */#define ATI_REG_SPDF_DMA_DT_CUR 0x5c /* RO */#define ATI_REG_SPDF_DMA_DT_SIZE 0x60#define ATI_REG_MODEM_MIRROR 0x7c#define ATI_REG_AUDIO_MIRROR 0x80#define ATI_REG_6CH_REORDER 0x84 /* reorder slots for 6ch */#define ATI_REG_6CH_REORDER_EN (1U<<0) /* 3,4,7,8,6,9 -> 3,4,6,9,7,8 */#define ATI_REG_FIFO_FLUSH 0x88#define ATI_REG_FIFO_OUT_FLUSH (1U<<0)#define ATI_REG_FIFO_IN_FLUSH (1U<<1)/* LINKPTR */#define ATI_REG_LINKPTR_EN (1U<<0)/* [INT|OUT|SPDIF]_DMA_DT_SIZE */#define ATI_REG_DMA_DT_SIZE (0xffffU<<0)#define ATI_REG_DMA_FIFO_USED (0x1fU<<16)#define ATI_REG_DMA_FIFO_FREE (0x1fU<<21)#define ATI_REG_DMA_STATE (7U<<26)#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */struct atiixp;/* * DMA packate descriptor */struct atiixp_dma_desc { u32 addr; /* DMA buffer address */ u16 status; /* status bits */ u16 size; /* size of the packet in dwords */ u32 next; /* address of the next packet descriptor */};/* * stream enum */enum { ATI_DMA_PLAYBACK, ATI_DMA_CAPTURE, ATI_DMA_SPDIF, NUM_ATI_DMAS }; /* DMAs */enum { ATI_PCM_OUT, ATI_PCM_IN, ATI_PCM_SPDIF, NUM_ATI_PCMS }; /* AC97 pcm slots */enum { ATI_PCMDEV_ANALOG, ATI_PCMDEV_DIGITAL, NUM_ATI_PCMDEVS }; /* pcm devices */#define NUM_ATI_CODECS 3/* * constants and callbacks for each DMA type */struct atiixp_dma_ops { int type; /* ATI_DMA_XXX */ unsigned int llp_offset; /* LINKPTR offset */ unsigned int dt_cur; /* DT_CUR offset */ /* called from open callback */ void (*enable_dma)(struct atiixp *chip, int on); /* called from trigger (START/STOP) */ void (*enable_transfer)(struct atiixp *chip, int on); /* called from trigger (STOP only) */ void (*flush_dma)(struct atiixp *chip);};/* * DMA stream */struct atiixp_dma { const struct atiixp_dma_ops *ops; struct snd_dma_buffer desc_buf; struct snd_pcm_substream *substream; /* assigned PCM substream */ unsigned int buf_addr, buf_bytes; /* DMA buffer address, bytes */ unsigned int period_bytes, periods; int opened; int running; int suspended; int pcm_open_flag; int ac97_pcm_type; /* index # of ac97_pcm to access, -1 = not used */ unsigned int saved_curptr;};/* * ATI IXP chip */struct atiixp { struct snd_card *card; struct pci_dev *pci; unsigned long addr; void __iomem *remap_addr; int irq; struct snd_ac97_bus *ac97_bus; struct snd_ac97 *ac97[NUM_ATI_CODECS]; spinlock_t reg_lock; struct atiixp_dma dmas[NUM_ATI_DMAS]; struct ac97_pcm *pcms[NUM_ATI_PCMS]; struct snd_pcm *pcmdevs[NUM_ATI_PCMDEVS]; int max_channels; /* max. channels for PCM out */ unsigned int codec_not_ready_bits; /* for codec detection */ int spdif_over_aclink; /* passed from the module option */ struct mutex open_mutex; /* playback open mutex */};/* */static struct pci_device_id snd_atiixp_ids[] = { { 0x1002, 0x4341, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SB200 */ { 0x1002, 0x4361, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SB300 */ { 0x1002, 0x4370, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SB400 */ { 0, }};MODULE_DEVICE_TABLE(pci, snd_atiixp_ids);static struct snd_pci_quirk atiixp_quirks[] __devinitdata = { SND_PCI_QUIRK(0x15bd, 0x3100, "DFI RS482", 0), { } /* terminator */};/* * lowlevel functions *//* * update the bits of the given register. * return 1 if the bits changed. */static int snd_atiixp_update_bits(struct atiixp *chip, unsigned int reg, unsigned int mask, unsigned int value){ void __iomem *addr = chip->remap_addr + reg; unsigned int data, old_data; old_data = data = readl(addr); data &= ~mask; data |= value; if (old_data == data) return 0; writel(data, addr); return 1;}/* * macros for easy use */#define atiixp_write(chip,reg,value) \ writel(value, chip->remap_addr + ATI_REG_##reg)#define atiixp_read(chip,reg) \ readl(chip->remap_addr + ATI_REG_##reg)#define atiixp_update(chip,reg,mask,val) \ snd_atiixp_update_bits(chip, ATI_REG_##reg, mask, val)/* * handling DMA packets * * we allocate a linear buffer for the DMA, and split it to each packet. * in a future version, a scatter-gather buffer should be implemented. */#define ATI_DESC_LIST_SIZE \ PAGE_ALIGN(ATI_MAX_DESCRIPTORS * sizeof(struct atiixp_dma_desc))/* * build packets ring for the given buffer size. * * IXP handles the buffer descriptors, which are connected as a linked * list. although we can change the list dynamically, in this version, * a static RING of buffer descriptors is used. * * the ring is built in this function, and is set up to the hardware. */static int atiixp_build_dma_packets(struct atiixp *chip, struct atiixp_dma *dma, struct snd_pcm_substream *substream, unsigned int periods, unsigned int period_bytes){ unsigned int i; u32 addr, desc_addr; unsigned long flags; if (periods > ATI_MAX_DESCRIPTORS) return -ENOMEM; if (dma->desc_buf.area == NULL) { if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), ATI_DESC_LIST_SIZE, &dma->desc_buf) < 0) return -ENOMEM; dma->period_bytes = dma->periods = 0; /* clear */ } if (dma->periods == periods && dma->period_bytes == period_bytes) return 0; /* reset DMA before changing the descriptor table */ spin_lock_irqsave(&chip->reg_lock, flags); writel(0, chip->remap_addr + dma->ops->llp_offset); dma->ops->enable_dma(chip, 0); dma->ops->enable_dma(chip, 1); spin_unlock_irqrestore(&chip->reg_lock, flags); /* fill the entries */ addr = (u32)substream->runtime->dma_addr; desc_addr = (u32)dma->desc_buf.addr; for (i = 0; i < periods; i++) { struct atiixp_dma_desc *desc; desc = &((struct atiixp_dma_desc *)dma->desc_buf.area)[i]; desc->addr = cpu_to_le32(addr); desc->status = 0; desc->size = period_bytes >> 2; /* in dwords */ desc_addr += sizeof(struct atiixp_dma_desc); if (i == periods - 1) desc->next = cpu_to_le32((u32)dma->desc_buf.addr); else desc->next = cpu_to_le32(desc_addr); addr += period_bytes; } writel((u32)dma->desc_buf.addr | ATI_REG_LINKPTR_EN, chip->remap_addr + dma->ops->llp_offset); dma->period_bytes = period_bytes; dma->periods = periods; return 0;}/* * remove the ring buffer and release it if assigned */static void atiixp_clear_dma_packets(struct atiixp *chip, struct atiixp_dma *dma, struct snd_pcm_substream *substream){ if (dma->desc_buf.area) { writel(0, chip->remap_addr + dma->ops->llp_offset); snd_dma_free_pages(&dma->desc_buf); dma->desc_buf.area = NULL; }}/* * AC97 interface */static int snd_atiixp_acquire_codec(struct atiixp *chip){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -