aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sound/pci/ctxfi/Makefile2
-rw-r--r--sound/pci/ctxfi/ct20k1reg.h2
-rw-r--r--sound/pci/ctxfi/ctatc.c21
-rw-r--r--sound/pci/ctxfi/ctatc.h9
-rw-r--r--sound/pci/ctxfi/cthardware.h19
-rw-r--r--sound/pci/ctxfi/cthw20k1.c43
-rw-r--r--sound/pci/ctxfi/ctpcm.c106
-rw-r--r--sound/pci/ctxfi/cttimer.c417
-rw-r--r--sound/pci/ctxfi/cttimer.h29
9 files changed, 543 insertions, 105 deletions
diff --git a/sound/pci/ctxfi/Makefile b/sound/pci/ctxfi/Makefile
index 2904323..15075f8 100644
--- a/sound/pci/ctxfi/Makefile
+++ b/sound/pci/ctxfi/Makefile
@@ -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
diff --git a/sound/pci/ctxfi/ct20k1reg.h b/sound/pci/ctxfi/ct20k1reg.h
index c62e677..f2e34e3 100644
--- a/sound/pci/ctxfi/ct20k1reg.h
+++ b/sound/pci/ctxfi/ct20k1reg.h
@@ -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
diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c
index 6849475..10b7419 100644
--- a/sound/pci/ctxfi/ctatc.c
+++ b/sound/pci/ctxfi/ctatc.c
@@ -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;
}
-
diff --git a/sound/pci/ctxfi/ctatc.h b/sound/pci/ctxfi/ctatc.h
index b86d12c..a3f9b1b 100644
--- a/sound/pci/ctxfi/ctatc.h
+++ b/sound/pci/ctxfi/ctatc.h
@@ -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;
};
diff --git a/sound/pci/ctxfi/cthardware.h b/sound/pci/ctxfi/cthardware.h
index b0512df..35350cf 100644
--- a/sound/pci/ctxfi/cthardware.h
+++ b/sound/pci/ctxfi/cthardware.h
@@ -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 */
diff --git a/sound/pci/ctxfi/cthw20k1.c b/sound/pci/ctxfi/cthw20k1.c
index e530a6d..550b30a 100644
--- a/sound/pci/ctxfi/cthw20k1.c
+++ b/sound/pci/ctxfi/cthw20k1.c
@@ -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;
diff --git a/sound/pci/ctxfi/ctpcm.c b/sound/pci/ctxfi/ctpcm.c
index 52ddf19..32b742d 100644
--- a/sound/pci/ctxfi/ctpcm.c
+++ b/sound/pci/ctxfi/ctpcm.c
@@ -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
index 0000000..3acb26d
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.c
@@ -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
index 0000000..9793482
--- /dev/null
+++ b/sound/pci/ctxfi/cttimer.h
@@ -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 */