xref: /illumos-gate/usr/src/uts/common/io/audio/impl/audio_ctrl.c (revision c9fd6b31f4fa1345ad799549dc0f1101b9cef127)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/list.h>
28 #include <sys/sysmacros.h>
29 #include <sys/ddi.h>
30 #include <sys/sunddi.h>
31 #include <sys/atomic.h>
32 
33 #include "audio_impl.h"
34 
35 /*
36  * Audio Control functions.
37  */
38 
39 /*
40  * Given a control structure - free all names
41  * strings allocated to it.
42  *
43  * ctrl             - The control who's names that will be free'd.
44  */
45 static void
46 audio_control_freenames(audio_ctrl_t *ctrl)
47 {
48 	int	indx;
49 
50 	if (ctrl->ctrl_name != NULL)
51 		strfree((char *)ctrl->ctrl_name);
52 	ctrl->ctrl_name = NULL;
53 
54 	for (indx = 0; indx < 64; indx++) {
55 		if (ctrl->ctrl_enum[indx] != NULL) {
56 			strfree((char *)ctrl->ctrl_enum[indx]);
57 			ctrl->ctrl_enum[indx] = NULL;
58 		}
59 	}
60 }
61 
62 /*
63  * This will allocate and register a control for my audio device.
64  *
65  * d                - The audio device the control will be attached to.
66  * desc             - Attributes about this new control
67  * read_fn          - Callback function in driver to read control
68  * write_fn         - Callback function in driver to write control.
69  * arg              - driver private context passed to read_fn/write_fn
70  *
71  * On success this will return a control structure else NULL.
72  *
73  * The value passed in for a control number in the audio_ctrl_desc_t
74  * has some special meaning. If it is less then AUDIO_CONTROL_EXTBASE
75  * then the control is assumed to be a known control. If it is
76  * AUDIO_CONTROL_EXTBASE then the framework will allocate a unique
77  * control number and replace it in the audio_ctrl_desc_t structure
78  * and this control is considered an extended driver private control.
79  * The number that is replaced in audio_ctrl_desc_t will be greater
80  * then AUDIO_CONTROL_EXTBASE.
81  *
82  */
83 audio_ctrl_t *
84 audio_dev_add_control(audio_dev_t *d, audio_ctrl_desc_t *desc,
85     audio_ctrl_rd_t read_fn, audio_ctrl_wr_t write_fn, void *arg)
86 {
87 	audio_ctrl_t *ctrl;
88 	audio_ctrl_desc_t *new_desc;
89 	char	scratch[16];
90 	const char	*name;
91 
92 	/* Verify arguments */
93 	ASSERT(d);
94 	ASSERT(desc);
95 
96 	/* We cannot deal with unnamed controls */
97 	if ((name = desc->acd_name) == NULL) {
98 		return (NULL);
99 	}
100 
101 	/*
102 	 * If this was called with a control name that was already
103 	 * added, then we do some special things. First we reuse the
104 	 * control audio_ctrl_t and as far as outside users are
105 	 * concerned the handle is reused. To users this looks like we
106 	 * are changing the controls attributes. But what we really do
107 	 * is free every thing allocated to the control and then
108 	 * reinit everything.  That way the same code can get used for
109 	 * both.
110 	 *
111 	 * We verify anything that could fail before we change the
112 	 * control or commit to any changes. If there is something bad
113 	 * return null to indicate an error but the original control
114 	 * is still usable and untouched.
115 	 */
116 	ctrl = auclnt_find_control(d, name);
117 
118 	if (ctrl == NULL) {
119 		/* Allocate a new control */
120 		ctrl = kmem_zalloc(sizeof (*ctrl), KM_SLEEP);
121 	} else {
122 		/* Re-configure an existing control */
123 		switch (desc->acd_type) {
124 		case AUDIO_CTRL_TYPE_BOOLEAN:
125 		case AUDIO_CTRL_TYPE_STEREO:
126 		case AUDIO_CTRL_TYPE_MONO:
127 		case AUDIO_CTRL_TYPE_METER:
128 		case AUDIO_CTRL_TYPE_ENUM:
129 			break;
130 		default:
131 			audio_dev_warn(d, "bad control type %d for %s "
132 			    "not replaced", desc->acd_type, desc->acd_name);
133 			return (NULL);
134 		}
135 
136 		/*
137 		 * By removing it from the list we prevent the need to lock
138 		 * and check for locks on the control itself.
139 		 * Also by doing this we can use the normal add code to do
140 		 * what it normally does below.
141 		 */
142 		mutex_enter(&d->d_ctrl_lock);
143 		list_remove(&d->d_controls, ctrl);
144 		mutex_exit(&d->d_ctrl_lock);
145 
146 		audio_control_freenames(ctrl);
147 		ctrl->ctrl_read_fn = NULL;
148 		ctrl->ctrl_write_fn = NULL;
149 		ctrl->ctrl_arg = NULL;
150 		ctrl->ctrl_dev = NULL;
151 	}
152 	new_desc = &ctrl->ctrl_des;
153 
154 	/* Fill in new control description */
155 	new_desc->acd_type = desc->acd_type;
156 	new_desc->acd_flags = desc->acd_flags;
157 	new_desc->acd_maxvalue = desc->acd_maxvalue;
158 	new_desc->acd_minvalue = desc->acd_minvalue;
159 	new_desc->acd_name = strdup(name);
160 
161 	/* Process type of control special actions, if any */
162 	switch (desc->acd_type) {
163 	case AUDIO_CTRL_TYPE_BOOLEAN:
164 	case AUDIO_CTRL_TYPE_STEREO:
165 	case AUDIO_CTRL_TYPE_MONO:
166 	case AUDIO_CTRL_TYPE_METER:
167 		break;
168 
169 	case AUDIO_CTRL_TYPE_ENUM:
170 		for (int bit = 0; bit < 64; bit++) {
171 			if (((1U << bit) & desc->acd_maxvalue) == 0)
172 				continue;
173 			name = desc->acd_enum[bit];
174 			if (name == NULL) {
175 				(void) snprintf(scratch, sizeof (scratch),
176 				    "bit%d", bit);
177 				name = scratch;
178 			}
179 			new_desc->acd_enum[bit] = strdup(name);
180 		}
181 		break;
182 	default:
183 		audio_dev_warn(d, "bad control type %d for %s",
184 		    desc->acd_type, desc->acd_name);
185 		goto ctrl_fail;
186 	}
187 
188 	ctrl->ctrl_dev = d;
189 	if (new_desc->acd_flags & AUDIO_CTRL_FLAG_READABLE) {
190 		ASSERT(read_fn);
191 		ctrl->ctrl_read_fn = read_fn;
192 		ctrl->ctrl_arg = arg;
193 	}
194 	if (new_desc->acd_flags & AUDIO_CTRL_FLAG_WRITEABLE) {
195 		ASSERT(write_fn);
196 		ctrl->ctrl_write_fn = write_fn;
197 		ctrl->ctrl_arg = arg;
198 	}
199 
200 	mutex_enter(&d->d_ctrl_lock);
201 	list_insert_tail(&d->d_controls, ctrl);
202 	mutex_exit(&d->d_ctrl_lock);
203 
204 	return (ctrl);
205 
206 
207 ctrl_fail:
208 	if (ctrl) {
209 		audio_control_freenames(ctrl);
210 		kmem_free(ctrl, sizeof (*ctrl));
211 	}
212 	return (NULL);
213 }
214 
215 /*
216  * This will remove a control from my audio device.
217  *
218  * ctrl             - The control will be removed.
219  */
220 void
221 audio_dev_del_control(audio_ctrl_t *ctrl)
222 {
223 	audio_dev_t *d;
224 
225 	/* Verify argument */
226 	ASSERT(ctrl);
227 	d = ctrl->ctrl_dev;
228 	ASSERT(d);
229 
230 	mutex_enter(&d->d_ctrl_lock);
231 	list_remove(&d->d_controls, ctrl);
232 	mutex_exit(&d->d_ctrl_lock);
233 
234 	audio_control_freenames(ctrl);
235 	kmem_free(ctrl, sizeof (*ctrl));
236 }
237 
238 int
239 audio_dev_add_soft_volume(audio_dev_t *d)
240 {
241 	audio_ctrl_desc_t	desc;
242 
243 	bzero(&desc, sizeof (desc));
244 	if (d->d_pcmvol_ctrl == NULL) {
245 		desc.acd_name = AUDIO_CTRL_ID_VOLUME;
246 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
247 		desc.acd_minvalue = 0;
248 		desc.acd_maxvalue = 100;
249 		desc.acd_flags = AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY |
250 		    AUDIO_CTRL_FLAG_PCMVOL;
251 		d->d_pcmvol_ctrl = audio_dev_add_control(d, &desc,
252 		    auimpl_get_pcmvol, auimpl_set_pcmvol, d);
253 		d->d_pcmvol = 75;
254 	}
255 	return (0);
256 }
257 
258 /*
259  * This will notify clients of need to reread control
260  * values since they have changed.
261  *
262  * There will be a routine that allows a client to register
263  * a callback.   For now we just update the serial number.
264  *
265  * d                - The device that needs updates.
266  */
267 void
268 audio_dev_update_controls(audio_dev_t *d)
269 {
270 	atomic_inc_uint(&d->d_serial);
271 }
272 
273 
274 /*
275  * This is used to read the current value of a control.
276  * Note, this will cause a callback into the driver to get the value.
277  *
278  * ctrl        - should be the valid control being read.
279  * value       - is a pointer to the place that will contain the value read.
280  *
281  * On return zero is returned on success else errno is returned.
282  *
283  */
284 int
285 audio_control_read(audio_ctrl_t *ctrl, uint64_t *value)
286 {
287 	audio_dev_t	*d = ctrl->ctrl_dev;
288 	uint64_t	my_value;
289 	int		ret;
290 
291 	ASSERT(value);
292 
293 	mutex_enter(&d->d_ctrl_lock);
294 	while (d->d_suspended) {
295 		cv_wait(&d->d_ctrl_cv, &d->d_ctrl_lock);
296 	}
297 
298 	if (!(ctrl->ctrl_flags & AUDIO_CTRL_FLAG_READABLE)) {
299 		mutex_exit(&d->d_ctrl_lock);
300 		return (ENXIO);
301 	}
302 
303 	ASSERT(ctrl->ctrl_read_fn);
304 
305 	ret = ctrl->ctrl_read_fn(ctrl->ctrl_arg, &my_value);
306 	mutex_exit(&d->d_ctrl_lock);
307 
308 	if (ret == 0) {
309 		*value = my_value;
310 	}
311 
312 	return (ret);
313 }
314 
315 /*
316  * This is used to write a value to a control.
317  * Note, this will cause a callback into the driver to write the value.
318  *
319  * ctrl        - should be the valid control being written.
320  * value       - is value to set the control to.
321  *
322  * On return zero is returned on success else errno is returned.
323  *
324  */
325 int
326 audio_control_write(audio_ctrl_t *ctrl, uint64_t value)
327 {
328 	int		ret;
329 	audio_dev_t	*d = ctrl->ctrl_dev;
330 
331 	mutex_enter(&d->d_ctrl_lock);
332 	while (d->d_suspended) {
333 		cv_wait(&d->d_ctrl_cv, &d->d_ctrl_lock);
334 	}
335 
336 	if (!(ctrl->ctrl_flags & AUDIO_CTRL_FLAG_WRITEABLE)) {
337 		mutex_exit(&d->d_ctrl_lock);
338 		return (ENXIO);
339 	}
340 
341 	ASSERT(ctrl->ctrl_write_fn);
342 
343 	ret = ctrl->ctrl_write_fn(ctrl->ctrl_arg, value);
344 	if (ret == 0) {
345 		ctrl->ctrl_saved = value;
346 		ctrl->ctrl_saved_ok = B_TRUE;
347 	}
348 	mutex_exit(&d->d_ctrl_lock);
349 
350 	if (ret == 0) {
351 		audio_dev_update_controls(d);
352 	}
353 
354 	return (ret);
355 }
356 
357 /*
358  * This is used to save control values.
359  */
360 int
361 auimpl_save_controls(audio_dev_t *d)
362 {
363 	audio_ctrl_t	*ctrl;
364 	list_t		*l;
365 	int		ret;
366 
367 	ASSERT(mutex_owned(&d->d_ctrl_lock));
368 	l = &d->d_controls;
369 
370 	for (ctrl = list_head(l); ctrl; ctrl = list_next(l, ctrl)) {
371 		if ((!(ctrl->ctrl_flags & AUDIO_CTRL_FLAG_WRITEABLE)) ||
372 		    (!(ctrl->ctrl_flags & AUDIO_CTRL_FLAG_WRITEABLE))) {
373 			continue;
374 		}
375 		ret = ctrl->ctrl_read_fn(ctrl->ctrl_arg, &ctrl->ctrl_saved);
376 		if (ret != 0) {
377 			audio_dev_warn(d,
378 			    "Unable to save value of control %s",
379 			    ctrl->ctrl_name);
380 			return (ret);
381 		} else {
382 			ctrl->ctrl_saved_ok = B_TRUE;
383 		}
384 	}
385 	return (0);
386 }
387 
388 int
389 auimpl_restore_controls(audio_dev_t *d)
390 {
391 	audio_ctrl_t	*ctrl;
392 	list_t		*l;
393 	int		ret;
394 	int		rv = 0;
395 
396 	ASSERT(mutex_owned(&d->d_ctrl_lock));
397 	l = &d->d_controls;
398 
399 	for (ctrl = list_head(l); ctrl; ctrl = list_next(l, ctrl)) {
400 		if (!ctrl->ctrl_saved_ok) {
401 			continue;
402 		}
403 		ret = ctrl->ctrl_write_fn(ctrl->ctrl_arg, ctrl->ctrl_saved);
404 		if (ret != 0) {
405 			audio_dev_warn(d,
406 			    "Unable to restore value of control %s",
407 			    ctrl->ctrl_name);
408 			rv = ret;
409 		}
410 	}
411 	return (rv);
412 }
413