diff options
Diffstat (limited to 'sound')
81 files changed, 2934 insertions, 525 deletions
diff --git a/sound/ac97/bus.c b/sound/ac97/bus.c index 0d31a6d71468..045330883a96 100644 --- a/sound/ac97/bus.c +++ b/sound/ac97/bus.c @@ -460,7 +460,7 @@ static ssize_t vendor_id_show(struct device *dev, { struct ac97_codec_device *codec = to_ac97_device(dev); - return sprintf(buf, "%08x", codec->vendor_id); + return sysfs_emit(buf, "%08x", codec->vendor_id); } DEVICE_ATTR_RO(vendor_id); diff --git a/sound/aoa/soundbus/sysfs.c b/sound/aoa/soundbus/sysfs.c index dead3105689b..e87b28428b99 100644 --- a/sound/aoa/soundbus/sysfs.c +++ b/sound/aoa/soundbus/sysfs.c @@ -10,19 +10,13 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, { struct soundbus_dev *sdev = to_soundbus_device(dev); struct platform_device *of = &sdev->ofdev; - int length; - if (*sdev->modalias) { - strscpy(buf, sdev->modalias, sizeof(sdev->modalias) + 1); - strcat(buf, "\n"); - length = strlen(buf); - } else { - length = sprintf(buf, "of:N%pOFn%c%s\n", - of->dev.of_node, 'T', - of_node_get_device_type(of->dev.of_node)); - } - - return length; + if (*sdev->modalias) + return sysfs_emit(buf, "%s\n", sdev->modalias); + else + return sysfs_emit(buf, "of:N%pOFn%c%s\n", + of->dev.of_node, 'T', + of_node_get_device_type(of->dev.of_node)); } static DEVICE_ATTR_RO(modalias); @@ -32,7 +26,7 @@ static ssize_t name_show(struct device *dev, struct soundbus_dev *sdev = to_soundbus_device(dev); struct platform_device *of = &sdev->ofdev; - return sprintf(buf, "%pOFn\n", of->dev.of_node); + return sysfs_emit(buf, "%pOFn\n", of->dev.of_node); } static DEVICE_ATTR_RO(name); @@ -42,7 +36,7 @@ static ssize_t type_show(struct device *dev, struct soundbus_dev *sdev = to_soundbus_device(dev); struct platform_device *of = &sdev->ofdev; - return sprintf(buf, "%s\n", of_node_get_device_type(of->dev.of_node)); + return sysfs_emit(buf, "%s\n", of_node_get_device_type(of->dev.of_node)); } static DEVICE_ATTR_RO(type); diff --git a/sound/core/Kconfig b/sound/core/Kconfig index dd7b40734723..12990d9a4dff 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -154,6 +154,16 @@ config SND_VERBOSE_PRINTK You don't need this unless you're debugging ALSA. +config SND_CTL_FAST_LOOKUP + bool "Fast lookup of control elements" if EXPERT + default y + select XARRAY_MULTI + help + This option enables the faster lookup of control elements. + It will consume more memory because of the additional Xarray. + If you want to choose the memory footprint over the performance + inevitably, turn this off. + config SND_DEBUG bool "Debug" help @@ -178,14 +188,29 @@ config SND_PCM_XRUN_DEBUG sound clicking when system is loaded, it may help to determine the process or driver which causes the scheduling gaps. -config SND_CTL_VALIDATION - bool "Perform sanity-checks for each control element access" +config SND_CTL_INPUT_VALIDATION + bool "Validate input data to control API" + help + Say Y to enable the additional validation for the input data to + each control element, including the value range checks. + An error is returned from ALSA core for invalid inputs without + passing to the driver. This is a kind of hardening for drivers + that have no proper error checks, at the cost of a slight + performance overhead. + +config SND_CTL_DEBUG + bool "Enable debugging feature for control API" depends on SND_DEBUG help - Say Y to enable the additional validation of each control element - access, including sanity-checks like whether the values returned - from the driver are in the proper ranges or the check of the invalid - access at out-of-array areas. + Say Y to enable the debugging feature for ALSA control API. + It performs the additional sanity-checks for each control element + read access, such as whether the values returned from the driver + are in the proper ranges or the check of the invalid access at + out-of-array areas. The error is printed when the driver gives + such unexpected values. + When you develop a driver that deals with control elements, it's + strongly recommended to try this one once and verify whether you see + any relevant errors or not. config SND_JACK_INJECTION_DEBUG bool "Sound jack injection interface via debugfs" diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index de514ec8c83d..243acad89fd3 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -810,7 +810,7 @@ static void error_delayed_work(struct work_struct *work) mutex_unlock(&stream->device->lock); } -/* +/** * snd_compr_stop_error: Report a fatal error on a stream * @stream: pointer to stream * @state: state to transition the stream to @@ -818,6 +818,8 @@ static void error_delayed_work(struct work_struct *work) * Stop the stream and set its state. * * Should be called with compressed device lock held. + * + * Return: zero if successful, or a negative error code */ int snd_compr_stop_error(struct snd_compr_stream *stream, snd_pcm_state_t state) @@ -1157,12 +1159,15 @@ static int snd_compress_dev_free(struct snd_device *device) return 0; } -/* +/** * snd_compress_new: create new compress device * @card: sound card pointer * @device: device number * @dirn: device direction, should be of type enum snd_compr_direction + * @id: ID string * @compr: compress device pointer + * + * Return: zero if successful, or a negative error code */ int snd_compress_new(struct snd_card *card, int device, int dirn, const char *id, struct snd_compr *compr) diff --git a/sound/core/control.c b/sound/core/control.c index a25c0d64d104..f3e893715369 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -127,6 +127,7 @@ static int snd_ctl_release(struct inode *inode, struct file *file) if (control->vd[idx].owner == ctl) control->vd[idx].owner = NULL; up_write(&card->controls_rwsem); + snd_fasync_free(ctl->fasync); snd_ctl_empty_read_queue(ctl); put_pid(ctl->pid); kfree(ctl); @@ -181,7 +182,7 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask, _found: wake_up(&ctl->change_sleep); spin_unlock(&ctl->read_lock); - kill_fasync(&ctl->fasync, SIGIO, POLL_IN); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_IN); } read_unlock_irqrestore(&card->ctl_files_rwlock, flags); } @@ -364,6 +365,93 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count) return 0; } +/* check whether the given id is contained in the given kctl */ +static bool elem_id_matches(const struct snd_kcontrol *kctl, + const struct snd_ctl_elem_id *id) +{ + return kctl->id.iface == id->iface && + kctl->id.device == id->device && + kctl->id.subdevice == id->subdevice && + !strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) && + kctl->id.index <= id->index && + kctl->id.index + kctl->count > id->index; +} + +#ifdef CONFIG_SND_CTL_FAST_LOOKUP +/* Compute a hash key for the corresponding ctl id + * It's for the name lookup, hence the numid is excluded. + * The hash key is bound in LONG_MAX to be used for Xarray key. + */ +#define MULTIPLIER 37 +static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id) +{ + unsigned long h; + const unsigned char *p; + + h = id->iface; + h = MULTIPLIER * h + id->device; + h = MULTIPLIER * h + id->subdevice; + for (p = id->name; *p; p++) + h = MULTIPLIER * h + *p; + h = MULTIPLIER * h + id->index; + h &= LONG_MAX; + return h; +} + +/* add hash entries to numid and ctl xarray tables */ +static void add_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id = kcontrol->id; + int i; + + xa_store_range(&card->ctl_numids, kcontrol->id.numid, + kcontrol->id.numid + kcontrol->count - 1, + kcontrol, GFP_KERNEL); + + for (i = 0; i < kcontrol->count; i++) { + id.index = kcontrol->id.index + i; + if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id), + kcontrol, GFP_KERNEL)) { + /* skip hash for this entry, noting we had collision */ + card->ctl_hash_collision = true; + dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n", + id.iface, id.name, id.index); + } + } +} + +/* remove hash entries that have been added */ +static void remove_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ + struct snd_ctl_elem_id id = kcontrol->id; + struct snd_kcontrol *matched; + unsigned long h; + int i; + + for (i = 0; i < kcontrol->count; i++) { + xa_erase(&card->ctl_numids, id.numid); + h = get_ctl_id_hash(&id); + matched = xa_load(&card->ctl_hash, h); + if (matched && (matched == kcontrol || + elem_id_matches(matched, &id))) + xa_erase(&card->ctl_hash, h); + id.index++; + id.numid++; + } +} +#else /* CONFIG_SND_CTL_FAST_LOOKUP */ +static inline void add_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ +} +static inline void remove_hash_entries(struct snd_card *card, + struct snd_kcontrol *kcontrol) +{ +} +#endif /* CONFIG_SND_CTL_FAST_LOOKUP */ + enum snd_ctl_add_mode { CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE, }; @@ -408,6 +496,8 @@ static int __snd_ctl_add_replace(struct snd_card *card, kcontrol->id.numid = card->last_numid + 1; card->last_numid += kcontrol->count; + add_hash_entries(card, kcontrol); + for (idx = 0; idx < kcontrol->count; idx++) snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx); @@ -479,6 +569,26 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, } EXPORT_SYMBOL(snd_ctl_replace); +static int __snd_ctl_remove(struct snd_card *card, + struct snd_kcontrol *kcontrol, + bool remove_hash) +{ + unsigned int idx; + + if (snd_BUG_ON(!card || !kcontrol)) + return -EINVAL; + list_del(&kcontrol->list); + + if (remove_hash) + remove_hash_entries(card, kcontrol); + + card->controls_count -= kcontrol->count; + for (idx = 0; idx < kcontrol->count; idx++) + snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx); + snd_ctl_free_one(kcontrol); + return 0; +} + /** * snd_ctl_remove - remove the control from the card and release it * @card: the card instance @@ -492,16 +602,7 @@ EXPORT_SYMBOL(snd_ctl_replace); */ int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol) { - unsigned int idx; - - if (snd_BUG_ON(!card || !kcontrol)) - return -EINVAL; - list_del(&kcontrol->list); - card->controls_count -= kcontrol->count; - for (idx = 0; idx < kcontrol->count; idx++) - snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx); - snd_ctl_free_one(kcontrol); - return 0; + return __snd_ctl_remove(card, kcontrol, true); } EXPORT_SYMBOL(snd_ctl_remove); @@ -642,14 +743,30 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, up_write(&card->controls_rwsem); return -ENOENT; } + remove_hash_entries(card, kctl); kctl->id = *dst_id; kctl->id.numid = card->last_numid + 1; card->last_numid += kctl->count; + add_hash_entries(card, kctl); up_write(&card->controls_rwsem); return 0; } EXPORT_SYMBOL(snd_ctl_rename_id); +#ifndef CONFIG_SND_CTL_FAST_LOOKUP +static struct snd_kcontrol * +snd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid) +{ + struct snd_kcontrol *kctl; + + list_for_each_entry(kctl, &card->controls, list) { + if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) + return kctl; + } + return NULL; +} +#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */ + /** * snd_ctl_find_numid - find the control instance with the given number-id * @card: the card instance @@ -665,15 +782,13 @@ EXPORT_SYMBOL(snd_ctl_rename_id); */ struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid) { - struct snd_kcontrol *kctl; - if (snd_BUG_ON(!card || !numid)) return NULL; - list_for_each_entry(kctl, &card->controls, list) { - if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid) - return kctl; - } - return NULL; +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + return xa_load(&card->ctl_numids, numid); +#else + return snd_ctl_find_numid_slow(card, numid); +#endif } EXPORT_SYMBOL(snd_ctl_find_numid); @@ -699,21 +814,18 @@ struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card, return NULL; if (id->numid != 0) return snd_ctl_find_numid(card, id->numid); - list_for_each_entry(kctl, &card->controls, list) { - if (kctl->id.iface != id->iface) - continue; - if (kctl->id.device != id->device) - continue; - if (kctl->id.subdevice != id->subdevice) - continue; - if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name))) - continue; - if (kctl->id.index > id->index) - continue; - if (kctl->id.index + kctl->count <= id->index) - continue; +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id)); + if (kctl && elem_id_matches(kctl, id)) return kctl; - } + if (!card->ctl_hash_collision) + return NULL; /* we can rely on only hash table */ +#endif + /* no matching in hash table - try all as the last resort */ + list_for_each_entry(kctl, &card->controls, list) + if (elem_id_matches(kctl, id)) + return kctl; + return NULL; } EXPORT_SYMBOL(snd_ctl_find_id); @@ -855,7 +967,6 @@ static const unsigned int value_sizes[] = { [SNDRV_CTL_ELEM_TYPE_INTEGER64] = sizeof(long long), }; -#ifdef CONFIG_SND_CTL_VALIDATION /* fill the remaining snd_ctl_elem_value data with the given pattern */ static void fill_remaining_elem_value(struct snd_ctl_elem_value *control, struct snd_ctl_elem_info *info, @@ -872,7 +983,7 @@ static void fill_remaining_elem_value(struct snd_ctl_elem_value *control, static int sanity_check_int_value(struct snd_card *card, const struct snd_ctl_elem_value *control, const struct snd_ctl_elem_info *info, - int i) + int i, bool print_error) { long long lval, lmin, lmax, lstep; u64 rem; @@ -906,21 +1017,23 @@ static int sanity_check_int_value(struct snd_card *card, } if (lval < lmin || lval > lmax) { - dev_err(card->dev, - "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", - control->id.iface, control->id.device, - control->id.subdevice, control->id.name, - control->id.index, lval, lmin, lmax, i); + if (print_error) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: value out of range %lld (%lld/%lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lmin, lmax, i); return -EINVAL; } if (lstep) { div64_u64_rem(lval, lstep, &rem); if (rem) { - dev_err(card->dev, - "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", - control->id.iface, control->id.device, - control->id.subdevice, control->id.name, - control->id.index, lval, lstep, i); + if (print_error) + dev_err(card->dev, + "control %i:%i:%i:%s:%i: unaligned value %lld (step %lld) at count %i\n", + control->id.iface, control->id.device, + control->id.subdevice, control->id.name, + control->id.index, lval, lstep, i); return -EINVAL; } } @@ -928,15 +1041,13 @@ static int sanity_check_int_value(struct snd_card *card, return 0; } -/* perform sanity checks to the given snd_ctl_elem_value object */ -static int sanity_check_elem_value(struct snd_card *card, - const struct snd_ctl_elem_value *control, - const struct snd_ctl_elem_info *info, - u32 pattern) +/* check whether the all input values are valid for the given elem value */ +static int sanity_check_input_values(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + bool print_error) { - size_t offset; - int i, ret = 0; - u32 *p; + int i, ret; switch (info->type) { case SNDRV_CTL_ELEM_TYPE_BOOLEAN: @@ -944,7 +1055,8 @@ static int sanity_check_elem_value(struct snd_card *card, case SNDRV_CTL_ELEM_TYPE_INTEGER64: case SNDRV_CTL_ELEM_TYPE_ENUMERATED: for (i = 0; i < info->count; i++) { - ret = sanity_check_int_value(card, control, info, i); + ret = sanity_check_int_value(card, control, info, i, + print_error); if (ret < 0) return ret; } @@ -953,6 +1065,23 @@ static int sanity_check_elem_value(struct snd_card *card, break; } + return 0; +} + +/* perform sanity checks to the given snd_ctl_elem_value object */ +static int sanity_check_elem_value(struct snd_card *card, + const struct snd_ctl_elem_value *control, + const struct snd_ctl_elem_info *info, + u32 pattern) +{ + size_t offset; + int ret; + u32 *p; + + ret = sanity_check_input_values(card, control, info, true); + if (ret < 0) + return ret; + /* check whether the remaining area kept untouched */ offset = value_sizes[info->type] * info->count; offset = DIV_ROUND_UP(offset, sizeof(u32)); @@ -967,21 +1096,6 @@ static int sanity_check_elem_value(struct snd_card *card, return ret; } -#else -static inline void fill_remaining_elem_value(struct snd_ctl_elem_value *control, - struct snd_ctl_elem_info *info, - u32 pattern) -{ -} - -static inline int sanity_check_elem_value(struct snd_card *card, - struct snd_ctl_elem_value *control, - struct snd_ctl_elem_info *info, - u32 pattern) -{ - return 0; -} -#endif static int __snd_ctl_elem_info(struct snd_card *card, struct snd_kcontrol *kctl, @@ -1077,7 +1191,7 @@ static int snd_ctl_elem_read(struct snd_card *card, snd_ctl_build_ioff(&control->id, kctl, index_offset); -#ifdef CONFIG_SND_CTL_VALIDATION +#ifdef CONFIG_SND_CTL_DEBUG /* info is needed only for validation */ memset(&info, 0, sizeof(info)); info.id = control->id; @@ -1154,6 +1268,17 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, snd_ctl_build_ioff(&control->id, kctl, index_offset); result = snd_power_ref_and_wait(card); + /* validate input values */ + if (IS_ENABLED(CONFIG_SND_CTL_INPUT_VALIDATION) && !result) { + struct snd_ctl_elem_info info; + + memset(&info, 0, sizeof(info)); + info.id = control->id; + result = __snd_ctl_elem_info(card, kctl, &info, NULL); + if (!result) + result = sanity_check_input_values(card, control, &info, + false); + } if (!result) result = kctl->put(kctl, control); snd_power_unref(card); @@ -1930,6 +2055,8 @@ static int _snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn, struct list_head * * @fcn: ioctl callback function * * called from each device manager like pcm.c, hwdep.c, etc. + * + * Return: zero if successful, or a negative error code */ int snd_ctl_register_ioctl(snd_kctl_ioctl_func_t fcn) { @@ -1942,6 +2069,8 @@ EXPORT_SYMBOL(snd_ctl_register_ioctl); * snd_ctl_register_ioctl_compat - register the device-specific 32bit compat * control-ioctls * @fcn: ioctl callback function + * + * Return: zero if successful, or a negative error code */ int snd_ctl_register_ioctl_compat(snd_kctl_ioctl_func_t fcn) { @@ -1977,6 +2106,8 @@ static int _snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn, /** * snd_ctl_unregister_ioctl - de-register the device-specific control-ioctls * @fcn: ioctl callback function to unregister + * + * Return: zero if successful, or a negative error code */ int snd_ctl_unregister_ioctl(snd_kctl_ioctl_func_t fcn) { @@ -1989,6 +2120,8 @@ EXPORT_SYMBOL(snd_ctl_unregister_ioctl); * snd_ctl_unregister_ioctl_compat - de-register the device-specific compat * 32bit control-ioctls * @fcn: ioctl callback function to unregister + * + * Return: zero if successful, or a negative error code */ int snd_ctl_unregister_ioctl_compat(snd_kctl_ioctl_func_t fcn) { @@ -2002,7 +2135,7 @@ static int snd_ctl_fasync(int fd, struct file * file, int on) struct snd_ctl_file *ctl; ctl = file->private_data; - return fasync_helper(fd, file, on, &ctl->fasync); + return snd_fasync_helper(fd, file, on, &ctl->fasync); } /* return the preferred subdevice number if already assigned; @@ -2044,7 +2177,7 @@ EXPORT_SYMBOL_GPL(snd_ctl_get_preferred_subdevice); * snd_ctl_request_layer - request to use the layer * @module_name: Name of the kernel module (NULL == build-in) * - * Return an error code when the module cannot be loaded. + * Return: zero if successful, or an error code when the module cannot be loaded */ int snd_ctl_request_layer(const char *module_name) { @@ -2170,7 +2303,7 @@ static int snd_ctl_dev_disconnect(struct snd_device *device) read_lock_irqsave(&card->ctl_files_rwlock, flags); list_for_each_entry(ctl, &card->ctl_files, list) { wake_up(&ctl->change_sleep); - kill_fasync(&ctl->fasync, SIGIO, POLL_ERR); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_ERR); } read_unlock_irqrestore(&card->ctl_files_rwlock, flags); @@ -2195,8 +2328,13 @@ static int snd_ctl_dev_free(struct snd_device *device) down_write(&card->controls_rwsem); while (!list_empty(&card->controls)) { control = snd_kcontrol(card->controls.next); - snd_ctl_remove(card, control); + __snd_ctl_remove(card, control, false); } + +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + xa_destroy(&card->ctl_numids); + xa_destroy(&card->ctl_hash); +#endif up_write(&card->controls_rwsem); put_device(&card->ctl_dev); return 0; @@ -2241,6 +2379,8 @@ int snd_ctl_create(struct snd_card *card) * * This is a function that can be used as info callback for a standard * boolean control with a single mono channel. + * + * Return: Zero (always successful) */ int snd_ctl_boolean_mono_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -2261,6 +2401,8 @@ EXPORT_SYMBOL(snd_ctl_boolean_mono_info); * * This is a function that can be used as info callback for a standard * boolean control with stereo two channels. + * + * Return: Zero (always successful) */ int snd_ctl_boolean_stereo_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -2284,7 +2426,7 @@ EXPORT_SYMBOL(snd_ctl_boolean_stereo_info); * If the control's accessibility is not the default (readable and writable), * the caller has to fill @info->access. * - * Return: Zero. + * Return: Zero (always successful) */ int snd_ctl_enum_info(struct snd_ctl_elem_info *info, unsigned int channels, unsigned int items, const char *const names[]) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 207828f30983..f975cc85772b 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -405,7 +405,7 @@ static ssize_t mode_show(struct device *dev, case MODE_ON: str = "on"; break; case MODE_OFF: str = "off"; break; } - return sprintf(buf, "%s\n", str); + return sysfs_emit(buf, "%s\n", str); } static ssize_t mode_store(struct device *dev, @@ -443,7 +443,7 @@ static ssize_t brightness_show(struct device *dev, { struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); - return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); + return sysfs_emit(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); } static DEVICE_ATTR_RW(mode); @@ -618,8 +618,7 @@ static ssize_t list_show(struct device *dev, struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); struct snd_card *card; struct snd_ctl_led_ctl *lctl; - char *buf2 = buf; - size_t l; + size_t l = 0; card = snd_card_ref(led_card->number); if (!card) @@ -627,23 +626,19 @@ static ssize_t list_show(struct device *dev, down_read(&card->controls_rwsem); mutex_lock(&snd_ctl_led_mutex); if (snd_ctl_led_card_valid[led_card->number]) { - list_for_each_entry(lctl, &led_card->led->controls, list) - if (lctl->card == card) { - if (buf2 - buf > PAGE_SIZE - 16) - break; - if (buf2 != buf) - *buf2++ = ' '; - l = scnprintf(buf2, 15, "%u", - lctl->kctl->id.numid + - lctl->index_offset); - buf2[l] = '\0'; - buf2 += l + 1; - } + list_for_each_entry(lctl, &led_card->led->controls, list) { + if (lctl->card != card) + continue; + if (l) + l += sysfs_emit_at(buf, l, " "); + l += sysfs_emit_at(buf, l, "%u", + lctl->kctl->id.numid + lctl->index_offset); + } } mutex_unlock(&snd_ctl_led_mutex); up_read(&card->controls_rwsem); snd_card_unref(card); - return buf2 - buf; + return l; } static DEVICE_ATTR_WO(attach); diff --git a/sound/core/device.c b/sound/core/device.c index bf0b04a7ee79..b57d80a17052 100644 --- a/sound/core/device.c +++ b/sound/core/device.c @@ -247,6 +247,8 @@ void snd_device_free_all(struct snd_card *card) * device, either @SNDRV_DEV_BUILD, @SNDRV_DEV_REGISTERED or * @SNDRV_DEV_DISCONNECTED is returned. * Or for a non-existing device, -1 is returned as an error. + * + * Return: the current state, or -1 if not found */ int snd_device_get_state(struct snd_card *card, void *device_data) { diff --git a/sound/core/info.c b/sound/core/info.c index 782fba87cc04..b8058b341178 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -868,6 +868,8 @@ EXPORT_SYMBOL(snd_info_register); * * This proc file entry will be registered via snd_card_register() call, and * it will be removed automatically at the card removal, too. + * + * Return: zero if successful, or a negative error code */ int snd_card_rw_proc_new(struct snd_card *card, const char *name, void *private_data, diff --git a/sound/core/init.c b/sound/core/init.c index 726a8353201f..193dae361fac 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -215,6 +215,8 @@ static void __snd_card_release(struct device *dev, void *data) * via snd_card_free() call in the error; otherwise it may lead to UAF due to * devres call orders. You can use snd_card_free_on_error() helper for * handling it more easily. + * + * Return: zero if successful, or a negative error code */ int snd_devm_card_new(struct device *parent, int idx, const char *xid, struct module *module, size_t extra_size, @@ -249,6 +251,8 @@ EXPORT_SYMBOL_GPL(snd_devm_card_new); * This function handles the explicit snd_card_free() call at the error from * the probe callback. It's just a small helper for simplifying the error * handling with the managed devices. + * + * Return: zero if successful, or a negative error code */ int snd_card_free_on_error(struct device *dev, int ret) { @@ -310,6 +314,10 @@ static int snd_card_init(struct snd_card *card, struct device *parent, rwlock_init(&card->ctl_files_rwlock); INIT_LIST_HEAD(&card->controls); INIT_LIST_HEAD(&card->ctl_files); +#ifdef CONFIG_SND_CTL_FAST_LOOKUP + xa_init(&card->ctl_numids); + xa_init(&card->ctl_hash); +#endif spin_lock_init(&card->files_lock); INIT_LIST_HEAD(&card->files_list); mutex_init(&card->memory_mutex); @@ -366,6 +374,8 @@ static int snd_card_init(struct snd_card *card, struct device *parent, * * Returns a card object corresponding to the given index or NULL if not found. * Release the object via snd_card_unref(). + * + * Return: a card object or NULL */ struct snd_card *snd_card_ref(int idx) { @@ -604,6 +614,8 @@ static int snd_card_do_free(struct snd_card *card) * resource immediately, but tries to disconnect at first. When the card * is still in use, the function returns before freeing the resources. * The card resources will be freed when the refcount gets to zero. + * + * Return: zero if successful, or a negative error code */ int snd_card_free_when_closed(struct snd_card *card) { @@ -772,7 +784,7 @@ static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf) { struct snd_card *card = container_of(dev, struct snd_card, card_dev); - return scnprintf(buf, PAGE_SIZE, "%s\n", card->id); + return sysfs_emit(buf, "%s\n", card->id); } static ssize_t id_store(struct device *dev, struct device_attribute *attr, @@ -810,7 +822,7 @@ static ssize_t number_show(struct device *dev, struct device_attribute *attr, char *buf) { struct snd_card *card = container_of(dev, struct snd_card, card_dev); - return scnprintf(buf, PAGE_SIZE, "%i\n", card->number); + return sysfs_emit(buf, "%i\n", card->number); } static DEVICE_ATTR_RO(number); @@ -829,6 +841,8 @@ static const struct attribute_group card_dev_attr_group = { * snd_card_add_dev_attr - Append a new sysfs attribute group to card * @card: card instance * @group: attribute group to append + * + * Return: zero if successful, or a negative error code */ int snd_card_add_dev_attr(struct snd_card *card, const struct attribute_group *group) diff --git a/sound/core/isadma.c b/sound/core/isadma.c index 1f45ede023b4..28768061d769 100644 --- a/sound/core/isadma.c +++ b/sound/core/isadma.c @@ -12,8 +12,8 @@ #undef HAVE_REALLY_SLOW_DMA_CONTROLLER #include <linux/export.h> +#include <linux/isa-dma.h> #include <sound/core.h> -#include <asm/dma.h> /** * snd_dma_program - program an ISA DMA transfer @@ -116,8 +116,9 @@ static void __snd_release_dma(struct device *dev, void *data) * @dma: the dma number * @name: the name string of the requester * - * Returns zero on success, or a negative error code. * The requested DMA will be automatically released at unbinding via devres. + * + * Return: zero on success, or a negative error code */ int snd_devm_request_dma(struct device *dev, int dma, const char *name) { diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 15dc7160ba34..d3885cb02270 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -147,7 +147,7 @@ static void __snd_release_pages(struct device *dev, void *res) * hence it can't work with SNDRV_DMA_TYPE_CONTINUOUS or * SNDRV_DMA_TYPE_VMALLOC type. * - * The function returns the snd_dma_buffer object at success, or NULL if failed. + * Return: the snd_dma_buffer object at success, or NULL if failed */ struct snd_dma_buffer * snd_devm_alloc_dir_pages(struct device *dev, int type, @@ -179,6 +179,8 @@ EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_pages); * snd_dma_buffer_mmap - perform mmap of the given DMA buffer * @dmab: buffer allocation information * @area: VM area information + * + * Return: zero if successful, or a negative error code */ int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, struct vm_area_struct *area) @@ -219,6 +221,8 @@ EXPORT_SYMBOL_GPL(snd_dma_buffer_sync); * snd_sgbuf_get_addr - return the physical address at the corresponding offset * @dmab: buffer allocation information * @offset: offset in the ring buffer + * + * Return: the physical address */ dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset) { @@ -235,6 +239,8 @@ EXPORT_SYMBOL(snd_sgbuf_get_addr); * snd_sgbuf_get_page - return the physical page at the corresponding offset * @dmab: buffer allocation information * @offset: offset in the ring buffer + * + * Return: the page pointer */ struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset) { @@ -253,6 +259,8 @@ EXPORT_SYMBOL(snd_sgbuf_get_page); * @dmab: buffer allocation information * @ofs: offset in the ring buffer * @size: the requested size + * + * Return: the chunk size */ unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size) @@ -431,33 +439,17 @@ static const struct snd_malloc_ops snd_dma_iram_ops = { */ static void *snd_dma_dev_alloc(struct snd_dma_buffer *dmab, size_t size) { - void *p; - - p = dma_alloc_coherent(dmab->dev.dev, size, &dmab->addr, DEFAULT_GFP); -#ifdef CONFIG_X86 - if (p && dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC) - set_memory_wc((unsigned long)p, PAGE_ALIGN(size) >> PAGE_SHIFT); -#endif - return p; + return dma_alloc_coherent(dmab->dev.dev, size, &dmab->addr, DEFAULT_GFP); } static void snd_dma_dev_free(struct snd_dma_buffer *dmab) { -#ifdef CONFIG_X86 - if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC) - set_memory_wb((unsigned long)dmab->area, - PAGE_ALIGN(dmab->bytes) >> PAGE_SHIFT); -#endif dma_free_coherent(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); } static int snd_dma_dev_mmap(struct snd_dma_buffer *dmab, struct vm_area_struct *area) { -#ifdef CONFIG_X86 - if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC) - area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); -#endif return dma_mmap_coherent(dmab->dev.dev, area, dmab->area, dmab->addr, dmab->bytes); } @@ -471,10 +463,6 @@ static const struct snd_malloc_ops snd_dma_dev_ops = { /* * Write-combined pages */ -#ifdef CONFIG_X86 -/* On x86, share the same ops as the standard dev ops */ -#define snd_dma_wc_ops snd_dma_dev_ops -#else /* CONFIG_X86 */ static void *snd_dma_wc_alloc(struct snd_dma_buffer *dmab, size_t size) { return dma_alloc_wc(dmab->dev.dev, size, &dmab->addr, DEFAULT_GFP); @@ -497,7 +485,6 @@ static const struct snd_malloc_ops snd_dma_wc_ops = { .free = snd_dma_wc_free, .mmap = snd_dma_wc_mmap, }; -#endif /* CONFIG_X86 */ #ifdef CONFIG_SND_DMA_SGBUF static void *snd_dma_sg_fallback_alloc(struct snd_dma_buffer *dmab, size_t size); diff --git a/sound/core/misc.c b/sound/core/misc.c index 50e4aaa6270d..d32a19976a2b 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -10,6 +10,7 @@ #include <linux/time.h> #include <linux/slab.h> #include <linux/ioport.h> +#include <linux/fs.h> #include <sound/core.h> #ifdef CONFIG_SND_DEBUG @@ -145,3 +146,96 @@ snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) } EXPORT_SYMBOL(snd_pci_quirk_lookup); #endif + +/* + * Deferred async signal helpers + * + * Below are a few helper functions to wrap the async signal handling + * in the deferred work. The main purpose is to avoid the messy deadlock + * around tasklist_lock and co at the kill_fasync() invocation. + * fasync_helper() and kill_fasync() are replaced with snd_fasync_helper() + * and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has + * to be called at releasing the relevant file object. + */ +struct snd_fasync { + struct fasync_struct *fasync; + int signal; + int poll; + int on; + struct list_head list; +}; + +static DEFINE_SPINLOCK(snd_fasync_lock); +static LIST_HEAD(snd_fasync_list); + +static void snd_fasync_work_fn(struct work_struct *work) +{ + struct snd_fasync *fasync; + + spin_lock_irq(&snd_fasync_lock); + while (!list_empty(&snd_fasync_list)) { + fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); + list_del_init(&fasync->list); + spin_unlock_irq(&snd_fasync_lock); + if (fasync->on) + kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); + spin_lock_irq(&snd_fasync_lock); + } + spin_unlock_irq(&snd_fasync_lock); +} + +static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); + +int snd_fasync_helper(int fd, struct file *file, int on, + struct snd_fasync **fasyncp) +{ + struct snd_fasync *fasync = NULL; + + if (on) { + fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); + if (!fasync) + return -ENOMEM; + INIT_LIST_HEAD(&fasync->list); + } + + spin_lock_irq(&snd_fasync_lock); + if (*fasyncp) { + kfree(fasync); + fasync = *fasyncp; + } else { + if (!fasync) { + spin_unlock_irq(&snd_fasync_lock); + return 0; + } + *fasyncp = fasync; + } + fasync->on = on; + spin_unlock_irq(&snd_fasync_lock); + return fasync_helper(fd, file, on, &fasync->fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_helper); + +void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll) +{ + unsigned long flags; + + if (!fasync || !fasync->on) + return; + spin_lock_irqsave(&snd_fasync_lock, flags); + fasync->signal = signal; + fasync->poll = poll; + list_move(&fasync->list, &snd_fasync_list); + schedule_work(&snd_fasync_work); + spin_unlock_irqrestore(&snd_fasync_lock, flags); +} +EXPORT_SYMBOL_GPL(snd_kill_fasync); + +void snd_fasync_free(struct snd_fasync *fasync) +{ + if (!fasync) + return; + fasync->on = 0; + flush_work(&snd_fasync_work); + kfree(fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_free); diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 977d54320a5c..82925709fa12 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -216,6 +216,8 @@ static const char * const snd_pcm_format_names[] = { /** * snd_pcm_format_name - Return a name string for the given PCM format * @format: PCM format + * + * Return: the format name string */ const char *snd_pcm_format_name(snd_pcm_format_t format) { @@ -1005,6 +1007,7 @@ void snd_pcm_detach_substream(struct snd_pcm_substream *substream) substream->runtime = NULL; } mutex_destroy(&runtime->buffer_mutex); + snd_fasync_free(runtime->fasync); kfree(runtime); put_pid(substream->pid); substream->pid = NULL; @@ -1028,7 +1031,7 @@ static ssize_t pcm_class_show(struct device *dev, str = "none"; else str = strs[pcm->dev_class]; - return sprintf(buf, "%s\n", str); + return sysfs_emit(buf, "%s\n", str); } static DEVICE_ATTR_RO(pcm_class); @@ -1138,6 +1141,8 @@ static int snd_pcm_dev_disconnect(struct snd_device *device) * This adds the given notifier to the global list so that the callback is * called for each registered PCM devices. This exists only for PCM OSS * emulation, so far. + * + * Return: zero if successful, or a negative error code */ int snd_pcm_notify(struct snd_pcm_notify *notify, int nfree) { diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index af6f717e1e7e..5b2ca028f5aa 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -48,6 +48,8 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_get_chan); * * This function can be used to initialize a dma_slave_config from a substream * and hw_params in a dmaengine based PCM driver implementation. + * + * Return: zero if successful, or a negative error code */ int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream, const struct snd_pcm_hw_params *params, @@ -175,10 +177,10 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) * @substream: PCM substream * @cmd: Trigger command * - * Returns 0 on success, a negative error code otherwise. - * * This function can be used as the PCM trigger callback for dmaengine based PCM * driver implementations. + * + * Return: 0 on success, a negative error code otherwise */ int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -223,6 +225,8 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_trigger); * * This function is deprecated and should not be used by new drivers, as its * results may be unreliable. + * + * Return: PCM position in frames */ snd_pcm_uframes_t snd_dmaengine_pcm_pointer_no_residue(struct snd_pcm_substream *substream) { @@ -237,6 +241,8 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer_no_residue); * * This function can be used as the PCM pointer callback for dmaengine based PCM * driver implementations. + * + * Return: PCM position in frames */ snd_pcm_uframes_t snd_dmaengine_pcm_pointer(struct snd_pcm_substream *substream) { @@ -266,9 +272,9 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_pointer); * @filter_fn: Filter function used to request the DMA channel * @filter_data: Data passed to the DMA filter function * - * Returns NULL or the requested DMA channel. - * * This function request a DMA channel for usage with dmaengine PCM. + * + * Return: NULL or the requested DMA channel */ struct dma_chan *snd_dmaengine_pcm_request_channel(dma_filter_fn filter_fn, void *filter_data) @@ -288,11 +294,11 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_request_channel); * @substream: PCM substream * @chan: DMA channel to use for data transfers * - * Returns 0 on success, a negative error code otherwise. - * * The function should usually be called from the pcm open callback. Note that * this function will use private_data field of the substream's runtime. So it * is not available to your pcm driver implementation. + * + * Return: 0 on success, a negative error code otherwise */ int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream, struct dma_chan *chan) @@ -326,12 +332,12 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open); * @filter_fn: Filter function used to request the DMA channel * @filter_data: Data passed to the DMA filter function * - * Returns 0 on success, a negative error code otherwise. - * * This function will request a DMA channel using the passed filter function and * data. The function should usually be called from the pcm open callback. Note * that this function will use private_data field of the substream's runtime. So * it is not available to your pcm driver implementation. + * + * Return: 0 on success, a negative error code otherwise */ int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, dma_filter_fn filter_fn, void *filter_data) @@ -344,6 +350,8 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan); /** * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream * @substream: PCM substream + * + * Return: 0 on success, a negative error code otherwise */ int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) { @@ -362,6 +370,8 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); * @substream: PCM substream * * Releases the DMA channel associated with the PCM substream. + * + * Return: zero if successful, or a negative error code */ int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream) { @@ -382,10 +392,10 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan); * @hw: PCM hw params * @chan: DMA channel to use for data transfers * - * Returns 0 on success, a negative error code otherwise. - * * This function will query DMA capability, then refine the pcm hardware * parameters. + * + * Return: 0 on success, a negative error code otherwise */ int snd_dmaengine_pcm_refine_runtime_hwparams( struct snd_pcm_substream *substream, diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 1fc7c50ffa62..40751e5aff09 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1822,7 +1822,7 @@ void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substrea snd_timer_interrupt(substream->timer, 1); #endif _end: - kill_fasync(&runtime->fasync, SIGIO, POLL_IN); + snd_kill_fasync(runtime->fasync, SIGIO, POLL_IN); } EXPORT_SYMBOL(snd_pcm_period_elapsed_under_stream_lock); diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index b8296b6eb2c1..7bde7fb64011 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -350,6 +350,8 @@ EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); * SNDRV_DMA_TYPE_VMALLOC type. * * Upon successful buffer allocation and setup, the function returns 0. + * + * Return: zero if successful, or a negative error code */ int snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type, struct device *data, size_t size, size_t max) @@ -369,6 +371,8 @@ EXPORT_SYMBOL(snd_pcm_set_managed_buffer); * * Do pre-allocation to all substreams of the given pcm for the specified DMA * type and size, and set the managed_buffer_alloc flag to each substream. + * + * Return: zero if successful, or a negative error code */ int snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, struct device *data, diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 4adaee62ef33..ad0541e9e888 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3412,6 +3412,8 @@ static long snd_pcm_ioctl(struct file *file, unsigned int cmd, * The function is provided primarily for OSS layer and USB gadget drivers, * and it allows only the limited set of ioctls (hw_params, sw_params, * prepare, start, drain, drop, forward). + * + * Return: zero if successful, or a negative error code */ int snd_pcm_kernel_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) @@ -3810,6 +3812,8 @@ static const struct vm_operations_struct snd_pcm_vm_ops_data_fault = { * * This is the default mmap handler for PCM data. When mmap pcm_ops is NULL, * this function is invoked implicitly. + * + * Return: zero if successful, or a negative error code */ int snd_pcm_lib_default_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *area) @@ -3836,6 +3840,8 @@ EXPORT_SYMBOL_GPL(snd_pcm_lib_default_mmap); * When your hardware uses the iomapped pages as the hardware buffer and * wants to mmap it, pass this function as mmap pcm_ops. Note that this * is supposed to work only on limited architectures. + * + * Return: zero if successful, or a negative error code */ int snd_pcm_lib_mmap_iomem(struct snd_pcm_substream *substream, struct vm_area_struct *area) @@ -3945,7 +3951,7 @@ static int snd_pcm_fasync(int fd, struct file * file, int on) runtime = substream->runtime; if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; - return fasync_helper(fd, file, on, &runtime->fasync); + return snd_fasync_helper(fd, file, on, &runtime->fasync); } /* diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index befa9809ff00..6963d5a487b3 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -102,13 +102,12 @@ static inline bool __snd_rawmidi_ready(struct snd_rawmidi_runtime *runtime) static bool snd_rawmidi_ready(struct snd_rawmidi_substream *substream) { - struct snd_rawmidi_runtime *runtime = substream->runtime; unsigned long flags; bool ready; - spin_lock_irqsave(&runtime->lock, flags); - ready = __snd_rawmidi_ready(runtime); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); + ready = __snd_rawmidi_ready(substream->runtime); + spin_unlock_irqrestore(&substream->lock, flags); return ready; } @@ -130,7 +129,7 @@ static void snd_rawmidi_input_event_work(struct work_struct *work) runtime->event(runtime->substream); } -/* buffer refcount management: call with runtime->lock held */ +/* buffer refcount management: call with substream->lock held */ static inline void snd_rawmidi_buffer_ref(struct snd_rawmidi_runtime *runtime) { runtime->buffer_ref++; @@ -141,6 +140,23 @@ static inline void snd_rawmidi_buffer_unref(struct snd_rawmidi_runtime *runtime) runtime->buffer_ref--; } +static void snd_rawmidi_buffer_ref_sync(struct snd_rawmidi_substream *substream) +{ + int loop = HZ; + + spin_lock_irq(&substream->lock); + while (substream->runtime->buffer_ref) { + spin_unlock_irq(&substream->lock); + if (!--loop) { + rmidi_err(substream->rmidi, "Buffer ref sync timeout\n"); + return; + } + schedule_timeout_uninterruptible(1); + spin_lock_irq(&substream->lock); + } + spin_unlock_irq(&substream->lock); +} + static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) { struct snd_rawmidi_runtime *runtime; @@ -149,7 +165,6 @@ static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) if (!runtime) return -ENOMEM; runtime->substream = substream; - spin_lock_init(&runtime->lock); init_waitqueue_head(&runtime->sleep); INIT_WORK(&runtime->event_work, snd_rawmidi_input_event_work); runtime->event = NULL; @@ -203,35 +218,48 @@ static void __reset_runtime_ptrs(struct snd_rawmidi_runtime *runtime, runtime->avail = is_input ? 0 : runtime->buffer_size; } -static void reset_runtime_ptrs(struct snd_rawmidi_runtime *runtime, +static void reset_runtime_ptrs(struct snd_rawmidi_substream *substream, bool is_input) { unsigned long flags; - spin_lock_irqsave(&runtime->lock, flags); - __reset_runtime_ptrs(runtime, is_input); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); + if (substream->opened && substream->runtime) + __reset_runtime_ptrs(substream->runtime, is_input); + spin_unlock_irqrestore(&substream->lock, flags); } int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) { snd_rawmidi_output_trigger(substream, 0); - reset_runtime_ptrs(substream->runtime, false); + reset_runtime_ptrs(substream, false); return 0; } EXPORT_SYMBOL(snd_rawmidi_drop_output); int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) { - int err; + int err = 0; long timeout; - struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_runtime *runtime; + + spin_lock_irq(&substream->lock); + runtime = substream->runtime; + if (!substream->opened || !runtime || !runtime->buffer) { + err = -EINVAL; + } else { + snd_rawmidi_buffer_ref(runtime); + runtime->drain = 1; + } + spin_unlock_irq(&substream->lock); + if (err < 0) + return err; - err = 0; - runtime->drain = 1; timeout = wait_event_interruptible_timeout(runtime->sleep, (runtime->avail >= runtime->buffer_size), 10*HZ); + + spin_lock_irq(&substream->lock); if (signal_pending(current)) err = -ERESTARTSYS; if (runtime->avail < runtime->buffer_size && !timeout) { @@ -241,6 +269,8 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) err = -EIO; } runtime->drain = 0; + spin_unlock_irq(&substream->lock); + if (err != -ERESTARTSYS) { /* we need wait a while to make sure that Tx FIFOs are empty */ if (substream->ops->drain) @@ -249,6 +279,11 @@ int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) msleep(50); snd_rawmidi_drop_output(substream); } + + spin_lock_irq(&substream->lock); + snd_rawmidi_buffer_unref(runtime); + spin_unlock_irq(&substream->lock); + return err; } EXPORT_SYMBOL(snd_rawmidi_drain_output); @@ -256,7 +291,7 @@ EXPORT_SYMBOL(snd_rawmidi_drain_output); int snd_rawmidi_drain_input(struct snd_rawmidi_substream *substream) { snd_rawmidi_input_trigger(substream, 0); - reset_runtime_ptrs(substream->runtime, true); + reset_runtime_ptrs(substream, true); return 0; } EXPORT_SYMBOL(snd_rawmidi_drain_input); @@ -311,12 +346,14 @@ static int open_substream(struct snd_rawmidi *rmidi, snd_rawmidi_runtime_free(substream); return err; } + spin_lock_irq(&substream->lock); substream->opened = 1; substream->active_sensing = 0; if (mode & SNDRV_RAWMIDI_LFLG_APPEND) substream->append = 1; substream->pid = get_pid(task_pid(current)); rmidi->streams[substream->stream].substream_opened++; + spin_unlock_irq(&substream->lock); } substream->use_count++; return 0; @@ -521,13 +558,16 @@ static void close_substream(struct snd_rawmidi *rmidi, if (snd_rawmidi_drain_output(substream) == -ERESTARTSYS) snd_rawmidi_output_trigger(substream, 0); } + snd_rawmidi_buffer_ref_sync(substream); } + spin_lock_irq(&substream->lock); + substream->opened = 0; + substream->append = 0; + spin_unlock_irq(&substream->lock); substream->ops->close(substream); if (substream->runtime->private_free) substream->runtime->private_free(substream); snd_rawmidi_runtime_free(substream); - substream->opened = 0; - substream->append = 0; put_pid(substream->pid); substream->pid = NULL; rmidi->streams[substream->stream].substream_opened--; @@ -676,10 +716,11 @@ static int snd_rawmidi_info_select_user(struct snd_card *card, return 0; } -static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, +static int resize_runtime_buffer(struct snd_rawmidi_substream *substream, struct snd_rawmidi_params *params, bool is_input) { + struct snd_rawmidi_runtime *runtime = substream->runtime; char *newbuf, *oldbuf; unsigned int framing = params->mode & SNDRV_RAWMIDI_MODE_FRAMING_MASK; @@ -693,9 +734,9 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, newbuf = kvzalloc(params->buffer_size, GFP_KERNEL); if (!newbuf) return -ENOMEM; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); if (runtime->buffer_ref) { - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); kvfree(newbuf); return -EBUSY; } @@ -703,7 +744,7 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, runtime->buffer = newbuf; runtime->buffer_size = params->buffer_size; __reset_runtime_ptrs(runtime, is_input); - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); kvfree(oldbuf); } runtime->avail_min = params->avail_min; @@ -713,11 +754,19 @@ static int resize_runtime_buffer(struct snd_rawmidi_runtime *runtime, int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, struct snd_rawmidi_params *params) { - if (substream->append && substream->use_count > 1) - return -EBUSY; + int err; + snd_rawmidi_drain_output(substream); - substream->active_sensing = !params->no_active_sensing; - return resize_runtime_buffer(substream->runtime, params, false); + mutex_lock(&substream->rmidi->open_mutex); + if (substream->append && substream->use_count > 1) + err = -EBUSY; + else + err = resize_runtime_buffer(substream, params, false); + + if (!err) + substream->active_sensing = !params->no_active_sensing; + mutex_unlock(&substream->rmidi->open_mutex); + return err; } EXPORT_SYMBOL(snd_rawmidi_output_params); @@ -728,19 +777,22 @@ int snd_rawmidi_input_params(struct snd_rawmidi_substream *substream, unsigned int clock_type = params->mode & SNDRV_RAWMIDI_MODE_CLOCK_MASK; int err; + snd_rawmidi_drain_input(substream); + mutex_lock(&substream->rmidi->open_mutex); if (framing == SNDRV_RAWMIDI_MODE_FRAMING_NONE && clock_type != SNDRV_RAWMIDI_MODE_CLOCK_NONE) - return -EINVAL; + err = -EINVAL; else if (clock_type > SNDRV_RAWMIDI_MODE_CLOCK_MONOTONIC_RAW) - return -EINVAL; - if (framing > SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) - return -EINVAL; - snd_rawmidi_drain_input(substream); - err = resize_runtime_buffer(substream->runtime, params, true); - if (err < 0) - return err; + err = -EINVAL; + else if (framing > SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) + err = -EINVAL; + else + err = resize_runtime_buffer(substream, params, true); - substream->framing = framing; - substream->clock_type = clock_type; + if (!err) { + substream->framing = framing; + substream->clock_type = clock_type; + } + mutex_unlock(&substream->rmidi->open_mutex); return 0; } EXPORT_SYMBOL(snd_rawmidi_input_params); @@ -752,9 +804,9 @@ static int snd_rawmidi_output_status(struct snd_rawmidi_substream *substream, memset(status, 0, sizeof(*status)); status->stream = SNDRV_RAWMIDI_STREAM_OUTPUT; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); status->avail = runtime->avail; - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return 0; } @@ -765,11 +817,11 @@ static int snd_rawmidi_input_status(struct snd_rawmidi_substream *substream, memset(status, 0, sizeof(*status)); status->stream = SNDRV_RAWMIDI_STREAM_INPUT; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); status->avail = runtime->avail; status->xruns = runtime->xruns; runtime->xruns = 0; - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return 0; } @@ -1064,17 +1116,21 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, unsigned long flags; struct timespec64 ts64 = get_framing_tstamp(substream); int result = 0, count1; - struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_runtime *runtime; - if (!substream->opened) - return -EBADFD; - if (runtime->buffer == NULL) { + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened) { + result = -EBADFD; + goto unlock; + } + runtime = substream->runtime; + if (!runtime || !runtime->buffer) { rmidi_dbg(substream->rmidi, "snd_rawmidi_receive: input is not active!!!\n"); - return -EINVAL; + result = -EINVAL; + goto unlock; } - spin_lock_irqsave(&runtime->lock, flags); if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { result = receive_with_tstamp_framing(substream, buffer, count, &ts64); } else if (count == 1) { /* special case, faster code */ @@ -1121,7 +1177,8 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, else if (__snd_rawmidi_ready(runtime)) wake_up(&runtime->sleep); } - spin_unlock_irqrestore(&runtime->lock, flags); + unlock: + spin_unlock_irqrestore(&substream->lock, flags); return result; } EXPORT_SYMBOL(snd_rawmidi_receive); @@ -1136,7 +1193,7 @@ static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, unsigned long appl_ptr; int err = 0; - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); snd_rawmidi_buffer_ref(runtime); while (count > 0 && runtime->avail) { count1 = runtime->buffer_size - runtime->appl_ptr; @@ -1154,11 +1211,11 @@ static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, if (kernelbuf) memcpy(kernelbuf + result, runtime->buffer + appl_ptr, count1); if (userbuf) { - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); if (copy_to_user(userbuf + result, runtime->buffer + appl_ptr, count1)) err = -EFAULT; - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); if (err) goto out; } @@ -1167,7 +1224,7 @@ static long snd_rawmidi_kernel_read1(struct snd_rawmidi_substream *substream, } out: snd_rawmidi_buffer_unref(runtime); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); return result > 0 ? result : err; } @@ -1196,31 +1253,31 @@ static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t coun snd_rawmidi_input_trigger(substream, 1); result = 0; while (count > 0) { - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); while (!__snd_rawmidi_ready(runtime)) { wait_queue_entry_t wait; if ((file->f_flags & O_NONBLOCK) != 0 || result > 0) { - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return result > 0 ? result : -EAGAIN; } init_waitqueue_entry(&wait, current); add_wait_queue(&runtime->sleep, &wait); set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); schedule(); remove_wait_queue(&runtime->sleep, &wait); if (rfile->rmidi->card->shutdown) return -ENODEV; if (signal_pending(current)) return result > 0 ? result : -ERESTARTSYS; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); if (!runtime->avail) { - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return result > 0 ? result : -EIO; } } - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); count1 = snd_rawmidi_kernel_read1(substream, (unsigned char __user *)buf, NULL/*kernelbuf*/, @@ -1242,23 +1299,25 @@ static ssize_t snd_rawmidi_read(struct file *file, char __user *buf, size_t coun */ int snd_rawmidi_transmit_empty(struct snd_rawmidi_substream *substream) { - struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_runtime *runtime; int result; unsigned long flags; - if (runtime->buffer == NULL) { + spin_lock_irqsave(&substream->lock, flags); + runtime = substream->runtime; + if (!substream->opened || !runtime || !runtime->buffer) { rmidi_dbg(substream->rmidi, "snd_rawmidi_transmit_empty: output is not active!!!\n"); - return 1; + result = 1; + } else { + result = runtime->avail >= runtime->buffer_size; } - spin_lock_irqsave(&runtime->lock, flags); - result = runtime->avail >= runtime->buffer_size; - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); return result; } EXPORT_SYMBOL(snd_rawmidi_transmit_empty); -/** +/* * __snd_rawmidi_transmit_peek - copy data from the internal buffer * @substream: the rawmidi substream * @buffer: the buffer pointer @@ -1266,8 +1325,8 @@ EXPORT_SYMBOL(snd_rawmidi_transmit_empty); * * This is a variant of snd_rawmidi_transmit_peek() without spinlock. */ -int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, - unsigned char *buffer, int count) +static int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, + unsigned char *buffer, int count) { int result, count1; struct snd_rawmidi_runtime *runtime = substream->runtime; @@ -1304,7 +1363,6 @@ int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, __skip: return result; } -EXPORT_SYMBOL(__snd_rawmidi_transmit_peek); /** * snd_rawmidi_transmit_peek - copy data from the internal buffer @@ -1323,25 +1381,28 @@ EXPORT_SYMBOL(__snd_rawmidi_transmit_peek); int snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, unsigned char *buffer, int count) { - struct snd_rawmidi_runtime *runtime = substream->runtime; int result; unsigned long flags; - spin_lock_irqsave(&runtime->lock, flags); - result = __snd_rawmidi_transmit_peek(substream, buffer, count); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened || !substream->runtime) + result = -EBADFD; + else + result = __snd_rawmidi_transmit_peek(substream, buffer, count); + spin_unlock_irqrestore(&substream->lock, flags); return result; } EXPORT_SYMBOL(snd_rawmidi_transmit_peek); -/** +/* * __snd_rawmidi_transmit_ack - acknowledge the transmission * @substream: the rawmidi substream * @count: the transferred count * * This is a variant of __snd_rawmidi_transmit_ack() without spinlock. */ -int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) +static int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, + int count) { struct snd_rawmidi_runtime *runtime = substream->runtime; @@ -1361,7 +1422,6 @@ int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int coun } return count; } -EXPORT_SYMBOL(__snd_rawmidi_transmit_ack); /** * snd_rawmidi_transmit_ack - acknowledge the transmission @@ -1376,13 +1436,15 @@ EXPORT_SYMBOL(__snd_rawmidi_transmit_ack); */ int snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, int count) { - struct snd_rawmidi_runtime *runtime = substream->runtime; int result; unsigned long flags; - spin_lock_irqsave(&runtime->lock, flags); - result = __snd_rawmidi_transmit_ack(substream, count); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); + if (!substream->opened || !substream->runtime) + result = -EBADFD; + else + result = __snd_rawmidi_transmit_ack(substream, count); + spin_unlock_irqrestore(&substream->lock, flags); return result; } EXPORT_SYMBOL(snd_rawmidi_transmit_ack); @@ -1400,11 +1462,10 @@ EXPORT_SYMBOL(snd_rawmidi_transmit_ack); int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, unsigned char *buffer, int count) { - struct snd_rawmidi_runtime *runtime = substream->runtime; int result; unsigned long flags; - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); if (!substream->opened) result = -EBADFD; else { @@ -1414,7 +1475,7 @@ int snd_rawmidi_transmit(struct snd_rawmidi_substream *substream, else result = __snd_rawmidi_transmit_ack(substream, count); } - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); return result; } EXPORT_SYMBOL(snd_rawmidi_transmit); @@ -1427,16 +1488,18 @@ EXPORT_SYMBOL(snd_rawmidi_transmit); */ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream) { - struct snd_rawmidi_runtime *runtime = substream->runtime; + struct snd_rawmidi_runtime *runtime; unsigned long flags; int count = 0; - spin_lock_irqsave(&runtime->lock, flags); - if (runtime->avail < runtime->buffer_size) { + spin_lock_irqsave(&substream->lock, flags); + runtime = substream->runtime; + if (substream->opened && runtime && + runtime->avail < runtime->buffer_size) { count = runtime->buffer_size - runtime->avail; __snd_rawmidi_transmit_ack(substream, count); } - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); return count; } EXPORT_SYMBOL(snd_rawmidi_proceed); @@ -1457,10 +1520,10 @@ static long snd_rawmidi_kernel_write1(struct snd_rawmidi_substream *substream, return -EINVAL; result = 0; - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); if (substream->append) { if ((long)runtime->avail < count) { - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); return -EAGAIN; } } @@ -1482,14 +1545,14 @@ static long snd_rawmidi_kernel_write1(struct snd_rawmidi_substream *substream, memcpy(runtime->buffer + appl_ptr, kernelbuf + result, count1); else if (userbuf) { - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); if (copy_from_user(runtime->buffer + appl_ptr, userbuf + result, count1)) { - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); result = result > 0 ? result : -EFAULT; goto __end; } - spin_lock_irqsave(&runtime->lock, flags); + spin_lock_irqsave(&substream->lock, flags); } result += count1; count -= count1; @@ -1497,7 +1560,7 @@ static long snd_rawmidi_kernel_write1(struct snd_rawmidi_substream *substream, __end: count1 = runtime->avail < runtime->buffer_size; snd_rawmidi_buffer_unref(runtime); - spin_unlock_irqrestore(&runtime->lock, flags); + spin_unlock_irqrestore(&substream->lock, flags); if (count1) snd_rawmidi_output_trigger(substream, 1); return result; @@ -1527,31 +1590,31 @@ static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, return -EIO; result = 0; while (count > 0) { - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); while (!snd_rawmidi_ready_append(substream, count)) { wait_queue_entry_t wait; if (file->f_flags & O_NONBLOCK) { - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return result > 0 ? result : -EAGAIN; } init_waitqueue_entry(&wait, current); add_wait_queue(&runtime->sleep, &wait); set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); timeout = schedule_timeout(30 * HZ); remove_wait_queue(&runtime->sleep, &wait); if (rfile->rmidi->card->shutdown) return -ENODEV; if (signal_pending(current)) return result > 0 ? result : -ERESTARTSYS; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); if (!runtime->avail && !timeout) { - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); return result > 0 ? result : -EIO; } } - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); count1 = snd_rawmidi_kernel_write1(substream, buf, NULL, count); if (count1 < 0) return result > 0 ? result : count1; @@ -1562,7 +1625,7 @@ static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, count -= count1; } if (file->f_flags & O_DSYNC) { - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); while (runtime->avail != runtime->buffer_size) { wait_queue_entry_t wait; unsigned int last_avail = runtime->avail; @@ -1570,16 +1633,16 @@ static ssize_t snd_rawmidi_write(struct file *file, const char __user *buf, init_waitqueue_entry(&wait, current); add_wait_queue(&runtime->sleep, &wait); set_current_state(TASK_INTERRUPTIBLE); - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); timeout = schedule_timeout(30 * HZ); remove_wait_queue(&runtime->sleep, &wait); if (signal_pending(current)) return result > 0 ? result : -ERESTARTSYS; if (runtime->avail == last_avail && !timeout) return result > 0 ? result : -EIO; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); } - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); } return result; } @@ -1650,10 +1713,10 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, " Owner PID : %d\n", pid_vnr(substream->pid)); runtime = substream->runtime; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); buffer_size = runtime->buffer_size; avail = runtime->avail; - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); snd_iprintf(buffer, " Mode : %s\n" " Buffer size : %lu\n" @@ -1677,11 +1740,11 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, " Owner PID : %d\n", pid_vnr(substream->pid)); runtime = substream->runtime; - spin_lock_irq(&runtime->lock); + spin_lock_irq(&substream->lock); buffer_size = runtime->buffer_size; avail = runtime->avail; xruns = runtime->xruns; - spin_unlock_irq(&runtime->lock); + spin_unlock_irq(&substream->lock); snd_iprintf(buffer, " Buffer size : %lu\n" " Avail : %lu\n" @@ -1733,6 +1796,7 @@ static int snd_rawmidi_alloc_substreams(struct snd_rawmidi *rmidi, substream->number = idx; substream->rmidi = rmidi; substream->pstr = stream; + spin_lock_init(&substream->lock); list_add_tail(&substream->list, &stream->substreams); stream->substream_count++; } diff --git a/sound/core/timer.c b/sound/core/timer.c index b3214baa8919..e08a37c23add 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -83,7 +83,7 @@ struct snd_timer_user { unsigned int filter; struct timespec64 tstamp; /* trigger tstamp */ wait_queue_head_t qchange_sleep; - struct fasync_struct *fasync; + struct snd_fasync *fasync; struct mutex ioctl_lock; }; @@ -1345,7 +1345,7 @@ static void snd_timer_user_interrupt(struct snd_timer_instance *timeri, } __wake: spin_unlock(&tu->qlock); - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); } @@ -1383,7 +1383,7 @@ static void snd_timer_user_ccallback(struct snd_timer_instance *timeri, spin_lock_irqsave(&tu->qlock, flags); snd_timer_user_append_to_tqueue(tu, &r1); spin_unlock_irqrestore(&tu->qlock, flags); - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); } @@ -1453,7 +1453,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, spin_unlock(&tu->qlock); if (append == 0) return; - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); } @@ -1521,6 +1521,7 @@ static int snd_timer_user_release(struct inode *inode, struct file *file) snd_timer_instance_free(tu->timeri); } mutex_unlock(&tu->ioctl_lock); + snd_fasync_free(tu->fasync); kfree(tu->queue); kfree(tu->tqueue); kfree(tu); @@ -2135,7 +2136,7 @@ static int snd_timer_user_fasync(int fd, struct file * file, int on) struct snd_timer_user *tu; tu = file->private_data; - return fasync_helper(fd, file, on, &tu->fasync); + return snd_fasync_helper(fd, file, on, &tu->fasync); } static ssize_t snd_timer_user_read(struct file *file, char __user *buffer, diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index ab36f9898711..d0f11f37889b 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -494,7 +494,8 @@ EXPORT_SYMBOL_GPL(snd_ctl_sync_vmaster); * @arg: optional function argument * * Apply the function @func to each follower kctl of the given vmaster kctl. - * Returns 0 if successful, or a negative error code. + * + * Return: 0 if successful, or a negative error code */ int snd_ctl_apply_vmaster_followers(struct snd_kcontrol *kctl, int (*func)(struct snd_kcontrol *vfollower, diff --git a/sound/hda/ext/hdac_ext_controller.c b/sound/hda/ext/hdac_ext_controller.c index b072392725c7..a42f66f561f5 100644 --- a/sound/hda/ext/hdac_ext_controller.c +++ b/sound/hda/ext/hdac_ext_controller.c @@ -15,13 +15,6 @@ #include <sound/hdaudio_ext.h> /* - * maximum HDAC capablities we should parse to avoid endless looping: - * currently we have 4 extended caps, so this is future proof for now. - * extend when this limit is seen meeting in real HW - */ -#define HDAC_MAX_CAPS 10 - -/* * processing pipe helpers - these helpers are useful for dealing with HDA * new capability of processing pipelines */ diff --git a/sound/hda/hdac_bus.c b/sound/hda/hdac_bus.c index 71db8592b33d..d497414a5538 100644 --- a/sound/hda/hdac_bus.c +++ b/sound/hda/hdac_bus.c @@ -183,7 +183,7 @@ static void snd_hdac_bus_process_unsol_events(struct work_struct *work) if (!(caddr & (1 << 4))) /* no unsolicited event? */ continue; codec = bus->caddr_tbl[caddr & 0x0f]; - if (!codec || !codec->dev.driver) + if (!codec || !codec->registered) continue; spin_unlock_irq(&bus->reg_lock); drv = drv_to_hdac_driver(codec->dev.driver); diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index f7bd6e2db085..9a60bfdb39ba 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -474,11 +474,8 @@ static void azx_int_disable(struct hdac_bus *bus) list_for_each_entry(azx_dev, &bus->stream_list, list) snd_hdac_stream_updateb(azx_dev, SD_CTL, SD_INT_MASK, 0); - /* disable SIE for all streams */ - snd_hdac_chip_writeb(bus, INTCTL, 0); - - /* disable controller CIE and GIE */ - snd_hdac_chip_updatel(bus, INTCTL, AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN, 0); + /* disable SIE for all streams & disable controller CIE and GIE */ + snd_hdac_chip_writel(bus, INTCTL, 0); } /* clear interrupts */ diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index 3e9e9ac804f6..b7e5032b61c9 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -660,6 +660,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { { 0x14f1, "Conexant" }, { 0x17e8, "Chrontel" }, { 0x1854, "LG" }, + { 0x19e5, "Huawei" }, { 0x1aec, "Wolfson Microelectronics" }, { 0x1af4, "QEMU" }, { 0x434d, "C-Media" }, diff --git a/sound/hda/hdac_i915.c b/sound/hda/hdac_i915.c index 3f35972e1cf7..161a9711cd63 100644 --- a/sound/hda/hdac_i915.c +++ b/sound/hda/hdac_i915.c @@ -119,21 +119,18 @@ static int i915_component_master_match(struct device *dev, int subcomponent, /* check whether Intel graphics is present and reachable */ static int i915_gfx_present(struct pci_dev *hdac_pci) { - unsigned int class = PCI_BASE_CLASS_DISPLAY << 16; struct pci_dev *display_dev = NULL; - bool match = false; - do { - display_dev = pci_get_class(class, display_dev); - - if (display_dev && display_dev->vendor == PCI_VENDOR_ID_INTEL && + for_each_pci_dev(display_dev) { + if (display_dev->vendor == PCI_VENDOR_ID_INTEL && + (display_dev->class >> 16) == PCI_BASE_CLASS_DISPLAY && connectivity_check(display_dev, hdac_pci)) { pci_dev_put(display_dev); - match = true; + return true; } - } while (!match && display_dev); + } - return match; + return false; } /** diff --git a/sound/hda/hdac_sysfs.c b/sound/hda/hdac_sysfs.c index 0d7771fca9f0..e47de49a32e3 100644 --- a/sound/hda/hdac_sysfs.c +++ b/sound/hda/hdac_sysfs.c @@ -22,7 +22,7 @@ static ssize_t type##_show(struct device *dev, \ char *buf) \ { \ struct hdac_device *codec = dev_to_hdac_dev(dev); \ - return sprintf(buf, "0x%x\n", codec->type); \ + return sysfs_emit(buf, "0x%x\n", codec->type); \ } \ static DEVICE_ATTR_RO(type) @@ -32,8 +32,8 @@ static ssize_t type##_show(struct device *dev, \ char *buf) \ { \ struct hdac_device *codec = dev_to_hdac_dev(dev); \ - return sprintf(buf, "%s\n", \ - codec->type ? codec->type : ""); \ + return sysfs_emit(buf, "%s\n", \ + codec->type ? codec->type : ""); \ } \ static DEVICE_ATTR_RO(type) @@ -161,7 +161,7 @@ static struct kobj_type widget_ktype = { 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)); + return sysfs_emit(buf, "0x%08x\n", get_wcaps(codec, nid)); } static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid, @@ -169,8 +169,8 @@ static ssize_t pin_caps_show(struct hdac_device *codec, hda_nid_t nid, { 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)); + return sysfs_emit(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, @@ -182,7 +182,7 @@ static ssize_t pin_cfg_show(struct hdac_device *codec, hda_nid_t nid, return 0; if (snd_hdac_read(codec, nid, AC_VERB_GET_CONFIG_DEFAULT, 0, &val)) return 0; - return sprintf(buf, "0x%08x\n", val); + return sysfs_emit(buf, "0x%08x\n", val); } static bool has_pcm_cap(struct hdac_device *codec, hda_nid_t nid) @@ -203,8 +203,8 @@ static ssize_t pcm_caps_show(struct hdac_device *codec, hda_nid_t nid, { if (!has_pcm_cap(codec, nid)) return 0; - return sprintf(buf, "0x%08x\n", - snd_hdac_read_parm(codec, nid, AC_PAR_PCM)); + return sysfs_emit(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, @@ -212,8 +212,8 @@ static ssize_t pcm_formats_show(struct hdac_device *codec, hda_nid_t nid, { if (!has_pcm_cap(codec, nid)) return 0; - return sprintf(buf, "0x%08x\n", - snd_hdac_read_parm(codec, nid, AC_PAR_STREAM)); + return sysfs_emit(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, @@ -221,8 +221,8 @@ static ssize_t amp_in_caps_show(struct hdac_device *codec, hda_nid_t nid, { 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)); + return sysfs_emit(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, @@ -230,8 +230,8 @@ static ssize_t amp_out_caps_show(struct hdac_device *codec, hda_nid_t nid, { 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)); + return sysfs_emit(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, @@ -239,15 +239,15 @@ static ssize_t power_caps_show(struct hdac_device *codec, hda_nid_t nid, { 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)); + return sysfs_emit(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)); + return sysfs_emit(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, @@ -261,8 +261,8 @@ static ssize_t connections_show(struct hdac_device *codec, hda_nid_t nid, 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"); + ret += sysfs_emit_at(buf, ret, "%s0x%02x", i ? " " : "", list[i]); + ret += sysfs_emit_at(buf, ret, "\n"); return ret; } diff --git a/sound/hda/intel-dsp-config.c b/sound/hda/intel-dsp-config.c index f6970a5d95e9..d84ffdf47210 100644 --- a/sound/hda/intel-dsp-config.c +++ b/sound/hda/intel-dsp-config.c @@ -196,6 +196,12 @@ static const struct config_entry config_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), } }, + { + .ident = "UP-WHL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + } + }, {} } }, @@ -358,6 +364,12 @@ static const struct config_entry config_table[] = { DMI_MATCH(DMI_SYS_VENDOR, "Google"), } }, + { + .ident = "UPX-TGL", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "AAEON"), + } + }, {} } }, diff --git a/sound/hda/intel-nhlt.c b/sound/hda/intel-nhlt.c index 4063da378283..9db5ccd9aa2d 100644 --- a/sound/hda/intel-nhlt.c +++ b/sound/hda/intel-nhlt.c @@ -55,8 +55,8 @@ int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt) /* find max number of channels based on format_configuration */ if (fmt_configs->fmt_count) { - dev_dbg(dev, "%s: found %d format definitions\n", - __func__, fmt_configs->fmt_count); + dev_dbg(dev, "found %d format definitions\n", + fmt_configs->fmt_count); for (i = 0; i < fmt_configs->fmt_count; i++) { struct wav_fmt_ext *fmt_ext; @@ -66,9 +66,9 @@ int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt) if (fmt_ext->fmt.channels > max_ch) max_ch = fmt_ext->fmt.channels; } - dev_dbg(dev, "%s: max channels found %d\n", __func__, max_ch); + dev_dbg(dev, "max channels found %d\n", max_ch); } else { - dev_dbg(dev, "%s: No format information found\n", __func__); + dev_dbg(dev, "No format information found\n"); } if (cfg->device_config.config_type != NHLT_CONFIG_TYPE_MIC_ARRAY) { @@ -95,17 +95,16 @@ int intel_nhlt_get_dmic_geo(struct device *dev, struct nhlt_acpi_table *nhlt) } if (dmic_geo > 0) { - dev_dbg(dev, "%s: Array with %d dmics\n", __func__, dmic_geo); + dev_dbg(dev, "Array with %d dmics\n", dmic_geo); } if (max_ch > dmic_geo) { - dev_dbg(dev, "%s: max channels %d exceed dmic number %d\n", - __func__, max_ch, dmic_geo); + dev_dbg(dev, "max channels %d exceed dmic number %d\n", + max_ch, dmic_geo); } } } - dev_dbg(dev, "%s: dmic number %d max_ch %d\n", - __func__, dmic_geo, max_ch); + dev_dbg(dev, "dmic number %d max_ch %d\n", dmic_geo, max_ch); return dmic_geo; } diff --git a/sound/hda/trace.h b/sound/hda/trace.h index 70af6c815089..2cc493434a8f 100644 --- a/sound/hda/trace.h +++ b/sound/hda/trace.h @@ -19,37 +19,48 @@ struct hdac_codec; TRACE_EVENT(hda_send_cmd, TP_PROTO(struct hdac_bus *bus, unsigned int cmd), TP_ARGS(bus, cmd), - TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_STRUCT__entry( + __string(name, dev_name((bus)->dev)) + __field(u32, cmd) + ), TP_fast_assign( - snprintf(__get_str(msg), HDAC_MSG_MAX, - "[%s:%d] val=0x%08x", - dev_name((bus)->dev), (cmd) >> 28, cmd); + __assign_str(name, dev_name((bus)->dev)); + __entry->cmd = cmd; ), - TP_printk("%s", __get_str(msg)) + TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->cmd >> 28, __entry->cmd) ); TRACE_EVENT(hda_get_response, TP_PROTO(struct hdac_bus *bus, unsigned int addr, unsigned int res), TP_ARGS(bus, addr, res), - TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_STRUCT__entry( + __string(name, dev_name((bus)->dev)) + __field(u32, addr) + __field(u32, res) + ), TP_fast_assign( - snprintf(__get_str(msg), HDAC_MSG_MAX, - "[%s:%d] val=0x%08x", - dev_name((bus)->dev), addr, res); + __assign_str(name, dev_name((bus)->dev)); + __entry->addr = addr; + __entry->res = res; ), - TP_printk("%s", __get_str(msg)) + TP_printk("[%s:%d] val=0x%08x", __get_str(name), __entry->addr, __entry->res) ); TRACE_EVENT(hda_unsol_event, TP_PROTO(struct hdac_bus *bus, u32 res, u32 res_ex), TP_ARGS(bus, res, res_ex), - TP_STRUCT__entry(__dynamic_array(char, msg, HDAC_MSG_MAX)), + TP_STRUCT__entry( + __string(name, dev_name((bus)->dev)) + __field(u32, res) + __field(u32, res_ex) + ), TP_fast_assign( - snprintf(__get_str(msg), HDAC_MSG_MAX, - "[%s:%d] res=0x%08x, res_ex=0x%08x", - dev_name((bus)->dev), res_ex & 0x0f, res, res_ex); + __assign_str(name, dev_name((bus)->dev)); + __entry->res = res; + __entry->res_ex = res_ex; ), - TP_printk("%s", __get_str(msg)) + TP_printk("[%s:%d] res=0x%08x, res_ex=0x%08x", __get_str(name), + __entry->res_ex & 0x0f, __entry->res, __entry->res_ex) ); DECLARE_EVENT_CLASS(hdac_stream, diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c index 2aaaa6807174..13ce96148fa3 100644 --- a/sound/isa/wavefront/wavefront_synth.c +++ b/sound/isa/wavefront/wavefront_synth.c @@ -581,8 +581,6 @@ demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes) int i; unsigned char *end = src + src_bytes; - end = src + src_bytes; - /* NOTE: src and dst *CAN* point to the same address */ for (i = 0; src != end; i++) { diff --git a/sound/pci/asihpi/hpi6000.c b/sound/pci/asihpi/hpi6000.c index aa4d06353126..88d902997b74 100644 --- a/sound/pci/asihpi/hpi6000.c +++ b/sound/pci/asihpi/hpi6000.c @@ -388,7 +388,7 @@ void HPI_6000(struct hpi_message *phm, struct hpi_response *phr) /* SUBSYSTEM */ /* create an adapter object and initialise it based on resource information - * passed in in the message + * passed in the message * NOTE - you cannot use this function AND the FindAdapters function at the * same time, the application must use only one of them to get the adapters */ diff --git a/sound/pci/asihpi/hpi6205.c b/sound/pci/asihpi/hpi6205.c index 3d6914c64c4a..27e11b5f70b9 100644 --- a/sound/pci/asihpi/hpi6205.c +++ b/sound/pci/asihpi/hpi6205.c @@ -445,7 +445,7 @@ void HPI_6205(struct hpi_message *phm, struct hpi_response *phr) /* SUBSYSTEM */ /** Create an adapter object and initialise it based on resource information - * passed in in the message + * passed in the message * *** NOTE - you cannot use this function AND the FindAdapters function at the * same time, the application must use only one of them to get the adapters *** */ diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index bd60308769ff..8634004a606b 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -74,36 +74,36 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_cs46xx_create(card, pci, external_amp[dev], thinkpad[dev]); if (err < 0) - return err; + goto error; card->private_data = chip; chip->accept_valid = mmap_valid[dev]; err = snd_cs46xx_pcm(chip, 0); if (err < 0) - return err; + goto error; #ifdef CONFIG_SND_CS46XX_NEW_DSP err = snd_cs46xx_pcm_rear(chip, 1); if (err < 0) - return err; + goto error; err = snd_cs46xx_pcm_iec958(chip, 2); if (err < 0) - return err; + goto error; #endif err = snd_cs46xx_mixer(chip, 2); if (err < 0) - return err; + goto error; #ifdef CONFIG_SND_CS46XX_NEW_DSP if (chip->nr_ac97_codecs ==2) { err = snd_cs46xx_pcm_center_lfe(chip, 3); if (err < 0) - return err; + goto error; } #endif err = snd_cs46xx_midi(chip, 0); if (err < 0) - return err; + goto error; err = snd_cs46xx_start_dsp(chip); if (err < 0) - return err; + goto error; snd_cs46xx_gameport(chip); @@ -117,11 +117,15 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - return err; + goto error; pci_set_drvdata(pci, card); dev++; return 0; + + error: + snd_card_free(card); + return err; } static struct pci_driver cs46xx_driver = { diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c index 9d26535f3fa3..edb3f1763719 100644 --- a/sound/pci/emu10k1/memory.c +++ b/sound/pci/emu10k1/memory.c @@ -324,7 +324,7 @@ snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *subst return NULL; } /* fill buffer addresses but pointers are not stored so that - * snd_free_pci_page() is not called in in synth_free() + * snd_free_pci_page() is not called in synth_free() */ idx = 0; for (page = blk->first_page; page <= blk->last_page; page++, idx++) { diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 79ade4787d95..a8e8cf98befa 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -93,16 +93,21 @@ config SND_HDA_PATCH_LOADER config SND_HDA_SCODEC_CS35L41 tristate + select SND_HDA_GENERIC + select REGMAP_IRQ + +config SND_HDA_CS_DSP_CONTROLS + tristate + select CS_DSP config SND_HDA_SCODEC_CS35L41_I2C tristate "Build CS35L41 HD-audio side codec support for I2C Bus" depends on I2C depends on ACPI depends on SND_SOC - select SND_HDA_GENERIC select SND_SOC_CS35L41_LIB select SND_HDA_SCODEC_CS35L41 - select REGMAP_IRQ + select SND_HDA_CS_DSP_CONTROLS help Say Y or M here to include CS35L41 I2C HD-audio side codec support in snd-hda-intel driver, such as ALC287. @@ -115,10 +120,9 @@ config SND_HDA_SCODEC_CS35L41_SPI depends on SPI_MASTER depends on ACPI depends on SND_SOC - select SND_HDA_GENERIC select SND_SOC_CS35L41_LIB select SND_HDA_SCODEC_CS35L41 - select REGMAP_IRQ + select SND_HDA_CS_DSP_CONTROLS help Say Y or M here to include CS35L41 SPI HD-audio side codec support in snd-hda-intel driver, such as ALC287. diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 3e7bc608d45f..00d306104484 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -31,6 +31,7 @@ snd-hda-codec-hdmi-objs := patch_hdmi.o hda_eld.o snd-hda-scodec-cs35l41-objs := cs35l41_hda.o snd-hda-scodec-cs35l41-i2c-objs := cs35l41_hda_i2c.o snd-hda-scodec-cs35l41-spi-objs := cs35l41_hda_spi.o +snd-hda-cs-dsp-ctls-objs := hda_cs_dsp_ctl.o # common driver obj-$(CONFIG_SND_HDA) := snd-hda-codec.o @@ -54,6 +55,7 @@ obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41) += snd-hda-scodec-cs35l41.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_I2C) += snd-hda-scodec-cs35l41-i2c.o obj-$(CONFIG_SND_HDA_SCODEC_CS35L41_SPI) += snd-hda-scodec-cs35l41-spi.o +obj-$(CONFIG_SND_HDA_CS_DSP_CONTROLS) += snd-hda-cs-dsp-ctls.o # this must be the last entry after codec drivers; # otherwise the codec patches won't be hooked before the PCI probe diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index cce27a86267f..129bffb431c2 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -8,30 +8,464 @@ #include <linux/acpi.h> #include <linux/module.h> +#include <linux/moduleparam.h> #include <sound/hda_codec.h> +#include <sound/soc.h> +#include <linux/pm_runtime.h> #include "hda_local.h" #include "hda_auto_parser.h" #include "hda_jack.h" #include "hda_generic.h" #include "hda_component.h" #include "cs35l41_hda.h" +#include "hda_cs_dsp_ctl.h" + +#define CS35L41_FIRMWARE_ROOT "cirrus/" +#define CS35L41_PART "cs35l41" + +#define HALO_STATE_DSP_CTL_NAME "HALO_STATE" +#define HALO_STATE_DSP_CTL_TYPE 5 +#define HALO_STATE_DSP_CTL_ALG 262308 +#define CAL_R_DSP_CTL_NAME "CAL_R" +#define CAL_STATUS_DSP_CTL_NAME "CAL_STATUS" +#define CAL_CHECKSUM_DSP_CTL_NAME "CAL_CHECKSUM" +#define CAL_AMBIENT_DSP_CTL_NAME "CAL_AMBIENT" +#define CAL_DSP_CTL_TYPE 5 +#define CAL_DSP_CTL_ALG 205 + +static bool firmware_autostart = 1; +module_param(firmware_autostart, bool, 0444); +MODULE_PARM_DESC(firmware_autostart, "Allow automatic firmware download on boot" + "(0=Disable, 1=Enable) (default=1); "); static const struct reg_sequence cs35l41_hda_config[] = { { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz { CS35L41_SP_ENABLES, 0x00010000 }, // ASP_RX1_EN = 1 { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000002 }, // Hi-Z unused + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot { CS35L41_DAC_PCM1_SRC, 0x00000008 }, // DACPCM1_SRC = ASPRX1 + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000032 }, // ASPTX3 SRC = ERRVOL + { CS35L41_ASP_TX4_SRC, 0x00000033 }, // ASPTX4 SRC = CLASSH_TGT + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000009 }, // DSP1RX2 SRC = ASPRX2 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000020 }, // DSP1RX5 SRC = ERRVOL { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB { CS35L41_AMP_GAIN_CTRL, 0x00000084 }, // AMP_GAIN_PCM 4.5 dB }; +static const struct reg_sequence cs35l41_hda_config_dsp[] = { + { CS35L41_PLL_CLK_CTRL, 0x00000430 }, // 3072000Hz, BCLK Input, PLL_REFCLK_EN = 1 + { CS35L41_DSP_CLK_CTRL, 0x00000003 }, // DSP CLK EN + { CS35L41_GLOBAL_CLK_CTRL, 0x00000003 }, // GLOBAL_FS = 48 kHz + { CS35L41_SP_ENABLES, 0x00010001 }, // ASP_RX1_EN = 1, ASP_TX1_EN = 1 + { CS35L41_SP_RATE_CTRL, 0x00000021 }, // ASP_BCLK_FREQ = 3.072 MHz + { CS35L41_SP_FORMAT, 0x20200200 }, // 32 bits RX/TX slots, I2S, clk consumer + { CS35L41_SP_HIZ_CTRL, 0x00000003 }, // Hi-Z unused/disabled + { CS35L41_SP_TX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_SP_RX_WL, 0x00000018 }, // 24 cycles/slot + { CS35L41_DAC_PCM1_SRC, 0x00000032 }, // DACPCM1_SRC = ERR_VOL + { CS35L41_ASP_TX1_SRC, 0x00000018 }, // ASPTX1 SRC = VMON + { CS35L41_ASP_TX2_SRC, 0x00000019 }, // ASPTX2 SRC = IMON + { CS35L41_ASP_TX3_SRC, 0x00000028 }, // ASPTX3 SRC = VPMON + { CS35L41_ASP_TX4_SRC, 0x00000029 }, // ASPTX4 SRC = VBSTMON + { CS35L41_DSP1_RX1_SRC, 0x00000008 }, // DSP1RX1 SRC = ASPRX1 + { CS35L41_DSP1_RX2_SRC, 0x00000008 }, // DSP1RX2 SRC = ASPRX1 + { CS35L41_DSP1_RX3_SRC, 0x00000018 }, // DSP1RX3 SRC = VMON + { CS35L41_DSP1_RX4_SRC, 0x00000019 }, // DSP1RX4 SRC = IMON + { CS35L41_DSP1_RX5_SRC, 0x00000029 }, // DSP1RX5 SRC = VBSTMON + { CS35L41_AMP_DIG_VOL_CTRL, 0x00000000 }, // AMP_VOL_PCM 0.0 dB + { CS35L41_AMP_GAIN_CTRL, 0x00000233 }, // AMP_GAIN_PCM = 17.5dB AMP_GAIN_PDM = 19.5dB +}; + static const struct reg_sequence cs35l41_hda_mute[] = { { CS35L41_AMP_GAIN_CTRL, 0x00000000 }, // AMP_GAIN_PCM 0.5 dB { CS35L41_AMP_DIG_VOL_CTRL, 0x0000A678 }, // AMP_VOL_PCM Mute }; +static int cs35l41_control_add(struct cs_dsp_coeff_ctl *cs_ctl) +{ + struct cs35l41_hda *cs35l41 = container_of(cs_ctl->dsp, struct cs35l41_hda, cs_dsp); + struct hda_cs_dsp_ctl_info info; + + info.device_name = cs35l41->amp_name; + info.fw_type = cs35l41->firmware_type; + info.card = cs35l41->codec->card; + + return hda_cs_dsp_control_add(cs_ctl, &info); +} + +static const struct cs_dsp_client_ops client_ops = { + .control_add = cs35l41_control_add, + .control_remove = hda_cs_dsp_control_remove, +}; + +static int cs35l41_request_firmware_file(struct cs35l41_hda *cs35l41, + const struct firmware **firmware, char **filename, + const char *dir, const char *ssid, const char *amp_name, + int spkid, const char *filetype) +{ + const char * const dsp_name = cs35l41->cs_dsp.name; + char *s, c; + int ret = 0; + + if (spkid > -1 && ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, spkid, amp_name, filetype); + else if (spkid > -1 && ssid) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-spkid%d.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, spkid, filetype); + else if (ssid && amp_name) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, amp_name, filetype); + else if (ssid) + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + ssid, filetype); + else + *filename = kasprintf(GFP_KERNEL, "%s%s-%s-%s.%s", dir, CS35L41_PART, + dsp_name, hda_cs_dsp_fw_ids[cs35l41->firmware_type], + filetype); + + if (*filename == NULL) + return -ENOMEM; + + /* + * Make sure that filename is lower-case and any non alpha-numeric + * characters except full stop and '/' are replaced with hyphens. + */ + s = *filename; + while (*s) { + c = *s; + if (isalnum(c)) + *s = tolower(c); + else if (c != '.' && c != '/') + *s = '-'; + s++; + } + + ret = firmware_request_nowarn(firmware, *filename, cs35l41->dev); + if (ret != 0) { + dev_dbg(cs35l41->dev, "Failed to request '%s'\n", *filename); + kfree(*filename); + *filename = NULL; + } + + return ret; +} + +static int cs35l41_request_firmware_files_spkid(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, cs35l41->amp_name, + cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-spkidN><-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, cs35l41->speaker_id, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub<-spkidN>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, cs35l41->speaker_id, "bin"); + return 0; + } + + /* fallback try cirrus/part-dspN-fwtype.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw"); + if (!ret) { + /* fallback try cirrus/part-dspN-fwtype.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + +static int cs35l41_request_firmware_files(struct cs35l41_hda *cs35l41, + const struct firmware **wmfw_firmware, + char **wmfw_filename, + const struct firmware **coeff_firmware, + char **coeff_filename) +{ + int ret; + + if (cs35l41->speaker_id > -1) + return cs35l41_request_firmware_files_spkid(cs35l41, wmfw_firmware, wmfw_filename, + coeff_firmware, coeff_filename); + + /* try cirrus/part-dspN-fwtype-sub<-ampname>.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "bin"); + return 0; + } + + /* try cirrus/part-dspN-fwtype-sub.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, cs35l41->acpi_subsystem_id, + NULL, -1, "wmfw"); + if (!ret) { + /* try cirrus/part-dspN-fwtype-sub<-ampname>.bin */ + ret = cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + cs35l41->amp_name, -1, "bin"); + if (ret) + /* try cirrus/part-dspN-fwtype-sub.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, + cs35l41->acpi_subsystem_id, + NULL, -1, "bin"); + return 0; + } + + /* fallback try cirrus/part-dspN-fwtype.wmfw */ + ret = cs35l41_request_firmware_file(cs35l41, wmfw_firmware, wmfw_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "wmfw"); + if (!ret) { + /* fallback try cirrus/part-dspN-fwtype.bin */ + cs35l41_request_firmware_file(cs35l41, coeff_firmware, coeff_filename, + CS35L41_FIRMWARE_ROOT, NULL, NULL, -1, "bin"); + return 0; + } + + dev_warn(cs35l41->dev, "Failed to request firmware\n"); + + return ret; +} + +#if IS_ENABLED(CONFIG_EFI) +static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient, + unsigned int r0, unsigned int status, unsigned int checksum) +{ + int ret; + + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_AMBIENT_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &ambient, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_AMBIENT_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_R_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &r0, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_R_DSP_CTL_NAME, ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_STATUS_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &status, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_STATUS_DSP_CTL_NAME, + ret); + return ret; + } + ret = hda_cs_dsp_write_ctl(&cs35l41->cs_dsp, CAL_CHECKSUM_DSP_CTL_NAME, CAL_DSP_CTL_TYPE, + CAL_DSP_CTL_ALG, &checksum, 4); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write Control: %s - %d\n", CAL_CHECKSUM_DSP_CTL_NAME, + ret); + return ret; + } + + return 0; +} + +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + static efi_guid_t efi_guid = EFI_GUID(0x02f9af02, 0x7734, 0x4233, 0xb4, 0x3d, 0x93, 0xfe, + 0x5a, 0xa3, 0x5d, 0xb3); + static efi_char16_t efi_name[] = L"CirrusSmartAmpCalibrationData"; + const struct cs35l41_amp_efi_data *efi_data; + const struct cs35l41_amp_cal_data *cl; + unsigned long data_size = 0; + efi_status_t status; + int ret = 0; + u8 *data = NULL; + u32 attr; + + /* Get real size of UEFI variable */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_BUFFER_TOO_SMALL) { + ret = -ENODEV; + /* Allocate data buffer of data_size bytes */ + data = vmalloc(data_size); + if (!data) + return -ENOMEM; + /* Get variable contents into buffer */ + status = efi.get_variable(efi_name, &efi_guid, &attr, &data_size, data); + if (status == EFI_SUCCESS) { + efi_data = (struct cs35l41_amp_efi_data *)data; + dev_dbg(cs35l41->dev, "Calibration: Size=%d, Amp Count=%d\n", + efi_data->size, efi_data->count); + if (efi_data->count > cs35l41->index) { + cl = &efi_data->data[cs35l41->index]; + dev_dbg(cs35l41->dev, + "Calibration: Ambient=%02x, Status=%02x, R0=%d\n", + cl->calAmbient, cl->calStatus, cl->calR); + + /* Calibration can only be applied whilst the DSP is not running */ + ret = cs35l41_apply_calibration(cs35l41, + cpu_to_be32(cl->calAmbient), + cpu_to_be32(cl->calR), + cpu_to_be32(cl->calStatus), + cpu_to_be32(cl->calR + 1)); + } + } + vfree(data); + } + return ret; +} +#else +static int cs35l41_save_calibration(struct cs35l41_hda *cs35l41) +{ + dev_warn(cs35l41->dev, "Calibration not supported without EFI support.\n"); + return 0; +} +#endif + +static int cs35l41_init_dsp(struct cs35l41_hda *cs35l41) +{ + const struct firmware *coeff_firmware = NULL; + const struct firmware *wmfw_firmware = NULL; + struct cs_dsp *dsp = &cs35l41->cs_dsp; + char *coeff_filename = NULL; + char *wmfw_filename = NULL; + int ret; + + if (!cs35l41->halo_initialized) { + cs35l41_configure_cs_dsp(cs35l41->dev, cs35l41->regmap, dsp); + dsp->client_ops = &client_ops; + + ret = cs_dsp_halo_init(&cs35l41->cs_dsp); + if (ret) + return ret; + cs35l41->halo_initialized = true; + } + + ret = cs35l41_request_firmware_files(cs35l41, &wmfw_firmware, &wmfw_filename, + &coeff_firmware, &coeff_filename); + if (ret < 0) + return ret; + + dev_dbg(cs35l41->dev, "Loading WMFW Firmware: %s\n", wmfw_filename); + if (coeff_filename) + dev_dbg(cs35l41->dev, "Loading Coefficient File: %s\n", coeff_filename); + else + dev_warn(cs35l41->dev, "No Coefficient File available.\n"); + + ret = cs_dsp_power_up(dsp, wmfw_firmware, wmfw_filename, coeff_firmware, coeff_filename, + hda_cs_dsp_fw_ids[cs35l41->firmware_type]); + if (ret) + goto err_release; + + ret = cs35l41_save_calibration(cs35l41); + +err_release: + release_firmware(wmfw_firmware); + release_firmware(coeff_firmware); + kfree(wmfw_filename); + kfree(coeff_filename); + + return ret; +} + +static void cs35l41_shutdown_dsp(struct cs35l41_hda *cs35l41) +{ + struct cs_dsp *dsp = &cs35l41->cs_dsp; + + cs_dsp_stop(dsp); + cs_dsp_power_down(dsp); + cs35l41->firmware_running = false; + dev_dbg(cs35l41->dev, "Unloaded Firmware\n"); +} + +static void cs35l41_remove_dsp(struct cs35l41_hda *cs35l41) +{ + struct cs_dsp *dsp = &cs35l41->cs_dsp; + + cancel_work_sync(&cs35l41->fw_load_work); + cs35l41_shutdown_dsp(cs35l41); + cs_dsp_remove(dsp); + cs35l41->halo_initialized = false; +} + /* Protection release cycle to get the speaker out of Safe-Mode */ static void cs35l41_error_release(struct device *dev, struct regmap *regmap, unsigned int mask) { @@ -53,9 +487,23 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) struct regmap *reg = cs35l41->regmap; int ret = 0; + mutex_lock(&cs35l41->fw_mutex); + switch (action) { case HDA_GEN_PCM_ACT_OPEN: - regmap_multi_reg_write(reg, cs35l41_hda_config, ARRAY_SIZE(cs35l41_hda_config)); + cs35l41->playback_started = true; + if (cs35l41->firmware_running) { + regmap_multi_reg_write(reg, cs35l41_hda_config_dsp, + ARRAY_SIZE(cs35l41_hda_config_dsp)); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 1 << CS35L41_VMON_EN_SHIFT | 1 << CS35L41_IMON_EN_SHIFT); + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_RESUME); + } else { + regmap_multi_reg_write(reg, cs35l41_hda_config, + ARRAY_SIZE(cs35l41_hda_config)); + } ret = regmap_update_bits(reg, CS35L41_PWR_CTRL2, CS35L41_AMP_EN_MASK, 1 << CS35L41_AMP_EN_SHIFT); if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) @@ -73,13 +521,23 @@ static void cs35l41_hda_playback_hook(struct device *dev, int action) CS35L41_AMP_EN_MASK, 0 << CS35L41_AMP_EN_SHIFT); if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) regmap_write(reg, CS35L41_GPIO1_CTRL1, 0x00000001); + if (cs35l41->firmware_running) { + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, + CSPL_MBOX_CMD_PAUSE); + regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2, + CS35L41_VMON_EN_MASK | CS35L41_IMON_EN_MASK, + 0 << CS35L41_VMON_EN_SHIFT | 0 << CS35L41_IMON_EN_SHIFT); + } cs35l41_irq_release(cs35l41); + cs35l41->playback_started = false; break; default: dev_warn(cs35l41->dev, "Playback action not supported: %d\n", action); break; } + mutex_unlock(&cs35l41->fw_mutex); + if (ret) dev_err(cs35l41->dev, "Regmap access fail: %d\n", ret); } @@ -104,10 +562,274 @@ static int cs35l41_hda_channel_map(struct device *dev, unsigned int tx_num, unsi rx_slot); } +static int cs35l41_runtime_suspend(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "Suspend\n"); + + if (!cs35l41->firmware_running) + return 0; + + if (cs35l41_enter_hibernate(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type) < 0) + return 0; + + regcache_cache_only(cs35l41->regmap, true); + regcache_mark_dirty(cs35l41->regmap); + + return 0; +} + +static int cs35l41_runtime_resume(struct device *dev) +{ + struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + int ret; + + dev_dbg(cs35l41->dev, "Resume.\n"); + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST_NO_VSPK_SWITCH) { + dev_dbg(cs35l41->dev, "System does not support Resume\n"); + return 0; + } + + if (!cs35l41->firmware_running) + return 0; + + regcache_cache_only(cs35l41->regmap, false); + + ret = cs35l41_exit_hibernate(cs35l41->dev, cs35l41->regmap); + if (ret) { + regcache_cache_only(cs35l41->regmap, true); + return ret; + } + + /* Test key needs to be unlocked to allow the OTP settings to re-apply */ + cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + ret = regcache_sync(cs35l41->regmap); + cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); + return ret; + } + + if (cs35l41->hw_cfg.bst_type == CS35L41_EXT_BOOST) + cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, &cs35l41->hw_cfg); + + return 0; +} + +static int cs35l41_hda_suspend_hook(struct device *dev) +{ + dev_dbg(dev, "Request Suspend\n"); + pm_runtime_mark_last_busy(dev); + return pm_runtime_put_autosuspend(dev); +} + +static int cs35l41_hda_resume_hook(struct device *dev) +{ + dev_dbg(dev, "Request Resume\n"); + return pm_runtime_get_sync(dev); +} + +static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) +{ + int halo_sts; + int ret; + + ret = cs35l41_init_dsp(cs35l41); + if (ret) { + dev_warn(cs35l41->dev, "Cannot Initialize Firmware. Error: %d\n", ret); + goto clean_dsp; + } + + ret = cs35l41_write_fs_errata(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Cannot Write FS Errata: %d\n", ret); + goto clean_dsp; + } + + ret = cs_dsp_run(&cs35l41->cs_dsp); + if (ret) { + dev_err(cs35l41->dev, "Fail to start dsp: %d\n", ret); + goto clean_dsp; + } + + ret = read_poll_timeout(hda_cs_dsp_read_ctl, ret, + be32_to_cpu(halo_sts) == HALO_STATE_CODE_RUN, + 1000, 15000, false, &cs35l41->cs_dsp, HALO_STATE_DSP_CTL_NAME, + HALO_STATE_DSP_CTL_TYPE, HALO_STATE_DSP_CTL_ALG, + &halo_sts, sizeof(halo_sts)); + + if (ret) { + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", + halo_sts); + goto clean_dsp; + } + + cs35l41_set_cspl_mbox_cmd(cs35l41->dev, cs35l41->regmap, CSPL_MBOX_CMD_PAUSE); + cs35l41->firmware_running = true; + + return 0; + +clean_dsp: + cs35l41_shutdown_dsp(cs35l41); + return ret; +} + +static void cs35l41_load_firmware(struct cs35l41_hda *cs35l41, bool load) +{ + pm_runtime_get_sync(cs35l41->dev); + + if (cs35l41->firmware_running && !load) { + dev_dbg(cs35l41->dev, "Unloading Firmware\n"); + cs35l41_shutdown_dsp(cs35l41); + } else if (!cs35l41->firmware_running && load) { + dev_dbg(cs35l41->dev, "Loading Firmware\n"); + cs35l41_smart_amp(cs35l41); + } else { + dev_dbg(cs35l41->dev, "Unable to Load firmware.\n"); + } + + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_put_autosuspend(cs35l41->dev); +} + +static int cs35l41_fw_load_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = cs35l41->request_fw_load; + return 0; +} + +static void cs35l41_fw_load_work(struct work_struct *work) +{ + struct cs35l41_hda *cs35l41 = container_of(work, struct cs35l41_hda, fw_load_work); + + mutex_lock(&cs35l41->fw_mutex); + + /* Recheck if playback is ongoing, mutex will block playback during firmware loading */ + if (cs35l41->playback_started) + dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); + else + cs35l41_load_firmware(cs35l41, cs35l41->request_fw_load); + + cs35l41->fw_request_ongoing = false; + mutex_unlock(&cs35l41->fw_mutex); +} + +static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + unsigned int ret = 0; + + mutex_lock(&cs35l41->fw_mutex); + + if (cs35l41->request_fw_load == ucontrol->value.integer.value[0]) + goto err; + + if (cs35l41->fw_request_ongoing) { + dev_dbg(cs35l41->dev, "Existing request not complete\n"); + ret = -EBUSY; + goto err; + } + + /* Check if playback is ongoing when initial request is made */ + if (cs35l41->playback_started) { + dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); + ret = -EBUSY; + goto err; + } + + cs35l41->fw_request_ongoing = true; + cs35l41->request_fw_load = ucontrol->value.integer.value[0]; + schedule_work(&cs35l41->fw_load_work); + +err: + mutex_unlock(&cs35l41->fw_mutex); + + return ret; +} + +static int cs35l41_fw_type_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = cs35l41->firmware_type; + + return 0; +} + +static int cs35l41_fw_type_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] < HDA_CS_DSP_NUM_FW) { + cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; + return 0; + } + + return -EINVAL; +} + +static int cs35l41_fw_type_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(hda_cs_dsp_fw_ids), hda_cs_dsp_fw_ids); +} + +static int cs35l41_create_controls(struct cs35l41_hda *cs35l41) +{ + char fw_type_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + char fw_load_ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new fw_type_ctl = { + .name = fw_type_ctl_name, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = cs35l41_fw_type_ctl_info, + .get = cs35l41_fw_type_ctl_get, + .put = cs35l41_fw_type_ctl_put, + }; + struct snd_kcontrol_new fw_load_ctl = { + .name = fw_load_ctl_name, + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .info = snd_ctl_boolean_mono_info, + .get = cs35l41_fw_load_ctl_get, + .put = cs35l41_fw_load_ctl_put, + }; + int ret; + + scnprintf(fw_type_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Type", + cs35l41->amp_name); + scnprintf(fw_load_ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s DSP1 Firmware Load", + cs35l41->amp_name); + + ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_type_ctl, cs35l41)); + if (ret) { + dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_type_ctl.name, ret); + return ret; + } + + dev_dbg(cs35l41->dev, "Added Control %s\n", fw_type_ctl.name); + + ret = snd_ctl_add(cs35l41->codec->card, snd_ctl_new1(&fw_load_ctl, cs35l41)); + if (ret) { + dev_err(cs35l41->dev, "Failed to add KControl %s = %d\n", fw_load_ctl.name, ret); + return ret; + } + + dev_dbg(cs35l41->dev, "Added Control %s\n", fw_load_ctl.name); + + return 0; +} + static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); struct hda_component *comps = master_data; + int ret = 0; if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS) return -EINVAL; @@ -116,11 +838,38 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas if (comps->dev) return -EBUSY; + pm_runtime_get_sync(dev); + comps->dev = dev; + if (!cs35l41->acpi_subsystem_id) + cs35l41->acpi_subsystem_id = devm_kasprintf(dev, GFP_KERNEL, "%.8x", + comps->codec->core.subsystem_id); + cs35l41->codec = comps->codec; strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + + cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT; + + if (firmware_autostart) { + dev_dbg(cs35l41->dev, "Firmware Autostart.\n"); + cs35l41->request_fw_load = true; + mutex_lock(&cs35l41->fw_mutex); + if (cs35l41_smart_amp(cs35l41) < 0) + dev_warn(cs35l41->dev, "Cannot Run Firmware, reverting to dsp bypass...\n"); + mutex_unlock(&cs35l41->fw_mutex); + } else { + dev_dbg(cs35l41->dev, "Firmware Autostart is disabled.\n"); + } + + ret = cs35l41_create_controls(cs35l41); + comps->playback_hook = cs35l41_hda_playback_hook; + comps->suspend_hook = cs35l41_hda_suspend_hook; + comps->resume_hook = cs35l41_hda_resume_hook; - return 0; + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + return ret; } static void cs35l41_hda_unbind(struct device *dev, struct device *master, void *master_data) @@ -215,7 +964,7 @@ static const struct regmap_irq cs35l41_reg_irqs[] = { CS35L41_REG_IRQ(IRQ1_STATUS1, AMP_SHORT_ERR), }; -static const struct regmap_irq_chip cs35l41_regmap_irq_chip = { +static struct regmap_irq_chip cs35l41_regmap_irq_chip = { .name = "cs35l41 IRQ1 Controller", .status_base = CS35L41_IRQ1_STATUS1, .mask_base = CS35L41_IRQ1_MASK1, @@ -223,6 +972,7 @@ static const struct regmap_irq_chip cs35l41_regmap_irq_chip = { .num_regs = 4, .irqs = cs35l41_reg_irqs, .num_irqs = ARRAY_SIZE(cs35l41_reg_irqs), + .runtime_pm = true, }; static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41) @@ -264,6 +1014,7 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41) break; case CS35L41_INTERRUPT: using_irq = true; + hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN; break; default: dev_err(cs35l41->dev, "Invalid GPIO2 function %d\n", hw_cfg->gpio2.func); @@ -297,6 +1048,130 @@ static int cs35l41_hda_apply_properties(struct cs35l41_hda *cs35l41) return cs35l41_hda_channel_map(cs35l41->dev, 0, NULL, 1, &hw_cfg->spk_pos); } +static int cs35l41_get_acpi_sub_string(struct device *dev, struct acpi_device *adev, + const char **subsysid) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + int ret = 0; + + status = acpi_evaluate_object(adev->handle, "_SUB", NULL, &buffer); + if (ACPI_SUCCESS(status)) { + obj = buffer.pointer; + if (obj->type == ACPI_TYPE_STRING) { + *subsysid = devm_kstrdup(dev, obj->string.pointer, GFP_KERNEL); + if (*subsysid == NULL) { + dev_err(dev, "Cannot allocate Subsystem ID"); + ret = -ENOMEM; + } + } else { + dev_warn(dev, "Warning ACPI _SUB did not return a string\n"); + ret = -ENODEV; + } + acpi_os_free(buffer.pointer); + } else { + dev_dbg(dev, "Warning ACPI _SUB failed: %#x\n", status); + ret = -ENODEV; + } + + return ret; +} + +static int cs35l41_get_speaker_id(struct device *dev, int amp_index, + int num_amps, int fixed_gpio_id) +{ + struct gpio_desc *speaker_id_desc; + int speaker_id = -ENODEV; + + if (fixed_gpio_id >= 0) { + dev_dbg(dev, "Found Fixed Speaker ID GPIO (index = %d)\n", fixed_gpio_id); + speaker_id_desc = gpiod_get_index(dev, NULL, fixed_gpio_id, GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + return speaker_id; + } + speaker_id = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } else { + int base_index; + int gpios_per_amp; + int count; + int tmp; + int i; + + count = gpiod_count(dev, "spk-id"); + if (count > 0) { + speaker_id = 0; + gpios_per_amp = count / num_amps; + base_index = gpios_per_amp * amp_index; + + if (count % num_amps) + return -EINVAL; + + dev_dbg(dev, "Found %d Speaker ID GPIOs per Amp\n", gpios_per_amp); + + for (i = 0; i < gpios_per_amp; i++) { + speaker_id_desc = gpiod_get_index(dev, "spk-id", i + base_index, + GPIOD_IN); + if (IS_ERR(speaker_id_desc)) { + speaker_id = PTR_ERR(speaker_id_desc); + break; + } + tmp = gpiod_get_value_cansleep(speaker_id_desc); + gpiod_put(speaker_id_desc); + if (tmp < 0) { + speaker_id = tmp; + break; + } + speaker_id |= tmp << i; + } + dev_dbg(dev, "Speaker ID = %d\n", speaker_id); + } + } + return speaker_id; +} + +/* + * Device CLSA010(0/1) doesn't have _DSD so a gpiod_get by the label reset won't work. + * And devices created by serial-multi-instantiate don't have their device struct + * pointing to the correct fwnode, so acpi_dev must be used here. + * And devm functions expect that the device requesting the resource has the correct + * fwnode. + */ +static int cs35l41_no_acpi_dsd(struct cs35l41_hda *cs35l41, struct device *physdev, int id, + const char *hid) +{ + struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; + + /* check I2C address to assign the index */ + cs35l41->index = id == 0x40 ? 0 : 1; + cs35l41->channel_index = 0; + cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); + cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + put_device(physdev); + + if (strncmp(hid, "CLSA0100", 8) == 0) { + hw_cfg->bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; + } else if (strncmp(hid, "CLSA0101", 8) == 0) { + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + } else { + hw_cfg->valid = false; + hw_cfg->gpio1.valid = false; + hw_cfg->gpio2.valid = false; + return -EINVAL; + } + + return 0; +} + static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) { struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; @@ -316,10 +1191,16 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i physdev = get_device(acpi_get_first_physical_node(adev)); acpi_dev_put(adev); + ret = cs35l41_get_acpi_sub_string(cs35l41->dev, adev, &cs35l41->acpi_subsystem_id); + if (ret) + dev_info(cs35l41->dev, "No Subsystem ID found in ACPI: %d", ret); + else + dev_dbg(cs35l41->dev, "Subsystem ID %s found", cs35l41->acpi_subsystem_id); + property = "cirrus,dev-index"; ret = device_property_count_u32(physdev, property); if (ret <= 0) - goto no_acpi_dsd; + return cs35l41_no_acpi_dsd(cs35l41, physdev, id, hid); if (ret > ARRAY_SIZE(values)) { ret = -EINVAL; @@ -347,7 +1228,7 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i /* To use the same release code for all laptop variants we can't use devm_ version of * gpiod_get here, as CLSA010* don't have a fully functional bios with an _DSD node */ - cs35l41->reset_gpio = fwnode_gpiod_get_index(&adev->fwnode, "reset", cs35l41->index, + cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(adev), "reset", cs35l41->index, GPIOD_OUT_LOW, "cs35l41-reset"); property = "cirrus,speaker-position"; @@ -396,6 +1277,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i else hw_cfg->bst_cap = -1; + cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, cs35l41->index, nval, -1); + if (hw_cfg->bst_ind > 0 || hw_cfg->bst_cap > 0 || hw_cfg->bst_ipk > 0) hw_cfg->bst_type = CS35L41_INT_BOOST; else @@ -411,30 +1294,6 @@ err: dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); return ret; - -no_acpi_dsd: - /* - * Device CLSA0100 doesn't have _DSD so a gpiod_get by the label reset won't work. - * And devices created by i2c-multi-instantiate don't have their device struct pointing to - * the correct fwnode, so acpi_dev must be used here. - * And devm functions expect that the device requesting the resource has the correct - * fwnode. - */ - if (strncmp(hid, "CLSA0100", 8) != 0) - return -EINVAL; - - /* check I2C address to assign the index */ - cs35l41->index = id == 0x40 ? 0 : 1; - cs35l41->hw_cfg.spk_pos = cs35l41->index; - cs35l41->channel_index = 0; - cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); - cs35l41->hw_cfg.bst_type = CS35L41_EXT_BOOST_NO_VSPK_SWITCH; - hw_cfg->gpio2.func = CS35L41_GPIO2_INT_OPEN_DRAIN; - hw_cfg->gpio2.valid = true; - cs35l41->hw_cfg.valid = true; - put_device(physdev); - - return 0; } int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, @@ -460,10 +1319,8 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i dev_set_drvdata(dev, cs35l41); ret = cs35l41_hda_read_acpi(cs35l41, device_name, id); - if (ret) { - dev_err_probe(cs35l41->dev, ret, "Platform not supported %d\n", ret); - return ret; - } + if (ret) + return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); if (IS_ERR(cs35l41->reset_gpio)) { ret = PTR_ERR(cs35l41->reset_gpio); @@ -471,7 +1328,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i if (ret == -EBUSY) { dev_info(cs35l41->dev, "Reset line busy, assuming shared reset\n"); } else { - dev_err_probe(cs35l41->dev, ret, "Failed to get reset GPIO: %d\n", ret); + dev_err_probe(cs35l41->dev, ret, "Failed to get reset GPIO\n"); goto err; } } @@ -536,13 +1393,26 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i if (ret) goto err; + INIT_WORK(&cs35l41->fw_load_work, cs35l41_fw_load_work); + mutex_init(&cs35l41->fw_mutex); + + pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000); + pm_runtime_use_autosuspend(cs35l41->dev); + pm_runtime_mark_last_busy(cs35l41->dev); + pm_runtime_set_active(cs35l41->dev); + pm_runtime_get_noresume(cs35l41->dev); + pm_runtime_enable(cs35l41->dev); + ret = cs35l41_hda_apply_properties(cs35l41); if (ret) - goto err; + goto err_pm; + + pm_runtime_put_autosuspend(cs35l41->dev); ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops); if (ret) { dev_err(cs35l41->dev, "Register component failed: %d\n", ret); + pm_runtime_disable(cs35l41->dev); goto err; } @@ -550,6 +1420,10 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i return 0; +err_pm: + pm_runtime_disable(cs35l41->dev); + pm_runtime_put_noidle(cs35l41->dev); + err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); @@ -563,14 +1437,28 @@ void cs35l41_hda_remove(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); + pm_runtime_get_sync(cs35l41->dev); + pm_runtime_disable(cs35l41->dev); + + if (cs35l41->halo_initialized) + cs35l41_remove_dsp(cs35l41); + component_del(cs35l41->dev, &cs35l41_hda_comp_ops); + pm_runtime_put_noidle(cs35l41->dev); + if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); gpiod_put(cs35l41->reset_gpio); } EXPORT_SYMBOL_NS_GPL(cs35l41_hda_remove, SND_HDA_SCODEC_CS35L41); +const struct dev_pm_ops cs35l41_hda_pm_ops = { + RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) +}; +EXPORT_SYMBOL_NS_GPL(cs35l41_hda_pm_ops, SND_HDA_SCODEC_CS35L41); + MODULE_DESCRIPTION("CS35L41 HDA Driver"); +MODULE_IMPORT_NS(SND_HDA_CS_DSP_CONTROLS); MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, <tanureal@opensource.cirrus.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index a52ffd1f7999..bdb35f3be68a 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -10,11 +10,29 @@ #ifndef __CS35L41_HDA_H__ #define __CS35L41_HDA_H__ +#include <linux/efi.h> #include <linux/regulator/consumer.h> #include <linux/gpio/consumer.h> #include <linux/device.h> #include <sound/cs35l41.h> +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> + +struct cs35l41_amp_cal_data { + u32 calTarget[2]; + u32 calTime[2]; + s8 calAmbient; + u8 calStatus; + u16 calR; +} __packed; + +struct cs35l41_amp_efi_data { + u32 size; + u32 count; + struct cs35l41_amp_cal_data data[]; +} __packed; + enum cs35l41_hda_spk_pos { CS35l41_LEFT, CS35l41_RIGHT, @@ -32,15 +50,36 @@ struct cs35l41_hda { struct regmap *regmap; struct gpio_desc *reset_gpio; struct cs35l41_hw_cfg hw_cfg; + struct hda_codec *codec; int irq; int index; int channel_index; unsigned volatile long irq_errors; const char *amp_name; + const char *acpi_subsystem_id; + int firmware_type; + int speaker_id; + struct mutex fw_mutex; + struct work_struct fw_load_work; + struct regmap_irq_chip_data *irq_data; + bool firmware_running; + bool request_fw_load; + bool fw_request_ongoing; + bool halo_initialized; + bool playback_started; + struct cs_dsp cs_dsp; }; +enum halo_state { + HALO_STATE_CODE_INIT_DOWNLOAD = 0, + HALO_STATE_CODE_START, + HALO_STATE_CODE_RUN +}; + +extern const struct dev_pm_ops cs35l41_hda_pm_ops; + int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, struct regmap *regmap); void cs35l41_hda_remove(struct device *dev); diff --git a/sound/pci/hda/cs35l41_hda_i2c.c b/sound/pci/hda/cs35l41_hda_i2c.c index e810b278fb91..5baacfde4f16 100644 --- a/sound/pci/hda/cs35l41_hda_i2c.c +++ b/sound/pci/hda/cs35l41_hda_i2c.c @@ -1,14 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 // -// cs35l41.c -- CS35l41 HDA I2C driver +// CS35l41 HDA I2C driver // // Copyright 2021 Cirrus Logic, Inc. // // Author: Lucas Tanure <tanureal@opensource.cirrus.com> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/i2c.h> -#include <linux/acpi.h> #include "cs35l41_hda.h" @@ -16,11 +16,14 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt, const struct i2c_device { const char *device_name; - /* Compare against the device name so it works for I2C, normal ACPI - * and for ACPI by i2c-multi-instantiate matching cases + /* + * Compare against the device name so it works for SPI, normal ACPI + * and for ACPI by serial-multi-instantiate matching cases. */ if (strstr(dev_name(&clt->dev), "CLSA0100")) device_name = "CLSA0100"; + else if (strstr(dev_name(&clt->dev), "CLSA0101")) + device_name = "CLSA0101"; else if (strstr(dev_name(&clt->dev), "CSC3551")) device_name = "CSC3551"; else @@ -42,19 +45,19 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = { {} }; -#ifdef CONFIG_ACPI static const struct acpi_device_id cs35l41_acpi_hda_match[] = { {"CLSA0100", 0 }, + {"CLSA0101", 0 }, {"CSC3551", 0 }, - { }, + {} }; MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_hda_match); -#endif static struct i2c_driver cs35l41_i2c_driver = { .driver = { .name = "cs35l41-hda", - .acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match), + .acpi_match_table = cs35l41_acpi_hda_match, + .pm = &cs35l41_hda_pm_ops, }, .id_table = cs35l41_hda_i2c_id, .probe = cs35l41_hda_i2c_probe, diff --git a/sound/pci/hda/cs35l41_hda_spi.c b/sound/pci/hda/cs35l41_hda_spi.c index 22e088f28438..71979cfb4d7e 100644 --- a/sound/pci/hda/cs35l41_hda_spi.c +++ b/sound/pci/hda/cs35l41_hda_spi.c @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-2.0 // -// cs35l41.c -- CS35l41 HDA SPI driver +// CS35l41 HDA SPI driver // // Copyright 2021 Cirrus Logic, Inc. // // Author: Lucas Tanure <tanureal@opensource.cirrus.com> -#include <linux/acpi.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/spi/spi.h> @@ -16,8 +16,9 @@ static int cs35l41_hda_spi_probe(struct spi_device *spi) { const char *device_name; - /* Compare against the device name so it works for SPI, normal ACPI - * and for ACPI by spi-multi-instantiate matching cases + /* + * Compare against the device name so it works for SPI, normal ACPI + * and for ACPI by serial-multi-instantiate matching cases. */ if (strstr(dev_name(&spi->dev), "CSC3551")) device_name = "CSC3551"; @@ -38,18 +39,17 @@ static const struct spi_device_id cs35l41_hda_spi_id[] = { {} }; -#ifdef CONFIG_ACPI static const struct acpi_device_id cs35l41_acpi_hda_match[] = { { "CSC3551", 0 }, - {}, + {} }; MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_hda_match); -#endif static struct spi_driver cs35l41_spi_driver = { .driver = { .name = "cs35l41-hda", - .acpi_match_table = ACPI_PTR(cs35l41_acpi_hda_match), + .acpi_match_table = cs35l41_acpi_hda_match, + .pm = &cs35l41_hda_pm_ops, }, .id_table = cs35l41_hda_spi_id, .probe = cs35l41_hda_spi_probe, diff --git a/sound/pci/hda/hda_auto_parser.c b/sound/pci/hda/hda_auto_parser.c index cd1db943b7e0..7c6b1fe8dfcc 100644 --- a/sound/pci/hda/hda_auto_parser.c +++ b/sound/pci/hda/hda_auto_parser.c @@ -819,7 +819,7 @@ static void set_pin_targets(struct hda_codec *codec, snd_hda_set_pin_ctl_cache(codec, cfg->nid, cfg->val); } -static void apply_fixup(struct hda_codec *codec, int id, int action, int depth) +void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth) { const char *modelname = codec->fixup_name; @@ -829,7 +829,7 @@ static void apply_fixup(struct hda_codec *codec, int id, int action, int depth) if (++depth > 10) break; if (fix->chained_before) - apply_fixup(codec, fix->chain_id, action, depth + 1); + __snd_hda_apply_fixup(codec, fix->chain_id, action, depth + 1); switch (fix->type) { case HDA_FIXUP_PINS: @@ -870,6 +870,7 @@ static void apply_fixup(struct hda_codec *codec, int id, int action, int depth) id = fix->chain_id; } } +EXPORT_SYMBOL_GPL(__snd_hda_apply_fixup); /** * snd_hda_apply_fixup - Apply the fixup chain with the given action @@ -879,7 +880,7 @@ static void apply_fixup(struct hda_codec *codec, int id, int action, int depth) void snd_hda_apply_fixup(struct hda_codec *codec, int action) { if (codec->fixup_list) - apply_fixup(codec, codec->fixup_id, action, 0); + __snd_hda_apply_fixup(codec, codec->fixup_id, action, 0); } EXPORT_SYMBOL_GPL(snd_hda_apply_fixup); diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c index c572fb5886d5..cae9a975cbcc 100644 --- a/sound/pci/hda/hda_bind.c +++ b/sound/pci/hda/hda_bind.c @@ -248,6 +248,13 @@ static bool is_likely_hdmi_codec(struct hda_codec *codec) { hda_nid_t nid; + /* + * For ASoC users, if snd_hda_hdmi_codec module is denylisted and any + * event causes i915 enumeration to fail, ->wcaps remains uninitialized. + */ + if (!codec->wcaps) + return true; + for_each_hda_codec_node(nid, codec) { unsigned int wcaps = get_wcaps(codec, nid); switch (get_wcaps_type(wcaps)) { diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 7579a6982f47..384426d7e9dd 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -772,11 +772,11 @@ static void codec_release_pcms(struct hda_codec *codec) */ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) { - if (codec->registered) { + if (codec->core.registered) { /* pm_runtime_put() is called in snd_hdac_device_exit() */ pm_runtime_get_noresume(hda_codec_dev(codec)); pm_runtime_disable(hda_codec_dev(codec)); - codec->registered = 0; + codec->core.registered = 0; } snd_hda_codec_disconnect_pcms(codec); @@ -825,14 +825,14 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) */ void snd_hda_codec_register(struct hda_codec *codec) { - if (codec->registered) + if (codec->core.registered) return; if (device_is_registered(hda_codec_dev(codec))) { snd_hda_codec_display_power(codec, true); pm_runtime_enable(hda_codec_dev(codec)); /* it was powered up in snd_hda_codec_new(), now all done */ snd_hda_power_down(codec); - codec->registered = 1; + codec->core.registered = 1; } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); @@ -950,6 +950,7 @@ int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card, unsigned int codec_addr, struct hda_codec **codecp) { struct hda_codec *codec; + int ret; codec = snd_hda_codec_device_init(bus, codec_addr, "hdaudioC%dD%d", card->number, codec_addr); @@ -957,7 +958,11 @@ int snd_hda_codec_new(struct hda_bus *bus, struct snd_card *card, return PTR_ERR(codec); *codecp = codec; - return snd_hda_codec_device_new(bus, card, codec_addr, *codecp, true); + ret = snd_hda_codec_device_new(bus, card, codec_addr, *codecp, true); + if (ret) + put_device(hda_codec_dev(*codecp)); + + return ret; } EXPORT_SYMBOL_GPL(snd_hda_codec_new); @@ -1012,19 +1017,17 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, if (codec->bus->modelname) { codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL); - if (!codec->modelname) { - err = -ENOMEM; - goto error; - } + if (!codec->modelname) + return -ENOMEM; } fg = codec->core.afg ? codec->core.afg : codec->core.mfg; err = read_widget_caps(codec, fg); if (err < 0) - goto error; + return err; err = read_pin_defaults(codec); if (err < 0) - goto error; + return err; /* power-up all before initialization */ hda_set_power_state(codec, AC_PWRST_D0); @@ -1042,17 +1045,19 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, /* ASoC features component management instead */ err = snd_device_new(card, SNDRV_DEV_CODEC, codec, &dev_ops); if (err < 0) - goto error; + return err; } +#ifdef CONFIG_PM /* PM runtime needs to be enabled later after binding codec */ - pm_runtime_forbid(&codec->core.dev); + if (codec->core.dev.power.runtime_auto) + pm_runtime_forbid(&codec->core.dev); + else + /* Keep the usage_count consistent across subsequent probing */ + pm_runtime_get_noresume(&codec->core.dev); +#endif return 0; - - error: - put_device(hda_codec_dev(codec)); - return err; } EXPORT_SYMBOL_GPL(snd_hda_codec_device_new); @@ -2935,8 +2940,7 @@ static int hda_codec_runtime_suspend(struct device *dev) if (!codec->card) return 0; - if (!codec->bus->jackpoll_in_suspend) - cancel_delayed_work_sync(&codec->jackpoll_work); + cancel_delayed_work_sync(&codec->jackpoll_work); state = hda_call_codec_suspend(codec); if (codec->link_down_at_suspend || @@ -2944,6 +2948,11 @@ static int hda_codec_runtime_suspend(struct device *dev) (state & AC_PWRST_CLK_STOP_OK))) snd_hdac_codec_link_down(&codec->core); snd_hda_codec_display_power(codec, false); + + if (codec->bus->jackpoll_in_suspend && + (dev->power.power_state.event != PM_EVENT_SUSPEND)) + schedule_delayed_work(&codec->jackpoll_work, + codec->jackpoll_interval); return 0; } @@ -2967,6 +2976,9 @@ static int hda_codec_runtime_resume(struct device *dev) #ifdef CONFIG_PM_SLEEP static int hda_codec_pm_prepare(struct device *dev) { + struct hda_codec *codec = dev_to_hda_codec(dev); + + cancel_delayed_work_sync(&codec->jackpoll_work); dev->power.power_state = PMSG_SUSPEND; return pm_runtime_suspended(dev); } @@ -2986,9 +2998,6 @@ static void hda_codec_pm_complete(struct device *dev) static int hda_codec_pm_suspend(struct device *dev) { - struct hda_codec *codec = dev_to_hda_codec(dev); - - cancel_delayed_work_sync(&codec->jackpoll_work); dev->power.power_state = PMSG_SUSPEND; return pm_runtime_force_suspend(dev); } @@ -3043,7 +3052,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec) struct hda_pcm *cpcm; /* Skip the shutdown if codec is not registered */ - if (!codec->registered) + if (!codec->core.registered) return; cancel_delayed_work_sync(&codec->jackpoll_work); diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index e26c896a13f3..1223621bd62c 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -14,5 +14,8 @@ struct hda_component { struct device *dev; char name[HDA_MAX_NAME_SIZE]; + struct hda_codec *codec; void (*playback_hook)(struct device *dev, int action); + int (*suspend_hook)(struct device *dev); + int (*resume_hook)(struct device *dev); }; diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c new file mode 100644 index 000000000000..89ee549cb7d5 --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// HDA DSP ALSA Control Driver +// +// Copyright 2022 Cirrus Logic, Inc. +// +// Author: Stefan Binding <sbinding@opensource.cirrus.com> + +#include <linux/module.h> +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> +#include <linux/firmware/cirrus/wmfw.h> +#include "hda_cs_dsp_ctl.h" + +#define ADSP_MAX_STD_CTRL_SIZE 512 + +struct hda_cs_dsp_coeff_ctl { + struct cs_dsp_coeff_ctl *cs_ctl; + struct snd_card *card; + struct snd_kcontrol *kctl; +}; + +static const char * const hda_cs_dsp_fw_text[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "Prot", + [HDA_CS_DSP_FW_SPK_CALI] = "Cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "Diag", + [HDA_CS_DSP_FW_MISC] = "Misc", +}; + +const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW] = { + [HDA_CS_DSP_FW_SPK_PROT] = "spk-prot", + [HDA_CS_DSP_FW_SPK_CALI] = "spk-cali", + [HDA_CS_DSP_FW_SPK_DIAG] = "spk-diag", + [HDA_CS_DSP_FW_MISC] = "misc", +}; +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_fw_ids, SND_HDA_CS_DSP_CONTROLS); + +static int hda_cs_dsp_coeff_info(struct snd_kcontrol *kctl, struct snd_ctl_elem_info *uinfo) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = cs_ctl->len; + + return 0; +} + +static int hda_cs_dsp_coeff_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret = 0; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static int hda_cs_dsp_coeff_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_cs_dsp_coeff_ctl *ctl = (struct hda_cs_dsp_coeff_ctl *)snd_kcontrol_chip(kctl); + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + char *p = ucontrol->value.bytes.data; + int ret; + + mutex_lock(&cs_ctl->dsp->pwr_lock); + ret = cs_dsp_coeff_read_ctrl(cs_ctl, 0, p, cs_ctl->len); + mutex_unlock(&cs_ctl->dsp->pwr_lock); + + return ret; +} + +static unsigned int wmfw_convert_flags(unsigned int in) +{ + unsigned int out, rd, wr, vol; + + rd = SNDRV_CTL_ELEM_ACCESS_READ; + wr = SNDRV_CTL_ELEM_ACCESS_WRITE; + vol = SNDRV_CTL_ELEM_ACCESS_VOLATILE; + + out = 0; + + if (in) { + out |= rd; + if (in & WMFW_CTL_FLAG_WRITEABLE) + out |= wr; + if (in & WMFW_CTL_FLAG_VOLATILE) + out |= vol; + } else { + out |= rd | wr | vol; + } + + return out; +} + +static int hda_cs_dsp_add_kcontrol(struct hda_cs_dsp_coeff_ctl *ctl, const char *name) +{ + struct cs_dsp_coeff_ctl *cs_ctl = ctl->cs_ctl; + struct snd_kcontrol_new kcontrol = {0}; + struct snd_kcontrol *kctl; + int ret = 0; + + if (cs_ctl->len > ADSP_MAX_STD_CTRL_SIZE) { + dev_err(cs_ctl->dsp->dev, "KControl %s: length %zu exceeds maximum %d\n", name, + cs_ctl->len, ADSP_MAX_STD_CTRL_SIZE); + return -EINVAL; + } + + kcontrol.name = name; + kcontrol.info = hda_cs_dsp_coeff_info; + kcontrol.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kcontrol.access = wmfw_convert_flags(cs_ctl->flags); + kcontrol.get = hda_cs_dsp_coeff_get; + kcontrol.put = hda_cs_dsp_coeff_put; + + /* Save ctl inside private_data, ctl is owned by cs_dsp, + * and will be freed when cs_dsp removes the control */ + kctl = snd_ctl_new1(&kcontrol, (void *)ctl); + if (!kctl) { + ret = -ENOMEM; + return ret; + } + + ret = snd_ctl_add(ctl->card, kctl); + if (ret) { + dev_err(cs_ctl->dsp->dev, "Failed to add KControl %s = %d\n", kcontrol.name, ret); + return ret; + } + + dev_dbg(cs_ctl->dsp->dev, "Added KControl: %s\n", kcontrol.name); + ctl->kctl = kctl; + + return 0; +} + +int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info) +{ + struct cs_dsp *cs_dsp = cs_ctl->dsp; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct hda_cs_dsp_coeff_ctl *ctl; + const char *region_name; + int ret; + + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + return 0; + + region_name = cs_dsp_mem_region_name(cs_ctl->alg_region.type); + if (!region_name) { + dev_err(cs_dsp->dev, "Unknown region type: %d\n", cs_ctl->alg_region.type); + return -EINVAL; + } + + ret = scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s %.12s %x", info->device_name, + cs_dsp->name, hda_cs_dsp_fw_text[info->fw_type], cs_ctl->alg_region.alg); + + if (cs_ctl->subname) { + int avail = SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret - 2; + int skip = 0; + + /* Truncate the subname from the start if it is too long */ + if (cs_ctl->subname_len > avail) + skip = cs_ctl->subname_len - avail; + + snprintf(name + ret, SNDRV_CTL_ELEM_ID_NAME_MAXLEN - ret, + " %.*s", cs_ctl->subname_len - skip, cs_ctl->subname + skip); + } + + ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return -ENOMEM; + + ctl->cs_ctl = cs_ctl; + ctl->card = info->card; + cs_ctl->priv = ctl; + + ret = hda_cs_dsp_add_kcontrol(ctl, name); + if (ret) { + dev_err(cs_dsp->dev, "Error (%d) adding control %s\n", ret, name); + kfree(ctl); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_add, SND_HDA_CS_DSP_CONTROLS); + +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) +{ + struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; + + kfree(ctl); +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, const void *buf, size_t len) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + struct hda_cs_dsp_coeff_ctl *ctl; + int ret; + + cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); + if (!cs_ctl) + return -EINVAL; + + ctl = cs_ctl->priv; + + ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); + if (ret) + return ret; + + if (cs_ctl->flags & WMFW_CTL_FLAG_SYS) + return 0; + + snd_ctl_notify(ctl->card, SNDRV_CTL_EVENT_MASK_VALUE, &ctl->kctl->id); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_write_ctl, SND_HDA_CS_DSP_CONTROLS); + +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len) +{ + struct cs_dsp_coeff_ctl *cs_ctl; + + cs_ctl = cs_dsp_get_ctl(dsp, name, type, alg); + if (!cs_ctl) + return -EINVAL; + + return cs_dsp_coeff_read_ctrl(cs_ctl, 0, buf, len); +} +EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_read_ctl, SND_HDA_CS_DSP_CONTROLS); + +MODULE_DESCRIPTION("CS_DSP ALSA Control HDA Library"); +MODULE_AUTHOR("Stefan Binding, <sbinding@opensource.cirrus.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/pci/hda/hda_cs_dsp_ctl.h b/sound/pci/hda/hda_cs_dsp_ctl.h new file mode 100644 index 000000000000..4babc69cf2f0 --- /dev/null +++ b/sound/pci/hda/hda_cs_dsp_ctl.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * HDA DSP ALSA Control Driver + * + * Copyright 2022 Cirrus Logic, Inc. + * + * Author: Stefan Binding <sbinding@opensource.cirrus.com> + */ + +#ifndef __HDA_CS_DSP_CTL_H__ +#define __HDA_CS_DSP_CTL_H__ + +#include <sound/soc.h> +#include <linux/firmware/cirrus/cs_dsp.h> + +enum hda_cs_dsp_fw_id { + HDA_CS_DSP_FW_SPK_PROT, + HDA_CS_DSP_FW_SPK_CALI, + HDA_CS_DSP_FW_SPK_DIAG, + HDA_CS_DSP_FW_MISC, + HDA_CS_DSP_NUM_FW +}; + +struct hda_cs_dsp_ctl_info { + struct snd_card *card; + enum hda_cs_dsp_fw_id fw_type; + const char *device_name; +}; + +extern const char * const hda_cs_dsp_fw_ids[HDA_CS_DSP_NUM_FW]; + +int hda_cs_dsp_control_add(struct cs_dsp_coeff_ctl *cs_ctl, struct hda_cs_dsp_ctl_info *info); +void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl); +int hda_cs_dsp_write_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, const void *buf, size_t len); +int hda_cs_dsp_read_ctl(struct cs_dsp *dsp, const char *name, int type, + unsigned int alg, void *buf, size_t len); + +#endif /*__HDA_CS_DSP_CTL_H__*/ diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 0a83eb6b88b1..a77165bd92a9 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2525,6 +2525,9 @@ static const struct pci_device_id azx_ids[] = { .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, { PCI_DEVICE(0x8086, 0x51cf), .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, + /* Meteorlake-P */ + { PCI_DEVICE(0x8086, 0x7e28), + .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE}, /* Broxton-P(Apollolake) */ { PCI_DEVICE(0x8086, 0x5a98), .driver_data = AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON }, diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index aca592651870..682dca2057db 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -348,6 +348,7 @@ void snd_hda_apply_verbs(struct hda_codec *codec); void snd_hda_apply_pincfgs(struct hda_codec *codec, const struct hda_pintbl *cfg); void snd_hda_apply_fixup(struct hda_codec *codec, int action); +void __snd_hda_apply_fixup(struct hda_codec *codec, int id, int action, int depth); void snd_hda_pick_fixup(struct hda_codec *codec, const struct hda_model_fixup *models, const struct snd_pci_quirk *quirk, diff --git a/sound/pci/hda/hda_sysfs.c b/sound/pci/hda/hda_sysfs.c index d5ffcba794e5..bf951c10ae61 100644 --- a/sound/pci/hda/hda_sysfs.c +++ b/sound/pci/hda/hda_sysfs.c @@ -33,7 +33,7 @@ static ssize_t power_on_acct_show(struct device *dev, { struct hda_codec *codec = dev_get_drvdata(dev); snd_hda_update_power_acct(codec); - return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct)); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_on_acct)); } static ssize_t power_off_acct_show(struct device *dev, @@ -42,7 +42,7 @@ static ssize_t power_off_acct_show(struct device *dev, { struct hda_codec *codec = dev_get_drvdata(dev); snd_hda_update_power_acct(codec); - return sprintf(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct)); + return sysfs_emit(buf, "%u\n", jiffies_to_msecs(codec->power_off_acct)); } static DEVICE_ATTR_RO(power_on_acct); @@ -55,7 +55,7 @@ static ssize_t type##_show(struct device *dev, \ char *buf) \ { \ struct hda_codec *codec = dev_get_drvdata(dev); \ - return sprintf(buf, "0x%x\n", codec->field); \ + return sysfs_emit(buf, "0x%x\n", codec->field); \ } #define CODEC_INFO_STR_SHOW(type, field) \ @@ -64,8 +64,8 @@ static ssize_t type##_show(struct device *dev, \ char *buf) \ { \ struct hda_codec *codec = dev_get_drvdata(dev); \ - return sprintf(buf, "%s\n", \ - codec->field ? codec->field : ""); \ + return sysfs_emit(buf, "%s\n", \ + codec->field ? codec->field : ""); \ } CODEC_INFO_SHOW(vendor_id, core.vendor_id); @@ -85,8 +85,8 @@ static ssize_t pin_configs_show(struct hda_codec *codec, int i, len = 0; mutex_lock(&codec->user_mutex); snd_array_for_each(list, i, pin) { - len += sprintf(buf + len, "0x%02x 0x%08x\n", - pin->nid, pin->cfg); + len += sysfs_emit_at(buf, len, "0x%02x 0x%08x\n", + pin->nid, pin->cfg); } mutex_unlock(&codec->user_mutex); return len; @@ -222,9 +222,8 @@ static ssize_t init_verbs_show(struct device *dev, int i, len = 0; mutex_lock(&codec->user_mutex); snd_array_for_each(&codec->init_verbs, i, v) { - len += scnprintf(buf + len, PAGE_SIZE - len, - "0x%02x 0x%03x 0x%04x\n", - v->nid, v->verb, v->param); + len += sysfs_emit_at(buf, len, "0x%02x 0x%03x 0x%04x\n", + v->nid, v->verb, v->param); } mutex_unlock(&codec->user_mutex); return len; @@ -272,8 +271,8 @@ static ssize_t hints_show(struct device *dev, int i, len = 0; mutex_lock(&codec->user_mutex); snd_array_for_each(&codec->hints, i, hint) { - len += scnprintf(buf + len, PAGE_SIZE - len, - "%s = %s\n", hint->key, hint->val); + len += sysfs_emit_at(buf, len, "%s = %s\n", + hint->key, hint->val); } mutex_unlock(&codec->user_mutex); return len; diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c index 678fbcaf2a3b..6807b4708a17 100644 --- a/sound/pci/hda/patch_cirrus.c +++ b/sound/pci/hda/patch_cirrus.c @@ -395,6 +395,7 @@ static const struct snd_pci_quirk cs420x_fixup_tbl[] = { /* codec SSID */ SND_PCI_QUIRK(0x106b, 0x0600, "iMac 14,1", CS420X_IMAC27_122), + SND_PCI_QUIRK(0x106b, 0x0900, "iMac 12,1", CS420X_IMAC27_122), SND_PCI_QUIRK(0x106b, 0x1c00, "MacBookPro 8,1", CS420X_MBP81), SND_PCI_QUIRK(0x106b, 0x2000, "iMac 12,2", CS420X_IMAC27_122), SND_PCI_QUIRK(0x106b, 0x2800, "MacBookPro 10,1", CS420X_MBP101), diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index aa360a0af284..7b1a30a551f6 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -222,6 +222,7 @@ enum { CXT_PINCFG_LEMOTE_A1205, CXT_PINCFG_COMPAQ_CQ60, CXT_FIXUP_STEREO_DMIC, + CXT_PINCFG_LENOVO_NOTEBOOK, CXT_FIXUP_INC_MIC_BOOST, CXT_FIXUP_HEADPHONE_MIC_PIN, CXT_FIXUP_HEADPHONE_MIC, @@ -772,6 +773,14 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_stereo_dmic, }, + [CXT_PINCFG_LENOVO_NOTEBOOK] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x05d71030 }, + { } + }, + .chain_id = CXT_FIXUP_STEREO_DMIC, + }, [CXT_FIXUP_INC_MIC_BOOST] = { .type = HDA_FIXUP_FUNC, .v.func = cxt5066_increase_mic_boost, @@ -944,6 +953,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x103c, 0x828c, "HP EliteBook 840 G4", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8299, "HP 800 G3 SFF", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x829a, "HP 800 G3 DM", CXT_FIXUP_HP_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x103c, 0x82b4, "HP ProDesk 600 G3", CXT_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x836e, "HP ProBook 455 G5", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x837f, "HP ProBook 470 G5", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x83b2, "HP EliteBook 840 G5", CXT_FIXUP_HP_DOCK), @@ -970,7 +980,7 @@ static const struct snd_pci_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x3905, "Lenovo G50-30", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x390b, "Lenovo G50-80", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x3975, "Lenovo U300s", CXT_FIXUP_STEREO_DMIC), - SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_FIXUP_STEREO_DMIC), + SND_PCI_QUIRK(0x17aa, 0x3977, "Lenovo IdeaPad U310", CXT_PINCFG_LENOVO_NOTEBOOK), SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo G50-70", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad", CXT_FIXUP_THINKPAD_ACPI), @@ -1052,6 +1062,13 @@ static int patch_conexant_auto(struct hda_codec *codec) snd_hda_pick_fixup(codec, cxt5051_fixup_models, cxt5051_fixups, cxt_fixups); break; + case 0x14f15098: + codec->pin_amp_workaround = 1; + spec->gen.mixer_nid = 0x22; + spec->gen.add_stereo_mix_input = HDA_HINT_STEREO_MIX_AUTO; + snd_hda_pick_fixup(codec, cxt5066_fixup_models, + cxt5066_fixups, cxt_fixups); + break; case 0x14f150f2: codec->power_save_node = 1; fallthrough; @@ -1072,11 +1089,11 @@ static int patch_conexant_auto(struct hda_codec *codec) if (err < 0) goto error; - err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + err = cx_auto_parse_beep(codec); if (err < 0) goto error; - err = cx_auto_parse_beep(codec); + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); if (err < 0) goto error; diff --git a/sound/pci/hda/patch_cs8409-tables.c b/sound/pci/hda/patch_cs8409-tables.c index 4f4cc8215917..e0d3a8be2e38 100644 --- a/sound/pci/hda/patch_cs8409-tables.c +++ b/sound/pci/hda/patch_cs8409-tables.c @@ -68,7 +68,7 @@ const struct hda_verb cs8409_cs42l42_init_verbs[] = { {} /* terminator */ }; -const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { +static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { { CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */ { CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */ { CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */ @@ -76,7 +76,7 @@ const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { {} /* terminator */ }; -const struct hda_pintbl cs8409_cs42l42_pincfgs_no_dmic[] = { +static const struct hda_pintbl cs8409_cs42l42_pincfgs_no_dmic[] = { { CS8409_PIN_ASP1_TRANSMITTER_A, 0x042120f0 }, /* ASP-1-TX */ { CS8409_PIN_ASP1_RECEIVER_A, 0x04a12050 }, /* ASP-1-RX */ { CS8409_PIN_ASP2_TRANSMITTER_A, 0x901000f0 }, /* ASP-2-TX */ @@ -279,7 +279,7 @@ const struct hda_verb dolphin_init_verbs[] = { {} /* terminator */ }; -const struct hda_pintbl dolphin_pincfgs[] = { +static const struct hda_pintbl dolphin_pincfgs[] = { { 0x24, 0x022210f0 }, /* ASP-1-TX-A */ { 0x25, 0x010240f0 }, /* ASP-1-TX-B */ { 0x34, 0x02a21050 }, /* ASP-1-RX */ diff --git a/sound/pci/hda/patch_cs8409.h b/sound/pci/hda/patch_cs8409.h index 260388a6256c..2a8dfb4ff046 100644 --- a/sound/pci/hda/patch_cs8409.h +++ b/sound/pci/hda/patch_cs8409.h @@ -358,13 +358,11 @@ extern const struct snd_pci_quirk cs8409_fixup_tbl[]; extern const struct hda_model_fixup cs8409_models[]; extern const struct hda_fixup cs8409_fixups[]; extern const struct hda_verb cs8409_cs42l42_init_verbs[]; -extern const struct hda_pintbl cs8409_cs42l42_pincfgs[]; extern const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[]; extern const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[]; extern struct sub_codec cs8409_cs42l42_codec; extern const struct hda_verb dolphin_init_verbs[]; -extern const struct hda_pintbl dolphin_pincfgs[]; extern const struct cs8409_cir_param dolphin_hw_cfg[]; extern struct sub_codec dolphin_cs42l42_0; extern struct sub_codec dolphin_cs42l42_1; diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 31fe41795571..6c209cd26c0c 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -4554,6 +4554,7 @@ HDA_CODEC_ENTRY(0x8086281a, "Jasperlake HDMI", patch_i915_icl_hdmi), HDA_CODEC_ENTRY(0x8086281b, "Elkhartlake HDMI", patch_i915_icl_hdmi), HDA_CODEC_ENTRY(0x8086281c, "Alderlake-P HDMI", patch_i915_adlp_hdmi), HDA_CODEC_ENTRY(0x8086281f, "Raptorlake-P HDMI", patch_i915_adlp_hdmi), +HDA_CODEC_ENTRY(0x8086281d, "Meteorlake HDMI", patch_i915_adlp_hdmi), HDA_CODEC_ENTRY(0x80862880, "CedarTrail HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x80862882, "Valleyview2 HDMI", patch_i915_byt_hdmi), HDA_CODEC_ENTRY(0x80862883, "Braswell HDMI", patch_i915_byt_hdmi), diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index f3ad454b3fbf..fd630d62b5a0 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -443,6 +443,7 @@ static void alc_fill_eapd_coef(struct hda_codec *codec) case 0x10ec0245: case 0x10ec0255: case 0x10ec0256: + case 0x19e58326: case 0x10ec0257: case 0x10ec0282: case 0x10ec0283: @@ -580,6 +581,7 @@ static void alc_shutup_pins(struct hda_codec *codec) switch (codec->core.vendor_id) { case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: case 0x10ec0283: case 0x10ec0286: case 0x10ec0288: @@ -2632,6 +2634,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x67e1, "Clevo PB71[DE][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x67e5, "Clevo PC70D[PRS](?:-D|-G)?", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x67f1, "Clevo PC70H[PRS]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), + SND_PCI_QUIRK(0x1558, 0x67f5, "Clevo PD70PN[NRT]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x70d1, "Clevo PC70[ER][CDF]", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x7714, "Clevo X170SM", ALC1220_FIXUP_CLEVO_PB51ED_PINS), SND_PCI_QUIRK(0x1558, 0x7715, "Clevo X170KM-G", ALC1220_FIXUP_CLEVO_PB51ED), @@ -3247,6 +3250,7 @@ static void alc_disable_headset_jack_key(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_write_coef_idx(codec, 0x48, 0x0); alc_update_coef_idx(codec, 0x49, 0x0045, 0x0); break; @@ -3275,6 +3279,7 @@ static void alc_enable_headset_jack_key(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_write_coef_idx(codec, 0x48, 0xd011); alc_update_coef_idx(codec, 0x49, 0x007f, 0x0045); break; @@ -4016,15 +4021,22 @@ static void alc5505_dsp_init(struct hda_codec *codec) static int alc269_suspend(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; + int i; if (spec->has_alc5505_dsp) alc5505_dsp_suspend(codec); + + for (i = 0; i < HDA_MAX_COMPONENTS; i++) + if (spec->comps[i].suspend_hook) + spec->comps[i].suspend_hook(spec->comps[i].dev); + return alc_suspend(codec); } static int alc269_resume(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; + int i; if (spec->codec_variant == ALC269_TYPE_ALC269VB) alc269vb_toggle_power_output(codec, 0); @@ -4055,6 +4067,10 @@ static int alc269_resume(struct hda_codec *codec) if (spec->has_alc5505_dsp) alc5505_dsp_resume(codec); + for (i = 0; i < HDA_MAX_COMPONENTS; i++) + if (spec->comps[i].resume_hook) + spec->comps[i].resume_hook(spec->comps[i].dev); + return 0; } #endif /* CONFIG_PM */ @@ -4910,6 +4926,7 @@ static void alc_headset_mode_unplugged(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_process_coef_fw(codec, coef0256); break; case 0x10ec0234: @@ -5025,6 +5042,7 @@ static void alc_headset_mode_mic_in(struct hda_codec *codec, hda_nid_t hp_pin, case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_write_coef_idx(codec, 0x45, 0xc489); snd_hda_set_pin_ctl_cache(codec, hp_pin, 0); alc_process_coef_fw(codec, coef0256); @@ -5175,6 +5193,7 @@ static void alc_headset_mode_default(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_write_coef_idx(codec, 0x1b, 0x0e4b); alc_write_coef_idx(codec, 0x45, 0xc089); msleep(50); @@ -5274,6 +5293,7 @@ static void alc_headset_mode_ctia(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_process_coef_fw(codec, coef0256); break; case 0x10ec0234: @@ -5388,6 +5408,7 @@ static void alc_headset_mode_omtp(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_process_coef_fw(codec, coef0256); break; case 0x10ec0234: @@ -5489,6 +5510,7 @@ static void alc_determine_headset_type(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_write_coef_idx(codec, 0x1b, 0x0e4b); alc_write_coef_idx(codec, 0x06, 0x6104); alc_write_coefex_idx(codec, 0x57, 0x3, 0x09a3); @@ -5783,6 +5805,7 @@ static void alc255_set_default_jack_type(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: alc_process_coef_fw(codec, alc256fw); break; } @@ -6385,6 +6408,7 @@ static void alc_combo_jack_hp_jd_restart(struct hda_codec *codec) case 0x10ec0236: case 0x10ec0255: case 0x10ec0256: + case 0x19e58326: alc_update_coef_idx(codec, 0x1b, 0x8000, 1 << 15); /* Reset HP JD */ alc_update_coef_idx(codec, 0x1b, 0x8000, 0 << 15); break; @@ -6597,8 +6621,20 @@ static int comp_bind(struct device *dev) { struct hda_codec *cdc = dev_to_hda_codec(dev); struct alc_spec *spec = cdc->spec; + int ret, i; + + ret = component_bind_all(dev, spec->comps); + if (ret) + return ret; + + if (snd_hdac_is_power_on(&cdc->core)) { + codec_dbg(cdc, "Resuming after bind.\n"); + for (i = 0; i < HDA_MAX_COMPONENTS; i++) + if (spec->comps[i].resume_hook) + spec->comps[i].resume_hook(spec->comps[i].dev); + } - return component_bind_all(dev, spec->comps); + return 0; } static void comp_unbind(struct device *dev) @@ -6641,6 +6677,7 @@ static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char "%s-%s:00-cs35l41-hda.%d", bus, hid, i); if (!name) return; + spec->comps[i].codec = cdc; component_match_add(dev, &spec->match, component_compare_dev_name, name); } ret = component_master_add_with_match(dev, &comp_master_ops, spec->match); @@ -6673,6 +6710,12 @@ static void alc287_fixup_legion_16achg6_speakers(struct hda_codec *cdc, const st cs35l41_generic_fixup(cdc, action, "i2c", "CLSA0100", 2); } +static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const struct hda_fixup *fix, + int action) +{ + cs35l41_generic_fixup(cdc, action, "i2c", "CLSA0101", 2); +} + /* for alc295_fixup_hp_top_speakers */ #include "hp_x360_helper.c" @@ -6774,6 +6817,43 @@ static void alc_fixup_dell4_mic_no_presence_quiet(struct hda_codec *codec, } } +static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* + * The Pin Complex 0x17 for the bass speakers is wrongly reported as + * unconnected. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + /* + * Avoid DAC 0x06 and 0x08, as they have no volume controls. + * DAC 0x02 and 0x03 would be fine. + */ + static const hda_nid_t conn[] = { 0x02, 0x03 }; + /* + * Prefer both speakerbar (0x14) and bass speakers (0x17) connected to DAC 0x02. + * Headphones (0x21) are connected to DAC 0x03. + */ + static const hda_nid_t preferred_pairs[] = { + 0x14, 0x02, + 0x17, 0x02, + 0x21, 0x03, + 0 + }; + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_apply_pincfgs(codec, pincfgs); + snd_hda_override_conn_list(codec, 0x17, ARRAY_SIZE(conn), conn); + spec->gen.preferred_dacs = preferred_pairs; + break; + } +} + enum { ALC269_FIXUP_GPIO2, ALC269_FIXUP_SONY_VAIO, @@ -6829,6 +6909,7 @@ enum { ALC269_FIXUP_LIMIT_INT_MIC_BOOST, ALC269VB_FIXUP_ASUS_ZENBOOK, ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, + ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED, ALC269VB_FIXUP_ORDISSIMO_EVE2, ALC283_FIXUP_CHROME_BOOK, @@ -6888,6 +6969,7 @@ enum { ALC298_FIXUP_LENOVO_SPK_VOLUME, ALC256_FIXUP_DELL_INSPIRON_7559_SUBWOOFER, ALC269_FIXUP_ATIV_BOOK_8, + ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE, ALC221_FIXUP_HP_MIC_NO_PRESENCE, ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, @@ -6992,6 +7074,7 @@ enum { ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, + ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_13S_GEN2_SPEAKERS, ALC256_FIXUP_SET_COEF_DEFAULTS, ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE, @@ -7008,8 +7091,28 @@ enum { ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED, ALC285_FIXUP_HP_SPEAKERS_MICMUTE_LED, ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, + ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, + ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, }; +/* A special fixup for Lenovo C940 and Yoga Duet 7; + * both have the very same PCI SSID, and we need to apply different fixups + * depending on the codec ID + */ +static void alc298_fixup_lenovo_c940_duet7(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + int id; + + if (codec->core.vendor_id == 0x10ec0298) + id = ALC298_FIXUP_LENOVO_SPK_VOLUME; /* C940 */ + else + id = ALC287_FIXUP_YOGA7_14ITL_SPEAKERS; /* Duet 7 */ + __snd_hda_apply_fixup(codec, id, action, 0); +} + static const struct hda_fixup alc269_fixups[] = { [ALC269_FIXUP_GPIO2] = { .type = HDA_FIXUP_FUNC, @@ -7395,6 +7498,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269VB_FIXUP_ASUS_ZENBOOK, }, + [ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x18, 0x01a110f0 }, /* use as headset mic */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MIC + }, [ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_limit_int_mic_boost, @@ -7806,6 +7918,16 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_NO_SHUTUP }, + [ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { 0x1a, 0x01813030 }, /* use as headphone mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC221_FIXUP_HP_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -8709,6 +8831,10 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE, }, + [ALC298_FIXUP_LENOVO_C940_DUET7] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_lenovo_c940_duet7, + }, [ALC287_FIXUP_13S_GEN2_SPEAKERS] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -8819,6 +8945,78 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC }, + [ALC287_FIXUP_LEGION_16ITHG6] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_16ithg6_speakers, + }, + [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + // enable left speaker + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x41 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x1a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xf }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x40 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + // enable right speaker + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x46 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xc }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xf }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x46 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x10 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x44 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { 0x20, AC_VERB_SET_COEF_INDEX, 0x26 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + { }, + }, + }, + [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga9_14iap7_bass_spk_pin, + .chained = true, + .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -8851,6 +9049,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1290, "Acer Veriton Z4860G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1291, "Acer Veriton Z4660G", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x129c, "Acer SWIFT SF314-55", ALC256_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x1025, 0x129d, "Acer SWIFT SF313-51", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1300, "Acer SWIFT SF314-56", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1308, "Acer Aspire Z24-890", ALC286_FIXUP_ACER_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x132a, "Acer TravelMate B114-21", ALC233_FIXUP_ACER_HEADSET_MIC), @@ -8860,6 +9059,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x1430, "Acer TravelMate B311R-31", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1025, 0x1466, "Acer Aspire A515-56", ALC255_FIXUP_ACER_HEADPHONE_AND_MIC), SND_PCI_QUIRK(0x1028, 0x0470, "Dell M101z", ALC269_FIXUP_DELL_M101Z), + SND_PCI_QUIRK(0x1028, 0x053c, "Dell Latitude E5430", ALC292_FIXUP_DELL_E7X), SND_PCI_QUIRK(0x1028, 0x054b, "Dell XPS one 2710", ALC275_FIXUP_DELL_XPS), SND_PCI_QUIRK(0x1028, 0x05bd, "Dell Latitude E6440", ALC292_FIXUP_DELL_E7X), SND_PCI_QUIRK(0x1028, 0x05be, "Dell Latitude E6540", ALC292_FIXUP_DELL_E7X), @@ -8975,6 +9175,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x2335, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2336, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2337, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), + SND_PCI_QUIRK(0x103c, 0x2b5e, "HP 288 Pro G2 MT", ALC221_FIXUP_HP_288PRO_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x802e, "HP Z240 SFF", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x802f, "HP Z240", ALC221_FIXUP_HP_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x8077, "HP", ALC256_FIXUP_HP_HEADSET_MIC), @@ -8995,6 +9196,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x861f, "HP Elite Dragonfly G1", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x869d, "HP", ALC236_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x86c7, "HP Envy AiO 32", ALC274_FIXUP_HP_ENVY_GPIO), + SND_PCI_QUIRK(0x103c, 0x86e7, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), + SND_PCI_QUIRK(0x103c, 0x86e8, "HP Spectre x360 15-eb0xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), SND_PCI_QUIRK(0x103c, 0x8716, "HP Elite Dragonfly G2 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8720, "HP EliteBook x360 1040 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8724, "HP EliteBook 850 G7", ALC285_FIXUP_HP_GPIO_LED), @@ -9010,6 +9213,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8783, "HP ZBook Fury 15 G7 Mobile Workstation", ALC285_FIXUP_HP_GPIO_AMP_INIT), + SND_PCI_QUIRK(0x103c, 0x8786, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8787, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8788, "HP OMEN 15", ALC285_FIXUP_HP_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x87c8, "HP", ALC287_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x87e5, "HP ProBook 440 G8 Notebook PC", ALC236_FIXUP_HP_GPIO_LED), @@ -9059,6 +9264,13 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x89c3, "Zbook Studio G9", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x89c6, "Zbook Fury 17 G9", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x89ca, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8a78, "HP Dev One", ALC285_FIXUP_HP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x103c, 0x8aa0, "HP ProBook 440 G9 (MB 8A9E)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aa3, "HP ProBook 450 G9 (MB 8AA1)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aa8, "HP EliteBook 640 G9 (MB 8AA6)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8aab, "HP EliteBook 650 G9 (MB 8AA9)", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8ad1, "HP EliteBook 840 14 inch G9 Notebook PC", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8ad2, "HP EliteBook 860 16 inch G9 Notebook PC", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x1043, 0x103e, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x103f, "ASUS TX300", ALC282_FIXUP_ASUS_TX300), SND_PCI_QUIRK(0x1043, 0x106d, "Asus K53BE", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), @@ -9073,6 +9285,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x12a0, "ASUS X441UV", ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x12e0, "ASUS X541SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x12f0, "ASUS X541UV", ALC256_FIXUP_ASUS_MIC), + SND_PCI_QUIRK(0x1043, 0x1313, "Asus K42JZ", ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x13b0, "ASUS Z550SA", ALC256_FIXUP_ASUS_MIC), SND_PCI_QUIRK(0x1043, 0x1427, "Asus Zenbook UX31E", ALC269VB_FIXUP_ASUS_ZENBOOK), SND_PCI_QUIRK(0x1043, 0x1517, "Asus Zenbook UX31A", ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A), @@ -9148,6 +9361,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x4018, "Clevo NV40M[BE]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x4019, "Clevo NV40MZ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x4020, "Clevo NV40MB", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x4041, "Clevo NV4[15]PZ", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x40a1, "Clevo NL40GU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x40c1, "Clevo NL40[CZ]U", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x40d1, "Clevo NL41DU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), @@ -9174,6 +9388,8 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x70f3, "Clevo NH77DPQ", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x70f4, "Clevo NH77EPY", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x70f6, "Clevo NH77DPQ-Y", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7716, "Clevo NS50PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1558, 0x7718, "Clevo L140PU", ALC256_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x8228, "Clevo NR40BU", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x8520, "Clevo NH50D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1558, 0x8521, "Clevo NH77D[CD]", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), @@ -9258,8 +9474,10 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3176, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x17aa, 0x3178, "ThinkCentre Station", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x17aa, 0x31af, "ThinkCentre Station", ALC623_FIXUP_LENOVO_THINKSTATION_P340), + SND_PCI_QUIRK(0x17aa, 0x3801, "Lenovo Yoga9 14IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo Yoga DuetITL 2021", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), - SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940", ALC298_FIXUP_LENOVO_SPK_VOLUME), + SND_PCI_QUIRK(0x17aa, 0x3818, "Lenovo C940 / Yoga Duet 7", ALC298_FIXUP_LENOVO_C940_DUET7), SND_PCI_QUIRK(0x17aa, 0x3819, "Lenovo 13s Gen2 ITL", ALC287_FIXUP_13S_GEN2_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3820, "Yoga Duet 7 13ITL6", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3824, "Legion Y9000X 2020", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), @@ -9271,6 +9489,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x384a, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3852, "Lenovo Yoga 7 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3853, "Lenovo Yoga 7 15ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), SND_PCI_QUIRK(0x17aa, 0x3902, "Lenovo E50-80", ALC269_FIXUP_DMIC_THINKPAD_ACPI), SND_PCI_QUIRK(0x17aa, 0x3977, "IdeaPad S210", ALC283_FIXUP_INT_MIC), SND_PCI_QUIRK(0x17aa, 0x3978, "Lenovo B50-70", ALC269_FIXUP_DMIC_THINKPAD_ACPI), @@ -9315,6 +9534,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1d72, 0x1602, "RedmiBook", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1701, "XiaomiNotebook Pro", ALC298_FIXUP_DELL1_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1d72, 0x1901, "RedmiBook 14", ALC256_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1d72, 0x1945, "Redmi G", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1d72, 0x1947, "RedmiBook Air", ALC255_FIXUP_XIAOMI_HEADSET_MIC), SND_PCI_QUIRK(0x8086, 0x2074, "Intel NUC 8", ALC233_FIXUP_INTEL_NUC8_DMIC), SND_PCI_QUIRK(0x8086, 0x2080, "Intel NUC 8 Rugged", ALC256_FIXUP_INTEL_NUC8_RUGGED), @@ -9501,6 +9721,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC285_FIXUP_HP_SPECTRE_X360, .name = "alc285-hp-spectre-x360"}, {.id = ALC285_FIXUP_HP_SPECTRE_X360_EB1, .name = "alc285-hp-spectre-x360-eb1"}, {.id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, .name = "alc287-ideapad-bass-spk-amp"}, + {.id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, .name = "alc287-yoga9-bass-spk-pin"}, {.id = ALC623_FIXUP_LENOVO_THINKSTATION_P340, .name = "alc623-lenovo-thinkstation-p340"}, {.id = ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, .name = "alc255-acer-headphone-and-mic"}, {.id = ALC285_FIXUP_HP_GPIO_AMP_INIT, .name = "alc285-hp-amp-init"}, @@ -10095,6 +10316,7 @@ static int patch_alc269(struct hda_codec *codec) case 0x10ec0230: case 0x10ec0236: case 0x10ec0256: + case 0x19e58326: spec->codec_variant = ALC269_TYPE_ALC256; spec->shutup = alc256_shutup; spec->init_hook = alc256_init; @@ -10722,6 +10944,7 @@ enum { ALC668_FIXUP_MIC_DET_COEF, ALC897_FIXUP_LENOVO_HEADSET_MIC, ALC897_FIXUP_HEADSET_MIC_PIN, + ALC897_FIXUP_HP_HSMIC_VERB, }; static const struct hda_fixup alc662_fixups[] = { @@ -11141,6 +11364,13 @@ static const struct hda_fixup alc662_fixups[] = { .chained = true, .chain_id = ALC897_FIXUP_LENOVO_HEADSET_MIC }, + [ALC897_FIXUP_HP_HSMIC_VERB] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + }, }; static const struct snd_pci_quirk alc662_fixup_tbl[] = { @@ -11166,7 +11396,9 @@ static const struct snd_pci_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0698, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x069f, "Dell", ALC668_FIXUP_DELL_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x103c, 0x1632, "HP RP5800", ALC662_FIXUP_HP_RP5800), + SND_PCI_QUIRK(0x103c, 0x8719, "HP", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x103c, 0x873e, "HP", ALC671_FIXUP_HP_HEADSET_MIC2), + SND_PCI_QUIRK(0x103c, 0x877e, "HP 288 Pro G6", ALC671_FIXUP_HP_HEADSET_MIC2), SND_PCI_QUIRK(0x103c, 0x885f, "HP 288 Pro G8", ALC671_FIXUP_HP_HEADSET_MIC2), SND_PCI_QUIRK(0x1043, 0x1080, "Asus UX501VW", ALC668_FIXUP_HEADSET_MODE), SND_PCI_QUIRK(0x1043, 0x11cd, "Asus N550", ALC662_FIXUP_ASUS_Nx50), @@ -11545,6 +11777,7 @@ static const struct hda_device_id snd_hda_id_realtek[] = { HDA_CODEC_ENTRY(0x10ec0b00, "ALCS1200A", patch_alc882), HDA_CODEC_ENTRY(0x10ec1168, "ALC1220", patch_alc882), HDA_CODEC_ENTRY(0x10ec1220, "ALC1220", patch_alc882), + HDA_CODEC_ENTRY(0x19e58326, "HW8326", patch_alc269), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_realtek); diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c index a05304f340df..aea7fae2ca4b 100644 --- a/sound/pci/hda/patch_via.c +++ b/sound/pci/hda/patch_via.c @@ -518,11 +518,11 @@ static int via_parse_auto_config(struct hda_codec *codec) if (err < 0) return err; - err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + err = auto_parse_beep(codec); if (err < 0) return err; - err = auto_parse_beep(codec); + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); if (err < 0) return err; diff --git a/sound/pci/ice1712/quartet.c b/sound/pci/ice1712/quartet.c index 0dfa093f7dca..20b3e8f94719 100644 --- a/sound/pci/ice1712/quartet.c +++ b/sound/pci/ice1712/quartet.c @@ -566,7 +566,7 @@ static int qtet_ain12_sw_put(struct snd_kcontrol *kcontrol, { struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); unsigned int old, new, tmp, masked_old; - old = new = get_scr(ice); + old = get_scr(ice); masked_old = old & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0); tmp = ucontrol->value.integer.value[0]; if (tmp == 2) diff --git a/sound/soc/bcm/cygnus-pcm.c b/sound/soc/bcm/cygnus-pcm.c index 3abeaf0f1b1c..8f488f92936b 100644 --- a/sound/soc/bcm/cygnus-pcm.c +++ b/sound/soc/bcm/cygnus-pcm.c @@ -1,15 +1,5 @@ -/* - * Copyright (C) 2014-2015 Broadcom Corporation - * - * This program 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2014-2015 Broadcom Corporation #include <linux/debugfs.h> #include <linux/dma-mapping.h> #include <linux/init.h> diff --git a/sound/soc/bcm/cygnus-ssp.c b/sound/soc/bcm/cygnus-ssp.c index 8b7a21573070..2a92e33e1fbf 100644 --- a/sound/soc/bcm/cygnus-ssp.c +++ b/sound/soc/bcm/cygnus-ssp.c @@ -1,15 +1,5 @@ -/* - * Copyright (C) 2014-2015 Broadcom Corporation - * - * This program 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2014-2015 Broadcom Corporation #include <linux/clk.h> #include <linux/delay.h> #include <linux/init.h> diff --git a/sound/soc/bcm/cygnus-ssp.h b/sound/soc/bcm/cygnus-ssp.h index 33dd34305928..74152b2d770d 100644 --- a/sound/soc/bcm/cygnus-ssp.h +++ b/sound/soc/bcm/cygnus-ssp.h @@ -1,15 +1,5 @@ -/* - * Copyright (C) 2014-2015 Broadcom Corporation - * - * This program 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 version 2. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2014-2015 Broadcom Corporation */ #ifndef __CYGNUS_SSP_H__ #define __CYGNUS_SSP_H__ diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index edcb8bc6806b..ad20a3dff9b7 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -274,7 +274,7 @@ static void hda_codec_remove(struct snd_soc_component *component) struct hdac_device *hdev = &codec->core; struct hdac_bus *bus = hdev->bus; struct hdac_ext_link *hlink; - bool was_registered = codec->registered; + bool was_registered = codec->core.registered; /* Don't allow any more runtime suspends */ pm_runtime_forbid(&hdev->dev); @@ -376,7 +376,7 @@ static int hda_hdev_detach(struct hdac_device *hdev) { struct hda_codec *codec = dev_to_hda_codec(&hdev->dev); - if (codec->registered) + if (codec->core.registered) cancel_delayed_work_sync(&codec->jackpoll_work); snd_soc_unregister_component(&hdev->dev); diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index d18b56e60433..1ea10dc70748 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -199,6 +199,7 @@ struct va_macro { struct clk *mclk; struct clk *macro; struct clk *dcodec; + struct clk *fsgen; struct clk_hw hw; struct lpass_macro *pds; @@ -467,9 +468,9 @@ static int va_macro_mclk_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - return va_macro_mclk_enable(va, true); + return clk_prepare_enable(va->fsgen); case SND_SOC_DAPM_POST_PMD: - return va_macro_mclk_enable(va, false); + clk_disable_unprepare(va->fsgen); } return 0; @@ -1473,6 +1474,12 @@ static int va_macro_probe(struct platform_device *pdev) if (ret) goto err_clkout; + va->fsgen = clk_hw_get_clk(&va->hw, "fsgen"); + if (IS_ERR(va->fsgen)) { + ret = PTR_ERR(va->fsgen); + goto err_clkout; + } + ret = devm_snd_soc_register_component(dev, &va_macro_component_drv, va_macro_dais, ARRAY_SIZE(va_macro_dais)); diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c index 4fe065ece17c..3e04c7f0cce4 100644 --- a/sound/soc/codecs/max98373-i2c.c +++ b/sound/soc/codecs/max98373-i2c.c @@ -442,7 +442,6 @@ static bool max98373_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case MAX98373_R2000_SW_RESET ... MAX98373_R2009_INT_FLAG3: - case MAX98373_R203E_AMP_PATH_GAIN: case MAX98373_R2054_MEAS_ADC_PVDD_CH_READBACK: case MAX98373_R2055_MEAS_ADC_THERM_CH_READBACK: case MAX98373_R20B6_BDE_CUR_STATE_READBACK: diff --git a/sound/soc/sh/rcar/ssiu.c b/sound/soc/sh/rcar/ssiu.c index d7f4646ee029..281bc20d4c5d 100644 --- a/sound/soc/sh/rcar/ssiu.c +++ b/sound/soc/sh/rcar/ssiu.c @@ -419,6 +419,7 @@ static struct rsnd_mod_ops rsnd_ssiu_ops_gen2 = { .name = SSIU_NAME, .dma_req = rsnd_ssiu_dma_req, .init = rsnd_ssiu_init_gen2, + .quit = rsnd_ssiu_quit, .start = rsnd_ssiu_start_gen2, .stop = rsnd_ssiu_stop_gen2, .get_status = rsnd_ssiu_get_status, diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 3f9d314fba16..b101db85446f 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -535,7 +535,7 @@ static int soc_tplg_kcontrol_bind_io(struct snd_soc_tplg_ctl_hdr *hdr, * return an -EINVAL error and prevent the card from * being configured. */ - if (IS_ENABLED(CONFIG_SND_CTL_VALIDATION) && sbe->max > 512) + if (sbe->max > 512) k->access |= SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK; ext_ops = tplg->bytes_ext_ops; diff --git a/sound/spi/Kconfig b/sound/spi/Kconfig index 44d39fa635ae..f407c37c37fa 100644 --- a/sound/spi/Kconfig +++ b/sound/spi/Kconfig @@ -19,7 +19,7 @@ config SND_AT73C213 DAC can be found on Atmel development boards. This driver requires the Atmel SSC driver for sound sink, a - peripheral found on most AT91 and AVR32 microprocessors. + peripheral found on most AT91 microprocessors. To compile this driver as a module, choose M here: the module will be called snd-at73c213. diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 7168f1c6a37a..32c39d8bd2e5 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -175,7 +175,7 @@ static int usb6fire_pcm_stream_start(struct pcm_runtime *rt) } } - /* wait for first out urb to return (sent in in urb handler) */ + /* wait for first out urb to return (sent in urb handler) */ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, HZ); if (rt->stream_wait_cond) diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index cd4a0bc6d278..7aec0a95c609 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -348,7 +348,8 @@ static int bcd2000_init_midi(struct bcd2000 *bcd2k) static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k, struct usb_interface *interface) { - /* usb_kill_urb not necessary, urb is aborted automatically */ + usb_kill_urb(bcd2k->midi_out_urb); + usb_kill_urb(bcd2k->midi_in_urb); usb_free_urb(bcd2k->midi_out_urb); usb_free_urb(bcd2k->midi_in_urb); diff --git a/sound/usb/card.c b/sound/usb/card.c index 0fff96a5d3ab..d356743de2ff 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -387,6 +387,14 @@ static const struct usb_audio_device_name usb_audio_names[] = { DEVICE_NAME(0x05e1, 0x0408, "Syntek", "STK1160"), DEVICE_NAME(0x05e1, 0x0480, "Hauppauge", "Woodbury"), + /* ASUS ROG Zenith II: this machine has also two devices, one for + * the front headphone and another for the rest + */ + PROFILE_NAME(0x0b05, 0x1915, "ASUS", "Zenith II Front Headphone", + "Zenith-II-Front-Headphone"), + PROFILE_NAME(0x0b05, 0x1916, "ASUS", "Zenith II Main Audio", + "Zenith-II-Main-Audio"), + /* ASUS ROG Strix */ PROFILE_NAME(0x0b05, 0x1917, "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index f9c921683948..0d7b73bf7945 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -133,7 +133,7 @@ static inline bool ep_state_running(struct snd_usb_endpoint *ep) static inline bool ep_state_update(struct snd_usb_endpoint *ep, int old, int new) { - return atomic_cmpxchg(&ep->state, old, new) == old; + return atomic_try_cmpxchg(&ep->state, &old, new); } /** diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c index 71f17f02f341..cf650fab54d7 100644 --- a/sound/usb/hiface/pcm.c +++ b/sound/usb/hiface/pcm.c @@ -225,7 +225,7 @@ static int hiface_pcm_stream_start(struct pcm_runtime *rt) } } - /* wait for first out urb to return (sent in in urb handler) */ + /* wait for first out urb to return (sent in urb handler) */ wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond, HZ); if (rt->stream_wait_cond) { diff --git a/sound/usb/line6/pod.c b/sound/usb/line6/pod.c index 16e644330c4d..cd41aa7f0385 100644 --- a/sound/usb/line6/pod.c +++ b/sound/usb/line6/pod.c @@ -235,7 +235,7 @@ static ssize_t serial_number_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%u\n", pod->serial_number); + return sysfs_emit(buf, "%u\n", pod->serial_number); } /* @@ -247,8 +247,8 @@ static ssize_t firmware_version_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%d.%02d\n", pod->firmware_version / 100, - pod->firmware_version % 100); + return sysfs_emit(buf, "%d.%02d\n", pod->firmware_version / 100, + pod->firmware_version % 100); } /* @@ -260,7 +260,7 @@ static ssize_t device_id_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_pod *pod = card->private_data; - return sprintf(buf, "%d\n", pod->device_id); + return sysfs_emit(buf, "%d\n", pod->device_id); } /* diff --git a/sound/usb/line6/podhd.c b/sound/usb/line6/podhd.c index b24bc82f89e3..ffd8c157a281 100644 --- a/sound/usb/line6/podhd.c +++ b/sound/usb/line6/podhd.c @@ -146,7 +146,7 @@ static ssize_t serial_number_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_podhd *pod = card->private_data; - return sprintf(buf, "%u\n", pod->serial_number); + return sysfs_emit(buf, "%u\n", pod->serial_number); } static ssize_t firmware_version_show(struct device *dev, @@ -155,7 +155,7 @@ static ssize_t firmware_version_show(struct device *dev, struct snd_card *card = dev_to_snd_card(dev); struct usb_line6_podhd *pod = card->private_data; - return sprintf(buf, "%06x\n", pod->firmware_version); + return sysfs_emit(buf, "%06x\n", pod->firmware_version); } static DEVICE_ATTR_RO(firmware_version); diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 3c795675f048..f4bd1e8ae4b6 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -374,13 +374,28 @@ static const struct usbmix_name_map corsair_virtuoso_map[] = { { 0 } }; -/* Some mobos shipped with a dummy HD-audio show the invalid GET_MIN/GET_MAX - * response for Input Gain Pad (id=19, control=12) and the connector status - * for SPDIF terminal (id=18). Skip them. - */ -static const struct usbmix_name_map asus_rog_map[] = { - { 18, NULL }, /* OT, connector control */ - { 19, NULL, 12 }, /* FU, Input Gain Pad */ +/* ASUS ROG Zenith II with Realtek ALC1220-VB */ +static const struct usbmix_name_map asus_zenith_ii_map[] = { + { 19, NULL, 12 }, /* FU, Input Gain Pad - broken response, disabled */ + { 16, "Speaker" }, /* OT */ + { 22, "Speaker Playback" }, /* FU */ + { 7, "Line" }, /* IT */ + { 19, "Line Capture" }, /* FU */ + { 8, "Mic" }, /* IT */ + { 20, "Mic Capture" }, /* FU */ + { 9, "Front Mic" }, /* IT */ + { 21, "Front Mic Capture" }, /* FU */ + { 17, "IEC958" }, /* OT */ + { 23, "IEC958 Playback" }, /* FU */ + {} +}; + +static const struct usbmix_connector_map asus_zenith_ii_connector_map[] = { + { 10, 16 }, /* (Back) Speaker */ + { 11, 17 }, /* SPDIF */ + { 13, 7 }, /* Line */ + { 14, 8 }, /* Mic */ + { 15, 9 }, /* Front Mic */ {} }; @@ -611,9 +626,10 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .map = gigabyte_b450_map, .connector_map = gigabyte_b450_connector_map, }, - { /* ASUS ROG Zenith II */ + { /* ASUS ROG Zenith II (main audio) */ .id = USB_ID(0x0b05, 0x1916), - .map = asus_rog_map, + .map = asus_zenith_ii_map, + .connector_map = asus_zenith_ii_connector_map, }, { /* ASUS ROG Strix */ .id = USB_ID(0x0b05, 0x1917), diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index d35cf54cab33..ab0d459f4271 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -24,6 +24,7 @@ #include <sound/asoundef.h> #include <sound/core.h> #include <sound/control.h> +#include <sound/hda_verbs.h> #include <sound/hwdep.h> #include <sound/info.h> #include <sound/tlv.h> @@ -1934,13 +1935,194 @@ static int snd_soundblaster_e1_switch_create(struct usb_mixer_interface *mixer) NULL); } +/* + * Dell WD15 dock jack detection + * + * The WD15 contains an ALC4020 USB audio controller and ALC3263 audio codec + * from Realtek. It is a UAC 1 device, and UAC 1 does not support jack + * detection. Instead, jack detection works by sending HD Audio commands over + * vendor-type USB messages. + */ + +#define HDA_VERB_CMD(V, N, D) (((N) << 20) | ((V) << 8) | (D)) + +#define REALTEK_HDA_VALUE 0x0038 + +#define REALTEK_HDA_SET 62 +#define REALTEK_MANUAL_MODE 72 +#define REALTEK_HDA_GET_OUT 88 +#define REALTEK_HDA_GET_IN 89 + +#define REALTEK_AUDIO_FUNCTION_GROUP 0x01 +#define REALTEK_LINE1 0x1a +#define REALTEK_VENDOR_REGISTERS 0x20 +#define REALTEK_HP_OUT 0x21 + +#define REALTEK_CBJ_CTRL2 0x50 + +#define REALTEK_JACK_INTERRUPT_NODE 5 + +#define REALTEK_MIC_FLAG 0x100 + +static int realtek_hda_set(struct snd_usb_audio *chip, u32 cmd) +{ + struct usb_device *dev = chip->dev; + __be32 buf = cpu_to_be32(cmd); + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_SET, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); +} + +static int realtek_hda_get(struct snd_usb_audio *chip, u32 cmd, u32 *value) +{ + struct usb_device *dev = chip->dev; + int err; + __be32 buf = cpu_to_be32(cmd); + + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_HDA_GET_OUT, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), REALTEK_HDA_GET_IN, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_IN, + REALTEK_HDA_VALUE, 0, &buf, sizeof(buf)); + if (err < 0) + return err; + + *value = be32_to_cpu(buf); + return 0; +} + +static int realtek_ctl_connector_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *cval = kcontrol->private_data; + struct snd_usb_audio *chip = cval->head.mixer->chip; + u32 pv = kcontrol->private_value; + u32 node_id = pv & 0xff; + u32 sense; + u32 cbj_ctrl2; + bool presence; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PIN_SENSE, node_id, 0), + &sense); + if (err < 0) + goto err; + if (pv & REALTEK_MIC_FLAG) { + err = realtek_hda_set(chip, + HDA_VERB_CMD(AC_VERB_SET_COEF_INDEX, + REALTEK_VENDOR_REGISTERS, + REALTEK_CBJ_CTRL2)); + if (err < 0) + goto err; + err = realtek_hda_get(chip, + HDA_VERB_CMD(AC_VERB_GET_PROC_COEF, + REALTEK_VENDOR_REGISTERS, 0), + &cbj_ctrl2); + if (err < 0) + goto err; + } +err: + snd_usb_unlock_shutdown(chip); + if (err < 0) + return err; + + presence = sense & AC_PINSENSE_PRESENCE; + if (pv & REALTEK_MIC_FLAG) + presence = presence && (cbj_ctrl2 & 0x0070) == 0x0070; + ucontrol->value.integer.value[0] = presence; + return 0; +} + +static const struct snd_kcontrol_new realtek_connector_ctl_ro = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "", /* will be filled later manually */ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_ctl_boolean_mono_info, + .get = realtek_ctl_connector_get, +}; + +static int realtek_resume_jack(struct usb_mixer_elem_list *list) +{ + snd_ctl_notify(list->mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &list->kctl->id); + return 0; +} + +static int realtek_add_jack(struct usb_mixer_interface *mixer, + char *name, u32 val) +{ + struct usb_mixer_elem_info *cval; + struct snd_kcontrol *kctl; + + cval = kzalloc(sizeof(*cval), GFP_KERNEL); + if (!cval) + return -ENOMEM; + snd_usb_mixer_elem_init_std(&cval->head, mixer, + REALTEK_JACK_INTERRUPT_NODE); + cval->head.resume = realtek_resume_jack; + cval->val_type = USB_MIXER_BOOLEAN; + cval->channels = 1; + cval->min = 0; + cval->max = 1; + kctl = snd_ctl_new1(&realtek_connector_ctl_ro, cval); + if (!kctl) { + kfree(cval); + return -ENOMEM; + } + kctl->private_value = val; + strscpy(kctl->id.name, name, sizeof(kctl->id.name)); + kctl->private_free = snd_usb_mixer_elem_free; + return snd_usb_mixer_add_control(&cval->head, kctl); +} + +static int dell_dock_mixer_create(struct usb_mixer_interface *mixer) +{ + int err; + struct usb_device *dev = mixer->chip->dev; + + /* Power down the audio codec to avoid loud pops in the next step. */ + realtek_hda_set(mixer->chip, + HDA_VERB_CMD(AC_VERB_SET_POWER_STATE, + REALTEK_AUDIO_FUNCTION_GROUP, + AC_PWRST_D3)); + + /* + * Turn off 'manual mode' in case it was enabled. This removes the need + * to power cycle the dock after it was attached to a Windows machine. + */ + snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), REALTEK_MANUAL_MODE, + USB_RECIP_DEVICE | USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, NULL, 0); + + err = realtek_add_jack(mixer, "Line Out Jack", REALTEK_LINE1); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headphone Jack", REALTEK_HP_OUT); + if (err < 0) + return err; + err = realtek_add_jack(mixer, "Headset Mic Jack", + REALTEK_HP_OUT | REALTEK_MIC_FLAG); + if (err < 0) + return err; + return 0; +} + static void dell_dock_init_vol(struct snd_usb_audio *chip, int ch, int id) { u16 buf = 0; snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC_SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT, - ch, snd_usb_ctrl_intf(chip) | (id << 8), + (UAC_FU_VOLUME << 8) | ch, + snd_usb_ctrl_intf(chip) | (id << 8), &buf, 2); } @@ -3238,6 +3420,7 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x1235, 0x8213): /* Focusrite Scarlett 8i6 3rd Gen */ case USB_ID(0x1235, 0x8214): /* Focusrite Scarlett 18i8 3rd Gen */ case USB_ID(0x1235, 0x8215): /* Focusrite Scarlett 18i20 3rd Gen */ + case USB_ID(0x1235, 0x820c): /* Focusrite Clarett+ 8Pre */ err = snd_scarlett_gen2_init(mixer); break; @@ -3245,6 +3428,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_soundblaster_e1_switch_create(mixer); break; case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ + err = dell_dock_mixer_create(mixer); + if (err < 0) + break; err = dell_dock_mixer_init(mixer); break; diff --git a/sound/usb/mixer_scarlett_gen2.c b/sound/usb/mixer_scarlett_gen2.c index 69a2cd429ee2..9d11bb08667e 100644 --- a/sound/usb/mixer_scarlett_gen2.c +++ b/sound/usb/mixer_scarlett_gen2.c @@ -1,13 +1,15 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Focusrite Scarlett Gen 2/3 Driver for ALSA + * Focusrite Scarlett Gen 2/3 and Clarett+ Driver for ALSA * * Supported models: * - 6i6/18i8/18i20 Gen 2 * - Solo/2i2/4i4/8i6/18i8/18i20 Gen 3 + * - Clarett+ 8Pre * * Copyright (c) 2018-2022 by Geoffrey D. Bennett <g at b4.vu> * Copyright (c) 2020-2021 by Vladimir Sadovnikov <sadko4u@gmail.com> + * Copyright (c) 2022 by Christian Colglazier <christian@cacolglazier.com> * * Based on the Scarlett (Gen 1) Driver for ALSA: * @@ -51,6 +53,9 @@ * Support for phantom power, direct monitoring, speaker switching, * and talkback added in May-June 2021. * + * Support for Clarett+ 8Pre added in Aug 2022 by Christian + * Colglazier. + * * This ALSA mixer gives access to (model-dependent): * - input, output, mixer-matrix muxes * - mixer-matrix gain stages @@ -203,7 +208,8 @@ enum { SCARLETT2_CONFIG_SET_NO_MIXER = 0, SCARLETT2_CONFIG_SET_GEN_2 = 1, SCARLETT2_CONFIG_SET_GEN_3 = 2, - SCARLETT2_CONFIG_SET_COUNT = 3 + SCARLETT2_CONFIG_SET_CLARETT = 3, + SCARLETT2_CONFIG_SET_COUNT = 4 }; /* Hardware port types: @@ -841,6 +847,61 @@ static const struct scarlett2_device_info s18i20_gen3_info = { } }, }; +static const struct scarlett2_device_info clarett_8pre_info = { + .usb_id = USB_ID(0x1235, 0x820c), + + .config_set = SCARLETT2_CONFIG_SET_CLARETT, + .line_out_hw_vol = 1, + .level_input_count = 2, + .air_input_count = 8, + + .line_out_descrs = { + "Monitor L", + "Monitor R", + NULL, + NULL, + NULL, + NULL, + "Headphones 1 L", + "Headphones 1 R", + "Headphones 2 L", + "Headphones 2 R", + }, + + .port_count = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 8, 10 }, + [SCARLETT2_PORT_TYPE_SPDIF] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_ADAT] = { 8, 8 }, + [SCARLETT2_PORT_TYPE_MIX] = { 10, 18 }, + [SCARLETT2_PORT_TYPE_PCM] = { 20, 18 }, + }, + + .mux_assignment = { { + { SCARLETT2_PORT_TYPE_PCM, 0, 18 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 8 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 14 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_ADAT, 0, 4 }, + { SCARLETT2_PORT_TYPE_MIX, 0, 18 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 8 }, + { 0, 0, 0 }, + }, { + { SCARLETT2_PORT_TYPE_PCM, 0, 12 }, + { SCARLETT2_PORT_TYPE_ANALOGUE, 0, 10 }, + { SCARLETT2_PORT_TYPE_SPDIF, 0, 2 }, + { SCARLETT2_PORT_TYPE_NONE, 0, 22 }, + { 0, 0, 0 }, + } }, +}; + static const struct scarlett2_device_info *scarlett2_devices[] = { /* Supported Gen 2 devices */ &s6i6_gen2_info, @@ -855,6 +916,9 @@ static const struct scarlett2_device_info *scarlett2_devices[] = { &s18i8_gen3_info, &s18i20_gen3_info, + /* Supported Clarett+ devices */ + &clarett_8pre_info, + /* End of list */ NULL }; @@ -1047,6 +1111,29 @@ static const struct scarlett2_config [SCARLETT2_CONFIG_TALKBACK_MAP] = { .offset = 0xb0, .size = 16, .activate = 10 }, + +/* Clarett+ 8Pre */ +}, { + [SCARLETT2_CONFIG_DIM_MUTE] = { + .offset = 0x31, .size = 8, .activate = 2 }, + + [SCARLETT2_CONFIG_LINE_OUT_VOLUME] = { + .offset = 0x34, .size = 16, .activate = 1 }, + + [SCARLETT2_CONFIG_MUTE_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 1 }, + + [SCARLETT2_CONFIG_SW_HW_SWITCH] = { + .offset = 0x66, .size = 8, .activate = 3 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x7c, .size = 8, .activate = 7 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x95, .size = 8, .activate = 8 }, + + [SCARLETT2_CONFIG_STANDALONE_SWITCH] = { + .offset = 0x8d, .size = 8, .activate = 6 }, } }; /* proprietary request/response format */ diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index b7b6f3834ed5..6eb7d93b358d 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -637,10 +637,10 @@ static int snd_get_meter_comp_index(struct snd_us16x08_meter_store *store) } } else { /* skip channels with no compressor active */ - while (!store->comp_store->val[ + while (store->comp_index <= SND_US16X08_MAX_CHANNELS + && !store->comp_store->val[ COMP_STORE_IDX(SND_US16X08_ID_COMP_SWITCH)] - [store->comp_index - 1] - && store->comp_index <= SND_US16X08_MAX_CHANNELS) { + [store->comp_index - 1]) { store->comp_index++; } ret = store->comp_index++; diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index b470404a5376..d45d1d7e6664 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -291,6 +291,9 @@ int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, bool is_playback; int err; + if (fmt->sync_ep) + return 0; /* already set up */ + alts = snd_usb_get_host_interface(chip, fmt->iface, fmt->altsetting); if (!alts) return 0; @@ -304,7 +307,7 @@ int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, * Generic sync EP handling */ - if (altsd->bNumEndpoints < 2) + if (fmt->ep_idx > 0 || altsd->bNumEndpoints < 2) return 0; is_playback = !(get_endpoint(alts, 0)->bEndpointAddress & USB_DIR_IN); @@ -1266,7 +1269,7 @@ static inline void fill_playback_urb_dsd_dop(struct snd_usb_substream *subs, unsigned int wrap = subs->buffer_bytes; u8 *dst = urb->transfer_buffer; u8 *src = runtime->dma_area; - u8 marker[] = { 0x05, 0xfa }; + static const u8 marker[] = { 0x05, 0xfa }; unsigned int queued = 0; /* diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 78eb41b621d6..f93201a830b5 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2658,7 +2658,12 @@ YAMAHA_DEVICE(0x7010, "UB99"), .nr_rates = 2, .rate_table = (unsigned int[]) { 44100, 48000 - } + }, + .sync_ep = 0x82, + .sync_iface = 0, + .sync_altsetting = 1, + .sync_ep_idx = 1, + .implicit_fb = 1, } }, { @@ -3798,6 +3803,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, /* + * MacroSilicon MS2100/MS2106 based AV capture cards + * + * These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch. + * They also need QUIRK_FLAG_ALIGN_TRANSFER, which makes one wonder if + * they pretend to be 96kHz mono as a workaround for stereo being broken + * by that... + * + * They also have an issue with initial stream alignment that causes the + * channels to be swapped and out of phase, which is dealt with in quirks.c. + */ +{ + USB_AUDIO_DEVICE(0x534d, 0x0021), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "MacroSilicon", + .product_name = "MS210x", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 2, + .type = QUIRK_AUDIO_STANDARD_MIXER, + }, + { + .ifnum = 3, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 48000, + .rate_max = 48000, + } + }, + { + .ifnum = -1 + } + } + } +}, + +/* * MacroSilicon MS2109 based HDMI capture cards * * These claim 96kHz 1ch in the descriptors, but are actually 48kHz 2ch. @@ -4114,6 +4167,206 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * Fiero SC-01 (firmware v1.0.0 @ 48 kHz) + */ + USB_DEVICE(0x2b53, 0x0023), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Fiero", + .product_name = "SC-01", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + /* Playback */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 }, + .clock = 0x29 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* + * Fiero SC-01 (firmware v1.0.0 @ 96 kHz) + */ + USB_DEVICE(0x2b53, 0x0024), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Fiero", + .product_name = "SC-01", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + /* Playback */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 }, + .clock = 0x29 + } + }, + { + .ifnum = -1 + } + } + } +}, +{ + /* + * Fiero SC-01 (firmware v1.1.0) + */ + USB_DEVICE(0x2b53, 0x0031), + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .vendor_name = "Fiero", + .product_name = "SC-01", + .ifnum = QUIRK_ANY_INTERFACE, + .type = QUIRK_COMPOSITE, + .data = &(const struct snd_usb_audio_quirk[]) { + { + .ifnum = 0, + .type = QUIRK_AUDIO_STANDARD_INTERFACE + }, + /* Playback */ + { + .ifnum = 1, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 48000, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 48000, 96000 }, + .clock = 0x29 + } + }, + /* Capture */ + { + .ifnum = 2, + .type = QUIRK_AUDIO_FIXED_ENDPOINT, + .data = &(const struct audioformat) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .fmt_bits = 24, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC | + USB_ENDPOINT_SYNC_ASYNC | + USB_ENDPOINT_USAGE_IMPLICIT_FB, + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 48000, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 48000, 96000 }, + .clock = 0x29 + } + }, + { + .ifnum = -1 + } + } + } +}, #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index e8468f9b007d..168fd802d70b 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -1478,6 +1478,7 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x041e, 0x3f19): /* E-Mu 0204 USB */ set_format_emu_quirk(subs, fmt); break; + case USB_ID(0x534d, 0x0021): /* MacroSilicon MS2100/MS2106 */ case USB_ID(0x534d, 0x2109): /* MacroSilicon MS2109 */ subs->stream_offset_adj = 2; break; @@ -1842,6 +1843,12 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_SHARE_MEDIA_DEVICE | QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1397, 0x0507, /* Behringer UMC202HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x0508, /* Behringer UMC204HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x1397, 0x0509, /* Behringer UMC404HD */ + QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x13e5, 0x0001, /* Serato Phono */ QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x154e, 0x1002, /* Denon DCD-1500RE */ @@ -1904,10 +1911,18 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_IGNORE_CTL_ERROR), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x534d, 0x0021, /* MacroSilicon MS2100/MS2106 */ + QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x2b53, 0x0023, /* Fiero SC-01 (firmware v1.0.0 @ 48 kHz) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2b53, 0x0024, /* Fiero SC-01 (firmware v1.0.0 @ 96 kHz) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2b53, 0x0031, /* Fiero SC-01 (firmware v1.1.0) */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 0d828e35b401..ab95fb34a635 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -33,6 +33,8 @@ #include <drm/intel_lpe_audio.h> #include "intel_hdmi_audio.h" +#define INTEL_HDMI_AUDIO_SUSPEND_DELAY_MS 5000 + #define for_each_pipe(card_ctx, pipe) \ for ((pipe) = 0; (pipe) < (card_ctx)->num_pipes; (pipe)++) #define for_each_port(card_ctx, port) \ @@ -1066,7 +1068,9 @@ static int had_pcm_open(struct snd_pcm_substream *substream) intelhaddata = snd_pcm_substream_chip(substream); runtime = substream->runtime; - pm_runtime_get_sync(intelhaddata->dev); + retval = pm_runtime_resume_and_get(intelhaddata->dev); + if (retval < 0) + return retval; /* set the runtime hw parameter with local snd_pcm_hardware struct */ runtime->hw = had_pcm_hardware; @@ -1534,8 +1538,12 @@ static void had_audio_wq(struct work_struct *work) container_of(work, struct snd_intelhad, hdmi_audio_wq); struct intel_hdmi_lpe_audio_pdata *pdata = ctx->dev->platform_data; struct intel_hdmi_lpe_audio_port_pdata *ppdata = &pdata->port[ctx->port]; + int ret; + + ret = pm_runtime_resume_and_get(ctx->dev); + if (ret < 0) + return; - pm_runtime_get_sync(ctx->dev); mutex_lock(&ctx->mutex); if (ppdata->pipe < 0) { dev_dbg(ctx->dev, "%s: Event: HAD_NOTIFY_HOT_UNPLUG : port = %d\n", @@ -1802,8 +1810,11 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev) pdata->notify_audio_lpe = notify_audio_lpe; spin_unlock_irq(&pdata->lpe_audio_slock); + pm_runtime_set_autosuspend_delay(&pdev->dev, INTEL_HDMI_AUDIO_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_enable(&pdev->dev); pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_idle(&pdev->dev); dev_dbg(&pdev->dev, "%s: handle pending notification\n", __func__); for_each_port(card_ctx, port) { |