Commit b7bbf876 authored by Takashi Iwai's avatar Takashi Iwai

ALSA: ctxfi - Use native timer interrupt on emu20k1

emu20k1 has a native timer interrupt based on the audio clock, which
is more accurate than the system timer (from the synchronization POV).
This patch adds the code to handle this with multiple streams.

The system timer is still used on emu20k2, and can be used also for
emu20k1 easily by changing USE_SYSTEM_TIMER to 1 in cttimer.c.
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 6bc5874a
snd-ctxfi-objs := xfi.o ctatc.o ctvmem.o ctpcm.o ctmixer.o ctresource.o \
ctsrc.o ctamixer.o ctdaio.o ctimap.o cthardware.o \
ctsrc.o ctamixer.o ctdaio.o ctimap.o cthardware.o cttimer.o \
cthw20k2.o cthw20k1.o
obj-$(CONFIG_SND_CTXFI) += snd-ctxfi.o
......@@ -589,6 +589,8 @@
#define WC 0x1C6000
#define TIMR 0x1C6004
# define TIMR_IE (1<<15)
# define TIMR_IP (1<<14)
#define GIP 0x1C6010
#define GIE 0x1C6014
......
......@@ -22,6 +22,7 @@
#include "ctsrc.h"
#include "ctamixer.h"
#include "ctdaio.h"
#include "cttimer.h"
#include <linux/delay.h>
#include <sound/pcm.h>
#include <sound/control.h>
......@@ -307,6 +308,8 @@ static int atc_pcm_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
src = apcm->src;
}
ct_timer_prepare(apcm->timer);
return 0;
error1:
......@@ -389,6 +392,7 @@ static int atc_pcm_playback_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
src->ops->set_state(src, SRC_STATE_INIT);
src->ops->commit_write(src);
ct_timer_start(apcm->timer);
return 0;
}
......@@ -397,6 +401,8 @@ static int atc_pcm_stop(struct ct_atc *atc, struct ct_atc_pcm *apcm)
struct src *src = NULL;
int i = 0;
ct_timer_stop(apcm->timer);
src = apcm->src;
src->ops->set_bm(src, 0);
src->ops->set_state(src, SRC_STATE_OFF);
......@@ -701,6 +707,8 @@ static int atc_pcm_capture_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
}
}
ct_timer_prepare(apcm->timer);
return 0;
}
......@@ -749,6 +757,7 @@ static int atc_pcm_capture_start(struct ct_atc *atc, struct ct_atc_pcm *apcm)
/* Enable relevant SRCs synchronously */
src_mgr->commit_write(src_mgr);
ct_timer_start(apcm->timer);
return 0;
}
......@@ -906,6 +915,8 @@ spdif_passthru_playback_prepare(struct ct_atc *atc, struct ct_atc_pcm *apcm)
dao->ops->set_right_input(dao, &amixer->rsc);
spin_unlock_irqrestore(&atc->atc_lock, flags);
ct_timer_prepare(apcm->timer);
return 0;
}
......@@ -1100,6 +1111,11 @@ static int ct_atc_destroy(struct ct_atc *atc)
if (NULL == atc)
return 0;
if (atc->timer) {
ct_timer_free(atc->timer);
atc->timer = NULL;
}
/* Stop hardware and disable all interrupts */
if (NULL != atc->hw)
((struct hw *)atc->hw)->card_stop(atc->hw);
......@@ -1586,6 +1602,10 @@ int ct_atc_create(struct snd_card *card, struct pci_dev *pci,
/* Build topology */
atc_connect_resources(atc);
atc->timer = ct_timer_new(atc);
if (!atc->timer)
goto error1;
atc->create_alsa_devs = ct_create_alsa_devs;
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, atc, &ops);
......@@ -1602,4 +1622,3 @@ error1:
printk(KERN_ERR "ctxfi: Something wrong!!!\n");
return err;
}
......@@ -59,16 +59,15 @@ struct ct_atc_chip_details {
};
struct ct_atc;
struct ct_timer;
struct ct_timer_instance;
/* alsa pcm stream descriptor */
struct ct_atc_pcm {
struct snd_pcm_substream *substream;
void (*interrupt)(struct ct_atc_pcm *apcm);
struct ct_timer_instance *timer;
unsigned int started:1;
unsigned int stop_timer:1;
struct timer_list timer;
spinlock_t timer_lock;
unsigned int position;
/* Only mono and interleaved modes are supported now. */
struct ct_vm_block *vm_block;
......@@ -144,6 +143,8 @@ struct ct_atc {
unsigned char n_src;
unsigned char n_srcimp;
unsigned char n_pcm;
struct ct_timer *timer;
};
......
......@@ -145,6 +145,12 @@ struct hw {
int (*daio_mgr_set_imapaddr)(void *blk, unsigned int addr);
int (*daio_mgr_commit_write)(struct hw *hw, void *blk);
int (*set_timer_irq)(struct hw *hw, int enable);
int (*set_timer_tick)(struct hw *hw, unsigned int tick);
void (*irq_callback)(void *data, unsigned int bit);
void *irq_callback_data;
struct pci_dev *pci; /* the pci kernel structure of this card */
int irq;
unsigned long io_base;
......@@ -157,4 +163,17 @@ int destroy_hw_obj(struct hw *hw);
unsigned int get_field(unsigned int data, unsigned int field);
void set_field(unsigned int *data, unsigned int field, unsigned int value);
/* IRQ bits */
#define PLL_INT (1 << 10) /* PLL input-clock out-of-range */
#define FI_INT (1 << 9) /* forced interrupt */
#define IT_INT (1 << 8) /* timer interrupt */
#define PCI_INT (1 << 7) /* PCI bus error pending */
#define URT_INT (1 << 6) /* UART Tx/Rx */
#define GPI_INT (1 << 5) /* GPI pin */
#define MIX_INT (1 << 4) /* mixer parameter segment FIFO channels */
#define DAI_INT (1 << 3) /* DAI (SR-tracker or SPDIF-receiver) */
#define TP_INT (1 << 2) /* transport priority queue */
#define DSP_INT (1 << 1) /* DSP */
#define SRC_INT (1 << 0) /* SRC channels */
#endif /* CTHARDWARE_H */
......@@ -1171,6 +1171,21 @@ static int daio_mgr_put_ctrl_blk(void *blk)
return 0;
}
/* Timer interrupt */
static int set_timer_irq(struct hw *hw, int enable)
{
hw_write_20kx(hw, GIE, enable ? IT_INT : 0);
return 0;
}
static int set_timer_tick(struct hw *hw, unsigned int ticks)
{
if (ticks)
ticks |= TIMR_IE | TIMR_IP;
hw_write_20kx(hw, TIMR, ticks);
return 0;
}
/* Card hardware initialization block */
struct dac_conf {
unsigned int msr; /* master sample rate in rsrs */
......@@ -1878,6 +1893,22 @@ static int uaa_to_xfi(struct pci_dev *pci)
return 0;
}
static irqreturn_t ct_20k1_interrupt(int irq, void *dev_id)
{
struct hw *hw = dev_id;
unsigned int status;
status = hw_read_20kx(hw, GIP);
if (!status)
return IRQ_NONE;
if (hw->irq_callback)
hw->irq_callback(hw->irq_callback_data, status);
hw_write_20kx(hw, GIP, status);
return IRQ_HANDLED;
}
static int hw_card_start(struct hw *hw)
{
int err = 0;
......@@ -1914,12 +1945,13 @@ static int hw_card_start(struct hw *hw)
hw->io_base = pci_resource_start(pci, 0);
}
/*if ((err = request_irq(pci->irq, ct_atc_interrupt, IRQF_SHARED,
atc->chip_details->nm_card, hw))) {
err = request_irq(pci->irq, ct_20k1_interrupt, IRQF_SHARED,
"ctxfi", hw);
if (err < 0) {
printk(KERN_ERR "XFi: Cannot get irq %d\n", pci->irq);
goto error2;
}
hw->irq = pci->irq;
*/
pci_set_master(pci);
......@@ -1936,6 +1968,8 @@ error1:
static int hw_card_stop(struct hw *hw)
{
/* TODO: Disable interrupt and so on... */
if (hw->irq >= 0)
synchronize_irq(hw->irq);
return 0;
}
......@@ -2215,6 +2249,9 @@ int create_20k1_hw_obj(struct hw **rhw)
hw->daio_mgr_set_imapaddr = daio_mgr_set_imapaddr;
hw->daio_mgr_commit_write = daio_mgr_commit_write;
hw->set_timer_irq = set_timer_irq;
hw->set_timer_tick = set_timer_tick;
*rhw = hw;
return 0;
......
......@@ -16,6 +16,7 @@
*/
#include "ctpcm.h"
#include "cttimer.h"
#include <sound/pcm.h>
/* Hardware descriptions for playback */
......@@ -108,6 +109,7 @@ static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime)
struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream);
atc->pcm_release_resources(atc, apcm);
ct_timer_instance_free(apcm->timer);
kfree(apcm);
runtime->private_data = NULL;
}
......@@ -124,8 +126,6 @@ static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
if (NULL == apcm)
return -ENOMEM;
spin_lock_init(&apcm->timer_lock);
apcm->stop_timer = 0;
apcm->substream = substream;
apcm->interrupt = ct_atc_pcm_interrupt;
runtime->private_data = apcm;
......@@ -153,6 +153,10 @@ static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
return err;
}
apcm->timer = ct_timer_instance_new(atc->timer, apcm);
if (!apcm->timer)
return -ENOMEM;
return 0;
}
......@@ -182,89 +186,6 @@ static int ct_pcm_hw_free(struct snd_pcm_substream *substream)
return snd_pcm_lib_free_pages(substream);
}
static void ct_pcm_timer_callback(unsigned long data)
{
struct ct_atc_pcm *apcm = (struct ct_atc_pcm *)data;
struct snd_pcm_substream *substream = apcm->substream;
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned int period_size = runtime->period_size;
unsigned int buffer_size = runtime->buffer_size;
unsigned long flags;
unsigned int position = 0, dist = 0, interval = 0;
position = substream->ops->pointer(substream);
dist = (position + buffer_size - apcm->position) % buffer_size;
if ((dist >= period_size) ||
(position/period_size != apcm->position/period_size)) {
apcm->interrupt(apcm);
apcm->position = position;
}
/* Add extra HZ*5/1000 to avoid overrun issue when recording
* at 8kHz in 8-bit format or at 88kHz in 24-bit format. */
interval = ((period_size - (position % period_size))
* HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000;
spin_lock_irqsave(&apcm->timer_lock, flags);
apcm->timer.expires = jiffies + interval;
if (!apcm->stop_timer)
add_timer(&apcm->timer);
spin_unlock_irqrestore(&apcm->timer_lock, flags);
}
static int ct_pcm_timer_prepare(struct ct_atc_pcm *apcm)
{
unsigned long flags;
spin_lock_irqsave(&apcm->timer_lock, flags);
if (timer_pending(&apcm->timer)) {
/* The timer has already been started. */
spin_unlock_irqrestore(&apcm->timer_lock, flags);
return 0;
}
init_timer(&apcm->timer);
apcm->timer.data = (unsigned long)apcm;
apcm->timer.function = ct_pcm_timer_callback;
spin_unlock_irqrestore(&apcm->timer_lock, flags);
apcm->position = 0;
return 0;
}
static int ct_pcm_timer_start(struct ct_atc_pcm *apcm)
{
struct snd_pcm_runtime *runtime = apcm->substream->runtime;
unsigned long flags;
spin_lock_irqsave(&apcm->timer_lock, flags);
if (timer_pending(&apcm->timer)) {
/* The timer has already been started. */
spin_unlock_irqrestore(&apcm->timer_lock, flags);
return 0;
}
apcm->timer.expires = jiffies + (runtime->period_size * HZ +
(runtime->rate - 1)) / runtime->rate;
apcm->stop_timer = 0;
add_timer(&apcm->timer);
spin_unlock_irqrestore(&apcm->timer_lock, flags);
return 0;
}
static int ct_pcm_timer_stop(struct ct_atc_pcm *apcm)
{
unsigned long flags;
spin_lock_irqsave(&apcm->timer_lock, flags);
apcm->stop_timer = 1;
del_timer(&apcm->timer);
spin_unlock_irqrestore(&apcm->timer_lock, flags);
try_to_del_timer_sync(&apcm->timer);
return 0;
}
static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
{
......@@ -283,8 +204,6 @@ static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
return err;
}
ct_pcm_timer_prepare(apcm);
return 0;
}
......@@ -300,12 +219,10 @@ ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
atc->pcm_playback_start(atc, apcm);
ct_pcm_timer_start(apcm);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
ct_pcm_timer_stop(apcm);
atc->pcm_playback_stop(atc, apcm);
break;
default:
......@@ -341,9 +258,7 @@ static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
if (NULL == apcm)
return -ENOMEM;
spin_lock_init(&apcm->timer_lock);
apcm->started = 0;
apcm->stop_timer = 0;
apcm->substream = substream;
apcm->interrupt = ct_atc_pcm_interrupt;
runtime->private_data = apcm;
......@@ -365,6 +280,10 @@ static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
return err;
}
apcm->timer = ct_timer_instance_new(atc->timer, apcm);
if (!apcm->timer)
return -ENOMEM;
return 0;
}
......@@ -388,8 +307,6 @@ static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream)
return err;
}
ct_pcm_timer_prepare(apcm);
return 0;
}
......@@ -403,14 +320,11 @@ ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
atc->pcm_capture_start(atc, apcm);
ct_pcm_timer_start(apcm);
break;
case SNDRV_PCM_TRIGGER_STOP:
ct_pcm_timer_stop(apcm);
atc->pcm_capture_stop(atc, apcm);
break;
default:
ct_pcm_timer_stop(apcm);
atc->pcm_capture_stop(atc, apcm);
break;
}
......
This diff is collapsed.
/*
* Timer handling
*/
#ifndef __CTTIMER_H
#define __CTTIMER_H
#include <linux/spinlock.h>
#include <linux/timer.h>
#include <linux/list.h>
struct snd_pcm_substream;
struct ct_atc;
struct ct_atc_pcm;
struct ct_timer;
struct ct_timer_instance;
struct ct_timer *ct_timer_new(struct ct_atc *atc);
void ct_timer_free(struct ct_timer *atimer);
struct ct_timer_instance *
ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm);
void ct_timer_instance_free(struct ct_timer_instance *ti);
void ct_timer_start(struct ct_timer_instance *ti);
void ct_timer_stop(struct ct_timer_instance *ti);
void ct_timer_prepare(struct ct_timer_instance *ti);
#endif /* __CTTIMER_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment