1 /* 2 * ALSA timer back-end using hrtimer 3 * Copyright (C) 2008 Takashi Iwai 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 * 19 */ 20 21 #include <linux/init.h> 22 #include <linux/slab.h> 23 #include <linux/module.h> 24 #include <linux/moduleparam.h> 25 #include <linux/hrtimer.h> 26 #include <sound/core.h> 27 #include <sound/timer.h> 28 29 MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); 30 MODULE_DESCRIPTION("ALSA hrtimer backend"); 31 MODULE_LICENSE("GPL"); 32 33 MODULE_ALIAS("snd-timer-" __stringify(SNDRV_TIMER_GLOBAL_HRTIMER)); 34 35 #define NANO_SEC 1000000000UL /* 10^9 in sec */ 36 static unsigned int resolution; 37 38 struct snd_hrtimer { 39 struct snd_timer *timer; 40 struct hrtimer hrt; 41 bool in_callback; 42 }; 43 44 static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt) 45 { 46 struct snd_hrtimer *stime = container_of(hrt, struct snd_hrtimer, hrt); 47 struct snd_timer *t = stime->timer; 48 ktime_t delta; 49 unsigned long ticks; 50 enum hrtimer_restart ret = HRTIMER_NORESTART; 51 52 spin_lock(&t->lock); 53 if (!t->running) 54 goto out; /* fast path */ 55 stime->in_callback = true; 56 ticks = t->sticks; 57 spin_unlock(&t->lock); 58 59 /* calculate the drift */ 60 delta = ktime_sub(hrt->base->get_time(), hrtimer_get_expires(hrt)); 61 if (delta > 0) 62 ticks += ktime_divns(delta, ticks * resolution); 63 64 snd_timer_interrupt(stime->timer, ticks); 65 66 spin_lock(&t->lock); 67 if (t->running) { 68 hrtimer_add_expires_ns(hrt, t->sticks * resolution); 69 ret = HRTIMER_RESTART; 70 } 71 72 stime->in_callback = false; 73 out: 74 spin_unlock(&t->lock); 75 return ret; 76 } 77 78 static int snd_hrtimer_open(struct snd_timer *t) 79 { 80 struct snd_hrtimer *stime; 81 82 stime = kzalloc(sizeof(*stime), GFP_KERNEL); 83 if (!stime) 84 return -ENOMEM; 85 hrtimer_init(&stime->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 86 stime->timer = t; 87 stime->hrt.function = snd_hrtimer_callback; 88 t->private_data = stime; 89 return 0; 90 } 91 92 static int snd_hrtimer_close(struct snd_timer *t) 93 { 94 struct snd_hrtimer *stime = t->private_data; 95 96 if (stime) { 97 spin_lock_irq(&t->lock); 98 t->running = 0; /* just to be sure */ 99 stime->in_callback = 1; /* skip start/stop */ 100 spin_unlock_irq(&t->lock); 101 102 hrtimer_cancel(&stime->hrt); 103 kfree(stime); 104 t->private_data = NULL; 105 } 106 return 0; 107 } 108 109 static int snd_hrtimer_start(struct snd_timer *t) 110 { 111 struct snd_hrtimer *stime = t->private_data; 112 113 if (stime->in_callback) 114 return 0; 115 hrtimer_start(&stime->hrt, ns_to_ktime(t->sticks * resolution), 116 HRTIMER_MODE_REL); 117 return 0; 118 } 119 120 static int snd_hrtimer_stop(struct snd_timer *t) 121 { 122 struct snd_hrtimer *stime = t->private_data; 123 124 if (stime->in_callback) 125 return 0; 126 hrtimer_try_to_cancel(&stime->hrt); 127 return 0; 128 } 129 130 static struct snd_timer_hardware hrtimer_hw = { 131 .flags = SNDRV_TIMER_HW_AUTO | SNDRV_TIMER_HW_TASKLET, 132 .open = snd_hrtimer_open, 133 .close = snd_hrtimer_close, 134 .start = snd_hrtimer_start, 135 .stop = snd_hrtimer_stop, 136 }; 137 138 /* 139 * entry functions 140 */ 141 142 static struct snd_timer *mytimer; 143 144 static int __init snd_hrtimer_init(void) 145 { 146 struct snd_timer *timer; 147 int err; 148 149 resolution = hrtimer_resolution; 150 151 /* Create a new timer and set up the fields */ 152 err = snd_timer_global_new("hrtimer", SNDRV_TIMER_GLOBAL_HRTIMER, 153 &timer); 154 if (err < 0) 155 return err; 156 157 timer->module = THIS_MODULE; 158 strcpy(timer->name, "HR timer"); 159 timer->hw = hrtimer_hw; 160 timer->hw.resolution = resolution; 161 timer->hw.ticks = NANO_SEC / resolution; 162 163 err = snd_timer_global_register(timer); 164 if (err < 0) { 165 snd_timer_global_free(timer); 166 return err; 167 } 168 mytimer = timer; /* remember this */ 169 170 return 0; 171 } 172 173 static void __exit snd_hrtimer_exit(void) 174 { 175 if (mytimer) { 176 snd_timer_global_free(mytimer); 177 mytimer = NULL; 178 } 179 } 180 181 module_init(snd_hrtimer_init); 182 module_exit(snd_hrtimer_exit); 183