diff options
Diffstat (limited to 'sound/pci/via82xx_modem.c')
-rw-r--r-- | sound/pci/via82xx_modem.c | 1245 |
1 files changed, 1245 insertions, 0 deletions
diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c new file mode 100644 index 000000000000..ea5c6f640159 --- /dev/null +++ b/sound/pci/via82xx_modem.c @@ -0,0 +1,1245 @@ +/* + * ALSA modem driver for VIA VT82xx (South Bridge) + * + * VT82C686A/B/C, VT8233A/C, VT8235 + * + * Copyright (c) 2000 Jaroslav Kysela <perex@suse.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@smlink.com> + * 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 +#endif + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("VIA VT82xx modem"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{VIA,VT82C686A/B/C modem,pci}}"); + +static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* Exclude the first card */ +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}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for VIA 82xx bridge."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for VIA 82xx bridge."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable modem part of VIA 82xx bridge."); +module_param_array(ac97_clock, int, NULL, 0444); +MODULE_PARM_DESC(ac97_clock, "AC'97 codec clock (default 48000Hz)."); + + +/* + * 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) + + +typedef struct _snd_via82xx_modem via82xx_t; +typedef struct via_dev viadev_t; + +/* + * pcm stream + */ + +struct snd_via_sg_table { + unsigned int offset; + unsigned int size; +} ; + +#define VIA_TABLE_SIZE 255 + +struct via_dev { + unsigned int reg_offset; + unsigned long port; + int direction; /* playback = 0, capture = 1 */ + snd_pcm_substream_t *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 2 + +struct _snd_via82xx_modem { + int irq; + + unsigned long port; + + unsigned int intr_mask; /* SGD_SHADOW mask to check interrupts */ + + struct pci_dev *pci; + snd_card_t *card; + + unsigned int num_devs; + unsigned int playback_devno, capture_devno; + viadev_t devs[VIA_MAX_MODEM_DEVS]; + + snd_pcm_t *pcms[2]; + + ac97_bus_t *ac97_bus; + ac97_t *ac97; + unsigned int ac97_clock; + unsigned int ac97_secondary; /* secondary AC'97 codec is present */ + + spinlock_t reg_lock; + snd_info_entry_t *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(viadev_t *dev, snd_pcm_substream_t *substream, + struct pci_dev *pci, + unsigned int periods, unsigned int fragsize) +{ + unsigned int i, idx, ofs, rest; + via82xx_t *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(viadev_t *dev, snd_pcm_substream_t *substream, + struct pci_dev *pci) +{ + if (dev->table.area) { + snd_dma_free_pages(&dev->table); + dev->table.area = NULL; + } + if (dev->idx_table) { + kfree(dev->idx_table); + dev->idx_table = NULL; + } + return 0; +} + +/* + * Basic I/O + */ + +static inline unsigned int snd_via82xx_codec_xread(via82xx_t *chip) +{ + return inl(VIAREG(chip, AC97)); +} + +static inline void snd_via82xx_codec_xwrite(via82xx_t *chip, unsigned int val) +{ + outl(val, VIAREG(chip, AC97)); +} + +static int snd_via82xx_codec_ready(via82xx_t *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(via82xx_t *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(ac97_t *ac97) +{ + via82xx_t *chip = ac97->private_data; + int err; + err = snd_via82xx_codec_ready(chip, ac97->num); + /* here we need to wait fairly for long time.. */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(HZ/2); +} + +static void snd_via82xx_codec_write(ac97_t *ac97, + unsigned short reg, + unsigned short val) +{ + via82xx_t *chip = ac97->private_data; + unsigned int xval; + + xval = !ac97->num ? VIA_REG_AC97_CODEC_ID_PRIMARY : VIA_REG_AC97_CODEC_ID_SECONDARY; + xval <<= VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= reg << VIA_REG_AC97_CMD_SHIFT; + xval |= val << VIA_REG_AC97_DATA_SHIFT; + snd_via82xx_codec_xwrite(chip, xval); + snd_via82xx_codec_ready(chip, ac97->num); +} + +static unsigned short snd_via82xx_codec_read(ac97_t *ac97, unsigned short reg) +{ + via82xx_t *chip = ac97->private_data; + unsigned int xval, val = 0xffff; + int again = 0; + + xval = ac97->num << VIA_REG_AC97_CODEC_ID_SHIFT; + xval |= ac97->num ? VIA_REG_AC97_SECONDARY_VALID : VIA_REG_AC97_PRIMARY_VALID; + xval |= VIA_REG_AC97_READ; + xval |= (reg & 0x7f) << VIA_REG_AC97_CMD_SHIFT; + while (1) { + if (again++ > 3) { + snd_printk(KERN_ERR "codec_read: codec %i is not valid [0x%x]\n", ac97->num, snd_via82xx_codec_xread(chip)); + return 0xffff; + } + snd_via82xx_codec_xwrite(chip, xval); + udelay (20); + if (snd_via82xx_codec_valid(chip, ac97->num) >= 0) { + udelay(25); + val = snd_via82xx_codec_xread(chip); + break; + } + } + return val & 0xffff; +} + +static void snd_via82xx_channel_reset(via82xx_t *chip, viadev_t *viadev) +{ + outb(VIA_REG_CTRL_PAUSE | VIA_REG_CTRL_TERMINATE | VIA_REG_CTRL_RESET, + VIADEV_REG(viadev, OFFSET_CONTROL)); + inb(VIADEV_REG(viadev, OFFSET_CONTROL)); + udelay(50); + /* disable interrupts */ + outb(0x00, VIADEV_REG(viadev, OFFSET_CONTROL)); + /* clear interrupts */ + outb(0x03, VIADEV_REG(viadev, OFFSET_STATUS)); + outb(0x00, VIADEV_REG(viadev, OFFSET_TYPE)); /* for via686 */ + // outl(0, VIADEV_REG(viadev, OFFSET_CURR_PTR)); + viadev->lastpos = 0; +} + + +/* + * Interrupt handler + */ + +static irqreturn_t snd_via82xx_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + via82xx_t *chip = dev_id; + unsigned int status; + unsigned int i; + + status = inl(VIAREG(chip, SGD_SHADOW)); + if (! (status & chip->intr_mask)) { + return IRQ_NONE; + } +// _skip_sgd: + + /* check status for each stream */ + spin_lock(&chip->reg_lock); + for (i = 0; i < chip->num_devs; i++) { + viadev_t *viadev = &chip->devs[i]; + unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); + c_status &= (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED); + if (! c_status) + continue; + if (viadev->substream && viadev->running) { + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(viadev->substream); + spin_lock(&chip->reg_lock); + } + outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ + } + spin_unlock(&chip->reg_lock); + return IRQ_HANDLED; +} + +/* + * PCM callbacks + */ + +/* + * trigger callback + */ +static int snd_via82xx_pcm_trigger(snd_pcm_substream_t * substream, int cmd) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + unsigned char val = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + val |= VIA_REG_CTRL_START; + viadev->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + val = VIA_REG_CTRL_TERMINATE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + val |= VIA_REG_CTRL_PAUSE; + viadev->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + viadev->running = 1; + break; + default: + return -EINVAL; + } + outb(val, VIADEV_REG(viadev, OFFSET_CONTROL)); + if (cmd == SNDRV_PCM_TRIGGER_STOP) + snd_via82xx_channel_reset(chip, viadev); + return 0; +} + +static int snd_via82xx_modem_pcm_trigger(snd_pcm_substream_t * substream, int cmd) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + unsigned int val = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + val = snd_ac97_read(chip->ac97, AC97_GPIO_STATUS); + outl(val|AC97_GPIO_LINE1_OH, VIAREG(chip, GPI_STATUS)); + break; + case SNDRV_PCM_TRIGGER_STOP: + val = snd_ac97_read(chip->ac97, AC97_GPIO_STATUS); + outl(val&~AC97_GPIO_LINE1_OH, VIAREG(chip, GPI_STATUS)); + break; + default: + break; + } + return snd_via82xx_pcm_trigger(substream, cmd); +} + +/* + * pointer callbacks + */ + +/* + * calculate the linear position at the given sg-buffer index and the rest count + */ + +#define check_invalid_pos(viadev,pos) \ + ((pos) < viadev->lastpos && ((pos) >= viadev->bufsize2 || viadev->lastpos < viadev->bufsize2)) + +static inline unsigned int calc_linear_pos(viadev_t *viadev, unsigned int idx, unsigned int count) +{ + unsigned int size, res; + + size = viadev->idx_table[idx].size; + res = viadev->idx_table[idx].offset + size - count; + + /* check the validity of the calculated position */ + if (size < count) { + snd_printd(KERN_ERR "invalid via82xx_cur_ptr (size = %d, count = %d)\n", (int)size, (int)count); + res = viadev->lastpos; + } else if (check_invalid_pos(viadev, res)) { +#ifdef POINTER_DEBUG + printk("fail: idx = %i/%i, lastpos = 0x%x, bufsize2 = 0x%x, offsize = 0x%x, size = 0x%x, count = 0x%x\n", idx, viadev->tbl_entries, viadev->lastpos, viadev->bufsize2, viadev->idx_table[idx].offset, viadev->idx_table[idx].size, count); +#endif + if (count && size < count) { + snd_printd(KERN_ERR "invalid via82xx_cur_ptr, using last valid pointer\n"); + res = viadev->lastpos; + } else { + if (! count) + /* bogus count 0 on the DMA boundary? */ + res = viadev->idx_table[idx].offset; + else + /* count register returns full size when end of buffer is reached */ + res = viadev->idx_table[idx].offset + size; + if (check_invalid_pos(viadev, res)) { + snd_printd(KERN_ERR "invalid via82xx_cur_ptr (2), using last valid pointer\n"); + res = viadev->lastpos; + } + } + } + viadev->lastpos = res; /* remember the last position */ + if (res >= viadev->bufsize) + res -= viadev->bufsize; + return res; +} + +/* + * get the current pointer on via686 + */ +static snd_pcm_uframes_t snd_via686_pcm_pointer(snd_pcm_substream_t *substream) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + unsigned int idx, ptr, count, res; + + snd_assert(viadev->tbl_entries, return 0); + if (!(inb(VIADEV_REG(viadev, OFFSET_STATUS)) & VIA_REG_STAT_ACTIVE)) + return 0; + + spin_lock(&chip->reg_lock); + count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)) & 0xffffff; + /* The via686a does not have the current index register, + * so we need to calculate the index from CURR_PTR. + */ + ptr = inl(VIADEV_REG(viadev, OFFSET_CURR_PTR)); + if (ptr <= (unsigned int)viadev->table.addr) + idx = 0; + else /* CURR_PTR holds the address + 8 */ + idx = ((ptr - (unsigned int)viadev->table.addr) / 8 - 1) % viadev->tbl_entries; + res = calc_linear_pos(viadev, idx, count); + spin_unlock(&chip->reg_lock); + + return bytes_to_frames(substream->runtime, res); +} + +/* + * hw_params callback: + * allocate the buffer and build up the buffer description table + */ +static int snd_via82xx_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + int err; + + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); + if (err < 0) + return err; + err = build_via_table(viadev, substream, chip->pci, + params_periods(hw_params), + params_period_bytes(hw_params)); + if (err < 0) + return err; + + snd_ac97_write(chip->ac97, AC97_LINE1_RATE, params_rate(hw_params)); + snd_ac97_write(chip->ac97, AC97_LINE1_LEVEL, 0); + + return 0; +} + +/* + * hw_free callback: + * clean up the buffer description table and release the buffer + */ +static int snd_via82xx_hw_free(snd_pcm_substream_t * substream) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + + clean_via_table(viadev, substream, chip->pci); + snd_pcm_lib_free_pages(substream); + return 0; +} + + +/* + * set up the table pointer + */ +static void snd_via82xx_set_table_ptr(via82xx_t *chip, viadev_t *viadev) +{ + snd_via82xx_codec_ready(chip, chip->ac97_secondary); + outl((u32)viadev->table.addr, VIADEV_REG(viadev, OFFSET_TABLE_PTR)); + udelay(20); + snd_via82xx_codec_ready(chip, chip->ac97_secondary); +} + +/* + * prepare callback for playback and capture + */ +static int snd_via82xx_pcm_prepare(snd_pcm_substream_t *substream) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + + snd_via82xx_channel_reset(chip, viadev); + /* this must be set after channel_reset */ + snd_via82xx_set_table_ptr(chip, viadev); + outb(VIA_REG_TYPE_AUTOSTART|VIA_REG_TYPE_INT_EOL|VIA_REG_TYPE_INT_FLAG, + VIADEV_REG(viadev, OFFSET_TYPE)); + return 0; +} + +/* + * pcm hardware definition, identical for both playback and capture + */ +static snd_pcm_hardware_t snd_via82xx_hw = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_KNOT, + .rate_min = 8000, + .rate_max = 16000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 32, + .period_bytes_max = 128 * 1024, + .periods_min = 2, + .periods_max = VIA_TABLE_SIZE / 2, + .fifo_size = 0, +}; + + +/* + * open callback skeleton + */ +static int snd_via82xx_modem_pcm_open(via82xx_t *chip, viadev_t *viadev, snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + static unsigned int rates[] = { 8000, 9600, 12000, 16000 }; + static snd_pcm_hw_constraint_list_t hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + + runtime->hw = snd_via82xx_hw; + + if ((err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates)) < 0) + return err; + + /* we may remove following constaint when we modify table entries + in interrupt */ + if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0) + return err; + + runtime->private_data = viadev; + viadev->substream = substream; + + return 0; +} + + +/* + * open callback for playback + */ +static int snd_via82xx_playback_open(snd_pcm_substream_t * substream) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = &chip->devs[chip->playback_devno + substream->number]; + + return snd_via82xx_modem_pcm_open(chip, viadev, substream); +} + +/* + * open callback for capture + */ +static int snd_via82xx_capture_open(snd_pcm_substream_t * substream) +{ + via82xx_t *chip = snd_pcm_substream_chip(substream); + viadev_t *viadev = &chip->devs[chip->capture_devno + substream->pcm->device]; + + return snd_via82xx_modem_pcm_open(chip, viadev, substream); +} + +/* + * close callback + */ +static int snd_via82xx_pcm_close(snd_pcm_substream_t * substream) +{ + viadev_t *viadev = (viadev_t *)substream->runtime->private_data; + + viadev->substream = NULL; + return 0; +} + + +/* via686 playback callbacks */ +static snd_pcm_ops_t snd_via686_playback_ops = { + .open = snd_via82xx_playback_open, + .close = snd_via82xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via82xx_pcm_prepare, + .trigger = snd_via82xx_modem_pcm_trigger, + .pointer = snd_via686_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + +/* via686 capture callbacks */ +static snd_pcm_ops_t snd_via686_capture_ops = { + .open = snd_via82xx_capture_open, + .close = snd_via82xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_via82xx_hw_params, + .hw_free = snd_via82xx_hw_free, + .prepare = snd_via82xx_pcm_prepare, + .trigger = snd_via82xx_modem_pcm_trigger, + .pointer = snd_via686_pcm_pointer, + .page = snd_pcm_sgbuf_ops_page, +}; + + +static void init_viadev(via82xx_t *chip, int idx, unsigned int reg_offset, int direction) +{ + chip->devs[idx].reg_offset = reg_offset; + chip->devs[idx].direction = direction; + chip->devs[idx].port = chip->port + reg_offset; +} + +/* + * create a pcm instance for via686a/b + */ +static int __devinit snd_via686_pcm_new(via82xx_t *chip) +{ + snd_pcm_t *pcm; + int err; + + chip->playback_devno = 0; + chip->capture_devno = 1; + chip->num_devs = 2; + chip->intr_mask = 0x330000; /* FLAGS | EOL for MR, MW */ + + err = snd_pcm_new(chip->card, chip->card->shortname, 0, 1, 1, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_via686_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_via686_capture_ops); + pcm->private_data = chip; + strcpy(pcm->name, chip->card->shortname); + chip->pcms[0] = pcm; + init_viadev(chip, 0, VIA_REG_MO_STATUS, 0); + init_viadev(chip, 1, VIA_REG_MI_STATUS, 1); + + if ((err = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG, + snd_dma_pci_data(chip->pci), 64*1024, 128*1024)) < 0) + return err; + + return 0; +} + + +/* + * Mixer part + */ + + +static void snd_via82xx_mixer_free_ac97_bus(ac97_bus_t *bus) +{ + via82xx_t *chip = bus->private_data; + chip->ac97_bus = NULL; +} + +static void snd_via82xx_mixer_free_ac97(ac97_t *ac97) +{ + via82xx_t *chip = ac97->private_data; + chip->ac97 = NULL; +} + + +static int __devinit snd_via82xx_mixer_new(via82xx_t *chip) +{ + ac97_template_t ac97; + int err; + static ac97_bus_ops_t ops = { + .write = snd_via82xx_codec_write, + .read = snd_via82xx_codec_read, + .wait = snd_via82xx_codec_wait, + }; + + if ((err = snd_ac97_bus(chip->card, 0, &ops, chip, &chip->ac97_bus)) < 0) + return err; + chip->ac97_bus->private_free = snd_via82xx_mixer_free_ac97_bus; + chip->ac97_bus->clock = chip->ac97_clock; + chip->ac97_bus->shared_type = AC97_SHARED_TYPE_VIA; + + memset(&ac97, 0, sizeof(ac97)); + ac97.private_data = chip; + ac97.private_free = snd_via82xx_mixer_free_ac97; + ac97.pci = chip->pci; + ac97.scaps = AC97_SCAP_SKIP_AUDIO; + ac97.num = chip->ac97_secondary; + + if ((err = snd_ac97_mixer(chip->ac97_bus, &ac97, &chip->ac97)) < 0) + return err; + + return 0; +} + + +/* + * proc interface + */ +static void snd_via82xx_proc_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer) +{ + via82xx_t *chip = entry->private_data; + int i; + + snd_iprintf(buffer, "%s\n\n", chip->card->longname); + for (i = 0; i < 0xa0; i += 4) { + snd_iprintf(buffer, "%02x: %08x\n", i, inl(chip->port + i)); + } +} + +static void __devinit snd_via82xx_proc_init(via82xx_t *chip) +{ + snd_info_entry_t *entry; + + if (! snd_card_proc_new(chip->card, "via82xx", &entry)) + snd_info_set_text_ops(entry, chip, 1024, snd_via82xx_proc_read); +} + +/* + * + */ + +static int __devinit snd_via82xx_chip_init(via82xx_t *chip) +{ + unsigned int val; + int max_count; + unsigned char pval; + + pci_read_config_byte(chip->pci, VIA_MC97_CTRL, &pval); + if((pval & VIA_MC97_CTRL_INIT) != VIA_MC97_CTRL_INIT) { + pci_write_config_byte(chip->pci, 0x44, pval|VIA_MC97_CTRL_INIT); + udelay(100); + } + + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (! (pval & VIA_ACLINK_C00_READY)) { /* codec not ready? */ + /* deassert ACLink reset, force SYNC */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_ENABLE | + VIA_ACLINK_CTRL_RESET | + VIA_ACLINK_CTRL_SYNC); + udelay(100); +#if 1 /* FIXME: should we do full reset here for all chip models? */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, 0x00); + udelay(100); +#else + /* deassert ACLink reset, force SYNC (warm AC'97 reset) */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, + VIA_ACLINK_CTRL_RESET|VIA_ACLINK_CTRL_SYNC); + udelay(2); +#endif + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + pci_read_config_byte(chip->pci, VIA_ACLINK_CTRL, &pval); + if ((pval & VIA_ACLINK_CTRL_INIT) != VIA_ACLINK_CTRL_INIT) { + /* ACLink on, deassert ACLink reset, VSR, SGD data out */ + pci_write_config_byte(chip->pci, VIA_ACLINK_CTRL, VIA_ACLINK_CTRL_INIT); + udelay(100); + } + + /* wait until codec ready */ + max_count = ((3 * HZ) / 4) + 1; + do { + pci_read_config_byte(chip->pci, VIA_ACLINK_STAT, &pval); + if (pval & VIA_ACLINK_C00_READY) /* primary codec ready */ + break; + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(1); + } while (--max_count > 0); + + if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_BUSY) + snd_printk("AC'97 codec is not ready [0x%x]\n", val); + + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + max_count = ((3 * HZ) / 4) + 1; + snd_via82xx_codec_xwrite(chip, VIA_REG_AC97_READ | + VIA_REG_AC97_SECONDARY_VALID | + (VIA_REG_AC97_CODEC_ID_SECONDARY << VIA_REG_AC97_CODEC_ID_SHIFT)); + do { + if ((val = snd_via82xx_codec_xread(chip)) & VIA_REG_AC97_SECONDARY_VALID) { + chip->ac97_secondary = 1; + goto __ac97_ok2; + } + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } while (--max_count > 0); + /* This is ok, the most of motherboards have only one codec */ + + __ac97_ok2: + + /* route FM trap to IRQ, disable FM trap */ + // pci_write_config_byte(chip->pci, VIA_FM_NMI_CTRL, 0); + /* disable all GPI interrupts */ + outl(0, VIAREG(chip, GPI_INTR)); + + return 0; +} + +#ifdef CONFIG_PM +/* + * power management + */ +static int snd_via82xx_suspend(snd_card_t *card, pm_message_t state) +{ + via82xx_t *chip = card->pm_private_data; + int i; + + for (i = 0; i < 2; i++) + if (chip->pcms[i]) + snd_pcm_suspend_all(chip->pcms[i]); + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + synchronize_irq(chip->irq); + snd_ac97_suspend(chip->ac97); + pci_set_power_state(chip->pci, 3); + pci_disable_device(chip->pci); + return 0; +} + +static int snd_via82xx_resume(snd_card_t *card) +{ + via82xx_t *chip = card->pm_private_data; + int i; + + pci_enable_device(chip->pci); + pci_set_power_state(chip->pci, 0); + pci_set_master(chip->pci); + + snd_via82xx_chip_init(chip); + + snd_ac97_resume(chip->ac97); + + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + return 0; +} +#endif /* CONFIG_PM */ + +static int snd_via82xx_free(via82xx_t *chip) +{ + unsigned int i; + + if (chip->irq < 0) + goto __end_hw; + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + synchronize_irq(chip->irq); + __end_hw: + if (chip->irq >= 0) + free_irq(chip->irq, (void *)chip); + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); + kfree(chip); + return 0; +} + +static int snd_via82xx_dev_free(snd_device_t *device) +{ + via82xx_t *chip = device->device_data; + return snd_via82xx_free(chip); +} + +static int __devinit snd_via82xx_create(snd_card_t * card, + struct pci_dev *pci, + int chip_type, + int revision, + unsigned int ac97_clock, + via82xx_t ** r_via) +{ + via82xx_t *chip; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_via82xx_dev_free, + }; + + if ((err = pci_enable_device(pci)) < 0) + return err; + + if ((chip = kcalloc(1, sizeof(*chip), GFP_KERNEL)) == NULL) { + pci_disable_device(pci); + return -ENOMEM; + } + + spin_lock_init(&chip->reg_lock); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + + if ((err = pci_request_regions(pci, card->driver)) < 0) { + kfree(chip); + pci_disable_device(pci); + return err; + } + chip->port = pci_resource_start(pci, 0); + if (request_irq(pci->irq, snd_via82xx_interrupt, SA_INTERRUPT|SA_SHIRQ, + card->driver, (void *)chip)) { + snd_printk("unable to grab IRQ %d\n", pci->irq); + snd_via82xx_free(chip); + return -EBUSY; + } + chip->irq = pci->irq; + if (ac97_clock >= 8000 && ac97_clock <= 48000) + chip->ac97_clock = ac97_clock; + synchronize_irq(chip->irq); + + if ((err = snd_via82xx_chip_init(chip)) < 0) { + snd_via82xx_free(chip); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_via82xx_free(chip); + return err; + } + + /* The 8233 ac97 controller does not implement the master bit + * in the pci command register. IMHO this is a violation of the PCI spec. + * We call pci_set_master here because it does not hurt. */ + pci_set_master(pci); + + snd_card_set_dev(card, &pci->dev); + + *r_via = chip; + return 0; +} + + +static int __devinit snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + snd_card_t *card; + via82xx_t *chip; + unsigned char revision; + int chip_type = 0, card_type; + unsigned int i; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + card_type = pci_id->driver_data; + pci_read_config_byte(pci, PCI_REVISION_ID, &revision); + switch (card_type) { + case TYPE_CARD_VIA82XX_MODEM: + strcpy(card->driver, "VIA82XX-MODEM"); + sprintf(card->shortname, "VIA 82XX modem"); + break; + default: + snd_printk(KERN_ERR "invalid card type %d\n", card_type); + err = -EINVAL; + goto __error; + } + + if ((err = snd_via82xx_create(card, pci, chip_type, revision, ac97_clock[dev], &chip)) < 0) + goto __error; + if ((err = snd_via82xx_mixer_new(chip)) < 0) + goto __error; + + if ((err = snd_via686_pcm_new(chip)) < 0 ) + goto __error; + + snd_card_set_pm_callback(card, snd_via82xx_suspend, snd_via82xx_resume, chip); + + /* disable interrupts */ + for (i = 0; i < chip->num_devs; i++) + snd_via82xx_channel_reset(chip, &chip->devs[i]); + + sprintf(card->longname, "%s at 0x%lx, irq %d", + card->shortname, chip->port, chip->irq); + + snd_via82xx_proc_init(chip); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + pci_set_drvdata(pci, card); + dev++; + return 0; + + __error: + snd_card_free(card); + return err; +} + +static void __devexit snd_via82xx_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} + +static struct pci_driver driver = { + .name = "VIA 82xx Modem", + .id_table = snd_via82xx_modem_ids, + .probe = snd_via82xx_probe, + .remove = __devexit_p(snd_via82xx_remove), + SND_PCI_PM_CALLBACKS +}; + +static int __init alsa_card_via82xx_init(void) +{ + return pci_module_init(&driver); +} + +static void __exit alsa_card_via82xx_exit(void) +{ + pci_unregister_driver(&driver); +} + +module_init(alsa_card_via82xx_init) +module_exit(alsa_card_via82xx_exit) |