1 /**
2 * \file control/control_hw.c
3 * \brief CTL HW Plugin Interface
4 * \author Jaroslav Kysela <perex@perex.cz>
5 * \date 2000
6 */
7 /*
8 * Control Interface - Hardware
9 * Copyright (c) 1998,1999,2000 by Jaroslav Kysela <perex@perex.cz>
10 * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
11 *
12 *
13 * This library is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU Lesser General Public License as
15 * published by the Free Software Foundation; either version 2.1 of
16 * the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26 *
27 */
28
29 #include "control_local.h"
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <signal.h>
34 #include <string.h>
35 #include <fcntl.h>
36 #include <sys/ioctl.h>
37
38 #ifndef PIC
39 /* entry for static linking */
40 const char *_snd_module_control_hw = "";
41 #endif
42
43 #ifndef DOC_HIDDEN
44
45 #ifndef F_SETSIG
46 #define F_SETSIG 10
47 #endif
48
49 #define SNDRV_FILE_CONTROL ALSA_DEVICE_DIRECTORY "controlC%i"
50 #define SNDRV_CTL_VERSION_MAX SNDRV_PROTOCOL_VERSION(2, 0, 4)
51
52 typedef struct {
53 int card;
54 int fd;
55 unsigned int protocol;
56 } snd_ctl_hw_t;
57 #endif /* DOC_HIDDEN */
58
snd_ctl_hw_close(snd_ctl_t *handle)59 static int snd_ctl_hw_close(snd_ctl_t *handle)
60 {
61 snd_ctl_hw_t *hw = handle->private_data;
62 int res;
63 res = close(hw->fd) < 0 ? -errno : 0;
64 free(hw);
65 return res;
66 }
67
snd_ctl_hw_nonblock(snd_ctl_t *handle, int nonblock)68 static int snd_ctl_hw_nonblock(snd_ctl_t *handle, int nonblock)
69 {
70 snd_ctl_hw_t *hw = handle->private_data;
71 long flags;
72 int fd = hw->fd;
73 if ((flags = fcntl(fd, F_GETFL)) < 0) {
74 SYSERR("F_GETFL failed");
75 return -errno;
76 }
77 if (nonblock)
78 flags |= O_NONBLOCK;
79 else
80 flags &= ~O_NONBLOCK;
81 if (fcntl(fd, F_SETFL, flags) < 0) {
82 SYSERR("F_SETFL for O_NONBLOCK failed");
83 return -errno;
84 }
85 return 0;
86 }
87
snd_ctl_hw_async(snd_ctl_t *ctl, int sig, pid_t pid)88 static int snd_ctl_hw_async(snd_ctl_t *ctl, int sig, pid_t pid)
89 {
90 long flags;
91 snd_ctl_hw_t *hw = ctl->private_data;
92 int fd = hw->fd;
93
94 if ((flags = fcntl(fd, F_GETFL)) < 0) {
95 SYSERR("F_GETFL failed");
96 return -errno;
97 }
98 if (sig >= 0)
99 flags |= O_ASYNC;
100 else
101 flags &= ~O_ASYNC;
102 if (fcntl(fd, F_SETFL, flags) < 0) {
103 SYSERR("F_SETFL for O_ASYNC failed");
104 return -errno;
105 }
106 if (sig < 0)
107 return 0;
108 if (fcntl(fd, F_SETSIG, (long)sig) < 0) {
109 SYSERR("F_SETSIG failed");
110 return -errno;
111 }
112 if (fcntl(fd, F_SETOWN, (long)pid) < 0) {
113 SYSERR("F_SETOWN failed");
114 return -errno;
115 }
116 return 0;
117 }
118
snd_ctl_hw_subscribe_events(snd_ctl_t *handle, int subscribe)119 static int snd_ctl_hw_subscribe_events(snd_ctl_t *handle, int subscribe)
120 {
121 snd_ctl_hw_t *hw = handle->private_data;
122 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
123 SYSERR("SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS failed");
124 return -errno;
125 }
126 return 0;
127 }
128
snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)129 static int snd_ctl_hw_card_info(snd_ctl_t *handle, snd_ctl_card_info_t *info)
130 {
131 snd_ctl_hw_t *hw = handle->private_data;
132 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_CARD_INFO, info) < 0) {
133 SYSERR("SNDRV_CTL_IOCTL_CARD_INFO failed");
134 return -errno;
135 }
136 return 0;
137 }
138
snd_ctl_hw_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)139 static int snd_ctl_hw_elem_list(snd_ctl_t *handle, snd_ctl_elem_list_t *list)
140 {
141 snd_ctl_hw_t *hw = handle->private_data;
142 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_LIST, list) < 0)
143 return -errno;
144 return 0;
145 }
146
snd_ctl_hw_elem_info(snd_ctl_t *handle, snd_ctl_elem_info_t *info)147 static int snd_ctl_hw_elem_info(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
148 {
149 snd_ctl_hw_t *hw = handle->private_data;
150 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_INFO, info) < 0)
151 return -errno;
152 return 0;
153 }
154
snd_ctl_hw_elem_add(snd_ctl_t *handle, snd_ctl_elem_info_t *info)155 static int snd_ctl_hw_elem_add(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
156 {
157 snd_ctl_hw_t *hw = handle->private_data;
158
159 if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
160 hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 7))
161 return -ENXIO;
162
163 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_ADD, info) < 0)
164 return -errno;
165 return 0;
166 }
167
snd_ctl_hw_elem_replace(snd_ctl_t *handle, snd_ctl_elem_info_t *info)168 static int snd_ctl_hw_elem_replace(snd_ctl_t *handle, snd_ctl_elem_info_t *info)
169 {
170 snd_ctl_hw_t *hw = handle->private_data;
171
172 if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED &&
173 hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 7))
174 return -ENXIO;
175
176 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_REPLACE, info) < 0)
177 return -errno;
178 return 0;
179 }
180
snd_ctl_hw_elem_remove(snd_ctl_t *handle, snd_ctl_elem_id_t *id)181 static int snd_ctl_hw_elem_remove(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
182 {
183 snd_ctl_hw_t *hw = handle->private_data;
184 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_REMOVE, id) < 0)
185 return -errno;
186 return 0;
187 }
188
snd_ctl_hw_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)189 static int snd_ctl_hw_elem_read(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
190 {
191 snd_ctl_hw_t *hw = handle->private_data;
192 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_READ, control) < 0)
193 return -errno;
194 return 0;
195 }
196
snd_ctl_hw_elem_write(snd_ctl_t *handle, snd_ctl_elem_value_t *control)197 static int snd_ctl_hw_elem_write(snd_ctl_t *handle, snd_ctl_elem_value_t *control)
198 {
199 snd_ctl_hw_t *hw = handle->private_data;
200 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, control) < 0)
201 return -errno;
202 return 0;
203 }
204
snd_ctl_hw_elem_lock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)205 static int snd_ctl_hw_elem_lock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
206 {
207 snd_ctl_hw_t *hw = handle->private_data;
208 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_LOCK, id) < 0)
209 return -errno;
210 return 0;
211 }
212
snd_ctl_hw_elem_unlock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)213 static int snd_ctl_hw_elem_unlock(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
214 {
215 snd_ctl_hw_t *hw = handle->private_data;
216 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_ELEM_UNLOCK, id) < 0)
217 return -errno;
218 return 0;
219 }
220
snd_ctl_hw_elem_tlv(snd_ctl_t *handle, int op_flag, unsigned int numid, unsigned int *tlv, unsigned int tlv_size)221 static int snd_ctl_hw_elem_tlv(snd_ctl_t *handle, int op_flag,
222 unsigned int numid,
223 unsigned int *tlv, unsigned int tlv_size)
224 {
225 unsigned int inum;
226 snd_ctl_hw_t *hw = handle->private_data;
227 struct snd_ctl_tlv *xtlv;
228
229 /* we don't support TLV on protocol ver 2.0.3 or earlier */
230 if (hw->protocol < SNDRV_PROTOCOL_VERSION(2, 0, 4))
231 return -ENXIO;
232
233 switch (op_flag) {
234 case -1: inum = SNDRV_CTL_IOCTL_TLV_COMMAND; break;
235 case 0: inum = SNDRV_CTL_IOCTL_TLV_READ; break;
236 case 1: inum = SNDRV_CTL_IOCTL_TLV_WRITE; break;
237 default: return -EINVAL;
238 }
239 xtlv = malloc(sizeof(struct snd_ctl_tlv) + tlv_size);
240 if (xtlv == NULL)
241 return -ENOMEM;
242 xtlv->numid = numid;
243 xtlv->length = tlv_size;
244 memcpy(xtlv->tlv, tlv, tlv_size);
245 if (ioctl(hw->fd, inum, xtlv) < 0) {
246 free(xtlv);
247 return -errno;
248 }
249 if (op_flag == 0) {
250 unsigned int size;
251 size = xtlv->tlv[SNDRV_CTL_TLVO_LEN] + 2 * sizeof(unsigned int);
252 if (size > tlv_size) {
253 free(xtlv);
254 return -EFAULT;
255 }
256 memcpy(tlv, xtlv->tlv, size);
257 }
258 free(xtlv);
259 return 0;
260 }
261
snd_ctl_hw_hwdep_next_device(snd_ctl_t *handle, int * device)262 static int snd_ctl_hw_hwdep_next_device(snd_ctl_t *handle, int * device)
263 {
264 snd_ctl_hw_t *hw = handle->private_data;
265 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE, device) < 0)
266 return -errno;
267 return 0;
268 }
269
snd_ctl_hw_hwdep_info(snd_ctl_t *handle, snd_hwdep_info_t * info)270 static int snd_ctl_hw_hwdep_info(snd_ctl_t *handle, snd_hwdep_info_t * info)
271 {
272 snd_ctl_hw_t *hw = handle->private_data;
273 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_HWDEP_INFO, info) < 0)
274 return -errno;
275 return 0;
276 }
277
snd_ctl_hw_pcm_next_device(snd_ctl_t *handle, int * device)278 static int snd_ctl_hw_pcm_next_device(snd_ctl_t *handle, int * device)
279 {
280 snd_ctl_hw_t *hw = handle->private_data;
281 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE, device) < 0)
282 return -errno;
283 return 0;
284 }
285
snd_ctl_hw_pcm_info(snd_ctl_t *handle, snd_pcm_info_t * info)286 static int snd_ctl_hw_pcm_info(snd_ctl_t *handle, snd_pcm_info_t * info)
287 {
288 snd_ctl_hw_t *hw = handle->private_data;
289 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_INFO, info) < 0)
290 return -errno;
291 /* may be configurable (optional) */
292 if (__snd_pcm_info_eld_fixup_check(info))
293 return __snd_pcm_info_eld_fixup(info);
294 return 0;
295 }
296
snd_ctl_hw_pcm_prefer_subdevice(snd_ctl_t *handle, int subdev)297 static int snd_ctl_hw_pcm_prefer_subdevice(snd_ctl_t *handle, int subdev)
298 {
299 snd_ctl_hw_t *hw = handle->private_data;
300 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE, &subdev) < 0)
301 return -errno;
302 return 0;
303 }
304
snd_ctl_hw_rawmidi_next_device(snd_ctl_t *handle, int * device)305 static int snd_ctl_hw_rawmidi_next_device(snd_ctl_t *handle, int * device)
306 {
307 snd_ctl_hw_t *hw = handle->private_data;
308 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE, device) < 0)
309 return -errno;
310 return 0;
311 }
312
snd_ctl_hw_rawmidi_info(snd_ctl_t *handle, snd_rawmidi_info_t * info)313 static int snd_ctl_hw_rawmidi_info(snd_ctl_t *handle, snd_rawmidi_info_t * info)
314 {
315 snd_ctl_hw_t *hw = handle->private_data;
316 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, info) < 0)
317 return -errno;
318 return 0;
319 }
320
snd_ctl_hw_rawmidi_prefer_subdevice(snd_ctl_t *handle, int subdev)321 static int snd_ctl_hw_rawmidi_prefer_subdevice(snd_ctl_t *handle, int subdev)
322 {
323 snd_ctl_hw_t *hw = handle->private_data;
324 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE, &subdev) < 0)
325 return -errno;
326 return 0;
327 }
328
snd_ctl_hw_ump_next_device(snd_ctl_t *handle, int *device)329 static int snd_ctl_hw_ump_next_device(snd_ctl_t *handle, int *device)
330 {
331 snd_ctl_hw_t *hw = handle->private_data;
332 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE, device) < 0)
333 return -errno;
334 return 0;
335 }
336
snd_ctl_hw_ump_endpoint_info(snd_ctl_t *handle, snd_ump_endpoint_info_t *info)337 static int snd_ctl_hw_ump_endpoint_info(snd_ctl_t *handle,
338 snd_ump_endpoint_info_t *info)
339 {
340 snd_ctl_hw_t *hw = handle->private_data;
341 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO, info) < 0)
342 return -errno;
343 return 0;
344 }
345
snd_ctl_hw_ump_block_info(snd_ctl_t *handle, snd_ump_block_info_t *info)346 static int snd_ctl_hw_ump_block_info(snd_ctl_t *handle,
347 snd_ump_block_info_t *info)
348 {
349 snd_ctl_hw_t *hw = handle->private_data;
350 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_UMP_BLOCK_INFO, info) < 0)
351 return -errno;
352 return 0;
353 }
354
snd_ctl_hw_set_power_state(snd_ctl_t *handle, unsigned int state)355 static int snd_ctl_hw_set_power_state(snd_ctl_t *handle, unsigned int state)
356 {
357 snd_ctl_hw_t *hw = handle->private_data;
358 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_POWER, &state) < 0)
359 return -errno;
360 return 0;
361 }
362
snd_ctl_hw_get_power_state(snd_ctl_t *handle, unsigned int *state)363 static int snd_ctl_hw_get_power_state(snd_ctl_t *handle, unsigned int *state)
364 {
365 snd_ctl_hw_t *hw = handle->private_data;
366 if (ioctl(hw->fd, SNDRV_CTL_IOCTL_POWER_STATE, state) < 0)
367 return -errno;
368 return 0;
369 }
370
snd_ctl_hw_read(snd_ctl_t *handle, snd_ctl_event_t *event)371 static int snd_ctl_hw_read(snd_ctl_t *handle, snd_ctl_event_t *event)
372 {
373 snd_ctl_hw_t *hw = handle->private_data;
374 ssize_t res = read(hw->fd, event, sizeof(*event));
375 if (res <= 0)
376 return -errno;
377 if (CHECK_SANITY(res != sizeof(*event))) {
378 SNDMSG("snd_ctl_hw_read: read size error (req:%d, got:%d)",
379 sizeof(*event), res);
380 return -EINVAL;
381 }
382 return 1;
383 }
384
385 static const snd_ctl_ops_t snd_ctl_hw_ops = {
386 .close = snd_ctl_hw_close,
387 .nonblock = snd_ctl_hw_nonblock,
388 .async = snd_ctl_hw_async,
389 .subscribe_events = snd_ctl_hw_subscribe_events,
390 .card_info = snd_ctl_hw_card_info,
391 .element_list = snd_ctl_hw_elem_list,
392 .element_info = snd_ctl_hw_elem_info,
393 .element_add = snd_ctl_hw_elem_add,
394 .element_replace = snd_ctl_hw_elem_replace,
395 .element_remove = snd_ctl_hw_elem_remove,
396 .element_read = snd_ctl_hw_elem_read,
397 .element_write = snd_ctl_hw_elem_write,
398 .element_lock = snd_ctl_hw_elem_lock,
399 .element_unlock = snd_ctl_hw_elem_unlock,
400 .element_tlv = snd_ctl_hw_elem_tlv,
401 .hwdep_next_device = snd_ctl_hw_hwdep_next_device,
402 .hwdep_info = snd_ctl_hw_hwdep_info,
403 .pcm_next_device = snd_ctl_hw_pcm_next_device,
404 .pcm_info = snd_ctl_hw_pcm_info,
405 .pcm_prefer_subdevice = snd_ctl_hw_pcm_prefer_subdevice,
406 .rawmidi_next_device = snd_ctl_hw_rawmidi_next_device,
407 .rawmidi_info = snd_ctl_hw_rawmidi_info,
408 .rawmidi_prefer_subdevice = snd_ctl_hw_rawmidi_prefer_subdevice,
409 .ump_next_device = snd_ctl_hw_ump_next_device,
410 .ump_endpoint_info = snd_ctl_hw_ump_endpoint_info,
411 .ump_block_info = snd_ctl_hw_ump_block_info,
412 .set_power_state = snd_ctl_hw_set_power_state,
413 .get_power_state = snd_ctl_hw_get_power_state,
414 .read = snd_ctl_hw_read,
415 };
416
417 /**
418 * \brief Creates a new hw control
419 * \param handle Returns created control handle
420 * \param name Name of control device
421 * \param card Number of card
422 * \param mode Control mode
423 * \retval zero on success otherwise a negative error code
424 * \warning Using of this function might be dangerous in the sense
425 * of compatibility reasons. The prototype might be freely
426 * changed in future.
427 */
snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)428 int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
429 {
430 int fd, ver;
431 char filename[sizeof(SNDRV_FILE_CONTROL) + 10];
432 int fmode;
433 snd_ctl_t *ctl;
434 snd_ctl_hw_t *hw;
435 int err;
436
437 *handle = NULL;
438
439 if (CHECK_SANITY(card < 0 || card >= SND_MAX_CARDS)) {
440 SNDMSG("Invalid card index %d", card);
441 return -EINVAL;
442 }
443 sprintf(filename, SNDRV_FILE_CONTROL, card);
444 if (mode & SND_CTL_READONLY)
445 fmode = O_RDONLY;
446 else
447 fmode = O_RDWR;
448 if (mode & SND_CTL_NONBLOCK)
449 fmode |= O_NONBLOCK;
450 if (mode & SND_CTL_ASYNC)
451 fmode |= O_ASYNC;
452 fd = snd_open_device(filename, fmode);
453 if (fd < 0) {
454 snd_card_load(card);
455 fd = snd_open_device(filename, fmode);
456 if (fd < 0)
457 return -errno;
458 }
459 if (ioctl(fd, SNDRV_CTL_IOCTL_PVERSION, &ver) < 0) {
460 err = -errno;
461 close(fd);
462 return err;
463 }
464 if (SNDRV_PROTOCOL_INCOMPATIBLE(ver, SNDRV_CTL_VERSION_MAX)) {
465 close(fd);
466 return -SND_ERROR_INCOMPATIBLE_VERSION;
467 }
468 hw = calloc(1, sizeof(snd_ctl_hw_t));
469 if (hw == NULL) {
470 close(fd);
471 return -ENOMEM;
472 }
473 hw->card = card;
474 hw->fd = fd;
475 hw->protocol = ver;
476
477 err = snd_ctl_new(&ctl, SND_CTL_TYPE_HW, name, mode);
478 if (err < 0) {
479 close(fd);
480 free(hw);
481 return err;
482 }
483 ctl->ops = &snd_ctl_hw_ops;
484 ctl->private_data = hw;
485 ctl->poll_fd = fd;
486 *handle = ctl;
487 return 0;
488 }
489
490 /*! \page control_plugins
491
492 \section control_plugins_hw Plugin: hw
493
494 This plugin communicates directly with the ALSA kernel driver. It is a raw
495 communication without any conversions.
496
497 \code
498 control.name {
499 type hw # Kernel PCM
500 card INT/STR # Card name (string) or number (integer)
501 }
502 \endcode
503
504 \subsection control_plugins_hw_funcref Function reference
505
506 <UL>
507 <LI>snd_ctl_hw_open()
508 <LI>_snd_ctl_hw_open()
509 </UL>
510
511 */
512
513 /**
514 * \brief Creates a new hw control handle
515 * \param handlep Returns created control handle
516 * \param name Name of control device
517 * \param root Root configuration node
518 * \param conf Configuration node with hw PCM description
519 * \param mode Control Mode
520 * \warning Using of this function might be dangerous in the sense
521 * of compatibility reasons. The prototype might be freely
522 * changed in future.
523 */
_snd_ctl_hw_open(snd_ctl_t **handlep, char *name, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, int mode)524 int _snd_ctl_hw_open(snd_ctl_t **handlep, char *name, snd_config_t *root ATTRIBUTE_UNUSED, snd_config_t *conf, int mode)
525 {
526 snd_config_iterator_t i, next;
527 long card = -1;
528 int err;
529 snd_config_for_each(i, next, conf) {
530 snd_config_t *n = snd_config_iterator_entry(i);
531 const char *id;
532 if (snd_config_get_id(n, &id) < 0)
533 continue;
534 if (_snd_conf_generic_id(id))
535 continue;
536 if (strcmp(id, "card") == 0) {
537 err = snd_config_get_card(n);
538 if (err < 0)
539 return err;
540 card = err;
541 continue;
542 }
543 return -EINVAL;
544 }
545 if (card < 0)
546 return -EINVAL;
547 return snd_ctl_hw_open(handlep, name, card, mode);
548 }
549 #ifndef DOC_HIDDEN
550 SND_DLSYM_BUILD_VERSION(_snd_ctl_hw_open, SND_CONTROL_DLSYM_VERSION);
551 #endif
552