Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0
2 : : /*
3 : : * sysfs support for HD-audio core device
4 : : */
5 : :
6 : : #include <linux/slab.h>
7 : : #include <linux/sysfs.h>
8 : : #include <linux/device.h>
9 : : #include <sound/core.h>
10 : : #include <sound/hdaudio.h>
11 : : #include "local.h"
12 : :
13 : : struct hdac_widget_tree {
14 : : struct kobject *root;
15 : : struct kobject *afg;
16 : : struct kobject **nodes;
17 : : };
18 : :
19 : : #define CODEC_ATTR(type) \
20 : : static ssize_t type##_show(struct device *dev, \
21 : : struct device_attribute *attr, \
22 : : char *buf) \
23 : : { \
24 : : struct hdac_device *codec = dev_to_hdac_dev(dev); \
25 : : return sprintf(buf, "0x%x\n", codec->type); \
26 : : } \
27 : : static DEVICE_ATTR_RO(type)
28 : :
29 : : #define CODEC_ATTR_STR(type) \
30 : : static ssize_t type##_show(struct device *dev, \
31 : : struct device_attribute *attr, \
32 : : char *buf) \
33 : : { \
34 : : struct hdac_device *codec = dev_to_hdac_dev(dev); \
35 : : return sprintf(buf, "%s\n", \
36 : : codec->type ? codec->type : ""); \
37 : : } \
38 : : static DEVICE_ATTR_RO(type)
39 : :
40 : 0 : CODEC_ATTR(type);
41 : 0 : CODEC_ATTR(vendor_id);
42 : 0 : CODEC_ATTR(subsystem_id);
43 : 0 : CODEC_ATTR(revision_id);
44 : 0 : CODEC_ATTR(afg);
45 : 0 : CODEC_ATTR(mfg);
46 [ # # ]: 0 : CODEC_ATTR_STR(vendor_name);
47 [ # # ]: 0 : CODEC_ATTR_STR(chip_name);
48 : :
49 : 0 : static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
50 : : char *buf)
51 : : {
52 : 0 : return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, 256);
53 : : }
54 : : static DEVICE_ATTR_RO(modalias);
55 : :
56 : : static struct attribute *hdac_dev_attrs[] = {
57 : : &dev_attr_type.attr,
58 : : &dev_attr_vendor_id.attr,
59 : : &dev_attr_subsystem_id.attr,
60 : : &dev_attr_revision_id.attr,
61 : : &dev_attr_afg.attr,
62 : : &dev_attr_mfg.attr,
63 : : &dev_attr_vendor_name.attr,
64 : : &dev_attr_chip_name.attr,
65 : : &dev_attr_modalias.attr,
66 : : NULL
67 : : };
68 : :
69 : : static struct attribute_group hdac_dev_attr_group = {
70 : : .attrs = hdac_dev_attrs,
71 : : };
72 : :
73 : : const struct attribute_group *hdac_dev_attr_groups[] = {
74 : : &hdac_dev_attr_group,
75 : : NULL
76 : : };
77 : :
78 : : /*
79 : : * Widget tree sysfs
80 : : *
81 : : * This is a tree showing the attributes of each widget. It appears like
82 : : * /sys/bus/hdaudioC0D0/widgets/04/caps
83 : : */
84 : :
85 : : struct widget_attribute;
86 : :
87 : : struct widget_attribute {
88 : : struct attribute attr;
89 : : ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid,
90 : : struct widget_attribute *attr, char *buf);
91 : : ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid,
92 : : struct widget_attribute *attr,
93 : : const char *buf, size_t count);
94 : : };
95 : :
96 : 0 : static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp)
97 : : {
98 : 0 : struct device *dev = kobj_to_dev(kobj->parent->parent);
99 : 0 : int nid;
100 : 0 : ssize_t ret;
101 : :
102 : 0 : ret = kstrtoint(kobj->name, 16, &nid);
103 [ # # # # ]: 0 : if (ret < 0)
104 : : return ret;
105 : 0 : *codecp = dev_to_hdac_dev(dev);
106 : 0 : return nid;
107 : : }
108 : :
109 : 0 : static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr,
110 : : char *buf)
111 : : {
112 : 0 : struct widget_attribute *wid_attr =
113 : 0 : container_of(attr, struct widget_attribute, attr);
114 : 0 : struct hdac_device *codec;
115 : 0 : int nid;
116 : :
117 [ # # ]: 0 : if (!wid_attr->show)
118 : : return -EIO;
119 : 0 : nid = get_codec_nid(kobj, &codec);
120 [ # # ]: 0 : if (nid < 0)
121 : 0 : return nid;
122 : 0 : return wid_attr->show(codec, nid, wid_attr, buf);
123 : : }
124 : :
125 : 0 : static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr,
126 : : const char *buf, size_t count)
127 : : {
128 : 0 : struct widget_attribute *wid_attr =
129 : 0 : container_of(attr, struct widget_attribute, attr);
130 : 0 : struct hdac_device *codec;
131 : 0 : int nid;
132 : :
133 [ # # ]: 0 : if (!wid_attr->store)
134 : : return -EIO;
135 : 0 : nid = get_codec_nid(kobj, &codec);
136 [ # # ]: 0 : if (nid < 0)
137 : 0 : return nid;
138 : 0 : return wid_attr->store(codec, nid, wid_attr, buf, count);
139 : : }
140 : :
141 : : static const struct sysfs_ops widget_sysfs_ops = {
142 : : .show = widget_attr_show,
143 : : .store = widget_attr_store,
144 : : };
145 : :
146 : 0 : static void widget_release(struct kobject *kobj)
147 : : {
148 : 0 : kfree(kobj);
149 : 0 : }
150 : :
151 : : static struct kobj_type widget_ktype = {
152 : : .release = widget_release,
153 : : .sysfs_ops = &widget_sysfs_ops,
154 : : };
155 : :
156 : : #define WIDGET_ATTR_RO(_name) \
157 : : struct widget_attribute wid_attr_##_name = __ATTR_RO(_name)
158 : : #define WIDGET_ATTR_RW(_name) \
159 : : struct widget_attribute wid_attr_##_name = __ATTR_RW(_name)
160 : :
161 : 0 : static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid,
162 : : struct widget_attribute *attr, char *buf)
163 : : {
164 : 0 : return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid));
165 : : }
166 : :
167 : 0 : static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid,
168 : : struct widget_attribute *attr, char *buf)
169 : : {
170 [ # # # # ]: 0 : if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
171 : : return 0;
172 : 0 : return sprintf(buf, "0x%08x\n",
173 : : snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP));
174 : : }
175 : :
176 : 0 : static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid,
177 : : struct widget_attribute *attr, char *buf)
178 : : {
179 : 0 : unsigned int val;
180 : :
181 [ # # # # ]: 0 : if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
182 : : return 0;
183 [ # # ]: 0 : if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
184 : : return 0;
185 : 0 : return sprintf(buf, "0x%08x\n", val);
186 : : }
187 : :
188 : 0 : static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid)
189 : : {
190 [ # # # # ]: 0 : if (nid == codec->afg || nid == codec->mfg)
191 : : return true;
192 [ # # # # ]: 0 : switch (get_wcaps_type(get_wcaps(codec, nid))) {
193 : : case AC_WID_AUD_OUT:
194 : : case AC_WID_AUD_IN:
195 : : return true;
196 : 0 : default:
197 : 0 : return false;
198 : : }
199 : : }
200 : :
201 : 0 : static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid,
202 : : struct widget_attribute *attr, char *buf)
203 : : {
204 [ # # ]: 0 : if (!has_pcm_cap(codec, nid))
205 : : return 0;
206 : 0 : return sprintf(buf, "0x%08x\n",
207 : : snd_hdac_read_parm(codec, nid, AC_PAR_PCM));
208 : : }
209 : :
210 : 0 : static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid,
211 : : struct widget_attribute *attr, char *buf)
212 : : {
213 [ # # ]: 0 : if (!has_pcm_cap(codec, nid))
214 : : return 0;
215 : 0 : return sprintf(buf, "0x%08x\n",
216 : : snd_hdac_read_parm(codec, nid, AC_PAR_STREAM));
217 : : }
218 : :
219 : 0 : static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid,
220 : : struct widget_attribute *attr, char *buf)
221 : : {
222 [ # # # # ]: 0 : if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
223 : : return 0;
224 : 0 : return sprintf(buf, "0x%08x\n",
225 : : snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP));
226 : : }
227 : :
228 : 0 : static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid,
229 : : struct widget_attribute *attr, char *buf)
230 : : {
231 [ # # # # ]: 0 : if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
232 : : return 0;
233 : 0 : return sprintf(buf, "0x%08x\n",
234 : : snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP));
235 : : }
236 : :
237 : 0 : static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid,
238 : : struct widget_attribute *attr, char *buf)
239 : : {
240 [ # # # # ]: 0 : if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER))
241 : : return 0;
242 : 0 : return sprintf(buf, "0x%08x\n",
243 : : snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE));
244 : : }
245 : :
246 : 0 : static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid,
247 : : struct widget_attribute *attr, char *buf)
248 : : {
249 : 0 : return sprintf(buf, "0x%08x\n",
250 : : snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP));
251 : : }
252 : :
253 : 0 : static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid,
254 : : struct widget_attribute *attr, char *buf)
255 : : {
256 : 0 : hda_nid_t list[32];
257 : 0 : int i, nconns;
258 : 0 : ssize_t ret = 0;
259 : :
260 : 0 : nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list));
261 [ # # ]: 0 : if (nconns <= 0)
262 : 0 : return nconns;
263 [ # # ]: 0 : for (i = 0; i < nconns; i++)
264 [ # # ]: 0 : ret += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]);
265 : 0 : ret += sprintf(buf + ret, "\n");
266 : 0 : return ret;
267 : : }
268 : :
269 : : static WIDGET_ATTR_RO(caps);
270 : : static WIDGET_ATTR_RO(pin_caps);
271 : : static WIDGET_ATTR_RO(pin_cfg);
272 : : static WIDGET_ATTR_RO(pcm_caps);
273 : : static WIDGET_ATTR_RO(pcm_formats);
274 : : static WIDGET_ATTR_RO(amp_in_caps);
275 : : static WIDGET_ATTR_RO(amp_out_caps);
276 : : static WIDGET_ATTR_RO(power_caps);
277 : : static WIDGET_ATTR_RO(gpio_caps);
278 : : static WIDGET_ATTR_RO(connections);
279 : :
280 : : static struct attribute *widget_node_attrs[] = {
281 : : &wid_attr_caps.attr,
282 : : &wid_attr_pin_caps.attr,
283 : : &wid_attr_pin_cfg.attr,
284 : : &wid_attr_pcm_caps.attr,
285 : : &wid_attr_pcm_formats.attr,
286 : : &wid_attr_amp_in_caps.attr,
287 : : &wid_attr_amp_out_caps.attr,
288 : : &wid_attr_power_caps.attr,
289 : : &wid_attr_connections.attr,
290 : : NULL,
291 : : };
292 : :
293 : : static struct attribute *widget_afg_attrs[] = {
294 : : &wid_attr_pcm_caps.attr,
295 : : &wid_attr_pcm_formats.attr,
296 : : &wid_attr_amp_in_caps.attr,
297 : : &wid_attr_amp_out_caps.attr,
298 : : &wid_attr_power_caps.attr,
299 : : &wid_attr_gpio_caps.attr,
300 : : NULL,
301 : : };
302 : :
303 : : static const struct attribute_group widget_node_group = {
304 : : .attrs = widget_node_attrs,
305 : : };
306 : :
307 : : static const struct attribute_group widget_afg_group = {
308 : : .attrs = widget_afg_attrs,
309 : : };
310 : :
311 : 0 : static void free_widget_node(struct kobject *kobj,
312 : : const struct attribute_group *group)
313 : : {
314 : 0 : if (kobj) {
315 : 0 : sysfs_remove_group(kobj, group);
316 : 0 : kobject_put(kobj);
317 : : }
318 : : }
319 : :
320 : : static void widget_tree_free(struct hdac_device *codec)
321 : : {
322 : : struct hdac_widget_tree *tree = codec->widgets;
323 : : struct kobject **p;
324 : :
325 : : if (!tree)
326 : : return;
327 : : free_widget_node(tree->afg, &widget_afg_group);
328 : : if (tree->nodes) {
329 : : for (p = tree->nodes; *p; p++)
330 : : free_widget_node(*p, &widget_node_group);
331 : : kfree(tree->nodes);
332 : : }
333 : : kobject_put(tree->root);
334 : : kfree(tree);
335 : : codec->widgets = NULL;
336 : : }
337 : :
338 : 0 : static int add_widget_node(struct kobject *parent, hda_nid_t nid,
339 : : const struct attribute_group *group,
340 : : struct kobject **res)
341 : : {
342 : 0 : struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
343 : 0 : int err;
344 : :
345 [ # # ]: 0 : if (!kobj)
346 : : return -ENOMEM;
347 : 0 : kobject_init(kobj, &widget_ktype);
348 : 0 : err = kobject_add(kobj, parent, "%02x", nid);
349 [ # # ]: 0 : if (err < 0)
350 : : return err;
351 : 0 : err = sysfs_create_group(kobj, group);
352 [ # # ]: 0 : if (err < 0) {
353 : 0 : kobject_put(kobj);
354 : 0 : return err;
355 : : }
356 : :
357 : 0 : *res = kobj;
358 : 0 : return 0;
359 : : }
360 : :
361 : 0 : static int widget_tree_create(struct hdac_device *codec)
362 : : {
363 : 0 : struct hdac_widget_tree *tree;
364 : 0 : int i, err;
365 : 0 : hda_nid_t nid;
366 : :
367 : 0 : tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL);
368 [ # # ]: 0 : if (!tree)
369 : : return -ENOMEM;
370 : :
371 : 0 : tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
372 [ # # ]: 0 : if (!tree->root)
373 : : return -ENOMEM;
374 : :
375 : 0 : tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes),
376 : : GFP_KERNEL);
377 [ # # ]: 0 : if (!tree->nodes)
378 : : return -ENOMEM;
379 : :
380 [ # # ]: 0 : for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
381 : 0 : err = add_widget_node(tree->root, nid, &widget_node_group,
382 : 0 : &tree->nodes[i]);
383 [ # # ]: 0 : if (err < 0)
384 : 0 : return err;
385 : : }
386 : :
387 [ # # ]: 0 : if (codec->afg) {
388 : 0 : err = add_widget_node(tree->root, codec->afg,
389 : : &widget_afg_group, &tree->afg);
390 [ # # ]: 0 : if (err < 0)
391 : : return err;
392 : : }
393 : :
394 : 0 : kobject_uevent(tree->root, KOBJ_CHANGE);
395 : 0 : return 0;
396 : : }
397 : :
398 : : /* call with codec->widget_lock held */
399 : 0 : int hda_widget_sysfs_init(struct hdac_device *codec)
400 : : {
401 : 0 : int err;
402 : :
403 [ # # ]: 0 : if (codec->widgets)
404 : : return 0; /* already created */
405 : :
406 : 0 : err = widget_tree_create(codec);
407 [ # # ]: 0 : if (err < 0) {
408 : 0 : widget_tree_free(codec);
409 : 0 : return err;
410 : : }
411 : :
412 : : return 0;
413 : : }
414 : :
415 : : /* call with codec->widget_lock held */
416 : 0 : void hda_widget_sysfs_exit(struct hdac_device *codec)
417 : : {
418 : 0 : widget_tree_free(codec);
419 : 0 : }
420 : :
421 : : /* call with codec->widget_lock held */
422 : 0 : int hda_widget_sysfs_reinit(struct hdac_device *codec,
423 : : hda_nid_t start_nid, int num_nodes)
424 : : {
425 : 0 : struct hdac_widget_tree *tree;
426 : 0 : hda_nid_t end_nid = start_nid + num_nodes;
427 : 0 : hda_nid_t nid;
428 : 0 : int i;
429 : :
430 [ # # ]: 0 : if (!codec->widgets)
431 : : return 0;
432 : :
433 : 0 : tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL);
434 [ # # ]: 0 : if (!tree)
435 : : return -ENOMEM;
436 : :
437 : 0 : tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL);
438 [ # # ]: 0 : if (!tree->nodes) {
439 : 0 : kfree(tree);
440 : 0 : return -ENOMEM;
441 : : }
442 : :
443 : : /* prune non-existing nodes */
444 [ # # ]: 0 : for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) {
445 [ # # ]: 0 : if (nid < start_nid || nid >= end_nid)
446 [ # # ]: 0 : free_widget_node(codec->widgets->nodes[i],
447 : : &widget_node_group);
448 : : }
449 : :
450 : : /* add new nodes */
451 [ # # ]: 0 : for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) {
452 [ # # # # ]: 0 : if (nid < codec->start_nid || nid >= codec->end_nid)
453 : 0 : add_widget_node(tree->root, nid, &widget_node_group,
454 : 0 : &tree->nodes[i]);
455 : : else
456 : 0 : tree->nodes[i] =
457 : 0 : codec->widgets->nodes[nid - codec->start_nid];
458 : : }
459 : :
460 : : /* replace with the new tree */
461 : 0 : kfree(codec->widgets->nodes);
462 : 0 : kfree(codec->widgets);
463 : 0 : codec->widgets = tree;
464 : :
465 : 0 : kobject_uevent(tree->root, KOBJ_CHANGE);
466 : 0 : return 0;
467 : : }
|