|
@@ -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);
|
|
|
+}
|
|
|
+
|