resetting manifest requested domain to floor
[platform/upstream/alsa-plugins.git] / maemo / dsp-ctl.c
1 /**
2  * @file dsp-ctl.c
3  * @brief CTL External plugin implementation.
4  * <p>
5  * Copyright (C) 2006 Nokia Corporation
6  * <p>
7  * Contact: Eduardo Bezerra Valentin <eduardo.valentin@indt.org.br>
8  * 
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  * */
24 #include <stdio.h>
25 #include <sys/ioctl.h>
26 #include <alsa/asoundlib.h>
27 #include <alsa/control_external.h>
28 #include <string.h>
29 #include "list.h"
30 #include "debug.h"
31 #include "dsp-protocol.h"
32 #include "constants.h"
33
34 #define PLAYBACK_VOLUME_CONTROL_NAME    "PCM Playback Volume"
35 #define PLAYBACK_MUTE_CONTROL_NAME      "PCM Playback Switch"
36 #define RECORDING_CONTROL_NAME          "Capture Switch"
37
38 /**
39  * data structure to represent a dsp task device node.
40  */
41 typedef struct {
42         dsp_protocol_t *dsp_protocol;
43         char *name;
44         int channels;
45         struct list_head list;
46 } control_list_t;
47
48 /**
49  * data structure to represent this plugin information.
50  */ 
51 typedef struct snd_ctl_dsp {
52         snd_ctl_ext_t ext;
53         int num_playbacks;
54         int num_recordings;
55         control_list_t **controls;
56         control_list_t playback_devices;
57         control_list_t recording_devices;
58 } snd_ctl_dsp_t;
59
60 static snd_ctl_dsp_t *free_ref;
61 /**
62  * @param control_list control list to be freed.
63  *
64  * It passes through this control list and frees
65  * all its nodes.
66  * 
67  * @return zero. success.
68  */ 
69 static int free_control_list(control_list_t * control_list)
70 {
71         struct list_head *pos, *q;
72         control_list_t *tmp;
73         list_for_each_safe(pos, q, &control_list->list) {
74                 tmp = list_entry(pos, control_list_t, list);
75                 list_del(pos);
76                 free(tmp->name);
77                 close(tmp->dsp_protocol->fd);
78                 dsp_protocol_destroy(&(tmp->dsp_protocol));
79                 free(tmp);
80         }
81         return 0;
82 }
83
84 /**
85  * @param ext snd_ctl_ext_t structure.
86  *
87  * It is the close event handler for this plugin.
88  * It frees all the allocated memory.
89  * 
90  * @return zero. success.
91  */ 
92 static void dsp_ctl_close(snd_ctl_ext_t * ext)
93 {
94         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
95         DENTER();
96         free(dsp_ctl->controls);
97         free_control_list(&(dsp_ctl->playback_devices));
98         free_control_list(&(dsp_ctl->recording_devices));
99 //      free(dsp_ctl);
100         DLEAVE(0);
101 }
102
103 /**
104  * @param ext snd_ctl_ext_t structure.
105  *
106  * It returns number of controls to be used by this
107  * plugin. It is based on number of recording and playback
108  * device nodes configured to be handled by this plugin.
109  * 
110  * @return number of controls.
111  */ 
112 static int dsp_ctl_elem_count(snd_ctl_ext_t * ext)
113 {
114         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
115         int ret = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings;
116         DENTER();
117         DLEAVE(ret);
118         return ret;
119 }
120
121 /**
122  * @param ext snd_ctl_ext_t structure.
123  * @param offset offset of current control.
124  * @param id id of current control element.
125  *
126  * It sets name and index for a control based
127  * of its offset.
128  * 
129  * @return zero. success.
130  */ 
131 static int dsp_ctl_elem_list(snd_ctl_ext_t * ext, unsigned int offset,
132                              snd_ctl_elem_id_t * id)
133 {
134         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
135         int ret = 0;
136
137         DENTER();
138         snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
139         if (offset < 2 * dsp_ctl->num_playbacks) {
140                 if (offset % 2 == 1)
141                         snd_ctl_elem_id_set_name(id,
142                                                  PLAYBACK_MUTE_CONTROL_NAME);
143                 else
144                         snd_ctl_elem_id_set_name(id,
145                                                  PLAYBACK_VOLUME_CONTROL_NAME);
146                 offset /= 2;
147         } else {
148                 offset -= 2 * dsp_ctl->num_playbacks;
149                 snd_ctl_elem_id_set_name(id, RECORDING_CONTROL_NAME);
150         }
151         snd_ctl_elem_id_set_index(id, offset);
152         DLEAVE(ret);
153         return ret;
154 }
155
156 /**
157  * @param ext snd_ctl_ext_t structure.
158  * @param id control element id from alsa-lib
159  *
160  * It searches for an control element using 
161  * its name and index. If this control can
162  * be found, it returns a key to represent it
163  * for future use. This key is an index of an
164  * array of controls whose is composed of three
165  * types of elements: PCM Volume, PCM Switch and
166  * Capture Switch. This array is organized as 
167  * follows:
168  * pv0, ps0, pv1, ps1, ..., pv_n, ps_n, cs0, cs1, ..., cs_m
169  *
170  * where, pvi is the i-th PCM Volume Control
171  *        psi is the i-th PCM Switch Control
172  *        csi is the i-th Capture Switch Control
173  *        n - is the number of Playback devices
174  *        m - is the number of Recording devices
175  *
176  * @return if control is not found, returns SND_CTL_EXT_KEY_NOT_FOUND.
177  * Otherwise, returns a key of control.
178  */
179 static snd_ctl_ext_key_t dsp_ctl_find_elem(snd_ctl_ext_t * ext,
180                                            const snd_ctl_elem_id_t * id)
181 {
182         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
183         snd_ctl_ext_key_t ret = SND_CTL_EXT_KEY_NOT_FOUND;
184         int idx;
185         const char *name;
186
187         DENTER();
188         idx = snd_ctl_elem_id_get_index(id);
189         name = snd_ctl_elem_id_get_name(id);
190         if (strcmp(PLAYBACK_VOLUME_CONTROL_NAME, name) == 0)
191                 ret = idx * 2;
192         else if (strcmp(PLAYBACK_MUTE_CONTROL_NAME, name) == 0)
193                 ret = idx * 2 + 1;
194         else
195                 ret = 2 * dsp_ctl->num_playbacks + idx;
196         if (ret == SND_CTL_EXT_KEY_NOT_FOUND)
197                 DPRINT("Not Found name %s, index %d\n",
198                        snd_ctl_elem_id_get_name(id), idx);
199         DLEAVE((int)ret);
200         return ret;
201 }
202
203 /**
204  * @param ext snd_ctl_ext_t structure.
205  * @param key current control key to be handled.
206  * @param type type of this control.
207  * @param acc access type of this control.
208  * @param count number of channels of this control.
209  *
210  * It provides information about a control. 
211  * Playback device node has an integer and a boolean
212  * control. Recording has only boolean control.
213  *
214  * @return zero. success.
215  */
216 static int dsp_ctl_get_attribute(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
217                                  int *type, unsigned int *acc,
218                                  unsigned int *count)
219 {
220         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
221         int ret = 0;
222         DENTER();
223
224         if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1)
225                 *type = SND_CTL_ELEM_TYPE_BOOLEAN;
226         else
227                 *type = SND_CTL_ELEM_TYPE_INTEGER;
228
229         *count = dsp_ctl->controls[key]->channels;
230         *acc = SND_CTL_EXT_ACCESS_READWRITE;
231         DLEAVE(ret);
232         return ret;
233 }
234
235 /**
236  * @param ext snd_ctl_ext_t structure.
237  * @param key current control key to be handled.
238  * @param imin minimum value of this integer control.
239  * @param imax maximum value of this integer control.
240  * @param istep steps value of this integer control.
241  *
242  * It provides information about integer control. It
243  * consideres both boolean and integer controls.
244  *
245  * @return  zero. success.
246  */
247 static int dsp_ctl_get_integer_info(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
248                                     long *imin, long *imax, long *istep)
249 {
250         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
251         DENTER();
252         *imin = 0;
253         if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1)
254                 *imax = 1;
255         else
256                 *imax = 100;
257         *istep = 0;
258         DLEAVE(0);
259         return 0;
260 }
261
262 /**
263  * @param ext snd_ctl_ext_t structure.
264  * @param key current control key to be handled.
265  * @param value return value holder.
266  *
267  * It reads volume information from dsp task node and 
268  * fills it into value to alsa-lib.
269  *
270  * @return zero. success.
271  */
272 static int dsp_ctl_read_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
273                                 long *value)
274 {
275         int ret = 0;
276         unsigned char left, right;
277         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
278         control_list_t *tmp = dsp_ctl->controls[key];
279
280         DENTER();
281         if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
282                 ret = dsp_protocol_get_mute(tmp->dsp_protocol);
283                 if (ret >= 0) {
284                         left = ret == 0 ? 1 : 0;
285                         right = left;
286                 }
287         } else
288                 ret = dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right);
289         if (ret >= 0) {
290                 value[0] = left;
291                 if (tmp->channels == 2)
292                         value[1] = right;
293         } else {
294                 value[0] = 0;
295                 if (tmp->channels == 2)
296                         value[1] = 0;
297                 ret = 0;
298         }
299
300         DLEAVE(ret);
301         return ret;
302 }
303
304 /**
305  * @param ext snd_ctl_ext_t structure.
306  * @param key current control key to be handled.
307  * @param value volume value to be written.
308  *
309  * It writes volume information to dsp task node from
310  * value that comes from alsa-lib. It checks if value
311  * coming from alsa-lib is different of value in dsp 
312  * side.
313  *
314  * @return zero if not updated. one if updated.
315  */
316 static int dsp_ctl_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key,
317                                  long *value)
318 {
319         int ret;
320         unsigned char left, right;
321         snd_ctl_dsp_t *dsp_ctl = ext->private_data;
322         control_list_t *tmp = dsp_ctl->controls[key];
323
324         DENTER();
325         if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
326                 if ((ret = dsp_protocol_get_mute(tmp->dsp_protocol)) < 0)
327                         goto zero;
328                 left = ret == 0 ? 1 : 0;
329                 right = left;
330         } else
331             if ((ret =
332                  dsp_protocol_get_volume(tmp->dsp_protocol, &left, &right)) < 0)
333                 goto zero;
334
335         if (tmp->channels == 2) {
336                 if (left == value[0] && right == value[1]) {
337                         ret = 0;
338                         goto out;
339                 }
340                 right = value[1];
341                 left = value[0];
342         } else {
343                 if (left == value[0]) {
344                         ret = 0;
345                         goto out;
346                 }
347                 right = left = value[0];
348         }
349
350         if (key >= 2 * dsp_ctl->num_playbacks || key % 2 == 1) {
351                 if ((ret =
352                      dsp_protocol_set_mute(tmp->dsp_protocol,
353                                            left == 0 ? 1 : 0)) < 0)
354                         goto zero;
355         } else
356             if ((ret =
357                  dsp_protocol_set_volume(tmp->dsp_protocol, left, right)) < 0)
358                 goto zero;
359         ret = 1;
360         goto out;
361       zero:
362         value[0] = 0;
363         if (tmp->channels == 2)
364                 value[1] = 0;
365         ret = 0;
366       out:
367         DLEAVE(ret);
368         return ret;
369 }
370
371 /**
372  * @param ext
373  * @param id
374  * @param event_mask
375  *
376  * @return -EIO
377  */
378 static int dsp_ctl_read_event(snd_ctl_ext_t * ext, snd_ctl_elem_id_t * id,
379                               unsigned int *event_mask)
380 {
381         return -EIO;
382 }
383
384 /**
385  * @param n configuration file parse tree. 
386  * @param device_list list of device files to be filled.
387  *
388  * It searches for device file names in given configuration parse
389  * tree. When one device file name is found, it is filled into device_list.
390  *
391  * @return zero if success, otherwise a negative error code.
392  */
393 static int fill_control_list(snd_config_t * n, control_list_t * control_list)
394 {
395         snd_config_iterator_t j, nextj;
396         control_list_t *tmp;
397         long idx = 0;
398         DENTER();
399         INIT_LIST_HEAD(&control_list->list);
400         snd_config_for_each(j, nextj, n) {
401                 snd_config_t *s = snd_config_iterator_entry(j);
402                 const char *id_number;
403                 long k;
404                 if (snd_config_get_id(s, &id_number) < 0)
405                         continue;
406                 if (safe_strtol(id_number, &k) < 0) {
407                         SNDERR("id of field %s is not an integer", id_number);
408                         idx = -EINVAL;
409                         goto out;
410                 }
411                 if (k == idx) {
412                         idx++;
413                         /* add to available dsp task nodes */
414                         tmp = (control_list_t *) malloc(sizeof(control_list_t));
415                         if (snd_config_get_ascii(s, &(tmp->name)) < 0) {
416                                 SNDERR("invalid ascii string for id %s\n",
417                                        id_number);
418                                 idx = -EINVAL;
419                                 goto out;
420                         }
421                         list_add(&(tmp->list), &(control_list->list));
422                 }
423         }
424       out:
425         DLEAVE((int)idx);
426         return idx;
427 }
428
429 /**
430  * @param dsp_ctl snd_dsp_t structure.
431  *
432  * It probes all dsp tasks configured to be handled by this
433  * plugin. It gets all needed information about volume controling.
434  *
435  * @return zero if success, otherwise a negative error code.
436  */
437 static int dsp_ctl_probe_dsp_task(snd_ctl_dsp_t * dsp_ctl)
438 {
439         int err = 0, i;
440         control_list_t *tmp;
441         struct list_head *lists[2] =
442             { &(dsp_ctl->playback_devices.list),
443 &(dsp_ctl->recording_devices.list) };
444         DENTER();
445         DPRINT("Probing dsp device nodes \n");
446         for (i = 0; i < 2; i++) {
447                 list_for_each_entry(tmp, lists[i], list) {
448                         DPRINT("Trying to use %s\n", tmp->name);
449                         /* Initialise the dsp_protocol and create connection */
450                         if ((err =
451                              dsp_protocol_create(&(tmp->dsp_protocol))) < 0)
452                                 goto out;
453                         if ((tmp->channels =
454                              dsp_protocol_probe_node(tmp->dsp_protocol,
455                                                      tmp->name)) < 0) {
456                                 DPRINT("%s is not available now\n", tmp->name);
457                                 err = tmp->channels;
458                                 close(tmp->dsp_protocol->fd);   //memory leak?!?
459                                 goto out;
460                         }
461                 }
462         }
463         if (err < 0) {
464                 DPRINT("No valid dsp task nodes for now. Exiting.\n");
465         }
466       out:
467         DLEAVE(err);
468         return err;
469 }
470
471 /**
472  * @param dsp_ctl snd_dsp_t structure.
473  *
474  * It fills an array of controls to represent PCM Volume,
475  * PCM Switch and Capture Switch controls.
476  *
477  * @return zero if success, otherwise a negative error code.
478  */
479 static int dsp_ctl_fill_controls(snd_ctl_dsp_t * dsp_ctl)
480 {
481         int ret = 0;
482         int i;
483         int num_controls = 2 * dsp_ctl->num_playbacks + dsp_ctl->num_recordings;
484         DENTER();
485         control_list_t *tmp;
486         dsp_ctl->controls = calloc(num_controls, sizeof(control_list_t *));
487         if (dsp_ctl->controls == NULL) {
488                 ret = -ENOMEM;
489                 goto out;
490         }
491         i = 0;
492         /* Each pcm task node should have a PCM Volume and PCM Switch control */
493         list_for_each_entry(tmp, &(dsp_ctl->playback_devices.list), list) {
494                 dsp_ctl->controls[i] = tmp;
495                 dsp_ctl->controls[i + 1] = tmp;
496                 i += 2;
497         }
498         /* Each pcm_rec node should have a Capture Switch control */
499         list_for_each_entry(tmp, &(dsp_ctl->recording_devices.list), list)
500             dsp_ctl->controls[i++] = tmp;
501
502       out:
503         DLEAVE(ret);
504         return ret;
505 }
506
507 /**
508  * Alsa-lib callback structure.
509  */
510 static snd_ctl_ext_callback_t dsp_ctl_ext_callback = {
511         .close = dsp_ctl_close,
512         .elem_count = dsp_ctl_elem_count,
513         .elem_list = dsp_ctl_elem_list,
514         .find_elem = dsp_ctl_find_elem,
515         .get_attribute = dsp_ctl_get_attribute,
516         .get_integer_info = dsp_ctl_get_integer_info,
517         .read_integer = dsp_ctl_read_integer,
518         .write_integer = dsp_ctl_write_integer,
519         .read_event = dsp_ctl_read_event,
520 };
521
522 /**
523  * It initializes the alsa ctl plugin. It reads the parameters and creates the 
524  * connection with the pcm device file.
525  *
526  * @return zero if success, otherwise a negative error code.
527  */
528 SND_CTL_PLUGIN_DEFINE_FUNC(dsp_ctl)
529 {
530         snd_config_iterator_t i, next;
531         snd_ctl_dsp_t *dsp_ctl;
532         int err;
533         int ret;
534
535         DENTER();
536         /* Allocate the structure */
537         dsp_ctl = calloc(1, sizeof(*dsp_ctl));
538         if (dsp_ctl == NULL) {
539                 ret = -ENOMEM;
540                 goto out;
541         }
542         /* Read the configuration searching for configurated devices */
543         snd_config_for_each(i, next, conf) {
544                 snd_config_t *n = snd_config_iterator_entry(i);
545                 const char *id;
546                 if (snd_config_get_id(n, &id) < 0)
547                         continue;
548                 if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0)
549                         continue;
550                 if (strcmp(id, "playback_devices") == 0) {
551                         if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){
552                                 if ((dsp_ctl->num_playbacks =
553                                      fill_control_list(n,
554                                                 &(dsp_ctl->
555                                                 playback_devices))) < 0) {
556                                         SNDERR("Could not fill control"
557                                                 " list for playback devices\n");
558                                         err = -EINVAL;
559                                         goto error;
560                                 }
561                         } else {
562                                 SNDERR("Invalid type for %s", id);
563                                 err = -EINVAL;
564                                 goto error;
565                         }
566                         continue;
567                 }
568                 if (strcmp(id, "recording_devices") == 0) {
569                         if (snd_config_get_type(n) == SND_CONFIG_TYPE_COMPOUND){
570                                 if ((dsp_ctl->num_recordings =
571                                      fill_control_list(n,
572                                                 &(dsp_ctl->
573                                                 recording_devices))) < 0) {
574                                         SNDERR("Could not fill string "
575                                                 "list for recording devices\n");
576                                         err = -EINVAL;
577                                         goto error;
578                                 }
579                         } else {
580                                 SNDERR("Invalid type for %s", id);
581                                 err = -EINVAL;
582                                 goto error;
583                         }
584                         continue;
585                 }
586                 SNDERR("Unknown field %s", id);
587                 err = -EINVAL;
588                 goto error;
589         }
590
591         if ((err = dsp_ctl_probe_dsp_task(dsp_ctl)) < 0)
592                 goto error;
593
594         if ((err = dsp_ctl_fill_controls(dsp_ctl)) < 0)
595                 goto error;
596         dsp_ctl->ext.version = SND_CTL_EXT_VERSION;
597         dsp_ctl->ext.card_idx = 0;      /* FIXME */
598         strcpy(dsp_ctl->ext.id, "ALSA-DSP-CTL");
599         strcpy(dsp_ctl->ext.name, "Alsa-DSP external ctl plugin");
600         strcpy(dsp_ctl->ext.longname, "External Control Alsa plugin for DSP");
601         strcpy(dsp_ctl->ext.mixername, "ALSA-DSP plugin Mixer");
602         dsp_ctl->ext.callback = &dsp_ctl_ext_callback;
603         dsp_ctl->ext.private_data = dsp_ctl;
604         free_ref = dsp_ctl;
605
606         if ((err = snd_ctl_ext_create(&dsp_ctl->ext, name, mode)) < 0)
607                 goto error;
608
609         *handlep = dsp_ctl->ext.handle;
610         ret = 0;
611         goto out;
612       error:
613         ret = err;
614         free(dsp_ctl);
615       out:
616         DLEAVE(ret);
617         return ret;
618 }
619 static void dsp_ctl_descructor(void) __attribute__ ((destructor));
620
621 static void dsp_ctl_descructor(void)
622 {
623         DENTER();
624         DPRINT("dsp ctl destructor\n");
625         DPRINT("checking for memories leaks and releasing resources\n");
626         if (free_ref) {
627                 if (free_ref->controls) 
628                         free(free_ref->controls);
629
630                 free_control_list(&(free_ref->playback_devices));
631
632                 free_control_list(&(free_ref->recording_devices));
633                 
634                 free(free_ref);
635                 free_ref = NULL;
636         }
637         DLEAVE(0);
638
639 }
640
641 SND_CTL_PLUGIN_SYMBOL(dsp_ctl);