|
@@ -47,6 +47,9 @@
|
|
|
#include <linux/reboot.h>
|
|
|
#include <linux/io.h>
|
|
|
#include <linux/pm_runtime.h>
|
|
|
+#include <linux/clocksource.h>
|
|
|
+#include <linux/time.h>
|
|
|
+
|
|
|
#ifdef CONFIG_X86
|
|
|
/* for snoop control */
|
|
|
#include <asm/pgtable.h>
|
|
@@ -419,6 +422,9 @@ struct azx_dev {
|
|
|
unsigned int insufficient :1;
|
|
|
unsigned int wc_marked:1;
|
|
|
unsigned int no_period_wakeup:1;
|
|
|
+
|
|
|
+ struct timecounter azx_tc;
|
|
|
+ struct cyclecounter azx_cc;
|
|
|
};
|
|
|
|
|
|
/* CORB/RIRB */
|
|
@@ -1749,6 +1755,64 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
|
|
|
azx_dev->opened = 0;
|
|
|
}
|
|
|
|
|
|
+static cycle_t azx_cc_read(const struct cyclecounter *cc)
|
|
|
+{
|
|
|
+ struct azx_dev *azx_dev = container_of(cc, struct azx_dev, azx_cc);
|
|
|
+ struct snd_pcm_substream *substream = azx_dev->substream;
|
|
|
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
|
|
|
+ struct azx *chip = apcm->chip;
|
|
|
+
|
|
|
+ return azx_readl(chip, WALLCLK);
|
|
|
+}
|
|
|
+
|
|
|
+static void azx_timecounter_init(struct snd_pcm_substream *substream,
|
|
|
+ bool force, cycle_t last)
|
|
|
+{
|
|
|
+ struct azx_dev *azx_dev = get_azx_dev(substream);
|
|
|
+ struct timecounter *tc = &azx_dev->azx_tc;
|
|
|
+ struct cyclecounter *cc = &azx_dev->azx_cc;
|
|
|
+ u64 nsec;
|
|
|
+
|
|
|
+ cc->read = azx_cc_read;
|
|
|
+ cc->mask = CLOCKSOURCE_MASK(32);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Converting from 24 MHz to ns means applying a 125/3 factor.
|
|
|
+ * To avoid any saturation issues in intermediate operations,
|
|
|
+ * the 125 factor is applied first. The division is applied
|
|
|
+ * last after reading the timecounter value.
|
|
|
+ * Applying the 1/3 factor as part of the multiplication
|
|
|
+ * requires at least 20 bits for a decent precision, however
|
|
|
+ * overflows occur after about 4 hours or less, not a option.
|
|
|
+ */
|
|
|
+
|
|
|
+ cc->mult = 125; /* saturation after 195 years */
|
|
|
+ cc->shift = 0;
|
|
|
+
|
|
|
+ nsec = 0; /* audio time is elapsed time since trigger */
|
|
|
+ timecounter_init(tc, cc, nsec);
|
|
|
+ if (force)
|
|
|
+ /*
|
|
|
+ * force timecounter to use predefined value,
|
|
|
+ * used for synchronized starts
|
|
|
+ */
|
|
|
+ tc->cycle_last = last;
|
|
|
+}
|
|
|
+
|
|
|
+static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
|
|
|
+ struct timespec *ts)
|
|
|
+{
|
|
|
+ struct azx_dev *azx_dev = get_azx_dev(substream);
|
|
|
+ u64 nsec;
|
|
|
+
|
|
|
+ nsec = timecounter_read(&azx_dev->azx_tc);
|
|
|
+ nsec = div_u64(nsec, 3); /* can be optimized */
|
|
|
+
|
|
|
+ *ts = ns_to_timespec(nsec);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static struct snd_pcm_hardware azx_pcm_hw = {
|
|
|
.info = (SNDRV_PCM_INFO_MMAP |
|
|
|
SNDRV_PCM_INFO_INTERLEAVED |
|
|
@@ -1758,6 +1822,7 @@ static struct snd_pcm_hardware azx_pcm_hw = {
|
|
|
/* SNDRV_PCM_INFO_RESUME |*/
|
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
|
SNDRV_PCM_INFO_SYNC_START |
|
|
|
+ SNDRV_PCM_INFO_HAS_WALL_CLOCK |
|
|
|
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP),
|
|
|
.formats = SNDRV_PCM_FMTBIT_S16_LE,
|
|
|
.rates = SNDRV_PCM_RATE_48000,
|
|
@@ -1797,6 +1862,12 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
|
|
runtime->hw.rates = hinfo->rates;
|
|
|
snd_pcm_limit_hw_rates(runtime);
|
|
|
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
|
|
|
+
|
|
|
+ /* avoid wrap-around with wall-clock */
|
|
|
+ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
|
|
|
+ 20,
|
|
|
+ 178000000);
|
|
|
+
|
|
|
if (chip->align_buffer_size)
|
|
|
/* constrain buffer sizes to be multiple of 128
|
|
|
bytes. This is more efficient in terms of memory
|
|
@@ -1836,6 +1907,12 @@ static int azx_pcm_open(struct snd_pcm_substream *substream)
|
|
|
mutex_unlock(&chip->open_mutex);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
+
|
|
|
+ /* disable WALLCLOCK timestamps for capture streams
|
|
|
+ until we figure out how to handle digital inputs */
|
|
|
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
|
|
|
+ runtime->hw.info &= ~SNDRV_PCM_INFO_HAS_WALL_CLOCK;
|
|
|
+
|
|
|
spin_lock_irqsave(&chip->reg_lock, flags);
|
|
|
azx_dev->substream = substream;
|
|
|
azx_dev->running = 0;
|
|
@@ -2072,6 +2149,22 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
|
azx_readl(chip, OLD_SSYNC) & ~sbits);
|
|
|
else
|
|
|
azx_writel(chip, SSYNC, azx_readl(chip, SSYNC) & ~sbits);
|
|
|
+ if (start) {
|
|
|
+ azx_timecounter_init(substream, 0, 0);
|
|
|
+ if (nsync > 1) {
|
|
|
+ cycle_t cycle_last;
|
|
|
+
|
|
|
+ /* same start cycle for master and group */
|
|
|
+ azx_dev = get_azx_dev(substream);
|
|
|
+ cycle_last = azx_dev->azx_tc.cycle_last;
|
|
|
+
|
|
|
+ snd_pcm_group_for_each_entry(s, substream) {
|
|
|
+ if (s->pcm->card != substream->pcm->card)
|
|
|
+ continue;
|
|
|
+ azx_timecounter_init(s, 1, cycle_last);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
spin_unlock(&chip->reg_lock);
|
|
|
return 0;
|
|
|
}
|
|
@@ -2306,6 +2399,7 @@ static struct snd_pcm_ops azx_pcm_ops = {
|
|
|
.prepare = azx_pcm_prepare,
|
|
|
.trigger = azx_pcm_trigger,
|
|
|
.pointer = azx_pcm_pointer,
|
|
|
+ .wall_clock = azx_get_wallclock_tstamp,
|
|
|
.mmap = azx_pcm_mmap,
|
|
|
.page = snd_pcm_sgbuf_ops_page,
|
|
|
};
|