470 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * sysfs support for HD-audio core device4 */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 sysfs_emit(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 sysfs_emit(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 NULL67};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 NULL76};77 78/*79 * Widget tree sysfs80 *81 * This is a tree showing the attributes of each widget. It appears like82 * /sys/bus/hdaudioC0D0/widgets/04/caps83 */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 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 161static 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 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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 sysfs_emit(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 += sysfs_emit_at(buf, ret, "%s0x%02x", i ? " " : "", list[i]);265 ret += sysfs_emit_at(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 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 363static 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 */401int 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 */418void hda_widget_sysfs_exit(struct hdac_device *codec)419{420 widget_tree_free(codec);421}422 423/* call with codec->widget_lock held */424int 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 else458 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