brintos

brintos / linux-shallow public Read only

0
0
Text · 11.2 KiB · 60b0a70 Raw
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