168 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ALSA timer back-end using hrtimer4 * Copyright (C) 2008 Takashi Iwai5 */6 7#include <linux/init.h>8#include <linux/slab.h>9#include <linux/module.h>10#include <linux/moduleparam.h>11#include <linux/hrtimer.h>12#include <sound/core.h>13#include <sound/timer.h>14 15MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");16MODULE_DESCRIPTION("ALSA hrtimer backend");17MODULE_LICENSE("GPL");18 19MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER));20 21#define NANO_SEC 1000000000UL /* 10^9 in sec */22static unsigned int resolution;23 24struct snd_hrtimer {25 struct snd_timer *timer;26 struct hrtimer hrt;27 bool in_callback;28};29 30static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)31{32 struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt);33 struct snd_timer *t = stime->timer;34 ktime_t delta;35 unsigned long ticks;36 enum hrtimer_restart ret = HRTIMER_NORESTART;37 38 scoped_guard(spinlock, &t->lock) {39 if (!t->running)40 return HRTIMER_NORESTART; /* fast path */41 stime->in_callback = true;42 ticks = t->sticks;43 }44 45 /* calculate the drift */46 delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt));47 if (delta > 0)48 ticks += ktime_divns(delta, ticks * resolution);49 50 snd_timer_interrupt(stime->timer, ticks);51 52 guard(spinlock)(&t->lock);53 if (t->running) {54 hrtimer_add_expires_ns(hrt, t->sticks * resolution);55 ret = HRTIMER_RESTART;56 }57 58 stime->in_callback = false;59 return ret;60}61 62static int snd_hrtimer_open(struct snd_timer *t)63{64 struct snd_hrtimer *stime;65 66 stime = kzalloc(sizeof(*stime), GFP_KERNEL);67 if (!stime)68 return -ENOMEM;69 hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);70 stime->timer = t;71 stime->hrt.function = snd_hrtimer_callback;72 t->private_data = stime;73 return 0;74}75 76static int snd_hrtimer_close(struct snd_timer *t)77{78 struct snd_hrtimer *stime = t->private_data;79 80 if (stime) {81 scoped_guard(spinlock_irq, &t->lock) {82 t->running = 0; /* just to be sure */83 stime->in_callback = 1; /* skip start/stop */84 }85 86 hrtimer_cancel(&stime->hrt);87 kfree(stime);88 t->private_data = NULL;89 }90 return 0;91}92 93static int snd_hrtimer_start(struct snd_timer *t)94{95 struct snd_hrtimer *stime = t->private_data;96 97 if (stime->in_callback)98 return 0;99 hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution),100 HRTIMER_MODE_REL);101 return 0;102}103 104static int snd_hrtimer_stop(struct snd_timer *t)105{106 struct snd_hrtimer *stime = t->private_data;107 108 if (stime->in_callback)109 return 0;110 hrtimer_try_to_cancel(&stime->hrt);111 return 0;112}113 114static const struct snd_timer_hardware hrtimer_hw __initconst = {115 .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_WORK,116 .open = snd_hrtimer_open,117 .close = snd_hrtimer_close,118 .start = snd_hrtimer_start,119 .stop = snd_hrtimer_stop,120};121 122/*123 * entry functions124 */125 126static struct snd_timer *mytimer;127 128static int __init snd_hrtimer_init(void)129{130 struct snd_timer *timer;131 int err;132 133 resolution = hrtimer_resolution;134 135 /* Create a new timer and set up the fields */136 err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER,137 &timer);138 if (err < 0)139 return err;140 141 timer->module = THIS_MODULE;142 strcpy(timer->name, "HR timer");143 timer->hw = hrtimer_hw;144 timer->hw.resolution = resolution;145 timer->hw.ticks = NANO_SEC / resolution;146 timer->max_instances = 100; /* lower the limit */147 148 err = snd_timer_global_register(timer);149 if (err < 0) {150 snd_timer_global_free(timer);151 return err;152 }153 mytimer = timer; /* remember this */154 155 return 0;156}157 158static void __exit snd_hrtimer_exit(void)159{160 if (mytimer) {161 snd_timer_global_free(mytimer);162 mytimer = NULL;163 }164}165 166module_init(snd_hrtimer_init);167module_exit(snd_hrtimer_exit);168