Linux-libre 5.4.48-gnu
[librecmc/linux-libre.git] / sound / core / seq / oss / seq_oss_timer.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * OSS compatible sequencer driver
4  *
5  * Timer control routines
6  *
7  * Copyright (C) 1998,99 Takashi Iwai <tiwai@suse.de>
8  */
9
10 #include "seq_oss_timer.h"
11 #include "seq_oss_event.h"
12 #include <sound/seq_oss_legacy.h>
13 #include <linux/slab.h>
14
15 /*
16  */
17 #define MIN_OSS_TEMPO           8
18 #define MAX_OSS_TEMPO           360
19 #define MIN_OSS_TIMEBASE        1
20 #define MAX_OSS_TIMEBASE        1000
21
22 /*
23  */
24 static void calc_alsa_tempo(struct seq_oss_timer *timer);
25 static int send_timer_event(struct seq_oss_devinfo *dp, int type, int value);
26
27
28 /*
29  * create and register a new timer.
30  * if queue is not started yet, start it.
31  */
32 struct seq_oss_timer *
33 snd_seq_oss_timer_new(struct seq_oss_devinfo *dp)
34 {
35         struct seq_oss_timer *rec;
36
37         rec = kzalloc(sizeof(*rec), GFP_KERNEL);
38         if (rec == NULL)
39                 return NULL;
40
41         rec->dp = dp;
42         rec->cur_tick = 0;
43         rec->realtime = 0;
44         rec->running = 0;
45         rec->oss_tempo = 60;
46         rec->oss_timebase = 100;
47         calc_alsa_tempo(rec);
48
49         return rec;
50 }
51
52
53 /*
54  * delete timer.
55  * if no more timer exists, stop the queue.
56  */
57 void
58 snd_seq_oss_timer_delete(struct seq_oss_timer *rec)
59 {
60         if (rec) {
61                 snd_seq_oss_timer_stop(rec);
62                 kfree(rec);
63         }
64 }
65
66
67 /*
68  * process one timing event
69  * return 1 : event proceseed -- skip this event
70  *        0 : not a timer event -- enqueue this event
71  */
72 int
73 snd_seq_oss_process_timer_event(struct seq_oss_timer *rec, union evrec *ev)
74 {
75         abstime_t parm = ev->t.time;
76
77         if (ev->t.code == EV_TIMING) {
78                 switch (ev->t.cmd) {
79                 case TMR_WAIT_REL:
80                         parm += rec->cur_tick;
81                         rec->realtime = 0;
82                         /* fall through */
83                 case TMR_WAIT_ABS:
84                         if (parm == 0) {
85                                 rec->realtime = 1;
86                         } else if (parm >= rec->cur_tick) {
87                                 rec->realtime = 0;
88                                 rec->cur_tick = parm;
89                         }
90                         return 1;       /* skip this event */
91                         
92                 case TMR_START:
93                         snd_seq_oss_timer_start(rec);
94                         return 1;
95
96                 }
97         } else if (ev->s.code == SEQ_WAIT) {
98                 /* time = from 1 to 3 bytes */
99                 parm = (ev->echo >> 8) & 0xffffff;
100                 if (parm > rec->cur_tick) {
101                         /* set next event time */
102                         rec->cur_tick = parm;
103                         rec->realtime = 0;
104                 }
105                 return 1;
106         }
107
108         return 0;
109 }
110
111
112 /*
113  * convert tempo units
114  */
115 static void
116 calc_alsa_tempo(struct seq_oss_timer *timer)
117 {
118         timer->tempo = (60 * 1000000) / timer->oss_tempo;
119         timer->ppq = timer->oss_timebase;
120 }
121
122
123 /*
124  * dispatch a timer event
125  */
126 static int
127 send_timer_event(struct seq_oss_devinfo *dp, int type, int value)
128 {
129         struct snd_seq_event ev;
130
131         memset(&ev, 0, sizeof(ev));
132         ev.type = type;
133         ev.source.client = dp->cseq;
134         ev.source.port = 0;
135         ev.dest.client = SNDRV_SEQ_CLIENT_SYSTEM;
136         ev.dest.port = SNDRV_SEQ_PORT_SYSTEM_TIMER;
137         ev.queue = dp->queue;
138         ev.data.queue.queue = dp->queue;
139         ev.data.queue.param.value = value;
140         return snd_seq_kernel_client_dispatch(dp->cseq, &ev, 1, 0);
141 }
142
143 /*
144  * set queue tempo and start queue
145  */
146 int
147 snd_seq_oss_timer_start(struct seq_oss_timer *timer)
148 {
149         struct seq_oss_devinfo *dp = timer->dp;
150         struct snd_seq_queue_tempo tmprec;
151
152         if (timer->running)
153                 snd_seq_oss_timer_stop(timer);
154
155         memset(&tmprec, 0, sizeof(tmprec));
156         tmprec.queue = dp->queue;
157         tmprec.ppq = timer->ppq;
158         tmprec.tempo = timer->tempo;
159         snd_seq_set_queue_tempo(dp->cseq, &tmprec);
160
161         send_timer_event(dp, SNDRV_SEQ_EVENT_START, 0);
162         timer->running = 1;
163         timer->cur_tick = 0;
164         return 0;
165 }
166
167
168 /*
169  * stop queue
170  */
171 int
172 snd_seq_oss_timer_stop(struct seq_oss_timer *timer)
173 {
174         if (! timer->running)
175                 return 0;
176         send_timer_event(timer->dp, SNDRV_SEQ_EVENT_STOP, 0);
177         timer->running = 0;
178         return 0;
179 }
180
181
182 /*
183  * continue queue
184  */
185 int
186 snd_seq_oss_timer_continue(struct seq_oss_timer *timer)
187 {
188         if (timer->running)
189                 return 0;
190         send_timer_event(timer->dp, SNDRV_SEQ_EVENT_CONTINUE, 0);
191         timer->running = 1;
192         return 0;
193 }
194
195
196 /*
197  * change queue tempo
198  */
199 int
200 snd_seq_oss_timer_tempo(struct seq_oss_timer *timer, int value)
201 {
202         if (value < MIN_OSS_TEMPO)
203                 value = MIN_OSS_TEMPO;
204         else if (value > MAX_OSS_TEMPO)
205                 value = MAX_OSS_TEMPO;
206         timer->oss_tempo = value;
207         calc_alsa_tempo(timer);
208         if (timer->running)
209                 send_timer_event(timer->dp, SNDRV_SEQ_EVENT_TEMPO, timer->tempo);
210         return 0;
211 }
212
213
214 /*
215  * ioctls
216  */
217 int
218 snd_seq_oss_timer_ioctl(struct seq_oss_timer *timer, unsigned int cmd, int __user *arg)
219 {
220         int value;
221
222         if (cmd == SNDCTL_SEQ_CTRLRATE) {
223                 /* if *arg == 0, just return the current rate */
224                 if (get_user(value, arg))
225                         return -EFAULT;
226                 if (value)
227                         return -EINVAL;
228                 value = ((timer->oss_tempo * timer->oss_timebase) + 30) / 60;
229                 return put_user(value, arg) ? -EFAULT : 0;
230         }
231
232         if (timer->dp->seq_mode == SNDRV_SEQ_OSS_MODE_SYNTH)
233                 return 0;
234
235         switch (cmd) {
236         case SNDCTL_TMR_START:
237                 return snd_seq_oss_timer_start(timer);
238         case SNDCTL_TMR_STOP:
239                 return snd_seq_oss_timer_stop(timer);
240         case SNDCTL_TMR_CONTINUE:
241                 return snd_seq_oss_timer_continue(timer);
242         case SNDCTL_TMR_TEMPO:
243                 if (get_user(value, arg))
244                         return -EFAULT;
245                 return snd_seq_oss_timer_tempo(timer, value);
246         case SNDCTL_TMR_TIMEBASE:
247                 if (get_user(value, arg))
248                         return -EFAULT;
249                 if (value < MIN_OSS_TIMEBASE)
250                         value = MIN_OSS_TIMEBASE;
251                 else if (value > MAX_OSS_TIMEBASE)
252                         value = MAX_OSS_TIMEBASE;
253                 timer->oss_timebase = value;
254                 calc_alsa_tempo(timer);
255                 return 0;
256
257         case SNDCTL_TMR_METRONOME:
258         case SNDCTL_TMR_SELECT:
259         case SNDCTL_TMR_SOURCE:
260                 /* not supported */
261                 return 0;
262         }
263         return 0;
264 }