trident.c
来自「linux 内核源代码」· C语言 代码 · 共 2,292 行 · 第 1/5 页
C
2,292 行
/* FIXME: figure out all this OSS fragment stuff */ bytepersec = dmabuf->rate << sample_shift[dmabuf->fmt]; bufsize = PAGE_SIZE << dmabuf->buforder; if (dmabuf->ossfragshift) { if ((1000 << dmabuf->ossfragshift) < bytepersec) dmabuf->fragshift = ld2(bytepersec / 1000); else dmabuf->fragshift = dmabuf->ossfragshift; } else { /* lets hand out reasonable big ass buffers by default */ dmabuf->fragshift = (dmabuf->buforder + PAGE_SHIFT - 2); } dmabuf->numfrag = bufsize >> dmabuf->fragshift; while (dmabuf->numfrag < 4 && dmabuf->fragshift > 3) { dmabuf->fragshift--; dmabuf->numfrag = bufsize >> dmabuf->fragshift; } dmabuf->fragsize = 1 << dmabuf->fragshift; if (dmabuf->ossmaxfrags >= 4 && dmabuf->ossmaxfrags < dmabuf->numfrag) dmabuf->numfrag = dmabuf->ossmaxfrags; dmabuf->fragsamples = dmabuf->fragsize >> sample_shift[dmabuf->fmt]; dmabuf->dmasize = dmabuf->numfrag << dmabuf->fragshift; memset(dmabuf->rawbuf, (dmabuf->fmt & TRIDENT_FMT_16BIT) ? 0 : 0x80, dmabuf->dmasize); spin_lock_irqsave(&s->card->lock, flags); if (rec == DM_RECORD) trident_rec_setup(s); else /* DM_PLAYBACK */ trident_play_setup(s); spin_unlock_irqrestore(&s->card->lock, flags); /* set the ready flag for the dma buffer */ dmabuf->ready = 1; pr_debug("trident: prog_dmabuf(%d), sample rate = %d, " "format = %d, numfrag = %d, fragsize = %d " "dmasize = %d\n", dmabuf->channel->num, dmabuf->rate, dmabuf->fmt, dmabuf->numfrag, dmabuf->fragsize, dmabuf->dmasize); } unlock_set_fmt(state); return 0;}static inline int prog_dmabuf_record(struct trident_state* state){ return prog_dmabuf(state, DM_RECORD);}static inline int prog_dmabuf_playback(struct trident_state* state){ return prog_dmabuf(state, DM_PLAYBACK);}/* we are doing quantum mechanics here, the buffer can only be empty, half or full filled i.e. |------------|------------| or |xxxxxxxxxxxx|------------| or |xxxxxxxxxxxx|xxxxxxxxxxxx| but we almost always get this |xxxxxx------|------------| or |xxxxxxxxxxxx|xxxxx-------| so we have to clear the tail space to "silence" |xxxxxx000000|------------| or |xxxxxxxxxxxx|xxxxxx000000|*/static voidtrident_clear_tail(struct trident_state *state){ struct dmabuf *dmabuf = &state->dmabuf; unsigned swptr; unsigned char silence = (dmabuf->fmt & TRIDENT_FMT_16BIT) ? 0 : 0x80; unsigned int len; unsigned long flags; spin_lock_irqsave(&state->card->lock, flags); swptr = dmabuf->swptr; spin_unlock_irqrestore(&state->card->lock, flags); if (swptr == 0 || swptr == dmabuf->dmasize / 2 || swptr == dmabuf->dmasize) return; if (swptr < dmabuf->dmasize / 2) len = dmabuf->dmasize / 2 - swptr; else len = dmabuf->dmasize - swptr; memset(dmabuf->rawbuf + swptr, silence, len); if (state->card->pci_id != PCI_DEVICE_ID_ALI_5451) { spin_lock_irqsave(&state->card->lock, flags); dmabuf->swptr += len; dmabuf->count += len; spin_unlock_irqrestore(&state->card->lock, flags); } /* restart the dma machine in case it is halted */ start_dac(state);}static intdrain_dac(struct trident_state *state, int nonblock){ DECLARE_WAITQUEUE(wait, current); struct dmabuf *dmabuf = &state->dmabuf; unsigned long flags; unsigned long tmo; int count; unsigned long diff = 0; if (dmabuf->mapped || !dmabuf->ready) return 0; add_wait_queue(&dmabuf->wait, &wait); for (;;) { /* It seems that we have to set the current state to TASK_INTERRUPTIBLE every time to make the process really go to sleep */ set_current_state(TASK_INTERRUPTIBLE); spin_lock_irqsave(&state->card->lock, flags); count = dmabuf->count; spin_unlock_irqrestore(&state->card->lock, flags); if (count <= 0) break; if (signal_pending(current)) break; if (nonblock) { remove_wait_queue(&dmabuf->wait, &wait); set_current_state(TASK_RUNNING); return -EBUSY; } /* No matter how much data is left in the buffer, we have to wait until CSO == ESO/2 or CSO == ESO when address engine interrupts */ if (state->card->pci_id == PCI_DEVICE_ID_ALI_5451 || state->card->pci_id == PCI_DEVICE_ID_INTERG_5050) { diff = dmabuf->swptr - trident_get_dma_addr(state) + dmabuf->dmasize; diff = diff % (dmabuf->dmasize); tmo = (diff * HZ) / dmabuf->rate; } else { tmo = (dmabuf->dmasize * HZ) / dmabuf->rate; } tmo >>= sample_shift[dmabuf->fmt]; if (!schedule_timeout(tmo ? tmo : 1) && tmo) { break; } } remove_wait_queue(&dmabuf->wait, &wait); set_current_state(TASK_RUNNING); if (signal_pending(current)) return -ERESTARTSYS; return 0;}/* update buffer manangement pointers, especially, *//* dmabuf->count and dmabuf->hwptr */static voidtrident_update_ptr(struct trident_state *state){ struct dmabuf *dmabuf = &state->dmabuf; unsigned hwptr, swptr; int clear_cnt = 0; int diff; unsigned char silence; unsigned half_dmasize; /* update hardware pointer */ hwptr = trident_get_dma_addr(state); diff = (dmabuf->dmasize + hwptr - dmabuf->hwptr) % dmabuf->dmasize; dmabuf->hwptr = hwptr; dmabuf->total_bytes += diff; /* error handling and process wake up for ADC */ if (dmabuf->enable == ADC_RUNNING) { if (dmabuf->mapped) { dmabuf->count -= diff; if (dmabuf->count >= (signed) dmabuf->fragsize) wake_up(&dmabuf->wait); } else { dmabuf->count += diff; if (dmabuf->count < 0 || dmabuf->count > dmabuf->dmasize) { /* buffer underrun or buffer overrun, */ /* we have no way to recover it here, just */ /* stop the machine and let the process */ /* force hwptr and swptr to sync */ __stop_adc(state); dmabuf->error++; } if (dmabuf->count < (signed) dmabuf->dmasize / 2) wake_up(&dmabuf->wait); } } /* error handling and process wake up for DAC */ if (dmabuf->enable == DAC_RUNNING) { if (dmabuf->mapped) { dmabuf->count += diff; if (dmabuf->count >= (signed) dmabuf->fragsize) wake_up(&dmabuf->wait); } else { dmabuf->count -= diff; if (dmabuf->count < 0 || dmabuf->count > dmabuf->dmasize) { /* buffer underrun or buffer overrun, we have no way to recover it here, just stop the machine and let the process force hwptr and swptr to sync */ __stop_dac(state); dmabuf->error++; } else if (!dmabuf->endcleared) { swptr = dmabuf->swptr; silence = (dmabuf->fmt & TRIDENT_FMT_16BIT ? 0 : 0x80); if (dmabuf->update_flag & ALI_ADDRESS_INT_UPDATE) { /* We must clear end data of 1/2 dmabuf if needed. According to 1/2 algorithm of Address Engine Interrupt, check the validation of the data of half dmasize. */ half_dmasize = dmabuf->dmasize / 2; if ((diff = hwptr - half_dmasize) < 0) diff = hwptr; if ((dmabuf->count + diff) < half_dmasize) { //there is invalid data in the end of half buffer if ((clear_cnt = half_dmasize - swptr) < 0) clear_cnt += half_dmasize; //clear the invalid data memset(dmabuf->rawbuf + swptr, silence, clear_cnt); if (state->chans_num == 6) { clear_cnt = clear_cnt / 2; swptr = swptr / 2; memset(state->other_states[0]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[1]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[2]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[3]->dmabuf.rawbuf + swptr, silence, clear_cnt); } dmabuf->endcleared = 1; } } else if (dmabuf->count < (signed) dmabuf->fragsize) { clear_cnt = dmabuf->fragsize; if ((swptr + clear_cnt) > dmabuf->dmasize) clear_cnt = dmabuf->dmasize - swptr; memset(dmabuf->rawbuf + swptr, silence, clear_cnt); if (state->chans_num == 6) { clear_cnt = clear_cnt / 2; swptr = swptr / 2; memset(state->other_states[0]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[1]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[2]->dmabuf.rawbuf + swptr, silence, clear_cnt); memset(state->other_states[3]->dmabuf.rawbuf + swptr, silence, clear_cnt); } dmabuf->endcleared = 1; } } /* trident_update_ptr is called by interrupt handler or by process via ioctl/poll, we only wake up the waiting process when we have more than 1/2 buffer free (always true for interrupt handler) */ if (dmabuf->count < (signed) dmabuf->dmasize / 2) wake_up(&dmabuf->wait); } } dmabuf->update_flag &= ~ALI_ADDRESS_INT_UPDATE;}static voidtrident_address_interrupt(struct trident_card *card){ int i; struct trident_state *state; unsigned int channel; /* Update the pointers for all channels we are running. */ /* FIXME: should read interrupt status only once */ for (i = 0; i < NR_HW_CH; i++) { channel = 63 - i; if (trident_check_channel_interrupt(card, channel)) { trident_ack_channel_interrupt(card, channel); if ((state = card->states[i]) != NULL) { trident_update_ptr(state); } else { printk(KERN_WARNING "trident: spurious channel " "irq %d.\n", channel); trident_stop_voice(card, channel); trident_disable_voice_irq(card, channel); } } }}static voidali_hwvol_control(struct trident_card *card, int opt){ u16 dwTemp, volume[2], mute, diff, *pVol[2]; dwTemp = ali_ac97_read(card->ac97_codec[0], 0x02); mute = dwTemp & 0x8000; volume[0] = dwTemp & 0x001f; volume[1] = (dwTemp & 0x1f00) >> 8; if (volume[0] < volume[1]) { pVol[0] = &volume[0]; pVol[1] = &volume[1]; } else { pVol[1] = &volume[0]; pVol[0] = &volume[1]; } diff = *(pVol[1]) - *(pVol[0]); if (opt == 1) { // MUTE dwTemp ^= 0x8000; ali_ac97_write(card->ac97_codec[0], 0x02, dwTemp); } else if (opt == 2) { // Down if (mute) return; if (*(pVol[1]) < 0x001f) { (*pVol[1])++; *(pVol[0]) = *(pVol[1]) - diff; } dwTemp &= 0xe0e0; dwTemp |= (volume[0]) | (volume[1] << 8); ali_ac97_write(card->ac97_codec[0], 0x02, dwTemp); card->ac97_codec[0]->mixer_state[0] = ((32 - volume[0]) * 25 / 8) | (((32 - volume[1]) * 25 / 8) << 8); } else if (opt == 4) { // Up if (mute) return; if (*(pVol[0]) > 0) { (*pVol[0])--; *(pVol[1]) = *(pVol[0]) + diff; } dwTemp &= 0xe0e0; dwTemp |= (volume[0]) | (volume[1] << 8); ali_ac97_write(card->ac97_codec[0], 0x02, dwTemp); card->ac97_codec[0]->mixer_state[0] = ((32 - volume[0]) * 25 / 8) | (((32 - volume[1]) * 25 / 8) << 8); } else { /* Nothing needs doing */ }}/* * Re-enable reporting of vol change after 0.1 seconds */static voidali_timeout(unsigned long ptr){ struct trident_card *card = (struct trident_card *) ptr; u16 temp = 0; /* Enable GPIO IRQ (MISCINT bit 18h) */ temp = inw(TRID_REG(card, T4D_MISCINT + 2)); temp |= 0x0004; outw(temp, TRID_REG(card, T4D_MISCINT + 2));}/* * Set up the timer to clear the vol change notification */static voidali_set_timer(struct trident_card *card){ /* Add Timer Routine to Enable GPIO IRQ */ del_timer(&card->timer); /* Never queue twice */ card->timer.function = ali_timeout; card->timer.data = (unsigned long) card; card->timer.expires = jiffies + HZ / 10; add_timer(&card->timer);}/* * Process a GPIO event */static voidali_queue_task(struct trident_card *card, int opt){ u16 temp; /* Disable GPIO IRQ (MISCINT bit 18h) */ temp = inw(TRID_REG(card, T4D_MISCINT + 2)); temp &= (u16) (~0x0004); outw(temp, TRID_REG(card, T4D_MISCINT + 2)); /* Adjust the volume */ ali_hwvol_control(card, opt); /* Set the timer for 1/10th sec */ ali_set_timer(card);}static voidcyber_address_interrupt(struct trident_card *card){ int i, irq_status; struct trident_state *state; unsigned int channel; /* Update the pointers for all channels we are running. */ /* FIXED: read interrupt status only once */ irq_status = inl(TRID_REG(card, T4D_AINT_A)); pr_debug("cyber_address_interrupt: irq_status 0x%X\n", irq_status); for (i = 0; i < NR_HW_CH; i++) { channel = 31 - i; if (irq_status & (1 << channel)) { /* clear bit by writing a 1, zeroes are ignored */ outl((1 << channel), TRID_REG(card, T4D_AINT_A)); pr_debug("cyber_interrupt: channel %d\n", channel); if ((state = card->states[i]) != NULL) { trident_update_ptr(state); } else { printk(KERN_WARNING "cyber5050: spurious " "channel irq %d.\n", channel); trident_stop_voice(card, channel); trident_disable_voice_irq(card, channel); } } }}static irqreturn_ttrident_interrupt(int irq, void *dev_id){ struct trident_card *card = (struct trident_card *) dev_id; u32 event; u32 gpio; spin_lock(&card->lock); event = inl(TRID_REG(card, T4D_MISCINT)); pr_debug("trident: trident_interrupt called, MISCINT = 0x%08x\n", event); if (event & ADDRESS_IRQ) { card->address_interrupt(card); } if (card->pci_id == PCI_DEVICE_ID_ALI_5451) { /* GPIO IRQ (H/W Volume Control) */ event = inl(TRID_REG(card, T4D_MISCINT)); if (event & (1 << 25)) { gpio = inl(TRID_REG(card, ALI_GPIO)); if (!timer_pending(&card->timer)) ali_queue_task(card, gpio & 0x07);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?