Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * ALSA sequencer device management | |
3 | * Copyright (c) 1999 by Takashi Iwai <tiwai@suse.de> | |
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 | * | |
22 | * This device handler separates the card driver module from sequencer | |
23 | * stuff (sequencer core, synth drivers, etc), so that user can avoid | |
24 | * to spend unnecessary resources e.g. if he needs only listening to | |
25 | * MP3s. | |
26 | * | |
27 | * The card (or lowlevel) driver creates a sequencer device entry | |
28 | * via snd_seq_device_new(). This is an entry pointer to communicate | |
29 | * with the sequencer device "driver", which is involved with the | |
30 | * actual part to communicate with the sequencer core. | |
31 | * Each sequencer device entry has an id string and the corresponding | |
32 | * driver with the same id is loaded when required. For example, | |
33 | * lowlevel codes to access emu8000 chip on sbawe card are included in | |
34 | * emu8000-synth module. To activate this module, the hardware | |
35 | * resources like i/o port are passed via snd_seq_device argument. | |
36 | * | |
37 | */ | |
38 | ||
1da177e4 | 39 | #include <linux/init.h> |
da155d5b | 40 | #include <linux/module.h> |
1da177e4 LT |
41 | #include <sound/core.h> |
42 | #include <sound/info.h> | |
43 | #include <sound/seq_device.h> | |
44 | #include <sound/seq_kernel.h> | |
45 | #include <sound/initval.h> | |
46 | #include <linux/kmod.h> | |
47 | #include <linux/slab.h> | |
1a60d4c5 | 48 | #include <linux/mutex.h> |
1da177e4 LT |
49 | |
50 | MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); | |
51 | MODULE_DESCRIPTION("ALSA sequencer device management"); | |
52 | MODULE_LICENSE("GPL"); | |
53 | ||
1da177e4 LT |
54 | /* driver state */ |
55 | #define DRIVER_EMPTY 0 | |
56 | #define DRIVER_LOADED (1<<0) | |
57 | #define DRIVER_REQUESTED (1<<1) | |
58 | #define DRIVER_LOCKED (1<<2) | |
59 | ||
60 | struct ops_list { | |
61 | char id[ID_LEN]; /* driver id */ | |
62 | int driver; /* driver state */ | |
63 | int used; /* reference counter */ | |
64 | int argsize; /* argument size */ | |
65 | ||
66 | /* operators */ | |
c7e0b5bf | 67 | struct snd_seq_dev_ops ops; |
1da177e4 | 68 | |
d93cf068 | 69 | /* registered devices */ |
1da177e4 LT |
70 | struct list_head dev_list; /* list of devices */ |
71 | int num_devices; /* number of associated devices */ | |
72 | int num_init_devices; /* number of initialized devices */ | |
1a60d4c5 | 73 | struct mutex reg_mutex; |
1da177e4 LT |
74 | |
75 | struct list_head list; /* next driver */ | |
76 | }; | |
77 | ||
78 | ||
79 | static LIST_HEAD(opslist); | |
80 | static int num_ops; | |
1a60d4c5 | 81 | static DEFINE_MUTEX(ops_mutex); |
04f141a8 | 82 | #ifdef CONFIG_PROC_FS |
6581f4e7 | 83 | static struct snd_info_entry *info_entry; |
04f141a8 | 84 | #endif |
1da177e4 LT |
85 | |
86 | /* | |
87 | * prototypes | |
88 | */ | |
c7e0b5bf TI |
89 | static int snd_seq_device_free(struct snd_seq_device *dev); |
90 | static int snd_seq_device_dev_free(struct snd_device *device); | |
91 | static int snd_seq_device_dev_register(struct snd_device *device); | |
92 | static int snd_seq_device_dev_disconnect(struct snd_device *device); | |
c7e0b5bf TI |
93 | |
94 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops); | |
95 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops); | |
96 | static struct ops_list *find_driver(char *id, int create_if_empty); | |
97 | static struct ops_list *create_driver(char *id); | |
98 | static void unlock_driver(struct ops_list *ops); | |
1da177e4 LT |
99 | static void remove_drivers(void); |
100 | ||
101 | /* | |
102 | * show all drivers and their status | |
103 | */ | |
104 | ||
04f141a8 | 105 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
106 | static void snd_seq_device_info(struct snd_info_entry *entry, |
107 | struct snd_info_buffer *buffer) | |
1da177e4 | 108 | { |
9244b2c3 | 109 | struct ops_list *ops; |
1da177e4 | 110 | |
1a60d4c5 | 111 | mutex_lock(&ops_mutex); |
9244b2c3 | 112 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
113 | snd_iprintf(buffer, "snd-%s%s%s%s,%d\n", |
114 | ops->id, | |
115 | ops->driver & DRIVER_LOADED ? ",loaded" : (ops->driver == DRIVER_EMPTY ? ",empty" : ""), | |
116 | ops->driver & DRIVER_REQUESTED ? ",requested" : "", | |
117 | ops->driver & DRIVER_LOCKED ? ",locked" : "", | |
118 | ops->num_devices); | |
119 | } | |
1a60d4c5 | 120 | mutex_unlock(&ops_mutex); |
1da177e4 | 121 | } |
04f141a8 | 122 | #endif |
1da177e4 LT |
123 | |
124 | /* | |
125 | * load all registered drivers (called from seq_clientmgr.c) | |
126 | */ | |
127 | ||
ee2da997 | 128 | #ifdef CONFIG_MODULES |
1da177e4 LT |
129 | /* avoid auto-loading during module_init() */ |
130 | static int snd_seq_in_init; | |
131 | void snd_seq_autoload_lock(void) | |
132 | { | |
133 | snd_seq_in_init++; | |
134 | } | |
135 | ||
136 | void snd_seq_autoload_unlock(void) | |
137 | { | |
138 | snd_seq_in_init--; | |
139 | } | |
140 | #endif | |
141 | ||
142 | void snd_seq_device_load_drivers(void) | |
143 | { | |
ee2da997 | 144 | #ifdef CONFIG_MODULES |
9244b2c3 | 145 | struct ops_list *ops; |
1da177e4 LT |
146 | |
147 | /* Calling request_module during module_init() | |
148 | * may cause blocking. | |
149 | */ | |
150 | if (snd_seq_in_init) | |
151 | return; | |
152 | ||
1a60d4c5 | 153 | mutex_lock(&ops_mutex); |
9244b2c3 | 154 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
155 | if (! (ops->driver & DRIVER_LOADED) && |
156 | ! (ops->driver & DRIVER_REQUESTED)) { | |
157 | ops->used++; | |
1a60d4c5 | 158 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
159 | ops->driver |= DRIVER_REQUESTED; |
160 | request_module("snd-%s", ops->id); | |
1a60d4c5 | 161 | mutex_lock(&ops_mutex); |
1da177e4 LT |
162 | ops->used--; |
163 | } | |
164 | } | |
1a60d4c5 | 165 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
166 | #endif |
167 | } | |
168 | ||
169 | /* | |
170 | * register a sequencer device | |
171 | * card = card info (NULL allowed) | |
172 | * device = device number (if any) | |
173 | * id = id of driver | |
174 | * result = return pointer (NULL allowed if unnecessary) | |
175 | */ | |
c7e0b5bf TI |
176 | int snd_seq_device_new(struct snd_card *card, int device, char *id, int argsize, |
177 | struct snd_seq_device **result) | |
1da177e4 | 178 | { |
c7e0b5bf TI |
179 | struct snd_seq_device *dev; |
180 | struct ops_list *ops; | |
1da177e4 | 181 | int err; |
c7e0b5bf | 182 | static struct snd_device_ops dops = { |
1da177e4 LT |
183 | .dev_free = snd_seq_device_dev_free, |
184 | .dev_register = snd_seq_device_dev_register, | |
185 | .dev_disconnect = snd_seq_device_dev_disconnect, | |
1da177e4 LT |
186 | }; |
187 | ||
188 | if (result) | |
189 | *result = NULL; | |
190 | ||
7eaa943c TI |
191 | if (snd_BUG_ON(!id)) |
192 | return -EINVAL; | |
1da177e4 LT |
193 | |
194 | ops = find_driver(id, 1); | |
195 | if (ops == NULL) | |
196 | return -ENOMEM; | |
197 | ||
ecca82b4 | 198 | dev = kzalloc(sizeof(*dev)*2 + argsize, GFP_KERNEL); |
1da177e4 LT |
199 | if (dev == NULL) { |
200 | unlock_driver(ops); | |
201 | return -ENOMEM; | |
202 | } | |
203 | ||
204 | /* set up device info */ | |
205 | dev->card = card; | |
206 | dev->device = device; | |
207 | strlcpy(dev->id, id, sizeof(dev->id)); | |
208 | dev->argsize = argsize; | |
209 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
210 | ||
211 | /* add this device to the list */ | |
1a60d4c5 | 212 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
213 | list_add_tail(&dev->list, &ops->dev_list); |
214 | ops->num_devices++; | |
1a60d4c5 | 215 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
216 | |
217 | unlock_driver(ops); | |
218 | ||
219 | if ((err = snd_device_new(card, SNDRV_DEV_SEQUENCER, dev, &dops)) < 0) { | |
220 | snd_seq_device_free(dev); | |
221 | return err; | |
222 | } | |
223 | ||
224 | if (result) | |
225 | *result = dev; | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | /* | |
231 | * free the existing device | |
232 | */ | |
c7e0b5bf | 233 | static int snd_seq_device_free(struct snd_seq_device *dev) |
1da177e4 | 234 | { |
c7e0b5bf | 235 | struct ops_list *ops; |
1da177e4 | 236 | |
7eaa943c TI |
237 | if (snd_BUG_ON(!dev)) |
238 | return -EINVAL; | |
1da177e4 LT |
239 | |
240 | ops = find_driver(dev->id, 0); | |
241 | if (ops == NULL) | |
242 | return -ENXIO; | |
243 | ||
244 | /* remove the device from the list */ | |
1a60d4c5 | 245 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
246 | list_del(&dev->list); |
247 | ops->num_devices--; | |
1a60d4c5 | 248 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
249 | |
250 | free_device(dev, ops); | |
251 | if (dev->private_free) | |
252 | dev->private_free(dev); | |
253 | kfree(dev); | |
254 | ||
255 | unlock_driver(ops); | |
256 | ||
257 | return 0; | |
258 | } | |
259 | ||
c7e0b5bf | 260 | static int snd_seq_device_dev_free(struct snd_device *device) |
1da177e4 | 261 | { |
c7e0b5bf | 262 | struct snd_seq_device *dev = device->device_data; |
1da177e4 LT |
263 | return snd_seq_device_free(dev); |
264 | } | |
265 | ||
266 | /* | |
267 | * register the device | |
268 | */ | |
c7e0b5bf | 269 | static int snd_seq_device_dev_register(struct snd_device *device) |
1da177e4 | 270 | { |
c7e0b5bf TI |
271 | struct snd_seq_device *dev = device->device_data; |
272 | struct ops_list *ops; | |
1da177e4 LT |
273 | |
274 | ops = find_driver(dev->id, 0); | |
275 | if (ops == NULL) | |
276 | return -ENOENT; | |
277 | ||
278 | /* initialize this device if the corresponding driver was | |
279 | * already loaded | |
280 | */ | |
281 | if (ops->driver & DRIVER_LOADED) | |
282 | init_device(dev, ops); | |
283 | ||
284 | unlock_driver(ops); | |
285 | return 0; | |
286 | } | |
287 | ||
288 | /* | |
289 | * disconnect the device | |
290 | */ | |
c7e0b5bf | 291 | static int snd_seq_device_dev_disconnect(struct snd_device *device) |
1da177e4 | 292 | { |
c7e0b5bf TI |
293 | struct snd_seq_device *dev = device->device_data; |
294 | struct ops_list *ops; | |
1da177e4 LT |
295 | |
296 | ops = find_driver(dev->id, 0); | |
297 | if (ops == NULL) | |
298 | return -ENOENT; | |
299 | ||
300 | free_device(dev, ops); | |
301 | ||
302 | unlock_driver(ops); | |
303 | return 0; | |
304 | } | |
305 | ||
1da177e4 LT |
306 | /* |
307 | * register device driver | |
308 | * id = driver id | |
309 | * entry = driver operators - duplicated to each instance | |
310 | */ | |
c7e0b5bf TI |
311 | int snd_seq_device_register_driver(char *id, struct snd_seq_dev_ops *entry, |
312 | int argsize) | |
1da177e4 | 313 | { |
c7e0b5bf | 314 | struct ops_list *ops; |
9244b2c3 | 315 | struct snd_seq_device *dev; |
1da177e4 LT |
316 | |
317 | if (id == NULL || entry == NULL || | |
318 | entry->init_device == NULL || entry->free_device == NULL) | |
319 | return -EINVAL; | |
320 | ||
321 | snd_seq_autoload_lock(); | |
322 | ops = find_driver(id, 1); | |
323 | if (ops == NULL) { | |
324 | snd_seq_autoload_unlock(); | |
325 | return -ENOMEM; | |
326 | } | |
327 | if (ops->driver & DRIVER_LOADED) { | |
328 | snd_printk(KERN_WARNING "driver_register: driver '%s' already exists\n", id); | |
329 | unlock_driver(ops); | |
330 | snd_seq_autoload_unlock(); | |
331 | return -EBUSY; | |
332 | } | |
333 | ||
1a60d4c5 | 334 | mutex_lock(&ops->reg_mutex); |
1da177e4 LT |
335 | /* copy driver operators */ |
336 | ops->ops = *entry; | |
337 | ops->driver |= DRIVER_LOADED; | |
338 | ops->argsize = argsize; | |
339 | ||
340 | /* initialize existing devices if necessary */ | |
9244b2c3 | 341 | list_for_each_entry(dev, &ops->dev_list, list) { |
1da177e4 LT |
342 | init_device(dev, ops); |
343 | } | |
1a60d4c5 | 344 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
345 | |
346 | unlock_driver(ops); | |
347 | snd_seq_autoload_unlock(); | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
352 | ||
353 | /* | |
354 | * create driver record | |
355 | */ | |
c7e0b5bf | 356 | static struct ops_list * create_driver(char *id) |
1da177e4 | 357 | { |
c7e0b5bf | 358 | struct ops_list *ops; |
1da177e4 | 359 | |
59feddb2 | 360 | ops = kzalloc(sizeof(*ops), GFP_KERNEL); |
1da177e4 LT |
361 | if (ops == NULL) |
362 | return ops; | |
1da177e4 LT |
363 | |
364 | /* set up driver entry */ | |
365 | strlcpy(ops->id, id, sizeof(ops->id)); | |
1a60d4c5 | 366 | mutex_init(&ops->reg_mutex); |
933a2efc AV |
367 | /* |
368 | * The ->reg_mutex locking rules are per-driver, so we create | |
369 | * separate per-driver lock classes: | |
370 | */ | |
371 | lockdep_set_class(&ops->reg_mutex, (struct lock_class_key *)id); | |
372 | ||
1da177e4 LT |
373 | ops->driver = DRIVER_EMPTY; |
374 | INIT_LIST_HEAD(&ops->dev_list); | |
375 | /* lock this instance */ | |
376 | ops->used = 1; | |
377 | ||
378 | /* register driver entry */ | |
1a60d4c5 | 379 | mutex_lock(&ops_mutex); |
1da177e4 LT |
380 | list_add_tail(&ops->list, &opslist); |
381 | num_ops++; | |
1a60d4c5 | 382 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
383 | |
384 | return ops; | |
385 | } | |
386 | ||
387 | ||
388 | /* | |
389 | * unregister the specified driver | |
390 | */ | |
391 | int snd_seq_device_unregister_driver(char *id) | |
392 | { | |
c7e0b5bf | 393 | struct ops_list *ops; |
9244b2c3 | 394 | struct snd_seq_device *dev; |
1da177e4 LT |
395 | |
396 | ops = find_driver(id, 0); | |
397 | if (ops == NULL) | |
398 | return -ENXIO; | |
399 | if (! (ops->driver & DRIVER_LOADED) || | |
400 | (ops->driver & DRIVER_LOCKED)) { | |
c7e0b5bf TI |
401 | snd_printk(KERN_ERR "driver_unregister: cannot unload driver '%s': status=%x\n", |
402 | id, ops->driver); | |
1da177e4 LT |
403 | unlock_driver(ops); |
404 | return -EBUSY; | |
405 | } | |
406 | ||
407 | /* close and release all devices associated with this driver */ | |
1a60d4c5 | 408 | mutex_lock(&ops->reg_mutex); |
1da177e4 | 409 | ops->driver |= DRIVER_LOCKED; /* do not remove this driver recursively */ |
9244b2c3 | 410 | list_for_each_entry(dev, &ops->dev_list, list) { |
1da177e4 LT |
411 | free_device(dev, ops); |
412 | } | |
413 | ||
414 | ops->driver = 0; | |
415 | if (ops->num_init_devices > 0) | |
c7e0b5bf TI |
416 | snd_printk(KERN_ERR "free_driver: init_devices > 0!! (%d)\n", |
417 | ops->num_init_devices); | |
1a60d4c5 | 418 | mutex_unlock(&ops->reg_mutex); |
1da177e4 LT |
419 | |
420 | unlock_driver(ops); | |
421 | ||
422 | /* remove empty driver entries */ | |
423 | remove_drivers(); | |
424 | ||
425 | return 0; | |
426 | } | |
427 | ||
428 | ||
429 | /* | |
430 | * remove empty driver entries | |
431 | */ | |
432 | static void remove_drivers(void) | |
433 | { | |
434 | struct list_head *head; | |
435 | ||
1a60d4c5 | 436 | mutex_lock(&ops_mutex); |
1da177e4 LT |
437 | head = opslist.next; |
438 | while (head != &opslist) { | |
c7e0b5bf | 439 | struct ops_list *ops = list_entry(head, struct ops_list, list); |
1da177e4 LT |
440 | if (! (ops->driver & DRIVER_LOADED) && |
441 | ops->used == 0 && ops->num_devices == 0) { | |
442 | head = head->next; | |
443 | list_del(&ops->list); | |
444 | kfree(ops); | |
445 | num_ops--; | |
446 | } else | |
447 | head = head->next; | |
448 | } | |
1a60d4c5 | 449 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
450 | } |
451 | ||
452 | /* | |
453 | * initialize the device - call init_device operator | |
454 | */ | |
c7e0b5bf | 455 | static int init_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
456 | { |
457 | if (! (ops->driver & DRIVER_LOADED)) | |
458 | return 0; /* driver is not loaded yet */ | |
459 | if (dev->status != SNDRV_SEQ_DEVICE_FREE) | |
460 | return 0; /* already initialized */ | |
461 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
462 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
463 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
464 | return -EINVAL; |
465 | } | |
466 | if (ops->ops.init_device(dev) >= 0) { | |
467 | dev->status = SNDRV_SEQ_DEVICE_REGISTERED; | |
468 | ops->num_init_devices++; | |
469 | } else { | |
c7e0b5bf TI |
470 | snd_printk(KERN_ERR "init_device failed: %s: %s\n", |
471 | dev->name, dev->id); | |
1da177e4 LT |
472 | } |
473 | ||
474 | return 0; | |
475 | } | |
476 | ||
477 | /* | |
478 | * release the device - call free_device operator | |
479 | */ | |
c7e0b5bf | 480 | static int free_device(struct snd_seq_device *dev, struct ops_list *ops) |
1da177e4 LT |
481 | { |
482 | int result; | |
483 | ||
484 | if (! (ops->driver & DRIVER_LOADED)) | |
485 | return 0; /* driver is not loaded yet */ | |
486 | if (dev->status != SNDRV_SEQ_DEVICE_REGISTERED) | |
487 | return 0; /* not registered */ | |
488 | if (ops->argsize != dev->argsize) { | |
c7e0b5bf TI |
489 | snd_printk(KERN_ERR "incompatible device '%s' for plug-in '%s' (%d %d)\n", |
490 | dev->name, ops->id, ops->argsize, dev->argsize); | |
1da177e4 LT |
491 | return -EINVAL; |
492 | } | |
493 | if ((result = ops->ops.free_device(dev)) >= 0 || result == -ENXIO) { | |
494 | dev->status = SNDRV_SEQ_DEVICE_FREE; | |
495 | dev->driver_data = NULL; | |
496 | ops->num_init_devices--; | |
497 | } else { | |
c7e0b5bf TI |
498 | snd_printk(KERN_ERR "free_device failed: %s: %s\n", |
499 | dev->name, dev->id); | |
1da177e4 LT |
500 | } |
501 | ||
502 | return 0; | |
503 | } | |
504 | ||
505 | /* | |
506 | * find the matching driver with given id | |
507 | */ | |
c7e0b5bf | 508 | static struct ops_list * find_driver(char *id, int create_if_empty) |
1da177e4 | 509 | { |
9244b2c3 | 510 | struct ops_list *ops; |
1da177e4 | 511 | |
1a60d4c5 | 512 | mutex_lock(&ops_mutex); |
9244b2c3 | 513 | list_for_each_entry(ops, &opslist, list) { |
1da177e4 LT |
514 | if (strcmp(ops->id, id) == 0) { |
515 | ops->used++; | |
1a60d4c5 | 516 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
517 | return ops; |
518 | } | |
519 | } | |
1a60d4c5 | 520 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
521 | if (create_if_empty) |
522 | return create_driver(id); | |
523 | return NULL; | |
524 | } | |
525 | ||
c7e0b5bf | 526 | static void unlock_driver(struct ops_list *ops) |
1da177e4 | 527 | { |
1a60d4c5 | 528 | mutex_lock(&ops_mutex); |
1da177e4 | 529 | ops->used--; |
1a60d4c5 | 530 | mutex_unlock(&ops_mutex); |
1da177e4 LT |
531 | } |
532 | ||
533 | ||
534 | /* | |
535 | * module part | |
536 | */ | |
537 | ||
538 | static int __init alsa_seq_device_init(void) | |
539 | { | |
04f141a8 | 540 | #ifdef CONFIG_PROC_FS |
c7e0b5bf TI |
541 | info_entry = snd_info_create_module_entry(THIS_MODULE, "drivers", |
542 | snd_seq_root); | |
1da177e4 LT |
543 | if (info_entry == NULL) |
544 | return -ENOMEM; | |
545 | info_entry->content = SNDRV_INFO_CONTENT_TEXT; | |
1da177e4 LT |
546 | info_entry->c.text.read = snd_seq_device_info; |
547 | if (snd_info_register(info_entry) < 0) { | |
548 | snd_info_free_entry(info_entry); | |
549 | return -ENOMEM; | |
550 | } | |
04f141a8 | 551 | #endif |
1da177e4 LT |
552 | return 0; |
553 | } | |
554 | ||
555 | static void __exit alsa_seq_device_exit(void) | |
556 | { | |
557 | remove_drivers(); | |
04f141a8 | 558 | #ifdef CONFIG_PROC_FS |
746d4a02 | 559 | snd_info_free_entry(info_entry); |
04f141a8 | 560 | #endif |
1da177e4 LT |
561 | if (num_ops) |
562 | snd_printk(KERN_ERR "drivers not released (%d)\n", num_ops); | |
563 | } | |
564 | ||
565 | module_init(alsa_seq_device_init) | |
566 | module_exit(alsa_seq_device_exit) | |
567 | ||
568 | EXPORT_SYMBOL(snd_seq_device_load_drivers); | |
569 | EXPORT_SYMBOL(snd_seq_device_new); | |
570 | EXPORT_SYMBOL(snd_seq_device_register_driver); | |
571 | EXPORT_SYMBOL(snd_seq_device_unregister_driver); | |
8dea9d38 | 572 | #ifdef CONFIG_MODULES |
1da177e4 LT |
573 | EXPORT_SYMBOL(snd_seq_autoload_lock); |
574 | EXPORT_SYMBOL(snd_seq_autoload_unlock); | |
8dea9d38 | 575 | #endif |