📄 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 <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_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */static int ac97_clock[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 48000};static int spdif_aclink[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};static int boot_devs;module_param_array(index, int, boot_devs, 0444);MODULE_PARM_DESC(index, "Index value for ATI IXP controller.");module_param_array(id, charp, boot_devs, 0444);MODULE_PARM_DESC(id, "ID string for ATI IXP controller.");module_param_array(enable, bool, boot_devs, 0444);MODULE_PARM_DESC(enable, "Enable audio part of ATI IXP controller.");module_param_array(ac97_clock, int, boot_devs, 0444);MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz).");module_param_array(spdif_aclink, bool, boot_devs, 0444);MODULE_PARM_DESC(spdif_aclink, "S/PDIF over AC-link.");/* */#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 *//* */typedef struct snd_atiixp atiixp_t;typedef struct snd_atiixp_dma atiixp_dma_t;typedef struct snd_atiixp_dma_ops atiixp_dma_ops_t;/* * DMA packate descriptor */typedef 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 */} atiixp_dma_desc_t;/* * 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 snd_atiixp_dma_ops { int type; /* ATI_DMA_XXX */ unsigned int llp_offset; /* LINKPTR offset */ unsigned int dt_cur; /* DT_CUR offset */ void (*enable_dma)(atiixp_t *chip, int on); /* called from open callback */ void (*enable_transfer)(atiixp_t *chip, int on); /* called from trigger (START/STOP) */ void (*flush_dma)(atiixp_t *chip); /* called from trigger (STOP only) */};/* * DMA stream */struct snd_atiixp_dma { const atiixp_dma_ops_t *ops; struct snd_dma_buffer desc_buf; snd_pcm_substream_t *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 snd_atiixp { snd_card_t *card; struct pci_dev *pci; unsigned long addr; void __iomem *remap_addr; int irq; ac97_bus_t *ac97_bus; ac97_t *ac97[NUM_ATI_CODECS]; spinlock_t reg_lock; spinlock_t ac97_lock; atiixp_dma_t dmas[NUM_ATI_DMAS]; struct ac97_pcm *pcms[NUM_ATI_PCMS]; snd_pcm_t *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 semaphore 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);/* * lowlevel functions *//* * update the bits of the given register. * return 1 if the bits changed. */static int snd_atiixp_update_bits(atiixp_t *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)/* delay for one tick */#define do_delay() do { \ set_current_state(TASK_UNINTERRUPTIBLE); \ schedule_timeout(1); \} while (0)/* * 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(atiixp_dma_desc_t))/* * 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(atiixp_t *chip, atiixp_dma_t *dma, snd_pcm_substream_t *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++) { atiixp_dma_desc_t *desc = &((atiixp_dma_desc_t *)dma->desc_buf.area)[i]; desc->addr = cpu_to_le32(addr); desc->status = 0; desc->size = period_bytes >> 2; /* in dwords */ desc_addr += sizeof(atiixp_dma_desc_t); 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(atiixp_t *chip, atiixp_dma_t *dma, snd_pcm_substream_t *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(atiixp_t *chip){ int timeout = 1000; while (atiixp_read(chip, PHYS_OUT_ADDR) & ATI_REG_PHYS_OUT_ADDR_EN) { if (! timeout--) { snd_printk(KERN_WARNING "atiixp: codec acquire timeout\n"); return -EBUSY; } udelay(1); } return 0;}static unsigned short snd_atiixp_codec_read(atiixp_t *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: codec read timeout (reg %x)\n", reg); return 0xffff;}static void snd_atiixp_codec_write(atiixp_t *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; atiixp_write(chip, PHYS_OUT_ADDR, data);}static unsigned short snd_atiixp_ac97_read(ac97_t *ac97, unsigned short reg){ atiixp_t *chip = ac97->private_data; unsigned short data; spin_lock(&chip->ac97_lock); data = snd_atiixp_codec_read(chip, ac97->num, reg); spin_unlock(&chip->ac97_lock); return data; }static void snd_atiixp_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short val){ atiixp_t *chip = ac97->private_data; spin_lock(&chip->ac97_lock); snd_atiixp_codec_write(chip, ac97->num, reg, val); spin_unlock(&chip->ac97_lock);}/* * reset AC link */static int snd_atiixp_aclink_reset(atiixp_t *chip){ int timeout; /* reset powerdoewn */ if (atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN, 0)) udelay(10); /* perform a software reset */ atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, ATI_REG_CMD_AC_SOFT_RESET); atiixp_read(chip, CMD); udelay(10); atiixp_update(chip, CMD, ATI_REG_CMD_AC_SOFT_RESET, 0); timeout = 10; while (! (atiixp_read(chip, CMD) & ATI_REG_CMD_ACLINK_ACTIVE)) { /* do a hard reset */ atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_SYNC); atiixp_read(chip, CMD); do_delay(); atiixp_update(chip, CMD, ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_RESET); if (--timeout) { snd_printk(KERN_ERR "atiixp: codec reset timeout\n"); break; } } /* deassert RESET and assert SYNC to make sure */ atiixp_update(chip, CMD, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET, ATI_REG_CMD_AC_SYNC|ATI_REG_CMD_AC_RESET); return 0;}#ifdef CONFIG_PMstatic int snd_atiixp_aclink_down(atiixp_t *chip){ // if (atiixp_read(chip, MODEM_MIRROR) & 0x1) /* modem running, too? */ // return -EBUSY; atiixp_update(chip, CMD, ATI_REG_CMD_POWERDOWN | ATI_REG_CMD_AC_RESET, ATI_REG_CMD_POWERDOWN); return 0;}#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -