ALSA: ctxfi - Use native timer interrupt on emu20k1
authorTakashi Iwai <tiwai@suse.de>
Fri, 5 Jun 2009 14:11:07 +0000 (16:11 +0200)
committerTakashi Iwai <tiwai@suse.de>
Fri, 5 Jun 2009 14:44:13 +0000 (16:44 +0200)
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: Takashi Iwai <tiwai@suse.de>
sound/pci/ctxfi/Makefile
sound/pci/ctxfi/ct20k1reg.h
sound/pci/ctxfi/ctatc.c
sound/pci/ctxfi/ctatc.h
sound/pci/ctxfi/cthardware.h
sound/pci/ctxfi/cthw20k1.c
sound/pci/ctxfi/ctpcm.c
sound/pci/ctxfi/cttimer.c [new file with mode: 0644]
sound/pci/ctxfi/cttimer.h [new file with mode: 0644]

index 2904323..15075f8 100644 (file)
@@ -1,5 +1,5 @@
 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
index c62e677..f2e34e3 100644 (file)
 
 #define                WC              0x1C6000
 #define                TIMR            0x1C6004
+# define       TIMR_IE         (1<<15)
+# define       TIMR_IP         (1<<14)
 
 #define                GIP             0x1C6010
 #define                GIE             0x1C6014
index 6849475..10b7419 100644 (file)
@@ -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;
 }
-
index b86d12c..a3f9b1b 100644 (file)
@@ -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;
 };
 
 
index b0512df..35350cf 100644 (file)
@@ -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 */
index e530a6d..550b30a 100644 (file)
@@ -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;
index 52ddf19..32b742d 100644 (file)
@@ -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;
        }
diff --git a/sound/pci/ctxfi/cttimer.c b/sound/pci/ctxfi/cttimer.c
new file mode 100644 (file)
index 0000000..3acb26d
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * PCM timer handling on ctxfi
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "ctatc.h"
+#include "cthardware.h"
+#include "cttimer.h"
+
+struct ct_timer_ops {
+       void (*init)(struct ct_timer_instance *);
+       void (*prepare)(struct ct_timer_instance *);
+       void (*start)(struct ct_timer_instance *);
+       void (*stop)(struct ct_timer_instance *);
+       void (*free_instance)(struct ct_timer_instance *);
+       void (*interrupt)(struct ct_timer *);
+       void (*free_global)(struct ct_timer *);
+};
+
+/* timer instance -- assigned to each PCM stream */
+struct ct_timer_instance {
+       spinlock_t lock;
+       struct ct_timer *timer_base;
+       struct ct_atc_pcm *apcm;
+       struct snd_pcm_substream *substream;
+       struct timer_list timer;
+       struct list_head instance_list;
+       struct list_head running_list;
+       unsigned int position;
+       unsigned int frag_count;
+       unsigned int running:1;
+       unsigned int need_update:1;
+};
+
+/* timer instance manager */
+struct ct_timer {
+       spinlock_t lock;                /* global timer lock (for xfitimer) */
+       spinlock_t list_lock;           /* lock for instance list */
+       struct ct_atc *atc;
+       struct ct_timer_ops *ops;
+       struct list_head instance_head;
+       struct list_head running_head;
+       unsigned int irq_handling:1;    /* in IRQ handling */
+       unsigned int reprogram:1;       /* need to reprogram the internval */
+       unsigned int running:1;         /* global timer running */
+};
+
+
+/*
+ * system-timer-based updates
+ */
+
+static void ct_systimer_callback(unsigned long data)
+{
+       struct ct_timer_instance *ti = (struct ct_timer_instance *)data;
+       struct snd_pcm_substream *substream = ti->substream;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct ct_atc_pcm *apcm = ti->apcm;
+       unsigned int period_size = runtime->period_size;
+       unsigned int buffer_size = runtime->buffer_size;
+       unsigned long flags;
+       unsigned int position, dist, interval;
+
+       position = substream->ops->pointer(substream);
+       dist = (position + buffer_size - ti->position) % buffer_size;
+       if (dist >= period_size ||
+           position / period_size != ti->position / period_size) {
+               apcm->interrupt(apcm);
+               ti->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(&ti->lock, flags);
+       if (ti->running)
+               mod_timer(&ti->timer, jiffies + interval);
+       spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_init(struct ct_timer_instance *ti)
+{
+       setup_timer(&ti->timer, ct_systimer_callback,
+                   (unsigned long)ti);
+}
+
+static void ct_systimer_start(struct ct_timer_instance *ti)
+{
+       struct snd_pcm_runtime *runtime = ti->substream->runtime;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ti->lock, flags);
+       ti->running = 1;
+       mod_timer(&ti->timer,
+                 jiffies + (runtime->period_size * HZ +
+                            (runtime->rate - 1)) / runtime->rate);
+       spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_stop(struct ct_timer_instance *ti)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&ti->lock, flags);
+       ti->running = 0;
+       del_timer(&ti->timer);
+       spin_unlock_irqrestore(&ti->lock, flags);
+}
+
+static void ct_systimer_prepare(struct ct_timer_instance *ti)
+{
+       ct_systimer_stop(ti);
+       try_to_del_timer_sync(&ti->timer);
+}
+
+#define ct_systimer_free       ct_systimer_prepare
+
+static struct ct_timer_ops ct_systimer_ops = {
+       .init = ct_systimer_init,
+       .free_instance = ct_systimer_free,
+       .prepare = ct_systimer_prepare,
+       .start = ct_systimer_start,
+       .stop = ct_systimer_stop,
+};
+
+
+/*
+ * Handling multiple streams using a global emu20k1 timer irq
+ */
+
+#define CT_TIMER_FREQ  48000
+#define MAX_TICKS      ((1 << 13) - 1)
+
+static void ct_xfitimer_irq_rearm(struct ct_timer *atimer, int ticks)
+{
+       struct hw *hw = atimer->atc->hw;
+       if (ticks > MAX_TICKS)
+               ticks = MAX_TICKS;
+       hw->set_timer_tick(hw, ticks);
+       if (!atimer->running)
+               hw->set_timer_irq(hw, 1);
+       atimer->running = 1;
+}
+
+static void ct_xfitimer_irq_stop(struct ct_timer *atimer)
+{
+       if (atimer->running) {
+               struct hw *hw = atimer->atc->hw;
+               hw->set_timer_irq(hw, 0);
+               hw->set_timer_tick(hw, 0);
+               atimer->running = 0;
+       }
+}
+
+/*
+ * reprogram the timer interval;
+ * checks the running instance list and determines the next timer interval.
+ * also updates the each stream position, returns the number of streams
+ * to call snd_pcm_period_elapsed() appropriately
+ *
+ * call this inside the lock and irq disabled
+ */
+static int ct_xfitimer_reprogram(struct ct_timer *atimer)
+{
+       struct ct_timer_instance *ti;
+       int min_intr = -1;
+       int updates = 0;
+
+       list_for_each_entry(ti, &atimer->running_head, running_list) {
+               struct snd_pcm_runtime *runtime;
+               unsigned int pos, diff;
+               int intr;
+               runtime = ti->substream->runtime;
+               pos = ti->substream->ops->pointer(ti->substream);
+               if (pos < ti->position)
+                       diff = runtime->buffer_size - ti->position + pos;
+               else
+                       diff = pos - ti->position;
+               ti->position = pos;
+               while (diff >= ti->frag_count) {
+                       ti->frag_count += runtime->period_size;
+                       ti->need_update = 1;
+                       updates++;
+               }
+               ti->frag_count -= diff;
+               intr = div_u64((u64)ti->frag_count * CT_TIMER_FREQ,
+                              runtime->rate);
+               if (min_intr < 0 || intr < min_intr)
+                       min_intr = intr;
+       }
+
+       if (min_intr > 0)
+               ct_xfitimer_irq_rearm(atimer, min_intr);
+       else
+               ct_xfitimer_irq_stop(atimer);
+
+       atimer->reprogram = 0; /* clear flag */
+       return updates;
+}
+
+/* look through the instance list and call period_elapsed if needed */
+static void ct_xfitimer_check_period(struct ct_timer *atimer)
+{
+       struct ct_timer_instance *ti;
+       unsigned long flags;
+
+       spin_lock_irqsave(&atimer->list_lock, flags);
+       list_for_each_entry(ti, &atimer->instance_head, instance_list) {
+               if (ti->need_update) {
+                       ti->need_update = 0;
+                       ti->apcm->interrupt(ti->apcm);
+               }
+       }
+       spin_unlock_irqrestore(&atimer->list_lock, flags);
+}
+
+/* Handle timer-interrupt */
+static void ct_xfitimer_callback(struct ct_timer *atimer)
+{
+       int update;
+       unsigned long flags;
+
+       spin_lock_irqsave(&atimer->lock, flags);
+       atimer->irq_handling = 1;
+       do {
+               update = ct_xfitimer_reprogram(atimer);
+               spin_unlock(&atimer->lock);
+               if (update)
+                       ct_xfitimer_check_period(atimer);
+               spin_lock(&atimer->lock);
+       } while (atimer->reprogram);
+       atimer->irq_handling = 0;
+       spin_unlock_irqrestore(&atimer->lock, flags);
+}
+
+static void ct_xfitimer_prepare(struct ct_timer_instance *ti)
+{
+       ti->frag_count = ti->substream->runtime->period_size;
+       ti->need_update = 0;
+}
+
+
+/* start/stop the timer */
+static void ct_xfitimer_update(struct ct_timer *atimer)
+{
+       unsigned long flags;
+       int update;
+
+       if (atimer->irq_handling) {
+               /* reached from IRQ handler; let it handle later */
+               atimer->reprogram = 1;
+               return;
+       }
+
+       spin_lock_irqsave(&atimer->lock, flags);
+       ct_xfitimer_irq_stop(atimer);
+       update = ct_xfitimer_reprogram(atimer);
+       spin_unlock_irqrestore(&atimer->lock, flags);
+       if (update)
+               ct_xfitimer_check_period(atimer);
+}
+
+static void ct_xfitimer_start(struct ct_timer_instance *ti)
+{
+       struct ct_timer *atimer = ti->timer_base;
+       unsigned long flags;
+
+       spin_lock_irqsave(&atimer->lock, flags);
+       list_add(&ti->running_list, &atimer->running_head);
+       spin_unlock_irqrestore(&atimer->lock, flags);
+       ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_stop(struct ct_timer_instance *ti)
+{
+       struct ct_timer *atimer = ti->timer_base;
+       unsigned long flags;
+
+       spin_lock_irqsave(&atimer->lock, flags);
+       list_del_init(&ti->running_list);
+       ti->need_update = 0;
+       spin_unlock_irqrestore(&atimer->lock, flags);
+       ct_xfitimer_update(atimer);
+}
+
+static void ct_xfitimer_free_global(struct ct_timer *atimer)
+{
+       ct_xfitimer_irq_stop(atimer);
+}
+
+static struct ct_timer_ops ct_xfitimer_ops = {
+       .prepare = ct_xfitimer_prepare,
+       .start = ct_xfitimer_start,
+       .stop = ct_xfitimer_stop,
+       .interrupt = ct_xfitimer_callback,
+       .free_global = ct_xfitimer_free_global,
+};
+
+/*
+ * timer instance
+ */
+
+struct ct_timer_instance *
+ct_timer_instance_new(struct ct_timer *atimer, struct ct_atc_pcm *apcm)
+{
+       struct ct_timer_instance *ti;
+
+       ti = kzalloc(sizeof(*ti), GFP_KERNEL);
+       if (!ti)
+               return NULL;
+       spin_lock_init(&ti->lock);
+       INIT_LIST_HEAD(&ti->instance_list);
+       INIT_LIST_HEAD(&ti->running_list);
+       ti->timer_base = atimer;
+       ti->apcm = apcm;
+       ti->substream = apcm->substream;
+       if (atimer->ops->init)
+               atimer->ops->init(ti);
+
+       spin_lock_irq(&atimer->list_lock);
+       list_add(&ti->instance_list, &atimer->instance_head);
+       spin_unlock_irq(&atimer->list_lock);
+
+       return ti;
+}
+
+void ct_timer_prepare(struct ct_timer_instance *ti)
+{
+       if (ti->timer_base->ops->prepare)
+               ti->timer_base->ops->prepare(ti);
+       ti->position = 0;
+       ti->running = 0;
+}
+
+void ct_timer_start(struct ct_timer_instance *ti)
+{
+       struct ct_timer *atimer = ti->timer_base;
+       atimer->ops->start(ti);
+}
+
+void ct_timer_stop(struct ct_timer_instance *ti)
+{
+       struct ct_timer *atimer = ti->timer_base;
+       atimer->ops->stop(ti);
+}
+
+void ct_timer_instance_free(struct ct_timer_instance *ti)
+{
+       struct ct_timer *atimer = ti->timer_base;
+
+       atimer->ops->stop(ti); /* to be sure */
+       if (atimer->ops->free_instance)
+               atimer->ops->free_instance(ti);
+
+       spin_lock_irq(&atimer->list_lock);
+       list_del(&ti->instance_list);
+       spin_unlock_irq(&atimer->list_lock);
+
+       kfree(ti);
+}
+
+/*
+ * timer manager
+ */
+
+#define USE_SYSTEM_TIMER       0
+
+static void ct_timer_interrupt(void *data, unsigned int status)
+{
+       struct ct_timer *timer = data;
+
+       /* Interval timer interrupt */
+       if ((status & IT_INT) && timer->ops->interrupt)
+               timer->ops->interrupt(timer);
+}
+
+struct ct_timer *ct_timer_new(struct ct_atc *atc)
+{
+       struct ct_timer *atimer;
+       struct hw *hw;
+
+       atimer = kzalloc(sizeof(*atimer), GFP_KERNEL);
+       if (!atimer)
+               return NULL;
+       spin_lock_init(&atimer->lock);
+       spin_lock_init(&atimer->list_lock);
+       INIT_LIST_HEAD(&atimer->instance_head);
+       INIT_LIST_HEAD(&atimer->running_head);
+       atimer->atc = atc;
+       hw = atc->hw;
+       if (!USE_SYSTEM_TIMER && hw->set_timer_irq) {
+               printk(KERN_INFO "ctxfi: Use xfi-native timer\n");
+               atimer->ops = &ct_xfitimer_ops;
+               hw->irq_callback_data = atimer;
+               hw->irq_callback = ct_timer_interrupt;
+       } else {
+               printk(KERN_INFO "ctxfi: Use system timer\n");
+               atimer->ops = &ct_systimer_ops;
+       }
+       return atimer;
+}
+
+void ct_timer_free(struct ct_timer *atimer)
+{
+       struct hw *hw = atimer->atc->hw;
+       hw->irq_callback = NULL;
+       if (atimer->ops->free_global)
+               atimer->ops->free_global(atimer);
+       kfree(atimer);
+}
+
diff --git a/sound/pci/ctxfi/cttimer.h b/sound/pci/ctxfi/cttimer.h
new file mode 100644 (file)
index 0000000..9793482
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 */