📄 via82xx_modem.c
字号:
/* * ALSA modem driver for VIA VT82xx (South Bridge) * * VT82C686A/B/C, VT8233A/C, VT8235 * * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz> * Tjeerd.Mulder <Tjeerd.Mulder@fujitsu-siemens.com> * 2002 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 * *//* * Changes: * * Sep. 2, 2004 Sasha Khapyorsky <sashak@alsa-project.org> * Modified from original audio driver 'via82xx.c' to support AC97 * modems. */#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>#if 0#define POINTER_DEBUG#endifMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");MODULE_DESCRIPTION("VIA VT82xx modem");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C modem,pci}}");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 VIA 82xx bridge.");module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge.");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);/* * Direct registers */#define VIAREG(via, x) ((via)->port + VIA_REG_##x)#define VIADEV_REG(viadev, x) ((viadev)->port + VIA_REG_##x)/* common offsets */#define VIA_REG_OFFSET_STATUS 0x00 /* byte - channel status */#define VIA_REG_STAT_ACTIVE 0x80 /* RO */#define VIA_REG_STAT_PAUSED 0x40 /* RO */#define VIA_REG_STAT_TRIGGER_QUEUED 0x08 /* RO */#define VIA_REG_STAT_STOPPED 0x04 /* RWC */#define VIA_REG_STAT_EOL 0x02 /* RWC */#define VIA_REG_STAT_FLAG 0x01 /* RWC */#define VIA_REG_OFFSET_CONTROL 0x01 /* byte - channel control */#define VIA_REG_CTRL_START 0x80 /* WO */#define VIA_REG_CTRL_TERMINATE 0x40 /* WO */#define VIA_REG_CTRL_AUTOSTART 0x20#define VIA_REG_CTRL_PAUSE 0x08 /* RW */#define VIA_REG_CTRL_INT_STOP 0x04 #define VIA_REG_CTRL_INT_EOL 0x02#define VIA_REG_CTRL_INT_FLAG 0x01#define VIA_REG_CTRL_RESET 0x01 /* RW - probably reset? undocumented */#define VIA_REG_CTRL_INT (VIA_REG_CTRL_INT_FLAG | VIA_REG_CTRL_INT_EOL | VIA_REG_CTRL_AUTOSTART)#define VIA_REG_OFFSET_TYPE 0x02 /* byte - channel type (686 only) */#define VIA_REG_TYPE_AUTOSTART 0x80 /* RW - autostart at EOL */#define VIA_REG_TYPE_16BIT 0x20 /* RW */#define VIA_REG_TYPE_STEREO 0x10 /* RW */#define VIA_REG_TYPE_INT_LLINE 0x00#define VIA_REG_TYPE_INT_LSAMPLE 0x04#define VIA_REG_TYPE_INT_LESSONE 0x08#define VIA_REG_TYPE_INT_MASK 0x0c#define VIA_REG_TYPE_INT_EOL 0x02#define VIA_REG_TYPE_INT_FLAG 0x01#define VIA_REG_OFFSET_TABLE_PTR 0x04 /* dword - channel table pointer */#define VIA_REG_OFFSET_CURR_PTR 0x04 /* dword - channel current pointer */#define VIA_REG_OFFSET_STOP_IDX 0x08 /* dword - stop index, channel type, sample rate */#define VIA_REG_OFFSET_CURR_COUNT 0x0c /* dword - channel current count (24 bit) */#define VIA_REG_OFFSET_CURR_INDEX 0x0f /* byte - channel current index (for via8233 only) */#define DEFINE_VIA_REGSET(name,val) \enum {\ VIA_REG_##name##_STATUS = (val),\ VIA_REG_##name##_CONTROL = (val) + 0x01,\ VIA_REG_##name##_TYPE = (val) + 0x02,\ VIA_REG_##name##_TABLE_PTR = (val) + 0x04,\ VIA_REG_##name##_CURR_PTR = (val) + 0x04,\ VIA_REG_##name##_STOP_IDX = (val) + 0x08,\ VIA_REG_##name##_CURR_COUNT = (val) + 0x0c,\}/* modem block */DEFINE_VIA_REGSET(MO, 0x40);DEFINE_VIA_REGSET(MI, 0x50);/* AC'97 */#define VIA_REG_AC97 0x80 /* dword */#define VIA_REG_AC97_CODEC_ID_MASK (3<<30)#define VIA_REG_AC97_CODEC_ID_SHIFT 30#define VIA_REG_AC97_CODEC_ID_PRIMARY 0x00#define VIA_REG_AC97_CODEC_ID_SECONDARY 0x01#define VIA_REG_AC97_SECONDARY_VALID (1<<27)#define VIA_REG_AC97_PRIMARY_VALID (1<<25)#define VIA_REG_AC97_BUSY (1<<24)#define VIA_REG_AC97_READ (1<<23)#define VIA_REG_AC97_CMD_SHIFT 16#define VIA_REG_AC97_CMD_MASK 0x7e#define VIA_REG_AC97_DATA_SHIFT 0#define VIA_REG_AC97_DATA_MASK 0xffff#define VIA_REG_SGD_SHADOW 0x84 /* dword */#define VIA_REG_SGD_STAT_PB_FLAG (1<<0)#define VIA_REG_SGD_STAT_CP_FLAG (1<<1)#define VIA_REG_SGD_STAT_FM_FLAG (1<<2)#define VIA_REG_SGD_STAT_PB_EOL (1<<4)#define VIA_REG_SGD_STAT_CP_EOL (1<<5)#define VIA_REG_SGD_STAT_FM_EOL (1<<6)#define VIA_REG_SGD_STAT_PB_STOP (1<<8)#define VIA_REG_SGD_STAT_CP_STOP (1<<9)#define VIA_REG_SGD_STAT_FM_STOP (1<<10)#define VIA_REG_SGD_STAT_PB_ACTIVE (1<<12)#define VIA_REG_SGD_STAT_CP_ACTIVE (1<<13)#define VIA_REG_SGD_STAT_FM_ACTIVE (1<<14)#define VIA_REG_SGD_STAT_MR_FLAG (1<<16)#define VIA_REG_SGD_STAT_MW_FLAG (1<<17)#define VIA_REG_SGD_STAT_MR_EOL (1<<20)#define VIA_REG_SGD_STAT_MW_EOL (1<<21)#define VIA_REG_SGD_STAT_MR_STOP (1<<24)#define VIA_REG_SGD_STAT_MW_STOP (1<<25)#define VIA_REG_SGD_STAT_MR_ACTIVE (1<<28)#define VIA_REG_SGD_STAT_MW_ACTIVE (1<<29)#define VIA_REG_GPI_STATUS 0x88#define VIA_REG_GPI_INTR 0x8c#define VIA_TBL_BIT_FLAG 0x40000000#define VIA_TBL_BIT_EOL 0x80000000/* pci space */#define VIA_ACLINK_STAT 0x40#define VIA_ACLINK_C11_READY 0x20#define VIA_ACLINK_C10_READY 0x10#define VIA_ACLINK_C01_READY 0x04 /* secondary codec ready */#define VIA_ACLINK_LOWPOWER 0x02 /* low-power state */#define VIA_ACLINK_C00_READY 0x01 /* primary codec ready */#define VIA_ACLINK_CTRL 0x41#define VIA_ACLINK_CTRL_ENABLE 0x80 /* 0: disable, 1: enable */#define VIA_ACLINK_CTRL_RESET 0x40 /* 0: assert, 1: de-assert */#define VIA_ACLINK_CTRL_SYNC 0x20 /* 0: release SYNC, 1: force SYNC hi */#define VIA_ACLINK_CTRL_SDO 0x10 /* 0: release SDO, 1: force SDO hi */#define VIA_ACLINK_CTRL_VRA 0x08 /* 0: disable VRA, 1: enable VRA */#define VIA_ACLINK_CTRL_PCM 0x04 /* 0: disable PCM, 1: enable PCM */#define VIA_ACLINK_CTRL_FM 0x02 /* via686 only */#define VIA_ACLINK_CTRL_SB 0x01 /* via686 only */#define VIA_ACLINK_CTRL_INIT (VIA_ACLINK_CTRL_ENABLE|\ VIA_ACLINK_CTRL_RESET|\ VIA_ACLINK_CTRL_PCM)#define VIA_FUNC_ENABLE 0x42#define VIA_FUNC_MIDI_PNP 0x80 /* FIXME: it's 0x40 in the datasheet! */#define VIA_FUNC_MIDI_IRQMASK 0x40 /* FIXME: not documented! */#define VIA_FUNC_RX2C_WRITE 0x20#define VIA_FUNC_SB_FIFO_EMPTY 0x10#define VIA_FUNC_ENABLE_GAME 0x08#define VIA_FUNC_ENABLE_FM 0x04#define VIA_FUNC_ENABLE_MIDI 0x02#define VIA_FUNC_ENABLE_SB 0x01#define VIA_PNP_CONTROL 0x43#define VIA_MC97_CTRL 0x44#define VIA_MC97_CTRL_ENABLE 0x80#define VIA_MC97_CTRL_SECONDARY 0x40#define VIA_MC97_CTRL_INIT (VIA_MC97_CTRL_ENABLE|\ VIA_MC97_CTRL_SECONDARY)/* * pcm stream */struct snd_via_sg_table { unsigned int offset; unsigned int size;} ;#define VIA_TABLE_SIZE 255struct viadev { unsigned int reg_offset; unsigned long port; int direction; /* playback = 0, capture = 1 */ struct snd_pcm_substream *substream; int running; unsigned int tbl_entries; /* # descriptors */ struct snd_dma_buffer table; struct snd_via_sg_table *idx_table; /* for recovery from the unexpected pointer */ unsigned int lastpos; unsigned int bufsize; unsigned int bufsize2;};enum { TYPE_CARD_VIA82XX_MODEM = 1 };#define VIA_MAX_MODEM_DEVS 2struct via82xx_modem { int irq; unsigned long port; unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */ struct pci_dev *pci; struct snd_card *card; unsigned int num_devs; unsigned int playback_devno, capture_devno; struct viadev devs[VIA_MAX_MODEM_DEVS]; struct snd_pcm *pcms[2]; struct snd_ac97_bus *ac97_bus; struct snd_ac97 *ac97; unsigned int ac97_clock; unsigned int ac97_secondary; /* secondary AC'97 codec is present */ spinlock_t reg_lock; struct snd_info_entry *proc_entry;};static struct pci_device_id snd_via82xx_modem_ids[] = { { 0x1106, 0x3068, PCI_ANY_ID, PCI_ANY_ID, 0, 0, TYPE_CARD_VIA82XX_MODEM, }, { 0, }};MODULE_DEVICE_TABLE(pci, snd_via82xx_modem_ids);/* *//* * allocate and initialize the descriptor buffers * periods = number of periods * fragsize = period size in bytes */static int build_via_table(struct viadev *dev, struct snd_pcm_substream *substream, struct pci_dev *pci, unsigned int periods, unsigned int fragsize){ unsigned int i, idx, ofs, rest; struct via82xx_modem *chip = snd_pcm_substream_chip(substream); struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); if (dev->table.area == NULL) { /* the start of each lists must be aligned to 8 bytes, * but the kernel pages are much bigger, so we don't care */ if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), PAGE_ALIGN(VIA_TABLE_SIZE * 2 * 8), &dev->table) < 0) return -ENOMEM; } if (! dev->idx_table) { dev->idx_table = kmalloc(sizeof(*dev->idx_table) * VIA_TABLE_SIZE, GFP_KERNEL); if (! dev->idx_table) return -ENOMEM; } /* fill the entries */ idx = 0; ofs = 0; for (i = 0; i < periods; i++) { rest = fragsize; /* fill descriptors for a period. * a period can be split to several descriptors if it's * over page boundary. */ do { unsigned int r; unsigned int flag; if (idx >= VIA_TABLE_SIZE) { snd_printk(KERN_ERR "via82xx: too much table size!\n"); return -EINVAL; } ((u32 *)dev->table.area)[idx << 1] = cpu_to_le32((u32)snd_pcm_sgbuf_get_addr(sgbuf, ofs)); r = PAGE_SIZE - (ofs % PAGE_SIZE); if (rest < r) r = rest; rest -= r; if (! rest) { if (i == periods - 1) flag = VIA_TBL_BIT_EOL; /* buffer boundary */ else flag = VIA_TBL_BIT_FLAG; /* period boundary */ } else flag = 0; /* period continues to the next */ // printk("via: tbl %d: at %d size %d (rest %d)\n", idx, ofs, r, rest); ((u32 *)dev->table.area)[(idx<<1) + 1] = cpu_to_le32(r | flag); dev->idx_table[idx].offset = ofs; dev->idx_table[idx].size = r; ofs += r; idx++; } while (rest > 0); } dev->tbl_entries = idx; dev->bufsize = periods * fragsize; dev->bufsize2 = dev->bufsize / 2; return 0;}static int clean_via_table(struct viadev *dev, struct snd_pcm_substream *substream, struct pci_dev *pci){ if (dev->table.area) { snd_dma_free_pages(&dev->table); dev->table.area = NULL; } kfree(dev->idx_table); dev->idx_table = NULL; return 0;}/* * Basic I/O */static inline unsigned int snd_via82xx_codec_xread(struct via82xx_modem *chip){ return inl(VIAREG(chip, AC97));} static inline void snd_via82xx_codec_xwrite(struct via82xx_modem *chip, unsigned int val){ outl(val, VIAREG(chip, AC97));} static int snd_via82xx_codec_ready(struct via82xx_modem *chip, int secondary){ unsigned int timeout = 1000; /* 1ms */ unsigned int val; while (timeout-- > 0) { udelay(1); if (!((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY)) return val & 0xffff; } snd_printk(KERN_ERR "codec_ready: codec %i is not ready [0x%x]\n", secondary, snd_via82xx_codec_xread(chip)); return -EIO;} static int snd_via82xx_codec_valid(struct via82xx_modem *chip, int secondary){ unsigned int timeout = 1000; /* 1ms */ unsigned int val, val1; unsigned int stat = !secondary ? VIA_REG_AC97_PRIMARY_VALID : VIA_REG_AC97_SECONDARY_VALID; while (timeout-- > 0) { val = snd_via82xx_codec_xread(chip); val1 = val & (VIA_REG_AC97_BUSY | stat); if (val1 == stat) return val & 0xffff; udelay(1); } return -EIO;} static void snd_via82xx_codec_wait(struct snd_ac97 *ac97){ struct via82xx_modem *chip = ac97->private_data; int err; err = snd_via82xx_codec_ready(chip, ac97->num); /* here we need to wait fairly for long time.. */ msleep(500);}static void snd_via82xx_codec_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -