// SPDX-License-Identifier: GPL-2.0 /* * sysfs support for HD-audio core device */ #include #include #include #include #include #include "local.h" struct hdac_widget_tree { struct kobject *root; struct kobject *afg; struct kobject **nodes; }; #define CODEC_ATTR(type) \ static ssize_t type##_show(struct device *dev, \ struct device_attribute *attr, \ char *buf) \ { \ struct hdac_device *codec = dev_to_hdac_dev(dev); \ return sprintf(buf, "0x%x\n", codec->type); \ } \ static DEVICE_ATTR_RO(type) #define CODEC_ATTR_STR(type) \ static ssize_t type##_show(struct device *dev, \ struct device_attribute *attr, \ char *buf) \ { \ struct hdac_device *codec = dev_to_hdac_dev(dev); \ return sprintf(buf, "%s\n", \ codec->type ? codec->type : ""); \ } \ static DEVICE_ATTR_RO(type) CODEC_ATTR(type); CODEC_ATTR(vendor_id); CODEC_ATTR(subsystem_id); CODEC_ATTR(revision_id); CODEC_ATTR(afg); CODEC_ATTR(mfg); CODEC_ATTR_STR(vendor_name); CODEC_ATTR_STR(chip_name); static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) { return snd_hdac_codec_modalias(dev_to_hdac_dev(dev), buf, 256); } static DEVICE_ATTR_RO(modalias); static struct attribute *hdac_dev_attrs[] = { &dev_attr_type.attr, &dev_attr_vendor_id.attr, &dev_attr_subsystem_id.attr, &dev_attr_revision_id.attr, &dev_attr_afg.attr, &dev_attr_mfg.attr, &dev_attr_vendor_name.attr, &dev_attr_chip_name.attr, &dev_attr_modalias.attr, NULL }; static struct attribute_group hdac_dev_attr_group = { .attrs = hdac_dev_attrs, }; const struct attribute_group *hdac_dev_attr_groups[] = { &hdac_dev_attr_group, NULL }; /* * Widget tree sysfs * * This is a tree showing the attributes of each widget. It appears like * /sys/bus/hdaudioC0D0/widgets/04/caps */ struct widget_attribute; struct widget_attribute { struct attribute attr; ssize_t (*show)(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf); ssize_t (*store)(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, const char *buf, size_t count); }; static int get_codec_nid(struct kobject *kobj, struct hdac_device **codecp) { struct device *dev = kobj_to_dev(kobj->parent->parent); int nid; ssize_t ret; ret = kstrtoint(kobj->name, 16, &nid); if (ret < 0) return ret; *codecp = dev_to_hdac_dev(dev); return nid; } static ssize_t widget_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct widget_attribute *wid_attr = container_of(attr, struct widget_attribute, attr); struct hdac_device *codec; int nid; if (!wid_attr->show) return -EIO; nid = get_codec_nid(kobj, &codec); if (nid < 0) return nid; return wid_attr->show(codec, nid, wid_attr, buf); } static ssize_t widget_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct widget_attribute *wid_attr = container_of(attr, struct widget_attribute, attr); struct hdac_device *codec; int nid; if (!wid_attr->store) return -EIO; nid = get_codec_nid(kobj, &codec); if (nid < 0) return nid; return wid_attr->store(codec, nid, wid_attr, buf, count); } static const struct sysfs_ops widget_sysfs_ops = { .show = widget_attr_show, .store = widget_attr_store, }; static void widget_release(struct kobject *kobj) { kfree(kobj); } static struct kobj_type widget_ktype = { .release = widget_release, .sysfs_ops = &widget_sysfs_ops, }; #define WIDGET_ATTR_RO(_name) \ struct widget_attribute wid_attr_##_name = __ATTR_RO(_name) #define WIDGET_ATTR_RW(_name) \ struct widget_attribute wid_attr_##_name = __ATTR_RW(_name) static ssize_t caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { return sprintf(buf, "0x%08x\n", get_wcaps(codec, nid)); } static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_PIN_CAP)); } static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { unsigned int val; if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) return 0; if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val)) return 0; return sprintf(buf, "0x%08x\n", val); } static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid) { if (nid == codec->afg || nid == codec->mfg) return true; switch (get_wcaps_type(get_wcaps(codec, nid))) { case AC_WID_AUD_OUT: case AC_WID_AUD_IN: return true; default: return false; } } static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (!has_pcm_cap(codec, nid)) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_PCM)); } static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (!has_pcm_cap(codec, nid)) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_STREAM)); } static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_IN_AMP)) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_AMP_IN_CAP)); } static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_OUT_AMP)) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_AMP_OUT_CAP)); } static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { if (nid != codec->afg && !(get_wcaps(codec, nid) & AC_WCAP_POWER)) return 0; return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_POWER_STATE)); } static ssize_t gpio_caps_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { return sprintf(buf, "0x%08x\n", snd_hdac_read_parm(codec, nid, AC_PAR_GPIO_CAP)); } static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid, struct widget_attribute *attr, char *buf) { hda_nid_t list[32]; int i, nconns; ssize_t ret = 0; nconns = snd_hdac_get_connections(codec, nid, list, ARRAY_SIZE(list)); if (nconns <= 0) return nconns; for (i = 0; i < nconns; i++) ret += sprintf(buf + ret, "%s0x%02x", i ? " " : "", list[i]); ret += sprintf(buf + ret, "\n"); return ret; } static WIDGET_ATTR_RO(caps); static WIDGET_ATTR_RO(pin_caps); static WIDGET_ATTR_RO(pin_cfg); static WIDGET_ATTR_RO(pcm_caps); static WIDGET_ATTR_RO(pcm_formats); static WIDGET_ATTR_RO(amp_in_caps); static WIDGET_ATTR_RO(amp_out_caps); static WIDGET_ATTR_RO(power_caps); static WIDGET_ATTR_RO(gpio_caps); static WIDGET_ATTR_RO(connections); static struct attribute *widget_node_attrs[] = { &wid_attr_caps.attr, &wid_attr_pin_caps.attr, &wid_attr_pin_cfg.attr, &wid_attr_pcm_caps.attr, &wid_attr_pcm_formats.attr, &wid_attr_amp_in_caps.attr, &wid_attr_amp_out_caps.attr, &wid_attr_power_caps.attr, &wid_attr_connections.attr, NULL, }; static struct attribute *widget_afg_attrs[] = { &wid_attr_pcm_caps.attr, &wid_attr_pcm_formats.attr, &wid_attr_amp_in_caps.attr, &wid_attr_amp_out_caps.attr, &wid_attr_power_caps.attr, &wid_attr_gpio_caps.attr, NULL, }; static const struct attribute_group widget_node_group = { .attrs = widget_node_attrs, }; static const struct attribute_group widget_afg_group = { .attrs = widget_afg_attrs, }; static void free_widget_node(struct kobject *kobj, const struct attribute_group *group) { if (kobj) { sysfs_remove_group(kobj, group); kobject_put(kobj); } } static void widget_tree_free(struct hdac_device *codec) { struct hdac_widget_tree *tree = codec->widgets; struct kobject **p; if (!tree) return; free_widget_node(tree->afg, &widget_afg_group); if (tree->nodes) { for (p = tree->nodes; *p; p++) free_widget_node(*p, &widget_node_group); kfree(tree->nodes); } kobject_put(tree->root); kfree(tree); codec->widgets = NULL; } static int add_widget_node(struct kobject *parent, hda_nid_t nid, const struct attribute_group *group, struct kobject **res) { struct kobject *kobj = kzalloc(sizeof(*kobj), GFP_KERNEL); int err; if (!kobj) return -ENOMEM; kobject_init(kobj, &widget_ktype); err = kobject_add(kobj, parent, "%02x", nid); if (err < 0) return err; err = sysfs_create_group(kobj, group); if (err < 0) { kobject_put(kobj); return err; } *res = kobj; return 0; } static int widget_tree_create(struct hdac_device *codec) { struct hdac_widget_tree *tree; int i, err; hda_nid_t nid; tree = codec->widgets = kzalloc(sizeof(*tree), GFP_KERNEL); if (!tree) return -ENOMEM; tree->root = kobject_create_and_add("widgets", &codec->dev.kobj); if (!tree->root) return -ENOMEM; tree->nodes = kcalloc(codec->num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL); if (!tree->nodes) return -ENOMEM; for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { err = add_widget_node(tree->root, nid, &widget_node_group, &tree->nodes[i]); if (err < 0) return err; } if (codec->afg) { err = add_widget_node(tree->root, codec->afg, &widget_afg_group, &tree->afg); if (err < 0) return err; } kobject_uevent(tree->root, KOBJ_CHANGE); return 0; } /* call with codec->widget_lock held */ int hda_widget_sysfs_init(struct hdac_device *codec) { int err; if (codec->widgets) return 0; /* already created */ err = widget_tree_create(codec); if (err < 0) { widget_tree_free(codec); return err; } return 0; } /* call with codec->widget_lock held */ void hda_widget_sysfs_exit(struct hdac_device *codec) { widget_tree_free(codec); } /* call with codec->widget_lock held */ int hda_widget_sysfs_reinit(struct hdac_device *codec, hda_nid_t start_nid, int num_nodes) { struct hdac_widget_tree *tree; hda_nid_t end_nid = start_nid + num_nodes; hda_nid_t nid; int i; if (!codec->widgets) return 0; tree = kmemdup(codec->widgets, sizeof(*tree), GFP_KERNEL); if (!tree) return -ENOMEM; tree->nodes = kcalloc(num_nodes + 1, sizeof(*tree->nodes), GFP_KERNEL); if (!tree->nodes) { kfree(tree); return -ENOMEM; } /* prune non-existing nodes */ for (i = 0, nid = codec->start_nid; i < codec->num_nodes; i++, nid++) { if (nid < start_nid || nid >= end_nid) free_widget_node(codec->widgets->nodes[i], &widget_node_group); } /* add new nodes */ for (i = 0, nid = start_nid; i < num_nodes; i++, nid++) { if (nid < codec->start_nid || nid >= codec->end_nid) add_widget_node(tree->root, nid, &widget_node_group, &tree->nodes[i]); else tree->nodes[i] = codec->widgets->nodes[nid - codec->start_nid]; } /* replace with the new tree */ kfree(codec->widgets->nodes); kfree(codec->widgets); codec->widgets = tree; kobject_uevent(tree->root, KOBJ_CHANGE); return 0; }