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 sysfs_emit(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 sysfs_emit(buf, "%s\n", \ |
36 | codec->type ? codec->type : ""); \ |
37 | } \ |
38 | static DEVICE_ATTR_RO(type) |
39 | |
40 | CODEC_ATTR(type); |
41 | CODEC_ATTR(vendor_id); |
42 | CODEC_ATTR(subsystem_id); |
43 | CODEC_ATTR(revision_id); |
44 | CODEC_ATTR(afg); |
45 | CODEC_ATTR(mfg); |
46 | CODEC_ATTR_STR(vendor_name); |
47 | CODEC_ATTR_STR(chip_name); |
48 | |
49 | static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, |
50 | char *buf) |
51 | { |
52 | return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, size: 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 const 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 | static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp) |
97 | { |
98 | struct device *dev = kobj_to_dev(kobj->parent->parent); |
99 | int nid; |
100 | ssize_t ret; |
101 | |
102 | ret = kstrtoint(s: kobj->name, base: 16, res: &nid); |
103 | if (ret < 0) |
104 | return ret; |
105 | *codecp = dev_to_hdac_dev(dev); |
106 | return nid; |
107 | } |
108 | |
109 | static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr, |
110 | char *buf) |
111 | { |
112 | struct widget_attribute *wid_attr = |
113 | container_of(attr, struct widget_attribute, attr); |
114 | struct hdac_device *codec; |
115 | int nid; |
116 | |
117 | if (!wid_attr->show) |
118 | return -EIO; |
119 | nid = get_codec_nid(kobj, codecp: &codec); |
120 | if (nid < 0) |
121 | return nid; |
122 | return wid_attr->show(codec, nid, wid_attr, buf); |
123 | } |
124 | |
125 | static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr, |
126 | const char *buf, size_t count) |
127 | { |
128 | struct widget_attribute *wid_attr = |
129 | container_of(attr, struct widget_attribute, attr); |
130 | struct hdac_device *codec; |
131 | int nid; |
132 | |
133 | if (!wid_attr->store) |
134 | return -EIO; |
135 | nid = get_codec_nid(kobj, codecp: &codec); |
136 | if (nid < 0) |
137 | return nid; |
138 | 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 | static void widget_release(struct kobject *kobj) |
147 | { |
148 | kfree(objp: kobj); |
149 | } |
150 | |
151 | static const 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 | static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid, |
162 | struct widget_attribute *attr, char *buf) |
163 | { |
164 | return sysfs_emit(buf, fmt: "0x%08x\n" , get_wcaps(codec, nid)); |
165 | } |
166 | |
167 | static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid, |
168 | struct widget_attribute *attr, char *buf) |
169 | { |
170 | if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) |
171 | return 0; |
172 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
173 | snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)); |
174 | } |
175 | |
176 | static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid, |
177 | struct widget_attribute *attr, char *buf) |
178 | { |
179 | unsigned int val; |
180 | |
181 | if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) |
182 | return 0; |
183 | if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, parm: 0, res: &val)) |
184 | return 0; |
185 | return sysfs_emit(buf, fmt: "0x%08x\n" , val); |
186 | } |
187 | |
188 | static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid) |
189 | { |
190 | if (nid == codec->afg || nid == codec->mfg) |
191 | return true; |
192 | switch (get_wcaps_type(get_wcaps(codec, nid))) { |
193 | case AC_WID_AUD_OUT: |
194 | case AC_WID_AUD_IN: |
195 | return true; |
196 | default: |
197 | return false; |
198 | } |
199 | } |
200 | |
201 | static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid, |
202 | struct widget_attribute *attr, char *buf) |
203 | { |
204 | if (!has_pcm_cap(codec, nid)) |
205 | return 0; |
206 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
207 | snd_hdac_read_parm(codec, nid, AC_PAR_PCM)); |
208 | } |
209 | |
210 | static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid, |
211 | struct widget_attribute *attr, char *buf) |
212 | { |
213 | if (!has_pcm_cap(codec, nid)) |
214 | return 0; |
215 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
216 | snd_hdac_read_parm(codec, nid, AC_PAR_STREAM)); |
217 | } |
218 | |
219 | static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid, |
220 | struct widget_attribute *attr, char *buf) |
221 | { |
222 | if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP)) |
223 | return 0; |
224 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
225 | snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP)); |
226 | } |
227 | |
228 | static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid, |
229 | struct widget_attribute *attr, char *buf) |
230 | { |
231 | if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP)) |
232 | return 0; |
233 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
234 | snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP)); |
235 | } |
236 | |
237 | static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid, |
238 | struct widget_attribute *attr, char *buf) |
239 | { |
240 | if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER)) |
241 | return 0; |
242 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
243 | snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE)); |
244 | } |
245 | |
246 | static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid, |
247 | struct widget_attribute *attr, char *buf) |
248 | { |
249 | return sysfs_emit(buf, fmt: "0x%08x\n" , |
250 | snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP)); |
251 | } |
252 | |
253 | static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid, |
254 | struct widget_attribute *attr, char *buf) |
255 | { |
256 | hda_nid_t list[32]; |
257 | int i, nconns; |
258 | ssize_t ret = 0; |
259 | |
260 | nconns = snd_hdac_get_connections(codec, nid, conn_list: list, ARRAY_SIZE(list)); |
261 | if (nconns <= 0) |
262 | return nconns; |
263 | for (i = 0; i < nconns; i++) |
264 | ret += sysfs_emit_at(buf, at: ret, fmt: "%s0x%02x" , i ? " " : "" , list[i]); |
265 | ret += sysfs_emit_at(buf, at: ret, fmt: "\n" ); |
266 | 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 | static void free_widget_node(struct kobject *kobj, |
312 | const struct attribute_group *group) |
313 | { |
314 | if (kobj) { |
315 | sysfs_remove_group(kobj, grp: group); |
316 | 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(kobj: tree->afg, group: &widget_afg_group); |
328 | if (tree->nodes) { |
329 | for (p = tree->nodes; *p; p++) |
330 | free_widget_node(kobj: *p, group: &widget_node_group); |
331 | kfree(objp: tree->nodes); |
332 | } |
333 | kobject_put(kobj: tree->root); |
334 | kfree(objp: tree); |
335 | codec->widgets = NULL; |
336 | } |
337 | |
338 | static int add_widget_node(struct kobject *parent, hda_nid_t nid, |
339 | const struct attribute_group *group, |
340 | struct kobject **res) |
341 | { |
342 | struct kobject *kobj = kzalloc(size: sizeof(*kobj), GFP_KERNEL); |
343 | int err; |
344 | |
345 | if (!kobj) |
346 | return -ENOMEM; |
347 | kobject_init(kobj, ktype: &widget_ktype); |
348 | err = kobject_add(kobj, parent, fmt: "%02x" , nid); |
349 | if (err < 0) { |
350 | kobject_put(kobj); |
351 | return err; |
352 | } |
353 | err = sysfs_create_group(kobj, grp: group); |
354 | if (err < 0) { |
355 | kobject_put(kobj); |
356 | return err; |
357 | } |
358 | |
359 | *res = kobj; |
360 | return 0; |
361 | } |
362 | |
363 | static int widget_tree_create(struct hdac_device *codec) |
364 | { |
365 | struct hdac_widget_tree *tree; |
366 | int i, err; |
367 | hda_nid_t nid; |
368 | |
369 | tree = codec->widgets = kzalloc(size: sizeof(*tree), GFP_KERNEL); |
370 | if (!tree) |
371 | return -ENOMEM; |
372 | |
373 | tree->root = kobject_create_and_add(name: "widgets" , parent: &codec->dev.kobj); |
374 | if (!tree->root) |
375 | return -ENOMEM; |
376 | |
377 | tree->nodes = kcalloc(n: codec->num_nodes + 1, size: sizeof(*tree->nodes), |
378 | GFP_KERNEL); |
379 | if (!tree->nodes) |
380 | return -ENOMEM; |
381 | |
382 | for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { |
383 | err = add_widget_node(parent: tree->root, nid, group: &widget_node_group, |
384 | res: &tree->nodes[i]); |
385 | if (err < 0) |
386 | return err; |
387 | } |
388 | |
389 | if (codec->afg) { |
390 | err = add_widget_node(parent: tree->root, nid: codec->afg, |
391 | group: &widget_afg_group, res: &tree->afg); |
392 | if (err < 0) |
393 | return err; |
394 | } |
395 | |
396 | kobject_uevent(kobj: tree->root, action: KOBJ_CHANGE); |
397 | return 0; |
398 | } |
399 | |
400 | /* call with codec->widget_lock held */ |
401 | int hda_widget_sysfs_init(struct hdac_device *codec) |
402 | { |
403 | int err; |
404 | |
405 | if (codec->widgets) |
406 | return 0; /* already created */ |
407 | |
408 | err = widget_tree_create(codec); |
409 | if (err < 0) { |
410 | widget_tree_free(codec); |
411 | return err; |
412 | } |
413 | |
414 | return 0; |
415 | } |
416 | |
417 | /* call with codec->widget_lock held */ |
418 | void hda_widget_sysfs_exit(struct hdac_device *codec) |
419 | { |
420 | widget_tree_free(codec); |
421 | } |
422 | |
423 | /* call with codec->widget_lock held */ |
424 | int hda_widget_sysfs_reinit(struct hdac_device *codec, |
425 | hda_nid_t start_nid, int num_nodes) |
426 | { |
427 | struct hdac_widget_tree *tree; |
428 | hda_nid_t end_nid = start_nid + num_nodes; |
429 | hda_nid_t nid; |
430 | int i; |
431 | |
432 | if (!codec->widgets) |
433 | return 0; |
434 | |
435 | tree = kmemdup(p: codec->widgets, size: sizeof(*tree), GFP_KERNEL); |
436 | if (!tree) |
437 | return -ENOMEM; |
438 | |
439 | tree->nodes = kcalloc(n: num_nodes + 1, size: sizeof(*tree->nodes), GFP_KERNEL); |
440 | if (!tree->nodes) { |
441 | kfree(objp: tree); |
442 | return -ENOMEM; |
443 | } |
444 | |
445 | /* prune non-existing nodes */ |
446 | for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { |
447 | if (nid < start_nid || nid >= end_nid) |
448 | free_widget_node(kobj: codec->widgets->nodes[i], |
449 | group: &widget_node_group); |
450 | } |
451 | |
452 | /* add new nodes */ |
453 | for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) { |
454 | if (nid < codec->start_nid || nid >= codec->end_nid) |
455 | add_widget_node(parent: tree->root, nid, group: &widget_node_group, |
456 | res: &tree->nodes[i]); |
457 | else |
458 | tree->nodes[i] = |
459 | codec->widgets->nodes[nid - codec->start_nid]; |
460 | } |
461 | |
462 | /* replace with the new tree */ |
463 | kfree(objp: codec->widgets->nodes); |
464 | kfree(objp: codec->widgets); |
465 | codec->widgets = tree; |
466 | |
467 | kobject_uevent(kobj: tree->root, action: KOBJ_CHANGE); |
468 | return 0; |
469 | } |
470 | |