📄 atiixp_modem.c
字号:
/* * ALSA driver for ATI IXP 150/200/250 AC97 modem 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 MC97 controller");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{ATI,IXP150/200/250}}");static int index = -2; /* Exclude the first card */static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */static int ac97_clock = 48000;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).");/* just for backward compatibility */static int enable;module_param(enable, bool, 0444);/* */#define ATI_REG_ISR 0x00 /* interrupt source */#define ATI_REG_ISR_MODEM_IN_XRUN (1U<<0)#define ATI_REG_ISR_MODEM_IN_STATUS (1U<<1)#define ATI_REG_ISR_MODEM_OUT1_XRUN (1U<<2)#define ATI_REG_ISR_MODEM_OUT1_STATUS (1U<<3)#define ATI_REG_ISR_MODEM_OUT2_XRUN (1U<<4)#define ATI_REG_ISR_MODEM_OUT2_STATUS (1U<<5)#define ATI_REG_ISR_MODEM_OUT3_XRUN (1U<<6)#define ATI_REG_ISR_MODEM_OUT3_STATUS (1U<<7)#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_ISR_MODEM_GPIO_DATA (1U<<14)#define ATI_REG_IER 0x04 /* interrupt enable */#define ATI_REG_IER_MODEM_IN_XRUN_EN (1U<<0)#define ATI_REG_IER_MODEM_STATUS_EN (1U<<1)#define ATI_REG_IER_MODEM_OUT1_XRUN_EN (1U<<2)#define ATI_REG_IER_MODEM_OUT2_XRUN_EN (1U<<4)#define ATI_REG_IER_MODEM_OUT3_XRUN_EN (1U<<6)#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_MODEM_GPIO_DATA_EN (1U<<14) /* (WO) modem is running */#define ATI_REG_IER_MODEM_SET_BUS_BUSY (1U<<15)#define ATI_REG_CMD 0x08 /* command */#define ATI_REG_CMD_POWERDOWN (1U<<0)#define ATI_REG_CMD_MODEM_RECEIVE_EN (1U<<1) /* modem only */#define ATI_REG_CMD_MODEM_SEND1_EN (1U<<2) /* modem only */#define ATI_REG_CMD_MODEM_SEND2_EN (1U<<3) /* modem only */#define ATI_REG_CMD_MODEM_SEND3_EN (1U<<4) /* modem only */#define ATI_REG_CMD_MODEM_STATUS_MEM (1U<<5) /* modem only */#define ATI_REG_CMD_MODEM_IN_DMA_EN (1U<<8) /* modem only */#define ATI_REG_CMD_MODEM_OUT_DMA1_EN (1U<<9) /* modem only */#define ATI_REG_CMD_MODEM_OUT_DMA2_EN (1U<<10) /* modem only */#define ATI_REG_CMD_MODEM_OUT_DMA3_EN (1U<<11) /* modem only */#define ATI_REG_CMD_AUDIO_PRESENT (1U<<20)#define ATI_REG_CMD_MODEM_GPIO_THRU_DMA (1U<<22) /* modem only */#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_MODEM_IN_DMA_LINKPTR 0x20#define ATI_REG_MODEM_IN_DMA_DT_START 0x24 /* RO */#define ATI_REG_MODEM_IN_DMA_DT_NEXT 0x28 /* RO */#define ATI_REG_MODEM_IN_DMA_DT_CUR 0x2c /* RO */#define ATI_REG_MODEM_IN_DMA_DT_SIZE 0x30#define ATI_REG_MODEM_OUT_FIFO 0x34 /* output threshold */#define ATI_REG_MODEM_OUT1_DMA_THRESHOLD_MASK (0xf<<16)#define ATI_REG_MODEM_OUT1_DMA_THRESHOLD_SHIFT 16#define ATI_REG_MODEM_OUT_DMA1_LINKPTR 0x38#define ATI_REG_MODEM_OUT_DMA2_LINKPTR 0x3c#define ATI_REG_MODEM_OUT_DMA3_LINKPTR 0x40#define ATI_REG_MODEM_OUT_DMA1_DT_START 0x44#define ATI_REG_MODEM_OUT_DMA1_DT_NEXT 0x48#define ATI_REG_MODEM_OUT_DMA1_DT_CUR 0x4c#define ATI_REG_MODEM_OUT_DMA2_DT_START 0x50#define ATI_REG_MODEM_OUT_DMA2_DT_NEXT 0x54#define ATI_REG_MODEM_OUT_DMA2_DT_CUR 0x58#define ATI_REG_MODEM_OUT_DMA3_DT_START 0x5c#define ATI_REG_MODEM_OUT_DMA3_DT_NEXT 0x60#define ATI_REG_MODEM_OUT_DMA3_DT_CUR 0x64#define ATI_REG_MODEM_OUT_DMA12_DT_SIZE 0x68#define ATI_REG_MODEM_OUT_DMA3_DT_SIZE 0x6c#define ATI_REG_MODEM_OUT_FIFO_USED 0x70#define ATI_REG_MODEM_OUT_GPIO 0x74#define ATI_REG_MODEM_OUT_GPIO_EN 1#define ATI_REG_MODEM_OUT_GPIO_DATA_SHIFT 5#define ATI_REG_MODEM_IN_GPIO 0x78#define ATI_REG_MODEM_MIRROR 0x7c#define ATI_REG_AUDIO_MIRROR 0x80#define ATI_REG_MODEM_FIFO_FLUSH 0x88#define ATI_REG_MODEM_FIFO_OUT1_FLUSH (1U<<0)#define ATI_REG_MODEM_FIFO_OUT2_FLUSH (1U<<1)#define ATI_REG_MODEM_FIFO_OUT3_FLUSH (1U<<2)#define ATI_REG_MODEM_FIFO_IN_FLUSH (1U<<3)/* LINKPTR */#define ATI_REG_LINKPTR_EN (1U<<0)#define ATI_MAX_DESCRIPTORS 256 /* max number of descriptor packets */struct atiixp_modem;/* * 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, NUM_ATI_DMAS }; /* DMAs */enum { ATI_PCM_OUT, ATI_PCM_IN, NUM_ATI_PCMS }; /* AC97 pcm slots */enum { ATI_PCMDEV_ANALOG, 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_modem *chip, int on); /* called from trigger (START/STOP) */ void (*enable_transfer)(struct atiixp_modem *chip, int on); /* called from trigger (STOP only) */ void (*flush_dma)(struct atiixp_modem *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 pcm_open_flag; int ac97_pcm_type; /* index # of ac97_pcm to access, -1 = not used */};/* * ATI IXP chip */struct atiixp_modem { struct snd_card *card; struct pci_dev *pci; struct resource *res; /* memory i/o */ 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, 0x434d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SB200 */ { 0x1002, 0x4378, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* SB400 */ { 0, }};MODULE_DEVICE_TABLE(pci, snd_atiixp_ids);/* * lowlevel functions *//* * update the bits of the given register. * return 1 if the bits changed. */static int snd_atiixp_update_bits(struct atiixp_modem *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_modem *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_modem *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_modem *chip){ int timeout = 1000; while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) { if (! timeout--) { snd_printk(KERN_WARNING "atiixp-modem: codec acquire timeout\n"); return -EBUSY; } udelay(1); } return 0;}static unsigned short snd_atiixp_codec_read(struct atiixp_modem *chip, unsigned short codec, unsigned short reg){ unsigned int data; int timeout; if (snd_atiixp_acquire_codec(chip) < 0) return 0xffff; data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | ATI_REG_PHYS_OUT_RW | codec; atiixp_write(chip, PHYS_OUT_ADDR, data); if (snd_atiixp_acquire_codec(chip) < 0) return 0xffff; timeout = 1000; do { data = atiixp_read(chip, PHYS_IN_ADDR); if (data & ATI_REG_PHYS_IN_READ_FLAG) return data >> ATI_REG_PHYS_IN_DATA_SHIFT; udelay(1); } while (--timeout); /* time out may happen during reset */ if (reg < 0x7c) snd_printk(KERN_WARNING "atiixp-modem: codec read timeout (reg %x)\n", reg); return 0xffff;}static void snd_atiixp_codec_write(struct atiixp_modem *chip, unsigned short codec, unsigned short reg, unsigned short val){ unsigned int data; if (snd_atiixp_acquire_codec(chip) < 0) return; data = ((unsigned int)val << ATI_REG_PHYS_OUT_DATA_SHIFT) | ((unsigned int)reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) | ATI_REG_PHYS_OUT_ADDR_EN | codec;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -