📄 via82xx.c
字号:
} /* 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; dev->fragsize = fragsize; 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 *chip){ return inl(VIAREG(chip, AC97));} static inline void snd_via82xx_codec_xwrite(struct via82xx *chip, unsigned int val){ outl(val, VIAREG(chip, AC97));} static int snd_via82xx_codec_ready(struct via82xx *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 *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 *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){ struct via82xx *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(struct snd_ac97 *ac97, unsigned short reg){ struct via82xx *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(struct via82xx *chip, struct viadev *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; viadev->hwptr_done = 0;}/* * Interrupt handler * Used for 686 and 8233A */static irqreturn_t snd_via686_interrupt(int irq, void *dev_id){ struct via82xx *chip = dev_id; unsigned int status; unsigned int i; status = inl(VIAREG(chip, SGD_SHADOW)); if (! (status & chip->intr_mask)) { if (chip->rmidi) /* check mpu401 interrupt */ return snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); return IRQ_NONE; } /* check status for each stream */ spin_lock(&chip->reg_lock); for (i = 0; i < chip->num_devs; i++) { struct viadev *viadev = &chip->devs[i]; unsigned char c_status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); if (! (c_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG|VIA_REG_STAT_STOPPED))) continue; if (viadev->substream && viadev->running) { /* * Update hwptr_done based on 'period elapsed' * interrupts. We'll use it, when the chip returns 0 * for OFFSET_CURR_COUNT. */ if (c_status & VIA_REG_STAT_EOL) viadev->hwptr_done = 0; else viadev->hwptr_done += viadev->fragsize; viadev->in_interrupt = c_status; spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(viadev->substream); spin_lock(&chip->reg_lock); viadev->in_interrupt = 0; } outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ } spin_unlock(&chip->reg_lock); return IRQ_HANDLED;}/* * Interrupt handler */static irqreturn_t snd_via8233_interrupt(int irq, void *dev_id){ struct via82xx *chip = dev_id; unsigned int status; unsigned int i; int irqreturn = 0; /* check status for each stream */ spin_lock(&chip->reg_lock); status = inl(VIAREG(chip, SGD_SHADOW)); for (i = 0; i < chip->num_devs; i++) { struct viadev *viadev = &chip->devs[i]; struct snd_pcm_substream *substream; unsigned char c_status, shadow_status; shadow_status = (status >> viadev->shadow_shift) & (VIA8233_SHADOW_STAT_ACTIVE|VIA_REG_STAT_EOL| VIA_REG_STAT_FLAG); c_status = shadow_status & (VIA_REG_STAT_EOL|VIA_REG_STAT_FLAG); if (!c_status) continue; substream = viadev->substream; if (substream && viadev->running) { /* * Update hwptr_done based on 'period elapsed' * interrupts. We'll use it, when the chip returns 0 * for OFFSET_CURR_COUNT. */ if (c_status & VIA_REG_STAT_EOL) viadev->hwptr_done = 0; else viadev->hwptr_done += viadev->fragsize; viadev->in_interrupt = c_status; if (shadow_status & VIA8233_SHADOW_STAT_ACTIVE) viadev->in_interrupt |= VIA_REG_STAT_ACTIVE; spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(substream); spin_lock(&chip->reg_lock); viadev->in_interrupt = 0; } outb(c_status, VIADEV_REG(viadev, OFFSET_STATUS)); /* ack */ irqreturn = 1; } spin_unlock(&chip->reg_lock); return IRQ_RETVAL(irqreturn);}/* * PCM callbacks *//* * trigger callback */static int snd_via82xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd){ struct via82xx *chip = snd_pcm_substream_chip(substream); struct viadev *viadev = substream->runtime->private_data; unsigned char val; if (chip->chip_type != TYPE_VIA686) val = VIA_REG_CTRL_INT; else val = 0; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: val |= VIA_REG_CTRL_START; viadev->running = 1; break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: 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;}/* * 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(struct viadev *viadev, unsigned int idx, unsigned int count){ unsigned int size, base, res; size = viadev->idx_table[idx].size; base = viadev->idx_table[idx].offset; res = base + size - count; if (res >= viadev->bufsize) res -= viadev->bufsize; /* 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 (! count) { /* Some mobos report count = 0 on the DMA boundary, * i.e. count = size indeed. * Let's check whether this step is above the expected size. */ int delta = res - viadev->lastpos; if (delta < 0) delta += viadev->bufsize; if ((unsigned int)delta > viadev->fragsize) res = base; } if (check_invalid_pos(viadev, res)) {#ifdef POINTER_DEBUG printk(KERN_DEBUG "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 /* count register returns full size when end of buffer is reached */ res = base + size; if (check_invalid_pos(viadev, res)) { snd_printd(KERN_ERR "invalid via82xx_cur_ptr (2), " "using last valid pointer\n"); res = viadev->lastpos; } } } return res;}/* * get the current pointer on via686 */static snd_pcm_uframes_t snd_via686_pcm_pointer(struct snd_pcm_substream *substream){ struct via82xx *chip = snd_pcm_substream_chip(substream); struct viadev *viadev = 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); viadev->lastpos = res; /* remember the last position */ spin_unlock(&chip->reg_lock); return bytes_to_frames(substream->runtime, res);}/* * get the current pointer on via823x */static snd_pcm_uframes_t snd_via8233_pcm_pointer(struct snd_pcm_substream *substream){ struct via82xx *chip = snd_pcm_substream_chip(substream); struct viadev *viadev = substream->runtime->private_data; unsigned int idx, count, res; int status; snd_assert(viadev->tbl_entries, return 0); spin_lock(&chip->reg_lock); count = inl(VIADEV_REG(viadev, OFFSET_CURR_COUNT)); status = viadev->in_interrupt; if (!status) status = inb(VIADEV_REG(viadev, OFFSET_STATUS)); /* An apparent bug in the 8251 is worked around by sending a * REG_CTRL_START. */ if (chip->revision == VIA_REV_8251 && (status & VIA_REG_STAT_EOL)) snd_via82xx_pcm_trigger(substream, SNDRV_PCM_TRIGGER_START); if (!(status & VIA_REG_STAT_ACTIVE)) { res = 0; goto unlock; } if (count & 0xffffff) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -