linux/sound/hda/hdac_sysfs.c
<<
>>
Prefs
   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}
 468