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