hdac_sysfs.c (11441B)
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 13struct hdac_widget_tree { 14 struct kobject *root; 15 struct kobject *afg; 16 struct kobject **nodes; 17}; 18 19#define CODEC_ATTR(type) \ 20static 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} \ 27static DEVICE_ATTR_RO(type) 28 29#define CODEC_ATTR_STR(type) \ 30static 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} \ 38static DEVICE_ATTR_RO(type) 39 40CODEC_ATTR(type); 41CODEC_ATTR(vendor_id); 42CODEC_ATTR(subsystem_id); 43CODEC_ATTR(revision_id); 44CODEC_ATTR(afg); 45CODEC_ATTR(mfg); 46CODEC_ATTR_STR(vendor_name); 47CODEC_ATTR_STR(chip_name); 48 49static 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} 54static DEVICE_ATTR_RO(modalias); 55 56static 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 69static const struct attribute_group hdac_dev_attr_group = { 70 .attrs = hdac_dev_attrs, 71}; 72 73const 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 85struct widget_attribute; 86 87struct 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 96static 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 109static 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 125static 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 141static const struct sysfs_ops widget_sysfs_ops = { 142 .show = widget_attr_show, 143 .store = widget_attr_store, 144}; 145 146static void widget_release(struct kobject *kobj) 147{ 148 kfree(kobj); 149} 150 151static 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 161static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid, 162 struct widget_attribute *attr, char *buf) 163{ 164 return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid)); 165} 166 167static 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 sprintf(buf, "0x%08x\n", 173 snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)); 174} 175 176static 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 sprintf(buf, "0x%08x\n", val); 186} 187 188static 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 201static 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 sprintf(buf, "0x%08x\n", 207 snd_hdac_read_parm(codec, nid, AC_PAR_PCM)); 208} 209 210static 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 sprintf(buf, "0x%08x\n", 216 snd_hdac_read_parm(codec, nid, AC_PAR_STREAM)); 217} 218 219static 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 sprintf(buf, "0x%08x\n", 225 snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP)); 226} 227 228static 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 sprintf(buf, "0x%08x\n", 234 snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP)); 235} 236 237static 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 sprintf(buf, "0x%08x\n", 243 snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE)); 244} 245 246static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid, 247 struct widget_attribute *attr, char *buf) 248{ 249 return sprintf(buf, "0x%08x\n", 250 snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP)); 251} 252 253static 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 += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]); 265 ret += sprintf(buf + ret, "\n"); 266 return ret; 267} 268 269static WIDGET_ATTR_RO(caps); 270static WIDGET_ATTR_RO(pin_caps); 271static WIDGET_ATTR_RO(pin_cfg); 272static WIDGET_ATTR_RO(pcm_caps); 273static WIDGET_ATTR_RO(pcm_formats); 274static WIDGET_ATTR_RO(amp_in_caps); 275static WIDGET_ATTR_RO(amp_out_caps); 276static WIDGET_ATTR_RO(power_caps); 277static WIDGET_ATTR_RO(gpio_caps); 278static WIDGET_ATTR_RO(connections); 279 280static 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 293static 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 303static const struct attribute_group widget_node_group = { 304 .attrs = widget_node_attrs, 305}; 306 307static const struct attribute_group widget_afg_group = { 308 .attrs = widget_afg_attrs, 309}; 310 311static 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 320static 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 338static 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 return err; 351 err = sysfs_create_group(kobj, group); 352 if (err < 0) { 353 kobject_put(kobj); 354 return err; 355 } 356 357 *res = kobj; 358 return 0; 359} 360 361static int widget_tree_create(struct hdac_device *codec) 362{ 363 struct hdac_widget_tree *tree; 364 int i, err; 365 hda_nid_t nid; 366 367 tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL); 368 if (!tree) 369 return -ENOMEM; 370 371 tree->root = kobject_create_and_add("widgets", &codec->dev.kobj); 372 if (!tree->root) 373 return -ENOMEM; 374 375 tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes), 376 GFP_KERNEL); 377 if (!tree->nodes) 378 return -ENOMEM; 379 380 for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { 381 err = add_widget_node(tree->root, nid, &widget_node_group, 382 &tree->nodes[i]); 383 if (err < 0) 384 return err; 385 } 386 387 if (codec->afg) { 388 err = add_widget_node(tree->root, codec->afg, 389 &widget_afg_group, &tree->afg); 390 if (err < 0) 391 return err; 392 } 393 394 kobject_uevent(tree->root, KOBJ_CHANGE); 395 return 0; 396} 397 398/* call with codec->widget_lock held */ 399int hda_widget_sysfs_init(struct hdac_device *codec) 400{ 401 int err; 402 403 if (codec->widgets) 404 return 0; /* already created */ 405 406 err = widget_tree_create(codec); 407 if (err < 0) { 408 widget_tree_free(codec); 409 return err; 410 } 411 412 return 0; 413} 414 415/* call with codec->widget_lock held */ 416void hda_widget_sysfs_exit(struct hdac_device *codec) 417{ 418 widget_tree_free(codec); 419} 420 421/* call with codec->widget_lock held */ 422int hda_widget_sysfs_reinit(struct hdac_device *codec, 423 hda_nid_t start_nid, int num_nodes) 424{ 425 struct hdac_widget_tree *tree; 426 hda_nid_t end_nid = start_nid + num_nodes; 427 hda_nid_t nid; 428 int i; 429 430 if (!codec->widgets) 431 return 0; 432 433 tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL); 434 if (!tree) 435 return -ENOMEM; 436 437 tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL); 438 if (!tree->nodes) { 439 kfree(tree); 440 return -ENOMEM; 441 } 442 443 /* prune non-existing nodes */ 444 for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { 445 if (nid < start_nid || nid >= end_nid) 446 free_widget_node(codec->widgets->nodes[i], 447 &widget_node_group); 448 } 449 450 /* add new nodes */ 451 for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) { 452 if (nid < codec->start_nid || nid >= codec->end_nid) 453 add_widget_node(tree->root, nid, &widget_node_group, 454 &tree->nodes[i]); 455 else 456 tree->nodes[i] = 457 codec->widgets->nodes[nid - codec->start_nid]; 458 } 459 460 /* replace with the new tree */ 461 kfree(codec->widgets->nodes); 462 kfree(codec->widgets); 463 codec->widgets = tree; 464 465 kobject_uevent(tree->root, KOBJ_CHANGE); 466 return 0; 467}