[alsa-devel] [PATCH 2/2] ALSA: hda: support for wallclock timestamps
Wang Xingchao
wangxingchao2011 at gmail.com
Thu Jun 14 05:32:44 CEST 2012
Hi Pierre,
2012/6/14 Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>:
> reuse code from clocksource to handle wall clock counter,
> translate cycles to timespec. The code wasn't reused as
> is as the HDA wall clock is based on 24MHz ticks. To avoid
> compounding rounding errors, the counters track cycles
> and will convert elapsed cycles to ns, instead of delta cycles
> as done in the cyclecounter code.
> Since wrapparound occurs, the timestamps are reinitialized with
> monotonic time on a trigger.
>
> TODO:
> - only re-init timecounter if there was no device active.
> - Keep the same timestamp for all devices on same chip.
> - make sure no overflow occurs in the 125/3 scaling implementation
>
I'm afraid you donot think much about the Wall Clock Counter register,
the counter will roll over to 0 within 179s under 24Mhz. Another case
maybe BCLK stop and controller reset.
thanks
--xingchao
> Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>
> ---
> sound/pci/hda/hda_intel.c | 106 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 files changed, 105 insertions(+), 1 deletions(-)
>
> diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
> index 2b6392b..f9b9bc1 100644
> --- a/sound/pci/hda/hda_intel.c
> +++ b/sound/pci/hda/hda_intel.c
> @@ -46,6 +46,10 @@
> #include <linux/mutex.h>
> #include <linux/reboot.h>
> #include <linux/io.h>
> +
> +#include <linux/clocksource.h>
> +#include <linux/time.h>
> +
> #ifdef CONFIG_X86
> /* for snoop control */
> #include <asm/pgtable.h>
> @@ -426,6 +430,15 @@ struct azx_pcm {
> struct list_head list;
> };
>
> +struct azx_timecounter {
> + cycle_t cycle_last;
> + cycle_t mask;
> + cycle_t elapsed_cycles;
> + u64 initial_time_nsec;
> + u32 mult;
> + u32 shift;
> +};
> +
> struct azx {
> struct snd_card *card;
> struct pci_dev *pci;
> @@ -496,6 +509,9 @@ struct azx {
>
> /* reboot notifier (for mysterious hangup problem at power-down) */
> struct notifier_block reboot_notifier;
> +
> + /* Wall clock counter */
> + struct azx_timecounter tc;
> };
>
> /* driver types */
> @@ -1699,6 +1715,89 @@ static inline void azx_release_device(struct azx_dev *azx_dev)
> azx_dev->opened = 0;
> }
>
> +static void azx_timecounter_init(struct azx *chip)
> +{
> + struct azx_timecounter *tc = &chip->tc;
> + struct timespec ts;
> +
> + tc->cycle_last = azx_readl(chip, WALLCLK);
> + tc->elapsed_cycles = 0;
> + tc->mask = CLOCKSOURCE_MASK(32);
> + /*
> + * conversion from 24MHz to nsec requires fractional operation,
> + * approximate 125/3 ratio
> + */
> +#define NBITS_NS 16
> + tc->mult = (u32)(1<<NBITS_NS)*125L/3L;
> + tc->shift = NBITS_NS;
> +
> + /* save initial time */
> + ktime_get_ts(&ts);
> + tc->initial_time_nsec = timespec_to_ns(&ts);
> +}
> +
> +/**
> + * azx_timecounter_read_delta - get nanoseconds since last call of this function
> + * @tc: Pointer to time counter
> + *
> + * When the underlying cycle counter runs over, this will be handled
> + * correctly as long as it does not run over more than once between
> + * calls.
> + *
> + * The first call to this function for a new time counter initializes
> + * the time tracking and returns an undefined result.
> + */
> +static u64 azx_timecounter_read_delta(struct azx_timecounter *tc,
> + cycle_t cycle_now)
> +{
> + cycle_t cycle_delta;
> + u64 nsec;
> +
> + /* calculate the delta since the last timecounter_read_delta(): */
> + cycle_delta = (cycle_now - tc->cycle_last) & tc->mask;
> +
> + tc->elapsed_cycles += cycle_delta;
> +
> + /* convert to nanoseconds: */
> + nsec = (u64)tc->elapsed_cycles;
> + nsec = (nsec * tc->mult) >> tc->shift;
> +
> + /* update time stamp of azx_timecounter_read_delta() call: */
> + tc->cycle_last = cycle_now;
> +
> + return nsec;
> +}
> +
> +u64 azx_timecounter_read(struct azx *chip)
> +{
> + u64 nsec;
> + struct azx_timecounter *tc = &chip->tc;
> + cycle_t cycle_now;
> +
> + /* read cycle counter: */
> + cycle_now = azx_readl(chip, WALLCLK);
> +
> + /* increment time by nanoseconds since last call */
> + nsec = azx_timecounter_read_delta(tc, cycle_now);
> + nsec += tc->initial_time_nsec;
> +
> + return nsec;
> +}
> +
> +
> +static int azx_get_wallclock_tstamp(struct snd_pcm_substream *substream,
> + struct timespec *ts)
> +{
> + struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
> + struct azx *chip = apcm->chip;
> + u64 nsec;
> +
> + nsec = azx_timecounter_read(chip);
> + *ts = ns_to_timespec(nsec);
> +
> + return 0;
> +}
> +
> static struct snd_pcm_hardware azx_pcm_hw = {
> .info = (SNDRV_PCM_INFO_MMAP |
> SNDRV_PCM_INFO_INTERLEAVED |
> @@ -1708,6 +1807,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,
> @@ -1981,8 +2081,10 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> }
> spin_unlock(&chip->reg_lock);
> if (start) {
> - if (nsync == 1)
> + if (nsync == 1) {
> + azx_timecounter_init(chip);
> return 0;
> + }
> /* wait until all FIFOs get ready */
> for (timeout = 5000; timeout; timeout--) {
> nwait = 0;
> @@ -1998,6 +2100,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> break;
> cpu_relax();
> }
> + azx_timecounter_init(chip);
> } else {
> /* wait until all RUN bits are cleared */
> for (timeout = 5000; timeout; timeout--) {
> @@ -2239,6 +2342,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,
> };
> --
> 1.7.6.5
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
More information about the Alsa-devel
mailing list