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", 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, "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, 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 (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, "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 && !(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 && !(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 && !(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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.