linux/sound/core/control_led.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  LED state routines for driver control interface
   4 *  Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>
   5 */
   6
   7#include <linux/slab.h>
   8#include <linux/module.h>
   9#include <linux/leds.h>
  10#include <sound/core.h>
  11#include <sound/control.h>
  12
  13MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
  14MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
  15MODULE_LICENSE("GPL");
  16
  17#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
  18                        >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)
  19
  20#define to_led_card_dev(_dev) \
  21        container_of(_dev, struct snd_ctl_led_card, dev)
  22
  23enum snd_ctl_led_mode {
  24         MODE_FOLLOW_MUTE = 0,
  25         MODE_FOLLOW_ROUTE,
  26         MODE_OFF,
  27         MODE_ON,
  28};
  29
  30struct snd_ctl_led_card {
  31        struct device dev;
  32        int number;
  33        struct snd_ctl_led *led;
  34};
  35
  36struct snd_ctl_led {
  37        struct device dev;
  38        struct list_head controls;
  39        const char *name;
  40        unsigned int group;
  41        enum led_audio trigger_type;
  42        enum snd_ctl_led_mode mode;
  43        struct snd_ctl_led_card *cards[SNDRV_CARDS];
  44};
  45
  46struct snd_ctl_led_ctl {
  47        struct list_head list;
  48        struct snd_card *card;
  49        unsigned int access;
  50        struct snd_kcontrol *kctl;
  51        unsigned int index_offset;
  52};
  53
  54static DEFINE_MUTEX(snd_ctl_led_mutex);
  55static bool snd_ctl_led_card_valid[SNDRV_CARDS];
  56static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {
  57        {
  58                .name = "speaker",
  59                .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
  60                .trigger_type = LED_AUDIO_MUTE,
  61                .mode = MODE_FOLLOW_MUTE,
  62        },
  63        {
  64                .name = "mic",
  65                .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
  66                .trigger_type = LED_AUDIO_MICMUTE,
  67                .mode = MODE_FOLLOW_MUTE,
  68        },
  69};
  70
  71static void snd_ctl_led_sysfs_add(struct snd_card *card);
  72static void snd_ctl_led_sysfs_remove(struct snd_card *card);
  73
  74#define UPDATE_ROUTE(route, cb) \
  75        do { \
  76                int route2 = (cb); \
  77                if (route2 >= 0) \
  78                        route = route < 0 ? route2 : (route | route2); \
  79        } while (0)
  80
  81static inline unsigned int access_to_group(unsigned int access)
  82{
  83        return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
  84                                SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
  85}
  86
  87static inline unsigned int group_to_access(unsigned int group)
  88{
  89        return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
  90}
  91
  92static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access)
  93{
  94        unsigned int group = access_to_group(access);
  95        if (group >= MAX_LED)
  96                return NULL;
  97        return &snd_ctl_leds[group];
  98}
  99
 100/*
 101 * A note for callers:
 102 *   The two static variables info and value are protected using snd_ctl_led_mutex.
 103 */
 104static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)
 105{
 106        static struct snd_ctl_elem_info info;
 107        static struct snd_ctl_elem_value value;
 108        struct snd_kcontrol *kctl = lctl->kctl;
 109        unsigned int i;
 110        int result;
 111
 112        memset(&info, 0, sizeof(info));
 113        info.id = kctl->id;
 114        info.id.index += lctl->index_offset;
 115        info.id.numid += lctl->index_offset;
 116        result = kctl->info(kctl, &info);
 117        if (result < 0)
 118                return -1;
 119        memset(&value, 0, sizeof(value));
 120        value.id = info.id;
 121        result = kctl->get(kctl, &value);
 122        if (result < 0)
 123                return -1;
 124        if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
 125            info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
 126                for (i = 0; i < info.count; i++)
 127                        if (value.value.integer.value[i] != info.value.integer.min)
 128                                return 1;
 129        } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
 130                for (i = 0; i < info.count; i++)
 131                        if (value.value.integer64.value[i] != info.value.integer64.min)
 132                                return 1;
 133        }
 134        return 0;
 135}
 136
 137static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
 138                                  struct snd_kcontrol *kctl, unsigned int ioff)
 139{
 140        struct snd_ctl_led *led;
 141        struct snd_ctl_led_ctl *lctl;
 142        int route;
 143        bool found;
 144
 145        led = snd_ctl_led_get_by_access(access);
 146        if (!led)
 147                return;
 148        route = -1;
 149        found = false;
 150        mutex_lock(&snd_ctl_led_mutex);
 151        /* the card may not be registered (active) at this point */
 152        if (card && !snd_ctl_led_card_valid[card->number]) {
 153                mutex_unlock(&snd_ctl_led_mutex);
 154                return;
 155        }
 156        list_for_each_entry(lctl, &led->controls, list) {
 157                if (lctl->kctl == kctl && lctl->index_offset == ioff)
 158                        found = true;
 159                UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
 160        }
 161        if (!found && kctl && card) {
 162                lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
 163                if (lctl) {
 164                        lctl->card = card;
 165                        lctl->access = access;
 166                        lctl->kctl = kctl;
 167                        lctl->index_offset = ioff;
 168                        list_add(&lctl->list, &led->controls);
 169                        UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
 170                }
 171        }
 172        mutex_unlock(&snd_ctl_led_mutex);
 173        switch (led->mode) {
 174        case MODE_OFF:          route = 1; break;
 175        case MODE_ON:           route = 0; break;
 176        case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break;
 177        case MODE_FOLLOW_MUTE:  /* noop */ break;
 178        }
 179        if (route >= 0)
 180                ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON);
 181}
 182
 183static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
 184{
 185        struct list_head *controls;
 186        struct snd_ctl_led_ctl *lctl;
 187        unsigned int group;
 188
 189        for (group = 0; group < MAX_LED; group++) {
 190                controls = &snd_ctl_leds[group].controls;
 191                list_for_each_entry(lctl, controls, list)
 192                        if (lctl->kctl == kctl && lctl->index_offset == ioff)
 193                                return lctl;
 194        }
 195        return NULL;
 196}
 197
 198static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
 199                                       unsigned int access)
 200{
 201        struct snd_ctl_led_ctl *lctl;
 202        unsigned int ret = 0;
 203
 204        mutex_lock(&snd_ctl_led_mutex);
 205        lctl = snd_ctl_led_find(kctl, ioff);
 206        if (lctl && (access == 0 || access != lctl->access)) {
 207                ret = lctl->access;
 208                list_del(&lctl->list);
 209                kfree(lctl);
 210        }
 211        mutex_unlock(&snd_ctl_led_mutex);
 212        return ret;
 213}
 214
 215static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
 216                               struct snd_kcontrol *kctl, unsigned int ioff)
 217{
 218        struct snd_kcontrol_volatile *vd;
 219        unsigned int access, access2;
 220
 221        if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
 222                access = snd_ctl_led_remove(kctl, ioff, 0);
 223                if (access)
 224                        snd_ctl_led_set_state(card, access, NULL, 0);
 225        } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {
 226                vd = &kctl->vd[ioff];
 227                access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
 228                access2 = snd_ctl_led_remove(kctl, ioff, access);
 229                if (access2)
 230                        snd_ctl_led_set_state(card, access2, NULL, 0);
 231                if (access)
 232                        snd_ctl_led_set_state(card, access, kctl, ioff);
 233        } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |
 234                            SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {
 235                vd = &kctl->vd[ioff];
 236                access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
 237                if (access)
 238                        snd_ctl_led_set_state(card, access, kctl, ioff);
 239        }
 240}
 241
 242static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id,
 243                              unsigned int group, bool set)
 244{
 245        struct snd_card *card;
 246        struct snd_kcontrol *kctl;
 247        struct snd_kcontrol_volatile *vd;
 248        unsigned int ioff, access, new_access;
 249        int err = 0;
 250
 251        card = snd_card_ref(card_number);
 252        if (card) {
 253                down_write(&card->controls_rwsem);
 254                kctl = snd_ctl_find_id(card, id);
 255                if (kctl) {
 256                        ioff = snd_ctl_get_ioff(kctl, id);
 257                        vd = &kctl->vd[ioff];
 258                        access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
 259                        if (access != 0 && access != group_to_access(group)) {
 260                                err = -EXDEV;
 261                                goto unlock;
 262                        }
 263                        new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;
 264                        if (set)
 265                                new_access |= group_to_access(group);
 266                        if (new_access != vd->access) {
 267                                vd->access = new_access;
 268                                snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff);
 269                        }
 270                } else {
 271                        err = -ENOENT;
 272                }
 273unlock:
 274                up_write(&card->controls_rwsem);
 275                snd_card_unref(card);
 276        } else {
 277                err = -ENXIO;
 278        }
 279        return err;
 280}
 281
 282static void snd_ctl_led_refresh(void)
 283{
 284        unsigned int group;
 285
 286        for (group = 0; group < MAX_LED; group++)
 287                snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
 288}
 289
 290static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl)
 291{
 292        list_del(&lctl->list);
 293        kfree(lctl);
 294}
 295
 296static void snd_ctl_led_clean(struct snd_card *card)
 297{
 298        unsigned int group;
 299        struct snd_ctl_led *led;
 300        struct snd_ctl_led_ctl *lctl;
 301
 302        for (group = 0; group < MAX_LED; group++) {
 303                led = &snd_ctl_leds[group];
 304repeat:
 305                list_for_each_entry(lctl, &led->controls, list)
 306                        if (!card || lctl->card == card) {
 307                                snd_ctl_led_ctl_destroy(lctl);
 308                                goto repeat;
 309                        }
 310        }
 311}
 312
 313static int snd_ctl_led_reset(int card_number, unsigned int group)
 314{
 315        struct snd_card *card;
 316        struct snd_ctl_led *led;
 317        struct snd_ctl_led_ctl *lctl;
 318        struct snd_kcontrol_volatile *vd;
 319        bool change = false;
 320
 321        card = snd_card_ref(card_number);
 322        if (!card)
 323                return -ENXIO;
 324
 325        mutex_lock(&snd_ctl_led_mutex);
 326        if (!snd_ctl_led_card_valid[card_number]) {
 327                mutex_unlock(&snd_ctl_led_mutex);
 328                snd_card_unref(card);
 329                return -ENXIO;
 330        }
 331        led = &snd_ctl_leds[group];
 332repeat:
 333        list_for_each_entry(lctl, &led->controls, list)
 334                if (lctl->card == card) {
 335                        vd = &lctl->kctl->vd[lctl->index_offset];
 336                        vd->access &= ~group_to_access(group);
 337                        snd_ctl_led_ctl_destroy(lctl);
 338                        change = true;
 339                        goto repeat;
 340                }
 341        mutex_unlock(&snd_ctl_led_mutex);
 342        if (change)
 343                snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
 344        snd_card_unref(card);
 345        return 0;
 346}
 347
 348static void snd_ctl_led_register(struct snd_card *card)
 349{
 350        struct snd_kcontrol *kctl;
 351        unsigned int ioff;
 352
 353        if (snd_BUG_ON(card->number < 0 ||
 354                       card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
 355                return;
 356        mutex_lock(&snd_ctl_led_mutex);
 357        snd_ctl_led_card_valid[card->number] = true;
 358        mutex_unlock(&snd_ctl_led_mutex);
 359        /* the register callback is already called with held card->controls_rwsem */
 360        list_for_each_entry(kctl, &card->controls, list)
 361                for (ioff = 0; ioff < kctl->count; ioff++)
 362                        snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);
 363        snd_ctl_led_refresh();
 364        snd_ctl_led_sysfs_add(card);
 365}
 366
 367static void snd_ctl_led_disconnect(struct snd_card *card)
 368{
 369        snd_ctl_led_sysfs_remove(card);
 370        mutex_lock(&snd_ctl_led_mutex);
 371        snd_ctl_led_card_valid[card->number] = false;
 372        snd_ctl_led_clean(card);
 373        mutex_unlock(&snd_ctl_led_mutex);
 374        snd_ctl_led_refresh();
 375}
 376
 377static void snd_ctl_led_card_release(struct device *dev)
 378{
 379        struct snd_ctl_led_card *led_card = to_led_card_dev(dev);
 380
 381        kfree(led_card);
 382}
 383
 384static void snd_ctl_led_release(struct device *dev)
 385{
 386}
 387
 388static void snd_ctl_led_dev_release(struct device *dev)
 389{
 390}
 391
 392/*
 393 * sysfs
 394 */
 395
 396static ssize_t mode_show(struct device *dev,
 397                         struct device_attribute *attr, char *buf)
 398{
 399        struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
 400        const char *str = NULL;
 401
 402        switch (led->mode) {
 403        case MODE_FOLLOW_MUTE:  str = "follow-mute"; break;
 404        case MODE_FOLLOW_ROUTE: str = "follow-route"; break;
 405        case MODE_ON:           str = "on"; break;
 406        case MODE_OFF:          str = "off"; break;
 407        }
 408        return sprintf(buf, "%s\n", str);
 409}
 410
 411static ssize_t mode_store(struct device *dev,
 412                          struct device_attribute *attr,
 413                          const char *buf, size_t count)
 414{
 415        struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
 416        char _buf[16];
 417        size_t l = min(count, sizeof(_buf) - 1);
 418        enum snd_ctl_led_mode mode;
 419
 420        memcpy(_buf, buf, l);
 421        _buf[l] = '\0';
 422        if (strstr(_buf, "mute"))
 423                mode = MODE_FOLLOW_MUTE;
 424        else if (strstr(_buf, "route"))
 425                mode = MODE_FOLLOW_ROUTE;
 426        else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0)
 427                mode = MODE_OFF;
 428        else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0)
 429                mode = MODE_ON;
 430        else
 431                return count;
 432
 433        mutex_lock(&snd_ctl_led_mutex);
 434        led->mode = mode;
 435        mutex_unlock(&snd_ctl_led_mutex);
 436
 437        snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);
 438        return count;
 439}
 440
 441static ssize_t brightness_show(struct device *dev,
 442                               struct device_attribute *attr, char *buf)
 443{
 444        struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
 445
 446        return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type));
 447}
 448
 449static DEVICE_ATTR_RW(mode);
 450static DEVICE_ATTR_RO(brightness);
 451
 452static struct attribute *snd_ctl_led_dev_attrs[] = {
 453        &dev_attr_mode.attr,
 454        &dev_attr_brightness.attr,
 455        NULL,
 456};
 457
 458static const struct attribute_group snd_ctl_led_dev_attr_group = {
 459        .attrs = snd_ctl_led_dev_attrs,
 460};
 461
 462static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {
 463        &snd_ctl_led_dev_attr_group,
 464        NULL,
 465};
 466
 467static char *find_eos(char *s)
 468{
 469        while (*s && *s != ',')
 470                s++;
 471        if (*s)
 472                s++;
 473        return s;
 474}
 475
 476static char *parse_uint(char *s, unsigned int *val)
 477{
 478        unsigned long long res;
 479        if (kstrtoull(s, 10, &res))
 480                res = 0;
 481        *val = res;
 482        return find_eos(s);
 483}
 484
 485static char *parse_string(char *s, char *val, size_t val_size)
 486{
 487        if (*s == '"' || *s == '\'') {
 488                char c = *s;
 489                s++;
 490                while (*s && *s != c) {
 491                        if (val_size > 1) {
 492                                *val++ = *s;
 493                                val_size--;
 494                        }
 495                        s++;
 496                }
 497        } else {
 498                while (*s && *s != ',') {
 499                        if (val_size > 1) {
 500                                *val++ = *s;
 501                                val_size--;
 502                        }
 503                        s++;
 504                }
 505        }
 506        *val = '\0';
 507        if (*s)
 508                s++;
 509        return s;
 510}
 511
 512static char *parse_iface(char *s, unsigned int *val)
 513{
 514        if (!strncasecmp(s, "card", 4))
 515                *val = SNDRV_CTL_ELEM_IFACE_CARD;
 516        else if (!strncasecmp(s, "mixer", 5))
 517                *val = SNDRV_CTL_ELEM_IFACE_MIXER;
 518        return find_eos(s);
 519}
 520
 521/*
 522 * These types of input strings are accepted:
 523 *
 524 *   unsigned integer - numid (equivaled to numid=UINT)
 525 *   string - basic mixer name (equivalent to iface=MIXER,name=STR)
 526 *   numid=UINT
 527 *   [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT]
 528 */
 529static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count,
 530                          bool attach)
 531{
 532        char buf2[256], *s, *os;
 533        size_t len = max(sizeof(s) - 1, count);
 534        struct snd_ctl_elem_id id;
 535        int err;
 536
 537        strncpy(buf2, buf, len);
 538        buf2[len] = '\0';
 539        memset(&id, 0, sizeof(id));
 540        id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
 541        s = buf2;
 542        while (*s) {
 543                os = s;
 544                if (!strncasecmp(s, "numid=", 6)) {
 545                        s = parse_uint(s + 6, &id.numid);
 546                } else if (!strncasecmp(s, "iface=", 6)) {
 547                        s = parse_iface(s + 6, &id.iface);
 548                } else if (!strncasecmp(s, "device=", 7)) {
 549                        s = parse_uint(s + 7, &id.device);
 550                } else if (!strncasecmp(s, "subdevice=", 10)) {
 551                        s = parse_uint(s + 10, &id.subdevice);
 552                } else if (!strncasecmp(s, "name=", 5)) {
 553                        s = parse_string(s + 5, id.name, sizeof(id.name));
 554                } else if (!strncasecmp(s, "index=", 6)) {
 555                        s = parse_uint(s + 6, &id.index);
 556                } else if (s == buf2) {
 557                        while (*s) {
 558                                if (*s < '0' || *s > '9')
 559                                        break;
 560                                s++;
 561                        }
 562                        if (*s == '\0')
 563                                parse_uint(buf2, &id.numid);
 564                        else {
 565                                for (; *s >= ' '; s++);
 566                                *s = '\0';
 567                                strscpy(id.name, buf2, sizeof(id.name));
 568                        }
 569                        break;
 570                }
 571                if (*s == ',')
 572                        s++;
 573                if (s == os)
 574                        break;
 575        }
 576
 577        err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach);
 578        if (err < 0)
 579                return err;
 580
 581        return count;
 582}
 583
 584static ssize_t attach_store(struct device *dev,
 585                            struct device_attribute *attr,
 586                            const char *buf, size_t count)
 587{
 588        struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
 589        return set_led_id(led_card, buf, count, true);
 590}
 591
 592static ssize_t detach_store(struct device *dev,
 593                            struct device_attribute *attr,
 594                            const char *buf, size_t count)
 595{
 596        struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
 597        return set_led_id(led_card, buf, count, false);
 598}
 599
 600static ssize_t reset_store(struct device *dev,
 601                           struct device_attribute *attr,
 602                           const char *buf, size_t count)
 603{
 604        struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
 605        int err;
 606
 607        if (count > 0 && buf[0] == '1') {
 608                err = snd_ctl_led_reset(led_card->number, led_card->led->group);
 609                if (err < 0)
 610                        return err;
 611        }
 612        return count;
 613}
 614
 615static ssize_t list_show(struct device *dev,
 616                         struct device_attribute *attr, char *buf)
 617{
 618        struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
 619        struct snd_card *card;
 620        struct snd_ctl_led_ctl *lctl;
 621        char *buf2 = buf;
 622        size_t l;
 623
 624        card = snd_card_ref(led_card->number);
 625        if (!card)
 626                return -ENXIO;
 627        down_read(&card->controls_rwsem);
 628        mutex_lock(&snd_ctl_led_mutex);
 629        if (snd_ctl_led_card_valid[led_card->number]) {
 630                list_for_each_entry(lctl, &led_card->led->controls, list)
 631                        if (lctl->card == card) {
 632                                if (buf2 - buf > PAGE_SIZE - 16)
 633                                        break;
 634                                if (buf2 != buf)
 635                                        *buf2++ = ' ';
 636                                l = scnprintf(buf2, 15, "%u",
 637                                                lctl->kctl->id.numid +
 638                                                        lctl->index_offset);
 639                                buf2[l] = '\0';
 640                                buf2 += l + 1;
 641                        }
 642        }
 643        mutex_unlock(&snd_ctl_led_mutex);
 644        up_read(&card->controls_rwsem);
 645        snd_card_unref(card);
 646        return buf2 - buf;
 647}
 648
 649static DEVICE_ATTR_WO(attach);
 650static DEVICE_ATTR_WO(detach);
 651static DEVICE_ATTR_WO(reset);
 652static DEVICE_ATTR_RO(list);
 653
 654static struct attribute *snd_ctl_led_card_attrs[] = {
 655        &dev_attr_attach.attr,
 656        &dev_attr_detach.attr,
 657        &dev_attr_reset.attr,
 658        &dev_attr_list.attr,
 659        NULL,
 660};
 661
 662static const struct attribute_group snd_ctl_led_card_attr_group = {
 663        .attrs = snd_ctl_led_card_attrs,
 664};
 665
 666static const struct attribute_group *snd_ctl_led_card_attr_groups[] = {
 667        &snd_ctl_led_card_attr_group,
 668        NULL,
 669};
 670
 671static struct device snd_ctl_led_dev;
 672
 673static void snd_ctl_led_sysfs_add(struct snd_card *card)
 674{
 675        unsigned int group;
 676        struct snd_ctl_led_card *led_card;
 677        struct snd_ctl_led *led;
 678        char link_name[32];
 679
 680        for (group = 0; group < MAX_LED; group++) {
 681                led = &snd_ctl_leds[group];
 682                led_card = kzalloc(sizeof(*led_card), GFP_KERNEL);
 683                if (!led_card)
 684                        goto cerr2;
 685                led_card->number = card->number;
 686                led_card->led = led;
 687                device_initialize(&led_card->dev);
 688                led_card->dev.release = snd_ctl_led_card_release;
 689                if (dev_set_name(&led_card->dev, "card%d", card->number) < 0)
 690                        goto cerr;
 691                led_card->dev.parent = &led->dev;
 692                led_card->dev.groups = snd_ctl_led_card_attr_groups;
 693                if (device_add(&led_card->dev))
 694                        goto cerr;
 695                led->cards[card->number] = led_card;
 696                snprintf(link_name, sizeof(link_name), "led-%s", led->name);
 697                WARN(sysfs_create_link(&card->ctl_dev.kobj, &led_card->dev.kobj, link_name),
 698                        "can't create symlink to controlC%i device\n", card->number);
 699                WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"),
 700                        "can't create symlink to card%i\n", card->number);
 701
 702                continue;
 703cerr:
 704                put_device(&led_card->dev);
 705cerr2:
 706                printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number);
 707        }
 708}
 709
 710static void snd_ctl_led_sysfs_remove(struct snd_card *card)
 711{
 712        unsigned int group;
 713        struct snd_ctl_led_card *led_card;
 714        struct snd_ctl_led *led;
 715        char link_name[32];
 716
 717        for (group = 0; group < MAX_LED; group++) {
 718                led = &snd_ctl_leds[group];
 719                led_card = led->cards[card->number];
 720                if (!led_card)
 721                        continue;
 722                snprintf(link_name, sizeof(link_name), "led-%s", led->name);
 723                sysfs_remove_link(&card->ctl_dev.kobj, link_name);
 724                sysfs_remove_link(&led_card->dev.kobj, "card");
 725                device_unregister(&led_card->dev);
 726                led->cards[card->number] = NULL;
 727        }
 728}
 729
 730/*
 731 * Control layer registration
 732 */
 733static struct snd_ctl_layer_ops snd_ctl_led_lops = {
 734        .module_name = SND_CTL_LAYER_MODULE_LED,
 735        .lregister = snd_ctl_led_register,
 736        .ldisconnect = snd_ctl_led_disconnect,
 737        .lnotify = snd_ctl_led_notify,
 738};
 739
 740static int __init snd_ctl_led_init(void)
 741{
 742        struct snd_ctl_led *led;
 743        unsigned int group;
 744
 745        device_initialize(&snd_ctl_led_dev);
 746        snd_ctl_led_dev.class = sound_class;
 747        snd_ctl_led_dev.release = snd_ctl_led_dev_release;
 748        dev_set_name(&snd_ctl_led_dev, "ctl-led");
 749        if (device_add(&snd_ctl_led_dev)) {
 750                put_device(&snd_ctl_led_dev);
 751                return -ENOMEM;
 752        }
 753        for (group = 0; group < MAX_LED; group++) {
 754                led = &snd_ctl_leds[group];
 755                INIT_LIST_HEAD(&led->controls);
 756                device_initialize(&led->dev);
 757                led->dev.parent = &snd_ctl_led_dev;
 758                led->dev.release = snd_ctl_led_release;
 759                led->dev.groups = snd_ctl_led_dev_attr_groups;
 760                dev_set_name(&led->dev, led->name);
 761                if (device_add(&led->dev)) {
 762                        put_device(&led->dev);
 763                        for (; group > 0; group--) {
 764                                led = &snd_ctl_leds[group - 1];
 765                                device_unregister(&led->dev);
 766                        }
 767                        device_unregister(&snd_ctl_led_dev);
 768                        return -ENOMEM;
 769                }
 770        }
 771        snd_ctl_register_layer(&snd_ctl_led_lops);
 772        return 0;
 773}
 774
 775static void __exit snd_ctl_led_exit(void)
 776{
 777        struct snd_ctl_led *led;
 778        struct snd_card *card;
 779        unsigned int group, card_number;
 780
 781        snd_ctl_disconnect_layer(&snd_ctl_led_lops);
 782        for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
 783                if (!snd_ctl_led_card_valid[card_number])
 784                        continue;
 785                card = snd_card_ref(card_number);
 786                if (card) {
 787                        snd_ctl_led_sysfs_remove(card);
 788                        snd_card_unref(card);
 789                }
 790        }
 791        for (group = 0; group < MAX_LED; group++) {
 792                led = &snd_ctl_leds[group];
 793                device_unregister(&led->dev);
 794        }
 795        device_unregister(&snd_ctl_led_dev);
 796        snd_ctl_led_clean(NULL);
 797}
 798
 799module_init(snd_ctl_led_init)
 800module_exit(snd_ctl_led_exit)
 801