blob: 48cda6c4c257b34d191d3512ff52658de4329669 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
Uwe Zeisbergerf30c2262006-10-03 23:01:26 +02002 * sound/oss/sound_timer.c
Linus Torvalds1da177e2005-04-16 15:20:36 -07003 */
4/*
5 * Copyright (C) by Hannu Savolainen 1993-1997
6 *
7 * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
8 * Version 2 (June 1991). See the "COPYING" file distributed with this software
9 * for more info.
10 */
11/*
12 * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed)
13 */
14#include <linux/string.h>
15#include <linux/spinlock.h>
16
17#include "sound_config.h"
18
19static volatile int initialized, opened, tmr_running;
20static volatile time_t tmr_offs, tmr_ctr;
21static volatile unsigned long ticks_offs;
22static volatile int curr_tempo, curr_timebase;
23static volatile unsigned long curr_ticks;
24static volatile unsigned long next_event_time;
25static unsigned long prev_event_time;
26static volatile unsigned long usecs_per_tmr; /* Length of the current interval */
27
28static struct sound_lowlev_timer *tmr;
Akinobu Mita3182c8a2010-08-28 13:25:33 +090029static DEFINE_SPINLOCK(lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -070030
31static unsigned long tmr2ticks(int tmr_value)
32{
33 /*
34 * Convert timer ticks to MIDI ticks
35 */
36
37 unsigned long tmp;
38 unsigned long scale;
39
40 tmp = tmr_value * usecs_per_tmr; /* Convert to usecs */
41 scale = (60 * 1000000) / (curr_tempo * curr_timebase); /* usecs per MIDI tick */
42 return (tmp + (scale / 2)) / scale;
43}
44
45void reprogram_timer(void)
46{
47 unsigned long usecs_per_tick;
48
49 /*
50 * The user is changing the timer rate before setting a timer
51 * slap, bad bad not allowed.
52 */
53
54 if(!tmr)
55 return;
56
57 usecs_per_tick = (60 * 1000000) / (curr_tempo * curr_timebase);
58
59 /*
60 * Don't kill the system by setting too high timer rate
61 */
62 if (usecs_per_tick < 2000)
63 usecs_per_tick = 2000;
64
65 usecs_per_tmr = tmr->tmr_start(tmr->dev, usecs_per_tick);
66}
67
68void sound_timer_syncinterval(unsigned int new_usecs)
69{
70 /*
71 * This routine is called by the hardware level if
72 * the clock frequency has changed for some reason.
73 */
74 tmr_offs = tmr_ctr;
75 ticks_offs += tmr2ticks(tmr_ctr);
76 tmr_ctr = 0;
77 usecs_per_tmr = new_usecs;
78}
Adrian Bunkece7f772006-10-04 02:17:31 -070079EXPORT_SYMBOL(sound_timer_syncinterval);
Linus Torvalds1da177e2005-04-16 15:20:36 -070080
81static void tmr_reset(void)
82{
83 unsigned long flags;
84
85 spin_lock_irqsave(&lock,flags);
86 tmr_offs = 0;
87 ticks_offs = 0;
88 tmr_ctr = 0;
89 next_event_time = (unsigned long) -1;
90 prev_event_time = 0;
91 curr_ticks = 0;
92 spin_unlock_irqrestore(&lock,flags);
93}
94
95static int timer_open(int dev, int mode)
96{
97 if (opened)
98 return -EBUSY;
99 tmr_reset();
100 curr_tempo = 60;
101 curr_timebase = 100;
102 opened = 1;
103 reprogram_timer();
104 return 0;
105}
106
107static void timer_close(int dev)
108{
109 opened = tmr_running = 0;
110 tmr->tmr_disable(tmr->dev);
111}
112
113static int timer_event(int dev, unsigned char *event)
114{
115 unsigned char cmd = event[1];
116 unsigned long parm = *(int *) &event[4];
117
118 switch (cmd)
119 {
120 case TMR_WAIT_REL:
121 parm += prev_event_time;
122 case TMR_WAIT_ABS:
123 if (parm > 0)
124 {
125 long time;
126
127 if (parm <= curr_ticks) /* It's the time */
128 return TIMER_NOT_ARMED;
129 time = parm;
130 next_event_time = prev_event_time = time;
131 return TIMER_ARMED;
132 }
133 break;
134
135 case TMR_START:
136 tmr_reset();
137 tmr_running = 1;
138 reprogram_timer();
139 break;
140
141 case TMR_STOP:
142 tmr_running = 0;
143 break;
144
145 case TMR_CONTINUE:
146 tmr_running = 1;
147 reprogram_timer();
148 break;
149
150 case TMR_TEMPO:
151 if (parm)
152 {
153 if (parm < 8)
154 parm = 8;
155 if (parm > 250)
156 parm = 250;
157 tmr_offs = tmr_ctr;
158 ticks_offs += tmr2ticks(tmr_ctr);
159 tmr_ctr = 0;
160 curr_tempo = parm;
161 reprogram_timer();
162 }
163 break;
164
165 case TMR_ECHO:
166 seq_copy_to_input(event, 8);
167 break;
168
169 default:;
170 }
171 return TIMER_NOT_ARMED;
172}
173
174static unsigned long timer_get_time(int dev)
175{
176 if (!opened)
177 return 0;
178 return curr_ticks;
179}
180
181static int timer_ioctl(int dev, unsigned int cmd, void __user *arg)
182{
183 int __user *p = arg;
184 int val;
185
186 switch (cmd)
187 {
188 case SNDCTL_TMR_SOURCE:
189 val = TMR_INTERNAL;
190 break;
191
192 case SNDCTL_TMR_START:
193 tmr_reset();
194 tmr_running = 1;
195 return 0;
196
197 case SNDCTL_TMR_STOP:
198 tmr_running = 0;
199 return 0;
200
201 case SNDCTL_TMR_CONTINUE:
202 tmr_running = 1;
203 return 0;
204
205 case SNDCTL_TMR_TIMEBASE:
206 if (get_user(val, p))
207 return -EFAULT;
208 if (val)
209 {
210 if (val < 1)
211 val = 1;
212 if (val > 1000)
213 val = 1000;
214 curr_timebase = val;
215 }
216 val = curr_timebase;
217 break;
218
219 case SNDCTL_TMR_TEMPO:
220 if (get_user(val, p))
221 return -EFAULT;
222 if (val)
223 {
224 if (val < 8)
225 val = 8;
226 if (val > 250)
227 val = 250;
228 tmr_offs = tmr_ctr;
229 ticks_offs += tmr2ticks(tmr_ctr);
230 tmr_ctr = 0;
231 curr_tempo = val;
232 reprogram_timer();
233 }
234 val = curr_tempo;
235 break;
236
237 case SNDCTL_SEQ_CTRLRATE:
238 if (get_user(val, p))
239 return -EFAULT;
240 if (val != 0) /* Can't change */
241 return -EINVAL;
242 val = ((curr_tempo * curr_timebase) + 30) / 60;
243 break;
244
245 case SNDCTL_SEQ_GETTIME:
246 val = curr_ticks;
247 break;
248
249 case SNDCTL_TMR_METRONOME:
250 default:
251 return -EINVAL;
252 }
253 return put_user(val, p);
254}
255
256static void timer_arm(int dev, long time)
257{
258 if (time < 0)
259 time = curr_ticks + 1;
260 else if (time <= curr_ticks) /* It's the time */
261 return;
262
263 next_event_time = prev_event_time = time;
264 return;
265}
266
267static struct sound_timer_operations sound_timer =
268{
269 .owner = THIS_MODULE,
270 .info = {"Sound Timer", 0},
271 .priority = 1, /* Priority */
272 .devlink = 0, /* Local device link */
273 .open = timer_open,
274 .close = timer_close,
275 .event = timer_event,
276 .get_time = timer_get_time,
277 .ioctl = timer_ioctl,
278 .arm_timer = timer_arm
279};
280
281void sound_timer_interrupt(void)
282{
283 unsigned long flags;
284
285 if (!opened)
286 return;
287
288 tmr->tmr_restart(tmr->dev);
289
290 if (!tmr_running)
291 return;
292
293 spin_lock_irqsave(&lock,flags);
294 tmr_ctr++;
295 curr_ticks = ticks_offs + tmr2ticks(tmr_ctr);
296
297 if (curr_ticks >= next_event_time)
298 {
299 next_event_time = (unsigned long) -1;
300 sequencer_timer(0);
301 }
302 spin_unlock_irqrestore(&lock,flags);
303}
Adrian Bunkece7f772006-10-04 02:17:31 -0700304EXPORT_SYMBOL(sound_timer_interrupt);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700305
306void sound_timer_init(struct sound_lowlev_timer *t, char *name)
307{
308 int n;
309
310 if (initialized)
311 {
312 if (t->priority <= tmr->priority)
313 return; /* There is already a similar or better timer */
314 tmr = t;
315 return;
316 }
317 initialized = 1;
318 tmr = t;
319
320 n = sound_alloc_timerdev();
321 if (n == -1)
322 n = 0; /* Overwrite the system timer */
323 strcpy(sound_timer.info.name, name);
324 sound_timer_devs[n] = &sound_timer;
325}
Adrian Bunkece7f772006-10-04 02:17:31 -0700326EXPORT_SYMBOL(sound_timer_init);
327