1/*
2 *  Mixer Interface - simple abstact module - base library
3 *  Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
4 *
5 *
6 *   This library is free software; you can redistribute it and/or modify
7 *   it under the terms of the GNU Lesser General Public License as
8 *   published by the Free Software Foundation; either version 2.1 of
9 *   the License, or (at your option) any later version.
10 *
11 *   This program is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *   GNU Lesser General Public License for more details.
15 *
16 *   You should have received a copy of the GNU Lesser General Public
17 *   License along with this library; if not, write to the Free Software
18 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 *
20 */
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <unistd.h>
25#include <string.h>
26#include <fcntl.h>
27#include <sys/ioctl.h>
28#include <math.h>
29#include "asoundlib.h"
30#include "mixer_abst.h"
31#include "sbase.h"
32
33/*
34 * Prototypes
35 */
36
37static int selem_read(snd_mixer_elem_t *elem);
38
39/*
40 * Helpers
41 */
42
43static unsigned int chanmap_to_channels(unsigned int chanmap)
44{
45	unsigned int i, res;
46
47	for (i = 0, res = 0; i < MAX_CHANNEL; i++)
48		if (chanmap & (1 << i))
49			res++;
50	return res;
51}
52
53#if 0
54static long to_user(struct selem_base *s, int dir, struct helem_base *c, long value)
55{
56	int64_t n;
57	if (c->max == c->min)
58		return s->dir[dir].min;
59	n = (int64_t) (value - c->min) * (s->dir[dir].max - s->dir[dir].min);
60	return s->dir[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
61}
62
63static long from_user(struct selem_base *s, int dir, struct helem_base *c, long value)
64{
65        int64_t n;
66	if (s->dir[dir].max == s->dir[dir].min)
67		return c->min;
68        n = (int64_t) (value - s->dir[dir].min) * (c->max - c->min);
69	return c->min + (n + (s->dir[dir].max - s->dir[dir].min) / 2) / (s->dir[dir].max - s->dir[dir].min);
70}
71#endif
72
73static void update_ranges(struct selem_base *s)
74{
75	static unsigned int mask[2] = { SM_CAP_PVOLUME, SM_CAP_CVOLUME };
76	static unsigned int gmask[2] = { SM_CAP_GVOLUME, SM_CAP_GVOLUME };
77	unsigned int dir, ok_flag;
78	struct list_head *pos;
79	struct helem_base *helem;
80
81	for (dir = 0; dir < 2; dir++) {
82		s->dir[dir].min = 0;
83		s->dir[dir].max = 0;
84		ok_flag = 0;
85		list_for_each(pos, &s->helems) {
86			helem = list_entry(pos, struct helem_base, list);
87			printf("min = %li, max = %li\n", helem->min, helem->max);
88			if (helem->caps & mask[dir]) {
89				s->dir[dir].min = helem->min;
90				s->dir[dir].max = helem->max;
91				ok_flag = 1;
92				break;
93			}
94		}
95		if (ok_flag)
96			continue;
97		list_for_each(pos, &s->helems) {
98			helem = list_entry(pos, struct helem_base, list);
99			if (helem->caps & gmask[dir]) {
100				s->dir[dir].min = helem->min;
101				s->dir[dir].max = helem->max;
102				break;
103			}
104		}
105	}
106}
107
108/*
109 * Simple Mixer Operations
110 */
111
112static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
113{
114	struct selem_base *s = snd_mixer_elem_get_private(elem);
115
116	switch (cmd) {
117
118	case SM_OPS_IS_ACTIVE: {
119		struct list_head *pos;
120		struct helem_base *helem;
121		list_for_each(pos, &s->helems) {
122			helem = list_entry(pos, struct helem_base, list);
123			if (helem->inactive)
124				return 0;
125		}
126		return 1;
127	}
128
129	case SM_OPS_IS_MONO:
130		return chanmap_to_channels(s->dir[dir].chanmap) == 1;
131
132	case SM_OPS_IS_CHANNEL:
133		if (val > MAX_CHANNEL)
134			return 0;
135		return !!((1 << val) & s->dir[dir].chanmap);
136
137	case SM_OPS_IS_ENUMERATED: {
138		struct helem_base *helem;
139		helem = list_entry(s->helems.next, struct helem_base, list);
140		return !!(helem->purpose == PURPOSE_ENUMLIST);
141	}
142
143	case SM_OPS_IS_ENUMCNT: {
144		struct helem_base *helem;
145		helem = list_entry(s->helems.next, struct helem_base, list);
146		return helem->max;
147	}
148
149	}
150
151	return 1;
152}
153
154static int get_range_ops(snd_mixer_elem_t *elem, int dir,
155			 long *min, long *max)
156{
157	struct selem_base *s = snd_mixer_elem_get_private(elem);
158
159	*min = s->dir[dir].min;
160	*max = s->dir[dir].max;
161
162	return 0;
163}
164
165static int get_dB_range_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
166			    int dir ATTRIBUTE_UNUSED,
167			    long *min ATTRIBUTE_UNUSED,
168			    long *max ATTRIBUTE_UNUSED)
169{
170	return -ENXIO;
171}
172
173static int set_range_ops(snd_mixer_elem_t *elem, int dir,
174			 long min, long max)
175{
176	struct selem_base *s = snd_mixer_elem_get_private(elem);
177	int err;
178
179	s->dir[dir].forced_range = 1;
180	s->dir[dir].min = min;
181	s->dir[dir].max = max;
182
183	if ((err = selem_read(elem)) < 0)
184		return err;
185	return 0;
186}
187
188static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
189			  snd_mixer_selem_channel_id_t channel, long *value)
190{
191	struct selem_base *s = snd_mixer_elem_get_private(elem);
192
193	*value = s->dir[dir].vol[channel];
194	return 0;
195}
196
197static int get_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
198		      int dir ATTRIBUTE_UNUSED,
199		      snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
200		      long *value ATTRIBUTE_UNUSED)
201{
202	return -ENXIO;
203}
204
205static int get_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
206			  int dir ATTRIBUTE_UNUSED,
207			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
208			  int *value)
209{
210	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
211	*value = 0;
212	return 0;
213}
214
215static int set_volume_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
216			  int dir ATTRIBUTE_UNUSED,
217			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
218			  long value ATTRIBUTE_UNUSED)
219{
220	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
221	return 0;
222}
223
224static int set_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
225		      int dir ATTRIBUTE_UNUSED,
226		      snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
227		      long value ATTRIBUTE_UNUSED,
228		      int xdir ATTRIBUTE_UNUSED)
229{
230	return -ENXIO;
231}
232
233static int set_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
234			  int dir ATTRIBUTE_UNUSED,
235			  snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
236			  int value ATTRIBUTE_UNUSED)
237{
238	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
239	/* int changed; */
240	return 0;
241}
242
243static int enum_item_name_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
244			      unsigned int item ATTRIBUTE_UNUSED,
245			      size_t maxlen ATTRIBUTE_UNUSED,
246			      char *buf ATTRIBUTE_UNUSED)
247{
248	/* struct selem_base *s = snd_mixer_elem_get_private(elem);*/
249	return 0;
250}
251
252static int get_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
253			     snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
254			     unsigned int *itemp ATTRIBUTE_UNUSED)
255{
256	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
257	return 0;
258}
259
260static int set_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
261			     snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
262			     unsigned int item ATTRIBUTE_UNUSED)
263{
264	/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
265	return 0;
266}
267
268static struct sm_elem_ops simple_ac97_ops = {
269	.is		= is_ops,
270	.get_range	= get_range_ops,
271	.get_dB_range	= get_dB_range_ops,
272	.set_range	= set_range_ops,
273	.get_volume	= get_volume_ops,
274	.get_dB		= get_dB_ops,
275	.set_volume	= set_volume_ops,
276	.set_dB		= set_dB_ops,
277	.get_switch	= get_switch_ops,
278	.set_switch	= set_switch_ops,
279	.enum_item_name	= enum_item_name_ops,
280	.get_enum_item	= get_enum_item_ops,
281	.set_enum_item	= set_enum_item_ops
282};
283
284/*
285 * event handling
286 */
287
288static int selem_read(snd_mixer_elem_t *elem)
289{
290	printf("elem read: %p\n", elem);
291	return 0;
292}
293
294static int simple_event_remove(snd_hctl_elem_t *helem,
295			       snd_mixer_elem_t *melem ATTRIBUTE_UNUSED)
296{
297	printf("event remove: %p\n", helem);
298	return 0;
299}
300
301static void selem_free(snd_mixer_elem_t *elem)
302{
303	struct selem_base *simple = snd_mixer_elem_get_private(elem);
304	struct helem_base *hsimple;
305	struct list_head *pos, *npos;
306
307	if (simple->selem.id)
308		snd_mixer_selem_id_free(simple->selem.id);
309	list_for_each_safe(pos, npos, &simple->helems) {
310		hsimple = list_entry(pos, struct helem_base, list);
311		free(hsimple);
312	}
313	free(simple);
314}
315
316static int simple_event_add1(snd_mixer_class_t *class,
317			     snd_hctl_elem_t *helem,
318			     struct helem_selector *sel)
319{
320	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
321	snd_mixer_elem_t *melem;
322	snd_mixer_selem_id_t *id;
323	snd_ctl_elem_info_t *info;
324	struct selem_base *simple;
325	struct helem_base *hsimple;
326	snd_ctl_elem_type_t ctype;
327	unsigned long values;
328	long min, max;
329	int err, new = 0;
330	struct list_head *pos;
331	struct bclass_sid *bsid;
332	struct melem_sids *sid;
333	unsigned int ui;
334
335	list_for_each(pos, &priv->sids) {
336		bsid = list_entry(pos, struct bclass_sid, list);
337		for (ui = 0; ui < bsid->count; ui++) {
338			if (bsid->sids[ui].sid == sel->sid) {
339				sid = &bsid->sids[ui];
340				goto __sid_ok;
341			}
342		}
343	}
344	return 0;
345
346      __sid_ok:
347	snd_ctl_elem_info_alloca(&info);
348	err = snd_hctl_elem_info(helem, info);
349	if (err < 0)
350		return err;
351	ctype = snd_ctl_elem_info_get_type(info);
352	values = snd_ctl_elem_info_get_count(info);
353	switch (ctype) {
354	case SND_CTL_ELEM_TYPE_ENUMERATED:
355		min = 0;
356		max = snd_ctl_elem_info_get_items(info);
357		break;
358	case SND_CTL_ELEM_TYPE_INTEGER:
359		min = snd_ctl_elem_info_get_min(info);
360		max = snd_ctl_elem_info_get_max(info);
361		break;
362	default:
363		min = max = 0;
364		break;
365	}
366
367	printf("event add: %p, %p (%s)\n", helem, sel, snd_hctl_elem_get_name(helem));
368	if (snd_mixer_selem_id_malloc(&id))
369		return -ENOMEM;
370	hsimple = calloc(1, sizeof(*hsimple));
371	if (hsimple == NULL) {
372		snd_mixer_selem_id_free(id);
373		return -ENOMEM;
374	}
375	switch (sel->purpose) {
376	case PURPOSE_SWITCH:
377		if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) {
378		      __invalid_type:
379		      	snd_mixer_selem_id_free(id);
380			return -EINVAL;
381		}
382		break;
383	case PURPOSE_VOLUME:
384		if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
385			goto __invalid_type;
386		break;
387	}
388	hsimple->purpose = sel->purpose;
389	hsimple->caps = sel->caps;
390	hsimple->min = min;
391	hsimple->max = max;
392	snd_mixer_selem_id_set_name(id, sid->sname);
393	snd_mixer_selem_id_set_index(id, sid->sindex);
394	melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
395	if (!melem) {
396		simple = calloc(1, sizeof(*simple));
397		if (!simple) {
398			snd_mixer_selem_id_free(id);
399			free(hsimple);
400			return -ENOMEM;
401		}
402		simple->selem.id = id;
403		simple->selem.ops = &simple_ac97_ops;
404		INIT_LIST_HEAD(&simple->helems);
405		simple->sid = sel->sid;
406		err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
407					 sid->weight,
408					 simple, selem_free);
409		if (err < 0) {
410			snd_mixer_selem_id_free(id);
411			free(hsimple);
412			free(simple);
413			return err;
414		}
415		new = 1;
416	} else {
417		simple = snd_mixer_elem_get_private(melem);
418		snd_mixer_selem_id_free(id);
419	}
420	list_add_tail(&hsimple->list, &simple->helems);
421	hsimple->inactive = snd_ctl_elem_info_is_inactive(info);
422	err = snd_mixer_elem_attach(melem, helem);
423	if (err < 0)
424		goto __error;
425	simple->dir[0].chanmap |= sid->chanmap[0];
426	simple->dir[1].chanmap |= sid->chanmap[1];
427	simple->selem.caps |= hsimple->caps;
428	update_ranges(simple);
429#if 0
430	err = simple_update(melem);
431	if (err < 0) {
432		if (new)
433			goto __error;
434		return err;
435	}
436#endif
437	if (new)
438		err = snd_mixer_elem_add(melem, class);
439	else
440		err = snd_mixer_elem_info(melem);
441	if (err < 0)
442		return err;
443	err = selem_read(melem);
444	if (err < 0)
445		return err;
446	if (err)
447		err = snd_mixer_elem_value(melem);
448	return err;
449      __error:
450      	if (new)
451      		snd_mixer_elem_free(melem);
452      	return -EINVAL;
453}
454
455static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
456{
457	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
458	struct bclass_selector *sel;
459	struct helem_selector *hsel;
460	struct list_head *pos;
461	snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem);
462	const char *name = snd_hctl_elem_get_name(helem);
463	unsigned int index = snd_hctl_elem_get_index(helem);
464	unsigned int ui;
465	int err;
466
467	list_for_each(pos, &priv->selectors) {
468		sel = list_entry(pos, struct bclass_selector, list);
469		for (ui = 0; ui < sel->count; ui++) {
470			hsel = &sel->selectors[ui];
471			if (hsel->iface == iface && !strcmp(hsel->name, name) && hsel->index == index) {
472				err = simple_event_add1(class, helem, hsel);
473				if (err < 0)
474					return err;	/* early exit? */
475			}
476		}
477	}
478	return 0;
479}
480
481int alsa_mixer_sbasic_event(snd_mixer_class_t *class, unsigned int mask,
482			    snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
483{
484	int err;
485	if (mask == SND_CTL_EVENT_MASK_REMOVE)
486		return simple_event_remove(helem, melem);
487	if (mask & SND_CTL_EVENT_MASK_ADD) {
488		err = simple_event_add(class, helem);
489		if (err < 0)
490			return err;
491	}
492	if (mask & SND_CTL_EVENT_MASK_INFO) {
493		err = simple_event_remove(helem, melem);
494		if (err < 0)
495			return err;
496		err = simple_event_add(class, helem);
497		if (err < 0)
498			return err;
499		return 0;
500	}
501	if (mask & SND_CTL_EVENT_MASK_VALUE) {
502		err = selem_read(melem);
503		if (err < 0)
504			return err;
505		if (err) {
506			err = snd_mixer_elem_value(melem);
507			if (err < 0)
508				return err;
509		}
510	}
511	return 0;
512}
513
514static void sbasic_cpriv_free(snd_mixer_class_t *class)
515{
516	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
517	struct bclass_selector *sel;
518	struct bclass_sid *sid;
519	struct list_head *pos, *pos1;
520
521	list_for_each_safe(pos, pos1, &priv->selectors) {
522		sel = list_entry(pos, struct bclass_selector, list);
523		free(sel);
524	}
525	list_for_each_safe(pos, pos1, &priv->sids) {
526		sid = list_entry(pos, struct bclass_sid, list);
527		free(sid);
528	}
529	free(priv);
530}
531
532void alsa_mixer_sbasic_initpriv(snd_mixer_class_t *class,
533				struct bclass_private *priv)
534{
535	INIT_LIST_HEAD(&priv->selectors);
536	INIT_LIST_HEAD(&priv->sids);
537	snd_mixer_sbasic_set_private(class, priv);
538	snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
539}
540
541int alsa_mixer_sbasic_selreg(snd_mixer_class_t *class,
542			     struct helem_selector *selectors,
543			     unsigned int count)
544{
545	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
546	struct bclass_selector *sel = calloc(1, sizeof(*sel));
547
548	if (sel == NULL)
549		return -ENOMEM;
550	if (priv == NULL) {
551		priv = calloc(1, sizeof(*priv));
552		if (priv == NULL) {
553			free(sel);
554			return -ENOMEM;
555		}
556	}
557	sel->selectors = selectors;
558	sel->count = count;
559	list_add_tail(&sel->list, &priv->selectors);
560	return 0;
561}
562
563int alsa_mixer_sbasic_sidreg(snd_mixer_class_t *class,
564			     struct melem_sids *sids,
565			     unsigned int count)
566{
567	struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
568	struct bclass_sid *sid = calloc(1, sizeof(*sid));
569
570	if (sid == NULL)
571		return -ENOMEM;
572	if (priv == NULL) {
573		priv = calloc(1, sizeof(*priv));
574		if (priv == NULL) {
575			free(sid);
576			return -ENOMEM;
577		}
578		INIT_LIST_HEAD(&priv->selectors);
579		INIT_LIST_HEAD(&priv->sids);
580		snd_mixer_sbasic_set_private(class, priv);
581		snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
582	}
583	sid->sids = sids;
584	sid->count = count;
585	list_add(&sid->list, &priv->sids);
586	return 0;
587}
588