aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2015-02-24 08:59:42 -0500
committerTakashi Iwai <tiwai@suse.de>2015-03-23 08:17:27 -0400
commit3256be6537751f65c76b3ecfbb4e667f87525a2f (patch)
tree8dc85bb89fa5c5c23ebf815e0d4a825a0b9d4367
parent7639a06c23c7d4cda34c2546bd7290d8753849ca (diff)
ALSA: hda - Add widget sysfs tree
This patch changes the sysfs files assigned to the codec device on the bus which were formerly identical with hwdep sysfs files. Now it shows only a few core parameter, vendor_id, subsystem_id, revision_id, afg, mfg, vendor_name and chip_name. In addition, now a widget tree is added to the bus device sysfs directory for showing the widget topology and attributes. It's just a flat tree consisting of subdirectories named as the widget NID including various attributes like widget capability bits. The AFG (usually NID 0x01) is always found there, and it contains always amp_in_caps, amp_out_caps and power_caps files. Each of these attributes show a single value. The rest are the widget nodes belonging to that AFG. Note that the child node might not start from 0x02 but from another value like 0x0a. Each child node may contain caps, pin_caps, amp_in_caps, amp_out_caps, power_caps and connections files. The caps (representing the widget capability bits) always contain a value. The rest may contain value(s) if the attribute exists on the node. Only connections file show multiple values while other attributes have zero or one single value. An example of ls -R output is like below: % ls -R /sys/bus/hdaudio/devices/hdaudioC0D0/ /sys/bus/hdaudio/devices/hdaudioC0D0/widgets/: 01/ 04/ 07/ 0a/ 0d/ 10/ 13/ 16/ 19/ 1c/ 1f/ 22/ 02/ 05/ 08/ 0b/ 0e/ 11/ 14/ 17/ 1a/ 1d/ 20/ 23/ 03/ 06/ 09/ 0c/ 0f/ 12/ 15/ 18/ 1b/ 1e/ 21/ /sys/bus/hdaudio/devices/hdaudioC0D0/widgets/01: amp_in_caps amp_out_caps power_caps /sys/bus/hdaudio/devices/hdaudioC0D0/widgets/02: amp_in_caps amp_out_caps caps connections pin_caps pin_cfg power_caps /sys/bus/hdaudio/devices/hdaudioC0D0/widgets/03: ..... Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--include/sound/hdaudio.h6
-rw-r--r--sound/hda/Makefile2
-rw-r--r--sound/hda/hdac_device.c35
-rw-r--r--sound/hda/hdac_sysfs.c404
-rw-r--r--sound/hda/local.h4
-rw-r--r--sound/pci/hda/hda_bind.c4
-rw-r--r--sound/pci/hda/hda_codec.c6
7 files changed, 454 insertions, 7 deletions
diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index b81b4bec6f05..6ed2b421e29e 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -14,6 +14,7 @@ typedef u16 hda_nid_t;
14struct hdac_bus; 14struct hdac_bus;
15struct hdac_device; 15struct hdac_device;
16struct hdac_driver; 16struct hdac_driver;
17struct hdac_widget_tree;
17 18
18/* 19/*
19 * exported bus type 20 * exported bus type
@@ -53,6 +54,9 @@ struct hdac_device {
53 54
54 /* misc flags */ 55 /* misc flags */
55 atomic_t in_pm; /* suspend/resume being performed */ 56 atomic_t in_pm; /* suspend/resume being performed */
57
58 /* sysfs */
59 struct hdac_widget_tree *widgets;
56}; 60};
57 61
58/* device/driver type used for matching */ 62/* device/driver type used for matching */
@@ -71,6 +75,8 @@ enum {
71int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus, 75int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus,
72 const char *name, unsigned int addr); 76 const char *name, unsigned int addr);
73void snd_hdac_device_exit(struct hdac_device *dev); 77void snd_hdac_device_exit(struct hdac_device *dev);
78int snd_hdac_device_register(struct hdac_device *codec);
79void snd_hdac_device_unregister(struct hdac_device *codec);
74 80
75int snd_hdac_refresh_widgets(struct hdac_device *codec); 81int snd_hdac_refresh_widgets(struct hdac_device *codec);
76 82
diff --git a/sound/hda/Makefile b/sound/hda/Makefile
index 3c7625e595cf..ae8b5128b5c3 100644
--- a/sound/hda/Makefile
+++ b/sound/hda/Makefile
@@ -1,3 +1,3 @@
1snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o 1snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o
2 2
3obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o 3obj-$(CONFIG_SND_HDA_CORE) += snd-hda-core.o
diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c
index a3f52ad4de37..1470ecc354db 100644
--- a/sound/hda/hdac_device.c
+++ b/sound/hda/hdac_device.c
@@ -45,6 +45,7 @@ int snd_hdac_device_init(struct hdac_device *codec, struct hdac_bus *bus,
45 dev->parent = bus->dev; 45 dev->parent = bus->dev;
46 dev->bus = &snd_hda_bus_type; 46 dev->bus = &snd_hda_bus_type;
47 dev->release = default_release; 47 dev->release = default_release;
48 dev->groups = hdac_dev_attr_groups;
48 dev_set_name(dev, "%s", name); 49 dev_set_name(dev, "%s", name);
49 device_enable_async_suspend(dev); 50 device_enable_async_suspend(dev);
50 51
@@ -128,6 +129,40 @@ void snd_hdac_device_exit(struct hdac_device *codec)
128EXPORT_SYMBOL_GPL(snd_hdac_device_exit); 129EXPORT_SYMBOL_GPL(snd_hdac_device_exit);
129 130
130/** 131/**
132 * snd_hdac_device_register - register the hd-audio codec base device
133 * codec: the device to register
134 */
135int snd_hdac_device_register(struct hdac_device *codec)
136{
137 int err;
138
139 err = device_add(&codec->dev);
140 if (err < 0)
141 return err;
142 err = hda_widget_sysfs_init(codec);
143 if (err < 0) {
144 device_del(&codec->dev);
145 return err;
146 }
147
148 return 0;
149}
150EXPORT_SYMBOL_GPL(snd_hdac_device_register);
151
152/**
153 * snd_hdac_device_unregister - unregister the hd-audio codec base device
154 * codec: the device to unregister
155 */
156void snd_hdac_device_unregister(struct hdac_device *codec)
157{
158 if (device_is_registered(&codec->dev)) {
159 hda_widget_sysfs_exit(codec);
160 device_del(&codec->dev);
161 }
162}
163EXPORT_SYMBOL_GPL(snd_hdac_device_unregister);
164
165/**
131 * snd_hdac_make_cmd - compose a 32bit command word to be sent to the 166 * snd_hdac_make_cmd - compose a 32bit command word to be sent to the
132 * HD-audio controller 167 * HD-audio controller
133 * @codec: the codec object 168 * @codec: the codec object
diff --git a/sound/hda/hdac_sysfs.c b/sound/hda/hdac_sysfs.c
new file mode 100644
index 000000000000..b358d5157802
--- /dev/null
+++ b/sound/hda/hdac_sysfs.c
@@ -0,0 +1,404 @@
1/*
2 * sysfs support for HD-audio core device
3 */
4
5#include <linux/slab.h>
6#include <linux/sysfs.h>
7#include <linux/device.h>
8#include <sound/core.h>
9#include <sound/hdaudio.h>
10#include "local.h"
11
12struct hdac_widget_tree {
13 struct kobject *root;
14 struct kobject *afg;
15 struct kobject **nodes;
16};
17
18#define CODEC_ATTR(type) \
19static ssize_t type##_show(struct device *dev, \
20 struct device_attribute *attr, \
21 char *buf) \
22{ \
23 struct hdac_device *codec = dev_to_hdac_dev(dev); \
24 return sprintf(buf, "0x%x\n", codec->type); \
25} \
26static DEVICE_ATTR_RO(type)
27
28#define CODEC_ATTR_STR(type) \
29static ssize_t type##_show(struct device *dev, \
30 struct device_attribute *attr, \
31 char *buf) \
32{ \
33 struct hdac_device *codec = dev_to_hdac_dev(dev); \
34 return sprintf(buf, "%s\n", \
35 codec->type ? codec->type : ""); \
36} \
37static DEVICE_ATTR_RO(type)
38
39CODEC_ATTR(vendor_id);
40CODEC_ATTR(subsystem_id);
41CODEC_ATTR(revision_id);
42CODEC_ATTR(afg);
43CODEC_ATTR(mfg);
44CODEC_ATTR_STR(vendor_name);
45CODEC_ATTR_STR(chip_name);
46
47static struct attribute *hdac_dev_attrs[] = {
48 &dev_attr_vendor_id.attr,
49 &dev_attr_subsystem_id.attr,
50 &dev_attr_revision_id.attr,
51 &dev_attr_afg.attr,
52 &dev_attr_mfg.attr,
53 &dev_attr_vendor_name.attr,
54 &dev_attr_chip_name.attr,
55 NULL
56};
57
58static struct attribute_group hdac_dev_attr_group = {
59 .attrs = hdac_dev_attrs,
60};
61
62const struct attribute_group *hdac_dev_attr_groups[] = {
63 &hdac_dev_attr_group,
64 NULL
65};
66
67/*
68 * Widget tree sysfs
69 *
70 * This is a tree showing the attributes of each widget. It appears like
71 * /sys/bus/hdaudioC0D0/widgets/04/caps
72 */
73
74struct widget_attribute;
75
76struct widget_attribute {
77 struct attribute attr;
78 ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid,
79 struct widget_attribute *attr, char *buf);
80 ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid,
81 struct widget_attribute *attr,
82 const char *buf, size_t count);
83};
84
85static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp)
86{
87 struct device *dev = kobj_to_dev(kobj->parent->parent);
88 int nid;
89 ssize_t ret;
90
91 ret = kstrtoint(kobj->name, 16, &nid);
92 if (ret < 0)
93 return ret;
94 *codecp = dev_to_hdac_dev(dev);
95 return nid;
96}
97
98static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr,
99 char *buf)
100{
101 struct widget_attribute *wid_attr =
102 container_of(attr, struct widget_attribute, attr);
103 struct hdac_device *codec;
104 int nid;
105
106 if (!wid_attr->show)
107 return -EIO;
108 nid = get_codec_nid(kobj, &codec);
109 if (nid < 0)
110 return nid;
111 return wid_attr->show(codec, nid, wid_attr, buf);
112}
113
114static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr,
115 const char *buf, size_t count)
116{
117 struct widget_attribute *wid_attr =
118 container_of(attr, struct widget_attribute, attr);
119 struct hdac_device *codec;
120 int nid;
121
122 if (!wid_attr->store)
123 return -EIO;
124 nid = get_codec_nid(kobj, &codec);
125 if (nid < 0)
126 return nid;
127 return wid_attr->store(codec, nid, wid_attr, buf, count);
128}
129
130static const struct sysfs_ops widget_sysfs_ops = {
131 .show = widget_attr_show,
132 .store = widget_attr_store,
133};
134
135static void widget_release(struct kobject *kobj)
136{
137 kfree(kobj);
138}
139
140static struct kobj_type widget_ktype = {
141 .release = widget_release,
142 .sysfs_ops = &widget_sysfs_ops,
143};
144
145#define WIDGET_ATTR_RO(_name) \
146 struct widget_attribute wid_attr_##_name = __ATTR_RO(_name)
147#define WIDGET_ATTR_RW(_name) \
148 struct widget_attribute wid_attr_##_name = __ATTR_RW(_name)
149
150static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid,
151 struct widget_attribute *attr, char *buf)
152{
153 return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid));
154}
155
156static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid,
157 struct widget_attribute *attr, char *buf)
158{
159 if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
160 return 0;
161 return sprintf(buf, "0x%08x\n",
162 snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP));
163}
164
165static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid,
166 struct widget_attribute *attr, char *buf)
167{
168 unsigned int val;
169
170 if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
171 return 0;
172 if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
173 return 0;
174 return sprintf(buf, "0x%08x\n", val);
175}
176
177static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid)
178{
179 if (nid == codec->afg || nid == codec->mfg)
180 return true;
181 switch (get_wcaps_type(get_wcaps(codec, nid))) {
182 case AC_WID_AUD_OUT:
183 case AC_WID_AUD_IN:
184 return true;
185 default:
186 return false;
187 }
188}
189
190static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid,
191 struct widget_attribute *attr, char *buf)
192{
193 if (!has_pcm_cap(codec, nid))
194 return 0;
195 return sprintf(buf, "0x%08x\n",
196 snd_hdac_read_parm(codec, nid, AC_PAR_PCM));
197}
198
199static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid,
200 struct widget_attribute *attr, char *buf)
201{
202 if (!has_pcm_cap(codec, nid))
203 return 0;
204 return sprintf(buf, "0x%08x\n",
205 snd_hdac_read_parm(codec, nid, AC_PAR_STREAM));
206}
207
208static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid,
209 struct widget_attribute *attr, char *buf)
210{
211 if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
212 return 0;
213 return sprintf(buf, "0x%08x\n",
214 snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP));
215}
216
217static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid,
218 struct widget_attribute *attr, char *buf)
219{
220 if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
221 return 0;
222 return sprintf(buf, "0x%08x\n",
223 snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP));
224}
225
226static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid,
227 struct widget_attribute *attr, char *buf)
228{
229 if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER))
230 return 0;
231 return sprintf(buf, "0x%08x\n",
232 snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE));
233}
234
235static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid,
236 struct widget_attribute *attr, char *buf)
237{
238 return sprintf(buf, "0x%08x\n",
239 snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP));
240}
241
242static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid,
243 struct widget_attribute *attr, char *buf)
244{
245 hda_nid_t list[32];
246 int i, nconns;
247 ssize_t ret = 0;
248
249 nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list));
250 if (nconns <= 0)
251 return nconns;
252 for (i = 0; i < nconns; i++)
253 ret += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]);
254 ret += sprintf(buf + ret, "\n");
255 return ret;
256}
257
258static WIDGET_ATTR_RO(caps);
259static WIDGET_ATTR_RO(pin_caps);
260static WIDGET_ATTR_RO(pin_cfg);
261static WIDGET_ATTR_RO(pcm_caps);
262static WIDGET_ATTR_RO(pcm_formats);
263static WIDGET_ATTR_RO(amp_in_caps);
264static WIDGET_ATTR_RO(amp_out_caps);
265static WIDGET_ATTR_RO(power_caps);
266static WIDGET_ATTR_RO(gpio_caps);
267static WIDGET_ATTR_RO(connections);
268
269static struct attribute *widget_node_attrs[] = {
270 &wid_attr_caps.attr,
271 &wid_attr_pin_caps.attr,
272 &wid_attr_pin_cfg.attr,
273 &wid_attr_pcm_caps.attr,
274 &wid_attr_pcm_formats.attr,
275 &wid_attr_amp_in_caps.attr,
276 &wid_attr_amp_out_caps.attr,
277 &wid_attr_power_caps.attr,
278 &wid_attr_connections.attr,
279 NULL,
280};
281
282static struct attribute *widget_afg_attrs[] = {
283 &wid_attr_pcm_caps.attr,
284 &wid_attr_pcm_formats.attr,
285 &wid_attr_amp_in_caps.attr,
286 &wid_attr_amp_out_caps.attr,
287 &wid_attr_power_caps.attr,
288 &wid_attr_gpio_caps.attr,
289 NULL,
290};
291
292static const struct attribute_group widget_node_group = {
293 .attrs = widget_node_attrs,
294};
295
296static const struct attribute_group widget_afg_group = {
297 .attrs = widget_afg_attrs,
298};
299
300static void free_widget_node(struct kobject *kobj,
301 const struct attribute_group *group)
302{
303 if (kobj) {
304 sysfs_remove_group(kobj, group);
305 kobject_put(kobj);
306 }
307}
308
309static void widget_tree_free(struct hdac_device *codec)
310{
311 struct hdac_widget_tree *tree = codec->widgets;
312 struct kobject **p;
313
314 if (!tree)
315 return;
316 if (tree->nodes) {
317 for (p = tree->nodes; *p; p++)
318 free_widget_node(*p, &widget_node_group);
319 kfree(tree->nodes);
320 }
321 free_widget_node(tree->afg, &widget_afg_group);
322 if (tree->root)
323 kobject_put(tree->root);
324 kfree(tree);
325 codec->widgets = NULL;
326}
327
328static int add_widget_node(struct kobject *parent, hda_nid_t nid,
329 const struct attribute_group *group,
330 struct kobject **res)
331{
332 struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
333 int err;
334
335 if (!kobj)
336 return -ENOMEM;
337 kobject_init(kobj, &widget_ktype);
338 err = kobject_add(kobj, parent, "%02x", nid);
339 if (err < 0)
340 return err;
341 err = sysfs_create_group(kobj, group);
342 if (err < 0) {
343 kobject_put(kobj);
344 return err;
345 }
346
347 *res = kobj;
348 return 0;
349}
350
351static int widget_tree_create(struct hdac_device *codec)
352{
353 struct hdac_widget_tree *tree;
354 int i, err;
355 hda_nid_t nid;
356
357 tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL);
358 if (!tree)
359 return -ENOMEM;
360
361 tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
362 if (!tree->root)
363 return -ENOMEM;
364
365 if (codec->afg) {
366 err = add_widget_node(tree->root, codec->afg,
367 &widget_afg_group, &tree->afg);
368 if (err < 0)
369 return err;
370 }
371
372 tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes),
373 GFP_KERNEL);
374 if (!tree->nodes)
375 return -ENOMEM;
376
377 for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
378 err = add_widget_node(tree->root, nid, &widget_node_group,
379 &tree->nodes[i]);
380 if (err < 0)
381 return err;
382 }
383
384 kobject_uevent(tree->root, KOBJ_CHANGE);
385 return 0;
386}
387
388int hda_widget_sysfs_init(struct hdac_device *codec)
389{
390 int err;
391
392 err = widget_tree_create(codec);
393 if (err < 0) {
394 widget_tree_free(codec);
395 return err;
396 }
397
398 return 0;
399}
400
401void hda_widget_sysfs_exit(struct hdac_device *codec)
402{
403 widget_tree_free(codec);
404}
diff --git a/sound/hda/local.h b/sound/hda/local.h
index a077d1f656f6..d692f417ddc0 100644
--- a/sound/hda/local.h
+++ b/sound/hda/local.h
@@ -16,4 +16,8 @@ static inline int get_wcaps_type(unsigned int wcaps)
16 return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; 16 return (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
17} 17}
18 18
19extern const struct attribute_group *hdac_dev_attr_groups[];
20int hda_widget_sysfs_init(struct hdac_device *codec);
21void hda_widget_sysfs_exit(struct hdac_device *codec);
22
19#endif /* __HDAC_LOCAL_H */ 23#endif /* __HDAC_LOCAL_H */
diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c
index ad276a9771db..130f672e6f37 100644
--- a/sound/pci/hda/hda_bind.c
+++ b/sound/pci/hda/hda_bind.c
@@ -240,7 +240,7 @@ int snd_hda_codec_configure(struct hda_codec *codec)
240 else 240 else
241 codec->probe_id = 0; 241 codec->probe_id = 0;
242 242
243 err = device_add(hda_codec_dev(codec)); 243 err = snd_hdac_device_register(&codec->core);
244 if (err < 0) 244 if (err < 0)
245 return err; 245 return err;
246 246
@@ -262,7 +262,7 @@ int snd_hda_codec_configure(struct hda_codec *codec)
262 return 0; 262 return 0;
263 263
264 error: 264 error:
265 device_del(hda_codec_dev(codec)); 265 snd_hdac_device_unregister(&codec->core);
266 return err; 266 return err;
267} 267}
268EXPORT_SYMBOL_GPL(snd_hda_codec_configure); 268EXPORT_SYMBOL_GPL(snd_hda_codec_configure);
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index ddfc0fbbee23..b162fc40348f 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -967,8 +967,7 @@ static int snd_hda_codec_dev_free(struct snd_device *device)
967 struct hda_codec *codec = device->device_data; 967 struct hda_codec *codec = device->device_data;
968 968
969 codec->in_freeing = 1; 969 codec->in_freeing = 1;
970 if (device_is_registered(hda_codec_dev(codec))) 970 snd_hdac_device_unregister(&codec->core);
971 device_del(hda_codec_dev(codec));
972 put_device(hda_codec_dev(codec)); 971 put_device(hda_codec_dev(codec));
973 return 0; 972 return 0;
974} 973}
@@ -2182,8 +2181,7 @@ int snd_hda_codec_reset(struct hda_codec *codec)
2182 return -EBUSY; 2181 return -EBUSY;
2183 2182
2184 /* OK, let it free */ 2183 /* OK, let it free */
2185 if (device_is_registered(hda_codec_dev(codec))) 2184 snd_hdac_device_unregister(&codec->core);
2186 device_del(hda_codec_dev(codec));
2187 2185
2188 /* allow device access again */ 2186 /* allow device access again */
2189 snd_hda_unlock_devices(bus); 2187 snd_hda_unlock_devices(bus);