/* * hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver * * Misc wrappers * * Copyright (c) Takashi Iwai * * This driver is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This driver is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include "hda-types.h" #include "hda-log.h" int snd_pcm_format_width(int format) { switch (format) { case SNDRV_PCM_FORMAT_U8: return 8; case SNDRV_PCM_FORMAT_S16_LE: return 16; case SNDRV_PCM_FORMAT_S32_LE: case SNDRV_PCM_FORMAT_FLOAT_LE: case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE: return 32; default: return -EINVAL; } } #ifdef CONFIG_SND_HDA_RECONFIG /* hint string pair */ struct hda_hint { const char *key; const char *val; /* contained in the same alloc as key */ }; static char *skip_spaces(const char *p) { while (*p && isspace(*p)) p++; return (char *)p; } static void remove_trail_spaces(char *str) { char *p; if (!*str) return; p = str + strlen(str) - 1; for (; isspace(*p); p--) { *p = 0; if (p == str) return; } } static struct hda_hint *get_hint(struct hda_codec *codec, const char *key) { int i; for (i = 0; i < codec->hints.used; i++) { struct hda_hint *hint = snd_array_elem(&codec->hints, i); if (!strcmp(hint->key, key)) return hint; } return NULL; } #define MAX_HINTS 1024 int _parse_hints(struct hda_codec *codec, const char *buf) { char *key, *val; struct hda_hint *hint; int err = 0; buf = skip_spaces(buf); if (!*buf || *buf == '#' || *buf == '\n') return 0; if (*buf == '=') return -EINVAL; key = strdup(buf); if (!key) return -ENOMEM; val = strrchr(buf, '\n'); if (val) *val = 0; /* extract key and val */ val = strchr(key, '='); if (!val) { kfree(key); return -EINVAL; } *val++ = 0; val = skip_spaces(val); remove_trail_spaces(key); remove_trail_spaces(val); hint = get_hint(codec, key); if (hint) { /* replace */ free((void *)hint->key); hint->key = key; hint->val = val; return 0; } /* allocate a new hint entry */ if (codec->hints.used >= MAX_HINTS) hint = NULL; else hint = snd_array_new(&codec->hints); if (hint) { hint->key = key; hint->val = val; } else { err = -ENOMEM; } if (err) free(key); return err; } int _show_hints(struct hda_codec *codec, const char *key) { int i; for (i = 0; i < codec->hints.used; i++) { struct hda_hint *hint = snd_array_elem(&codec->hints, i); if (!key || !strcmp(hint->key, key)) hda_log(HDA_LOG_INFO, "%s = %s\n", hint->key, hint->val); } return 0; } const char *snd_hda_get_hint(struct hda_codec *codec, const char *key) { struct hda_hint *hint = get_hint(codec, key); return hint ? hint->val : NULL; } int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key) { const char *p; int ret; p = snd_hda_get_hint(codec, key); if (!p || !*p) ret = -ENOENT; else { switch (toupper(*p)) { case 'T': /* true */ case 'Y': /* yes */ case '1': ret = 1; break; default: ret = 0; break; } } return ret; } int snd_hda_get_int_hint(struct hda_codec *codec, const char *key, int *valp) { const char *p; int ret; p = snd_hda_get_hint(codec, key); if (!p) ret = -ENOENT; else { *valp = strtoul(p, NULL, 0); ret = 0; } return ret; } #endif /* CONFIG_SND_HDA_RECONFIG */ int snd_hda_create_hwdep(struct hda_codec *codec) { #ifdef CONFIG_SND_HDA_RECONFIG #ifdef HAVE_CODEC_USER_MUTEX mutex_init(&codec->user_mutex); #endif snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); snd_array_init(&codec->hints, sizeof(struct hda_hint), 32); snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16); #endif /* CONFIG_SND_HDA_RECONFIG */ return 0; } int snd_hda_hwdep_add_sysfs(struct hda_codec *codec) { return 0; } /* */ void (*snd_iprintf_dumper)(struct snd_info_buffer *buf, const char *fmt, va_list ap); void snd_iprintf(struct snd_info_buffer *buf, const char *fmt, ...) { va_list ap; if (!snd_iprintf_dumper) return; va_start(ap, fmt); snd_iprintf_dumper(buf, fmt, ap); va_end(ap); } /* there is a compat wrapper in the latest SLE11 kernel */ #ifndef snd_pci_quirk_lookup /* * quirk lookup */ /** * snd_pci_quirk_lookup_id - look up a PCI SSID quirk list * @vendor: PCI SSV id * @device: PCI SSD id * @list: quirk list, terminated by a null entry * * Look through the given quirk list and finds a matching entry * with the same PCI SSID. When subdevice is 0, all subdevice * values may match. * * Returns the matched entry pointer, or NULL if nothing matched. */ const struct snd_pci_quirk * snd_pci_quirk_lookup_id(u16 vendor, u16 device, const struct snd_pci_quirk *list) { const struct snd_pci_quirk *q; for (q = list; q->subvendor || q->subdevice; q++) { if (q->subvendor != vendor) continue; if (!q->subdevice || (device & q->subdevice_mask) == q->subdevice) return q; } return NULL; } const struct snd_pci_quirk * snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) { if (!pci) return NULL; return snd_pci_quirk_lookup_id(pci->subsystem_vendor, pci->subsystem_device, list); } #endif /* snd_pci_quirk_lookup */ /* malloc debug */ struct __hda_malloc_elem { void *ptr; size_t size; const char *file; int line; struct list_head list; }; static LIST_HEAD(malloc_list); void *__hda_malloc(size_t size, const char *file, int line, int gfp) { struct __hda_malloc_elem *elem = malloc(sizeof(*elem)); if (!elem) return NULL; if (gfp & __GFP_ZERO) elem->ptr = calloc(1, size); else elem->ptr = malloc(size); if (!elem->ptr) { free(elem); return NULL; } elem->file = file; elem->size = size; elem->line = line; list_add_tail(&elem->list, &malloc_list); return elem->ptr; } void __hda_free(void *ptr, const char *file, int line) { struct __hda_malloc_elem *elem; if (!ptr) return; list_for_each_entry(elem, &malloc_list, list) { if (elem->ptr == ptr) { list_del(&elem->list); free(elem->ptr); free(elem); return; } } hda_log(HDA_LOG_ERR, "Untracked malloc freed in %s:%d\n", file, line); assert(0); } void *__hda_realloc(const void *p, size_t new_size, const char *file, int line, int gfp) { struct __hda_malloc_elem *elem; if (!p) return __hda_malloc(new_size, file, line, gfp); if (!new_size) { __hda_free((void *)p, file, line); return NULL; } list_for_each_entry(elem, &malloc_list, list) { if (elem->ptr == p) { void *nptr; if (gfp & __GFP_ZERO) nptr = calloc(1, new_size); else nptr = malloc(new_size); if (nptr) { memcpy(nptr, elem->ptr, elem->size); free(elem->ptr); elem->ptr = nptr; elem->size = new_size; } return nptr; } } hda_log(HDA_LOG_ERR, "Untracked malloc realloced in %s:%d\n", file, line); return __hda_malloc(new_size, file, line, gfp); } void *__hda_strdup(const char *str, const char *file, int line, int gfp) { char *dest = __hda_malloc(strlen(str) + 1, file, line, gfp); if (!dest) return NULL; strcpy(dest, str); return dest; } /* jack API */ #include #ifdef NEW_JACK_API int snd_jack_new(struct snd_card *card, const char *id, int type, struct snd_jack **jack, bool initial_kctl, bool phantom_jack) #else int snd_jack_new(struct snd_card *card, const char *id, int type, struct snd_jack **jack) #endif { struct snd_jack *jp; jp = calloc(1, sizeof(*jp)); if (!jp) return -ENOMEM; jp->id = strdup(id); if (!jp->id) return -ENOMEM; jp->card = card; jp->type = type; hda_log(HDA_LOG_INFO, "JACK created %s, type %d\n", id, type); *jack = jp; #ifdef NEW_JACK_API jp->kctl = snd_kctl_jack_new(id, card); snd_ctl_add(card, jp->kctl); #endif return 0; } void snd_jack_set_parent(struct snd_jack *jack, struct device *parent) { /* NOP */ } void snd_jack_report(struct snd_jack *jack, int status) { hda_log(HDA_LOG_INFO, "JACK report %s, status %d\n", jack->id, status); #ifdef NEW_JACK_API snd_kctl_jack_report(jack->card, jack->kctl, status); #endif } /* * lock */ void mylock_init(int *lock) { *lock = MYLOCK_UNLOCKED; } void mylock_lock(int *lock, const char *file, int line) { switch (*lock) { case MYLOCK_UNINIT: hda_log(HDA_LOG_ERR, "Locking uninitialized at %s:%d\n", file, line); break; case MYLOCK_UNLOCKED: *lock = MYLOCK_LOCKED; break; case MYLOCK_LOCKED: hda_log(HDA_LOG_ERR, "Double-lock detected at %s:%d\n", file, line); break; default: hda_log(HDA_LOG_ERR, "Unknown lock state %d! at %s:%d\n", *lock, file, line); break; } } void mylock_unlock(int *lock, const char *file, int line) { switch (*lock) { case MYLOCK_UNINIT: hda_log(HDA_LOG_ERR, "Unlocking uninitialized at %s:%d\n", file, line); break; case MYLOCK_UNLOCKED: hda_log(HDA_LOG_ERR, "Double-unlock detected at %s:%d\n", file, line); break; case MYLOCK_LOCKED: *lock = MYLOCK_UNLOCKED; break; default: hda_log(HDA_LOG_ERR, "Unknown lock state %d! at %s:%d\n", *lock, file, line); break; } } #define MYLOCK_WRITE_LOCKED 0x10000 void mylock_read_lock(int *lock, const char *file, int line) { if (*lock == MYLOCK_UNINIT) { hda_log(HDA_LOG_ERR, "Read-locking uninitialized obj at %s:%d\n", file, line); return; } if (*lock >= MYLOCK_WRITE_LOCKED) { hda_log(HDA_LOG_ERR, "Read-locking write-locked obj at %s:%d\n", file, line); return; } (*lock)++; } void mylock_read_unlock(int *lock, const char *file, int line) { if (*lock == MYLOCK_UNINIT) { hda_log(HDA_LOG_ERR, "Read-unlocking uninitialized obj at %s:%d\n", file, line); return; } if (*lock == MYLOCK_UNLOCKED) { hda_log(HDA_LOG_ERR, "Read-unlocking unlocked obj at %s:%d\n", file, line); return; } if (*lock >= MYLOCK_WRITE_LOCKED) { hda_log(HDA_LOG_ERR, "Read-unlocking write-locked obj at %s:%d\n", file, line); return; } (*lock)--; } void mylock_write_lock(int *lock, const char *file, int line) { if (*lock == MYLOCK_UNINIT) { hda_log(HDA_LOG_ERR, "Write-locking uninitialized obj at %s:%d\n", file, line); return; } if (*lock == MYLOCK_WRITE_LOCKED) { hda_log(HDA_LOG_ERR, "Double write-locking at %s:%d\n", file, line); return; } if (*lock != MYLOCK_UNLOCKED) { hda_log(HDA_LOG_ERR, "Write-locking read-locked obj at %s:%d\n", file, line); return; } *lock = MYLOCK_WRITE_LOCKED; } void mylock_write_unlock(int *lock, const char *file, int line) { if (*lock == MYLOCK_UNINIT) { hda_log(HDA_LOG_ERR, "Write-unlocking uninitialized obj at %s:%d\n", file, line); return; } if (*lock == MYLOCK_UNLOCKED) { hda_log(HDA_LOG_ERR, "Write-unlocking unlocked obj at %s:%d\n", file, line); return; } if (*lock != MYLOCK_WRITE_LOCKED) { hda_log(HDA_LOG_ERR, "Write-unlocking read-locked obj at %s:%d\n", file, line); return; } *lock = MYLOCK_UNLOCKED; } /* * standard channel mapping helpers */ #include /* default channel maps for multi-channel playbacks, up to 8 channels */ const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = { { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, { } }; EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps); /* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */ const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = { { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } }, { } }; EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps); static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch) { if (ch > info->max_channels) return false; return !info->channel_mask || (info->channel_mask & (1U << ch)); } static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 0; uinfo->count = info->max_channels; uinfo->value.integer.min = 0; uinfo->value.integer.max = SNDRV_CHMAP_LAST; return 0; } /* get callback for channel map ctl element * stores the channel position firstly matching with the current channels */ static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); struct snd_pcm_substream *substream; const struct snd_pcm_chmap_elem *map; if (snd_BUG_ON(!info->chmap)) return -EINVAL; substream = snd_pcm_chmap_substream(info, idx); if (!substream) return -ENODEV; memset(ucontrol->value.integer.value, 0, sizeof(ucontrol->value.integer.value)); if (!substream->runtime) return 0; /* no channels set */ for (map = info->chmap; map->channels; map++) { int i; if (map->channels == substream->runtime->channels && valid_chmap_channels(info, map->channels)) { for (i = 0; i < map->channels; i++) ucontrol->value.integer.value[i] = map->map[i]; return 0; } } return -EINVAL; } /* tlv callback for channel map ctl element * expands the pre-defined channel maps in a form of TLV */ static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *tlv) { struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); const struct snd_pcm_chmap_elem *map; unsigned int __user *dst; int c, count = 0; if (snd_BUG_ON(!info->chmap)) return -EINVAL; if (size < 8) return -ENOMEM; if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv)) return -EFAULT; size -= 8; dst = tlv + 2; for (map = info->chmap; map->channels; map++) { int chs_bytes = map->channels * 4; if (!valid_chmap_channels(info, map->channels)) continue; if (size < 8) return -ENOMEM; if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) || put_user(chs_bytes, dst + 1)) return -EFAULT; dst += 2; size -= 8; count += 8; if (size < chs_bytes) return -ENOMEM; size -= chs_bytes; count += chs_bytes; for (c = 0; c < map->channels; c++) { if (put_user(map->map[c], dst)) return -EFAULT; dst++; } } if (put_user(count, tlv + 1)) return -EFAULT; return 0; } static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol) { struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol); info->pcm->streams[info->stream].chmap_kctl = NULL; kfree(info); } /** * snd_pcm_add_chmap_ctls - create channel-mapping control elements * @pcm: the assigned PCM instance * @stream: stream direction * @chmap: channel map elements (for query) * @max_channels: the max number of channels for the stream * @private_value: the value passed to each kcontrol's private_value field * @info_ret: store struct snd_pcm_chmap instance if non-NULL * * Create channel-mapping control elements assigned to the given PCM stream(s). * Returns zero if succeed, or a negative error value. */ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, const struct snd_pcm_chmap_elem *chmap, int max_channels, unsigned long private_value, struct snd_pcm_chmap **info_ret) { struct snd_pcm_chmap *info; struct snd_kcontrol_new knew = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, .info = pcm_chmap_ctl_info, .get = pcm_chmap_ctl_get, .tlv.c = pcm_chmap_ctl_tlv, }; int err; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; info->pcm = pcm; info->stream = stream; info->chmap = chmap; info->max_channels = max_channels; if (stream == SNDRV_PCM_STREAM_PLAYBACK) knew.name = "Playback Channel Map"; else knew.name = "Capture Channel Map"; knew.device = pcm->device; knew.count = pcm->streams[stream].substream_count; knew.private_value = private_value; info->kctl = snd_ctl_new1(&knew, info); if (!info->kctl) { kfree(info); return -ENOMEM; } info->kctl->private_free = pcm_chmap_ctl_private_free; err = snd_ctl_add(pcm->card, info->kctl); if (err < 0) return err; pcm->streams[stream].chmap_kctl = info->kctl; if (info_ret) *info_ret = info; return 0; } EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls); int tpacpi_led_set(int whichled, bool on) { hda_log(HDA_LOG_INFO, "Setting thinkpad LED %d to %s\n", whichled, on ? "on" : "off" ); return 0; } int dell_app_wmi_led_set(int whichled, int on) { hda_log(HDA_LOG_INFO, "Setting Dell LED %d to %s\n", whichled, on ? "on" : "off" ); return 0; } int dell_micmute_led_set(int led) { hda_log(HDA_LOG_INFO, "Setting Dell LED to %d\n", led); return 0; } /* * dummy entries just for builds */ void snd_hda_sysfs_init(struct hda_codec *codec) {} void snd_hda_sysfs_clear(struct hda_codec *codec) {} struct class *sound_class; const struct attribute_group *snd_hda_dev_attr_groups[1]; /* * for device binding */ static LIST_HEAD(registered_drivers); int driver_register(struct device_driver *drv) { list_add_tail(&drv->list, ®istered_drivers); return 0; } void driver_unregister(struct device_driver *drv) { list_del(&drv->list); } static struct bus_type *_bus; int bus_register(struct bus_type *bus) { _bus = bus; return 0; } void bus_unregister(struct bus_type *bus) { } void device_initialize(struct device *dev) { dev->pmcnt = 0; dev->power.runtime_auto = 1; } int device_add(struct device *dev) { dev->registered = true; return device_attach(dev); } void device_del(struct device *dev) { dev->registered = false; if (dev->driver) { dev->driver->remove(dev); dev->driver = NULL; } } int device_attach(struct device *dev) { struct device_driver *drv; int err; if (!_bus) { hda_log(HDA_LOG_ERR, "Bus not registered!!\n"); return -ENXIO; } list_for_each_entry(drv, ®istered_drivers, list) { if (!_bus->match(dev, drv)) continue; dev->driver = drv; err = drv->probe(dev); if (!err) return 1; /* bound */ dev->driver = NULL; if (err < 0) { hda_log(HDA_LOG_INFO, "Driver %s not bound\n", drv->name); continue; } } return 0; } int device_release_driver(struct device *dev) { if (dev->driver) { dev->driver->remove(dev); dev->driver = NULL; } return 0; } /* */ static void check_resume(struct device *dev) { if (dev->pmcnt > 0 && dev->pmsuspended) { hda_log(HDA_LOG_INFO, "Codec resuming...\n"); dev->pmsuspended = false; if (dev->driver) dev->driver->pm->runtime_resume(dev); } } int pm_runtime_get_sync(struct device *dev) { dev->pmcnt++; check_resume(dev); return 0; } int pm_runtime_get_if_in_use(struct device *dev) { if (!hda_get_power_save()) return -EINVAL; if (dev->pmcnt > 0) { dev->pmcnt++; return 1; } return 0; } static void check_suspend(struct device *dev) { if (!dev->pmcnt && !dev->pmsuspended && dev->pmallow) { hda_log(HDA_LOG_INFO, "Codec suspending...\n"); if (dev->driver) dev->driver->pm->runtime_suspend(dev); dev->pmsuspended = true; } } int pm_runtime_put_autosuspend(struct device *dev) { dev->pmcnt--; check_suspend(dev); return 0; } int pm_runtime_force_suspend(struct device *dev) { if (!dev->driver || !dev->driver->pm) return -ENODEV; return dev->driver->pm->runtime_suspend(dev); } int pm_runtime_force_resume(struct device *dev) { if (!dev->driver || !dev->driver->pm) return -ENODEV; return dev->driver->pm->runtime_resume(dev); } int pm_runtime_suspend(struct device *dev) { if (!pm_runtime_suspended(dev)) return pm_runtime_force_suspend(dev); return 0; } int pm_runtime_resume(struct device *dev) { if (pm_runtime_suspended(dev)) return pm_runtime_force_resume(dev); return 0; } void pm_runtime_allow(struct device *dev) { dev->pmallow = 1; check_suspend(dev); } void pm_runtime_forbid(struct device *dev) { dev->pmallow = 0; check_resume(dev); } static enum led_brightness led_states[2]; static char *led_names[2] = { "mute", "micmute" }; static struct led_classdev *led_devs[2]; enum led_brightness ledtrig_audio_get(enum led_audio type) { return led_states[type]; } void ledtrig_audio_set(enum led_audio type, enum led_brightness state) { led_states[type] = state; hda_log(HDA_LOG_INFO, "LED state %s = %d\n", led_names[type], state); if (led_devs[type]) led_devs[type]->brightness_set_blocking(led_devs[type], state); } int led_classdev_register_ext(struct device *parent, struct led_classdev *led_cdev, struct led_init_data *init_data) { if (!strcmp(led_cdev->name, "hda::mute")) { led_devs[LED_AUDIO_MUTE] = led_cdev; } else if (!strcmp(led_cdev->name, "hda::micmute")) { led_devs[LED_AUDIO_MICMUTE] = led_cdev; } else { hda_log(HDA_LOG_ERR, "Invalid LED cdev name %s\n", led_cdev->name); return -EINVAL; } led_cdev->dev = calloc(sizeof(struct device), 1); led_cdev->dev->parent = parent; return 0; } void led_classdev_unregister(struct led_classdev *led_cdev) { } #ifdef SNDRV_CTL_ELEM_ACCESS_SPK_LED /* XXX called from hda-ctlsh.c */ void __led_trigger(unsigned int flags, long *vals) { struct led_classdev *cdev; if (flags & SNDRV_CTL_ELEM_ACCESS_SPK_LED) cdev = led_devs[LED_AUDIO_MUTE]; else if (flags & SNDRV_CTL_ELEM_ACCESS_MIC_LED) cdev = led_devs[LED_AUDIO_MICMUTE]; else return; if (!cdev) return; cdev->brightness_set_blocking(cdev, *vals); } #endif