xref: /openbmc/linux/sound/core/hrtimer.c (revision bc5aa3a0)
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.tv64 > 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