Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/hda/core/sysfs.c
26424 views
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, 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(kobj->name, 16, &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, &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, &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(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, "0x%08x\n", snd_hdac_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 (snd_hdac_get_wcaps_type(snd_hdac_get_wcaps(codec, nid)) != AC_WID_PIN)
171
return 0;
172
return sysfs_emit(buf, "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 (snd_hdac_get_wcaps_type(snd_hdac_get_wcaps(codec, nid)) != AC_WID_PIN)
182
return 0;
183
if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val))
184
return 0;
185
return sysfs_emit(buf, "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 (snd_hdac_get_wcaps_type(snd_hdac_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, "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, "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 && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
223
return 0;
224
return sysfs_emit(buf, "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 && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_OUT_AMP))
232
return 0;
233
return sysfs_emit(buf, "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 && !(snd_hdac_get_wcaps(codec, nid) & AC_WCAP_POWER))
241
return 0;
242
return sysfs_emit(buf, "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, "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, list, ARRAY_SIZE(list));
261
if (nconns <= 0)
262
return nconns;
263
for (i = 0; i < nconns; i++)
264
ret += sysfs_emit_at(buf, ret, "%s0x%02x", i ? " " : "", list[i]);
265
ret += sysfs_emit_at(buf, ret, "\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, 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(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
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(sizeof(*kobj), GFP_KERNEL);
343
int err;
344
345
if (!kobj)
346
return -ENOMEM;
347
kobject_init(kobj, &widget_ktype);
348
err = kobject_add(kobj, parent, "%02x", nid);
349
if (err < 0) {
350
kobject_put(kobj);
351
return err;
352
}
353
err = sysfs_create_group(kobj, 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(sizeof(*tree), GFP_KERNEL);
370
if (!tree)
371
return -ENOMEM;
372
373
tree->root = kobject_create_and_add("widgets", &codec->dev.kobj);
374
if (!tree->root)
375
return -ENOMEM;
376
377
tree->nodes = kcalloc(codec->num_nodes + 1, 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(tree->root, nid, &widget_node_group,
384
&tree->nodes[i]);
385
if (err < 0)
386
return err;
387
}
388
389
if (codec->afg) {
390
err = add_widget_node(tree->root, codec->afg,
391
&widget_afg_group, &tree->afg);
392
if (err < 0)
393
return err;
394
}
395
396
kobject_uevent(tree->root, 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(codec->widgets, sizeof(*tree), GFP_KERNEL);
436
if (!tree)
437
return -ENOMEM;
438
439
tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL);
440
if (!tree->nodes) {
441
kfree(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(codec->widgets->nodes[i],
449
&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(tree->root, nid, &widget_node_group,
456
&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(codec->widgets->nodes);
464
kfree(codec->widgets);
465
codec->widgets = tree;
466
467
kobject_uevent(tree->root, KOBJ_CHANGE);
468
return 0;
469
}
470
471