diff options
| author | Takashi Iwai <tiwai@suse.de> | 2026-10-19 10:39:44 +0200 |
|---|---|---|
| committer | Takashi Iwai <tiwai@suse.de> | 2026-10-19 10:40:00 +0200 |
| commit | 7e246ac8bed799402b743550d283bdd202048fa7 (patch) | |
| tree | d6dc95e8d6f96a581ec2c37fcaea09911a4099a5 | |
| parent | 8e8cd9a4c6dc006e9bd51b8ad0079fb4576b48d3 (diff) | |
| parent | 40409fa23d32c8ed51e832031daa6decd9d6fd11 (diff) | |
Merge branch 'for-next' into for-linus
Pull 7.4 merge-window stuff.
Signed-off-by: Takashi Iwai <tiwai@suse.de>
195 files changed, 5300 insertions, 1541 deletions
diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index e500ec84e3ef..689eccf6a592 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2407,6 +2407,12 @@ quirk_flags the requested rate. A device advertising a single rate is otherwise never sent the request at all, and some require it before streaming will start. + * bit 33: ``swap_rates`` + The clock source applies a requested rate to the other base-rate + family, i.e. asking for 44100 makes the device run at 48000 and vice + versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver + sends the partner rate in SET_CUR so the device ends up running at the + requested one. This module supports multiple devices, autoprobe and hotplugging. diff --git a/Documentation/sound/cards/hdspm.rst b/Documentation/sound/cards/hdspm.rst index 5373e51ed076..deba92267942 100644 --- a/Documentation/sound/cards/hdspm.rst +++ b/Documentation/sound/cards/hdspm.rst @@ -6,9 +6,11 @@ Software Interface ALSA-DSP MADI Driver 2004 - winfried ritsch +driver name - snd-hdspm + Full functionality has been added to the driver. Since some of -the Controls and startup-options are ALSA-Standard and only the +the Controls and startup options are ALSA-Standard, only the special Controls are described and discussed below. @@ -18,7 +20,7 @@ Hardware functionality Audio transmission ------------------ -* number of channels -- depends on transmission mode +* number of channels -- depends on transmission mode The number of channels chosen is from 1..Nmax. The reason to use for a lower number of channels is only resource allocation, @@ -29,29 +31,29 @@ Audio transmission * Single Speed -- 1..64 channels .. note:: - (Note: Choosing the 56channel mode for transmission or as + Choosing the 56-channel mode for transmission or as receiver, only 56 are transmitted/received over the MADI, but all 64 channels are available for the mixer, so channel count - for the driver) + for the driver. * Double Speed -- 1..32 channels .. note:: - Note: Choosing the 56-channel mode for - transmission/receive-mode , only 28 are transmitted/received + Choosing the 56-channel mode for + transmission/receive-mode, only 28 are transmitted/received over the MADI, but all 32 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. * Quad Speed -- 1..16 channels .. note:: Choosing the 56-channel mode for - transmission/receive-mode , only 14 are transmitted/received + transmission/receive-mode, only 14 are transmitted/received over the MADI, but all 16 channels are available for the mixer, - so channel count for the driver + so channel count for the driver. -* Format -- signed 32 Bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) +* Format -- signed 32-bit Little Endian (SNDRV_PCM_FMTBIT_S32_LE) * Sample Rates -- @@ -67,30 +69,6 @@ Audio transmission * fragments -- 2 -* Hardware-pointer -- 2 Modi - - - The Card supports the readout of the actual Buffer-pointer, - where DMA reads/writes. Since of the bulk mode of PCI it is only - 64 Byte accurate. SO it is not really usable for the - ALSA-mid-level functions (here the buffer-ID gives a better - result), but if MMAP is used by the application. Therefore it - can be configured at load-time with the parameter - precise-pointer. - - -.. hint:: - (Hint: Experimenting I found that the pointer is maximum 64 to - large never to small. So if you subtract 64 you always have a - safe pointer for writing, which is used on this mode inside - ALSA. In theory now you can get now a latency as low as 16 - Samples, which is a quarter of the interrupt possibilities.) - - * Precise Pointer -- off - interrupt used for pointer-calculation - - * Precise Pointer -- on - hardware pointer used. Controller ---------- @@ -104,7 +82,7 @@ hwdep-interface. Also all 128+256 Peak and RMS-Meter can be accessed via the hwdep-interface. Since it could be a performance problem always copying and converting Peak and RMS-Levels even if you just need -one, I decided to export the hardware structure, so that of +one, I decided to export the hardware structure, so that if needed some driver-guru can implement a memory-mapping of mixer or peak-meters over ioctl, or also to do only copying and no conversion. A test-application shows the usage of the controller. @@ -112,9 +90,9 @@ conversion. A test-application shows the usage of the controller. * Latency Controls --- not implemented !!! .. note:: - Note: Within the windows-driver the latency is accessible of a - control-panel, but buffer-sizes are controlled with ALSA from - hwparams-calls and should not be changed in run-state, I did not + Within the Windows driver the latency is accessible via + Control Panel, but buffer sizes are controlled with ALSA from + hwparams calls and should not be changed in run-state. I did not implement it here. @@ -127,12 +105,12 @@ conversion. A test-application shows the usage of the controller. * Values -- "Master" "Slave" .. note:: - !!!! This is a hardware-function but is in conflict with the - Clock-source controller, which is a kind of ALSA-standard. I + !!!! This is a hardware function but is in conflict with the + Clock-source controller, which is a kind of ALSA-standard. It makes sense to set the card to a special mode (master at some - frequency or slave), since even not using an Audio-application + frequency or slave), since even not using an Audio application a studio should have working synchronisations setup. So use - Clock-source-controller instead !!!! + Clock-source controller instead !!!! * Clock Source @@ -145,11 +123,11 @@ conversion. A test-application shows the usage of the controller. "Internal 96.0 kHz" Choose between Master at a specific Frequency and so also the - Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref" + Speed-mode or Slave (Autosync). Also see "Preferred Sync Ref". .. warning:: - !!!! This is no pure hardware function but was implemented by - ALSA by some ALSA-drivers before, so I use it also. !!! + !!!! This is not a pure hardware function but was implemented by + ALSA by some ALSA drivers before, so I use it also. !!! * Preferred Sync Ref @@ -165,7 +143,7 @@ conversion. A test-application shows the usage of the controller. chosen. If it is not available another is used if possible. .. note:: - Note: Since MADI has a much higher bit-rate than word-clock, the + Since MADI has a much higher bit-rate than word-clock, the card should synchronise better in MADI Mode. But since the RME-PLL is very good, there are almost no problems with word-clock too. I never found a difference. @@ -179,12 +157,12 @@ conversion. A test-application shows the usage of the controller. * Values -- 0 1 - Using 64-channel-modus (1) or 56-channel-modus for + Using 64-channel mode (1) or 56-channel mode for MADI-transmission (0). .. note:: - Note: This control is for output only. Input-mode is detected + This control is for output only. Input mode is detected automatically from hardware sending MADI. @@ -200,7 +178,7 @@ conversion. A test-application shows the usage of the controller. Don't use to lower 5 Audio-bits on AES as additional Bits. -* Safe Mode oder Auto Input +* Safe Mode or Auto Input * Name -- "Safe Mode" @@ -222,7 +200,7 @@ conversion. A test-application shows the usage of the controller. * Values -- optical coaxial - Choosing the Input, optical or coaxial. If Safe-mode is active, + Choosing the Input, optical or coaxial. If Safe mode is active, this is the preferred Input. Mixer @@ -240,7 +218,7 @@ Mixer Here as a first value the channel-index is taken to get/set the corresponding mixer channel, where 0-63 are the input to output fader and 64-127 the playback to outputs fader. Value 0 - is channel muted 0 and 32768 an amplification of 1. + is channel muted 0 and 32768 an amplification of 1. * Chn 1-64 @@ -250,14 +228,14 @@ Mixer * Line Out - * Name -- "Line Out" + * Name -- "Line Out" * Access -- Read Write * Values -- 0 1 Switching on and off the analog out, which has nothing to do - with mixing or routing. the analog outs reflects channel 63,64. + with mixing or routing. The analog outs reflects channel 63,64. Information (only read access) @@ -280,7 +258,7 @@ Information (only read access) Should be "Autosync Rate", but Name used is - ALSA-Scheme. External Sample frequency liked used on Autosync is + ALSA-Scheme. External Sample frequency like used on Autosync is reported. @@ -315,19 +293,19 @@ Information (only read access) Sync-Reference is either "WordClock", "MADI" or none. -* RX 64ch --- noch nicht implementiert +* RX 64ch --- not yet implemented - MADI-Receiver is in 64 channel mode oder 56 channel mode. + MADI-Receiver is in 64-channel mode or 56-channel mode. -* AB_inp --- not tested +* AB_inp --- not tested Used input for Auto-Input. * actual Buffer Position --- not implemented - !!! this is a ALSA internal function, so no control is used !!! + !!! This is an ALSA internal function, so no control is used !!! @@ -352,22 +330,15 @@ Calling Parameter note: ALSA-standard -* precise_ptr int array (min = 1, max = 8) - - Enable precise pointer, or disable. - -.. note:: - note: Use only when the application supports this (which is a special case). - * line_outs_monitor int array (min = 1, max = 8) Send playback streams to analog outs by default. .. note:: - note: each playback channel is mixed to the same numbered output + Each playback channel is mixed to the same numbered output channel (routed). This is against the ALSA-convention, where all channels have to be muted on after loading the driver, but was - used before on other cards, so i historically use it again) + used before on other cards, so I historically use it again. @@ -375,5 +346,5 @@ Calling Parameter Enable Analog Out on Channel 63/64 by default. -.. note :: - note: here the analog output is enabled (but not routed). +.. note:: + Here the analog output is enabled (but not routed). diff --git a/MAINTAINERS b/MAINTAINERS index f53047aa8d3c..8883889b25c8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4346,6 +4346,13 @@ F: drivers/net/ieee802154/at86rf230.h F: drivers/net/ieee802154/atusb.c F: drivers/net/ieee802154/atusb.h +AUDIENT EVO MIXER DRIVER +M: Christian Ruppert <arc@gmx.li> +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/usb/mixer_evo.c +F: sound/usb/mixer_evo.h + AUDIT SUBSYSTEM M: Paul Moore <paul@paul-moore.com> M: Eric Paris <eparis@redhat.com> @@ -4419,6 +4426,12 @@ L: linux-media@vger.kernel.org S: Maintained F: drivers/media/pci/hws/ +AWINIC AW88399 HDA SIDE CODEC DRIVER +M: Marco Giunta <marco_giunta@outlook.it> +L: linux-sound@vger.kernel.org +S: Maintained +F: sound/hda/codecs/side-codecs/aw88399* + AWINIC AW99706 WLED BACKLIGHT DRIVER M: Junjie Cao <junjie.cao@linux.dev> S: Maintained diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 163a3cccf197..41a4756f0ea9 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1764,6 +1764,7 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, {"CLSA0101", }, + {"CLSA0102", }, /* * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c index 3e89fcdd45f7..d535025a3f73 100644 --- a/drivers/platform/x86/serial-multi-instantiate.c +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -426,6 +426,7 @@ static const struct acpi_device_id smi_acpi_ids[] = { /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, + { "CLSA0102", (unsigned long)&cs35l41_hda }, { } }; MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); diff --git a/include/sound/control.h b/include/sound/control.h index 909db0d0485d..8e24b668d796 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -244,7 +244,7 @@ int _snd_ctl_add_follower(struct snd_kcontrol *master, * @follower: follower element to add * * Add a virtual follower control to the given master element created via - * snd_ctl_create_virtual_master() beforehand. + * snd_ctl_make_virtual_master() beforehand. * * All followers must be the same type (returning the same information * via info callback). The function doesn't check it, so it's your @@ -289,7 +289,7 @@ snd_ctl_add_follower_uncached(struct snd_kcontrol *master, return _snd_ctl_add_follower(master, follower, SND_CTL_FOLLOWER_NEED_UPDATE); } -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kctl, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kctl, void (*hook)(void *private_data, int), void *private_data); void snd_ctl_sync_vmaster(struct snd_kcontrol *kctl, bool hook_only); diff --git a/include/sound/core.h b/include/sound/core.h index 15be947a730e..d6584967fd49 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -84,6 +84,7 @@ struct snd_device { struct snd_refcount { atomic_t count; wait_queue_head_t waiter; + spinlock_t lock; }; void snd_refcount_init(struct snd_refcount *ref); @@ -300,7 +301,8 @@ int snd_card_locked(int card); #define SND_MIXER_OSS_NOTIFY_REGISTER 0 #define SND_MIXER_OSS_NOTIFY_DISCONNECT 1 #define SND_MIXER_OSS_NOTIFY_FREE 2 -extern int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int cmd); +void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *card, int cmd)); +void snd_mixer_oss_notify(struct snd_card *card, int cmd); #endif int snd_card_new(struct device *parent, int idx, const char *xid, @@ -314,7 +316,6 @@ void snd_card_disconnect(struct snd_card *card); void snd_card_disconnect_sync(struct snd_card *card); void snd_card_free(struct snd_card *card); void snd_card_free_when_closed(struct snd_card *card); -int snd_card_free_on_error(struct device *dev, int ret); void snd_card_set_id(struct snd_card *card, const char *id); int snd_card_register(struct snd_card *card); int snd_card_info_init(void); diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 4f94565c9d15..6474239a11c1 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -633,8 +633,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define A_ADCCR_RCHANENABLE 0x00000020 #define A_ADCCR_LCHANENABLE 0x00000010 -#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ -#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ +#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate converter output rate */ +#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate converter output rate */ #define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ #define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ #define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ diff --git a/include/sound/emux_legacy.h b/include/sound/emux_legacy.h index 1127e30d33bb..55a7fbfb9bfa 100644 --- a/include/sound/emux_legacy.h +++ b/include/sound/emux_legacy.h @@ -5,7 +5,7 @@ /* * Copyright (c) 1999-2000 Takashi Iwai <tiwai@suse.de> * - * Definitions of OSS compatible headers for Emu8000 device informations + * Definitions of OSS compatible headers for Emu8000 device information */ #include <sound/seq_oss_legacy.h> diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 1d05f991f2ce..c7f29ad64e02 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -28,6 +28,7 @@ struct hda_codec; struct hda_pcm; struct hda_pcm_stream; struct hda_codec_ops; +struct hda_jack_tbl; /* * codec bus @@ -274,7 +275,8 @@ struct hda_codec { struct hda_codec *codec, hda_nid_t nid); /* jack detection */ - struct snd_array jacktbl; + struct hda_jack_tbl *jacktbl, *jacktbl_last; /* linked list head/tail */ + int jacktbl_used; /* number of jacktbl elements */ unsigned long jackpoll_interval; /* In jiffies. Zero means no poll, rely on unsol events */ struct delayed_work jackpoll_work; @@ -519,7 +521,7 @@ void snd_hda_update_power_acct(struct hda_codec *codec); static inline bool hda_codec_need_resume(struct hda_codec *codec) { - return !codec->relaxed_resume && codec->jacktbl.used; + return !codec->relaxed_resume && codec->jacktbl_used; } /* diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index aa994d6e6d35..04fd4794bcee 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -96,7 +96,8 @@ struct hdac_device { bool lazy_cache:1; /* don't wake up for writes */ bool caps_overwriting:1; /* caps overwrite being in process */ bool cache_coef:1; /* cache COEF read/write too */ - unsigned int registered:1; /* codec was registered */ + bool registered; /* codec was registered */ + bool unsol_disabled; /* unsol events blocked; protected by bus->reg_lock */ }; /* device/driver type used for matching */ @@ -123,6 +124,7 @@ int snd_hdac_device_init(struct hdac_device *dev, struct hdac_bus *bus, const char *name, unsigned int addr); void snd_hdac_device_exit(struct hdac_device *dev); int snd_hdac_device_register(struct hdac_device *codec); +void snd_hdac_device_disable_unsol(struct hdac_device *codec); void snd_hdac_device_unregister(struct hdac_device *codec); int snd_hdac_device_set_chip_name(struct hdac_device *codec, const char *name); int snd_hdac_codec_modalias(const struct hdac_device *hdac, char *buf, size_t size); @@ -179,9 +181,9 @@ static inline int snd_hdac_read_parm(struct hdac_device *codec, hda_nid_t nid, #ifdef CONFIG_PM int snd_hdac_power_up(struct hdac_device *codec); -int snd_hdac_power_down(struct hdac_device *codec); +void snd_hdac_power_down(struct hdac_device *codec); int snd_hdac_power_up_pm(struct hdac_device *codec); -int snd_hdac_power_down_pm(struct hdac_device *codec); +void snd_hdac_power_down_pm(struct hdac_device *codec); int snd_hdac_keep_power_up(struct hdac_device *codec); /* call this at entering into suspend/resume callbacks in codec driver */ @@ -207,9 +209,9 @@ static inline bool snd_hdac_is_power_on(struct hdac_device *codec) } #else static inline int snd_hdac_power_up(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down(struct hdac_device *codec) {} static inline int snd_hdac_power_up_pm(struct hdac_device *codec) { return 0; } -static inline int snd_hdac_power_down_pm(struct hdac_device *codec) { return 0; } +static inline void snd_hdac_power_down_pm(struct hdac_device *codec) {} static inline int snd_hdac_keep_power_up(struct hdac_device *codec) { return 0; } static inline void snd_hdac_enter_pm(struct hdac_device *codec) {} static inline void snd_hdac_leave_pm(struct hdac_device *codec) {} diff --git a/include/sound/jack.h b/include/sound/jack.h index 715b95983188..cf19c333e2eb 100644 --- a/include/sound/jack.h +++ b/include/sound/jack.h @@ -62,10 +62,10 @@ struct snd_jack { struct list_head kctl_list; struct snd_card *card; const char *id; + bool registered; #ifdef CONFIG_SND_JACK_INPUT_DEV struct input_dev *input_dev; struct mutex input_dev_lock; - int registered; int type; char name[100]; unsigned int key[6]; /* Keep in sync with definitions above */ diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 02d8689354c4..1cf511f8d880 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -8,6 +8,7 @@ * Abramo Bagnara <abramo@alsa-project.org> */ +#include <sound/core.h> #include <sound/asound.h> #include <sound/memalloc.h> #include <sound/minors.h> @@ -414,6 +415,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */ wait_queue_head_t sleep; /* poll sleep */ wait_queue_head_t tsleep; /* transfer sleep */ + struct snd_refcount refcnt; /* refcount for draining, etc */ struct snd_fasync *fasync; bool stop_operating; /* sync_stop will be called */ struct mutex buffer_mutex; /* protect for buffer changes */ diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h index c96f23c7755b..43f765300031 100644 --- a/include/sound/pcm_oss.h +++ b/include/sound/pcm_oss.h @@ -70,6 +70,7 @@ struct snd_pcm_oss_stream { struct snd_pcm_oss { int reg; unsigned int reg_mask; + bool registered; }; #endif /* __SOUND_PCM_OSS_H */ diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 4154035af414..d812f70725bd 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -86,6 +86,7 @@ struct snd_rawmidi_substream { bool opened; /* open flag */ bool append; /* append flag (merge more streams) */ bool active_sensing; /* send active sensing when close */ + bool disconnected; /* already disconnected */ unsigned int framing; /* whether to frame input data */ unsigned int clock_type; /* clock source to use for input framing */ int use_count; /* use counter (for output) */ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index c11da9656e38..767c96273245 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -99,9 +99,10 @@ enum { SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */ + SNDRV_HWDEP_IFACE_CAIAQ, /* Native Instruments caiaq USB devices */ /* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_CAIAQ }; struct snd_hwdep_info { diff --git a/sound/core/control.c b/sound/core/control.c index 4199342d4ffe..535ceba294ac 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1802,6 +1802,8 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, alloc_size = compute_user_elem_size(private_size, count); guard(rwsem_write)(&card->controls_rwsem); + if (card->shutdown) + return -ENODEV; if (check_user_elem_overflow(card, alloc_size)) return -ENOMEM; diff --git a/sound/core/control_led.c b/sound/core/control_led.c index ec0e6c7ad657..501387287ae6 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -563,12 +563,43 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si return count; } +/* + * snd_ctl_led_disconnect() removes these files with card->controls_rwsem + * held. so don't let the removal wait for a callback that takes it. + */ +static struct kernfs_node *snd_ctl_led_sysfs_enter(struct device *dev, + struct device_attribute *attr) +{ + struct kernfs_node *kn; + + if (!try_module_get(THIS_MODULE)) + return NULL; + kn = sysfs_break_active_protection(&dev->kobj, &attr->attr); + if (!kn) + module_put(THIS_MODULE); + return kn; +} + +static void snd_ctl_led_sysfs_exit(struct kernfs_node *kn) +{ + sysfs_unbreak_active_protection(kn); + module_put(THIS_MODULE); +} + static ssize_t attach_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, true); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, true); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t detach_store(struct device *dev, @@ -576,7 +607,15 @@ static ssize_t detach_store(struct device *dev, const char *buf, size_t count) { struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); - return set_led_id(led_card, buf, count, false); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = set_led_id(led_card, buf, count, false); + snd_ctl_led_sysfs_exit(kn); + return ret; } static ssize_t reset_store(struct device *dev, @@ -594,10 +633,8 @@ static ssize_t reset_store(struct device *dev, return count; } -static ssize_t list_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t snd_ctl_led_list(struct snd_ctl_led_card *led_card, char *buf) { - struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); struct snd_ctl_led_ctl *lctl; size_t l = 0; struct snd_card *card __free(snd_card_unref) = @@ -620,6 +657,21 @@ static ssize_t list_show(struct device *dev, return l; } +static ssize_t list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); + struct kernfs_node *kn; + ssize_t ret; + + kn = snd_ctl_led_sysfs_enter(dev, attr); + if (!kn) + return -ENODEV; + ret = snd_ctl_led_list(led_card, buf); + snd_ctl_led_sysfs_exit(kn); + return ret; +} + static DEVICE_ATTR_WO(attach); static DEVICE_ATTR_WO(detach); static DEVICE_ATTR_WO(reset); diff --git a/sound/core/ctljack.c b/sound/core/ctljack.c index 6b6ab34fbde8..36700fda79f3 100644 --- a/sound/core/ctljack.c +++ b/sound/core/ctljack.c @@ -38,6 +38,7 @@ static int get_available_index(struct snd_card *card, const char *name) sid.iface = SNDRV_CTL_ELEM_IFACE_CARD; strscpy(sid.name, name, sizeof(sid.name)); + guard(rwsem_read)(&card->controls_rwsem); while (snd_ctl_find_id(card, &sid)) { sid.index++; /* reset numid; otherwise snd_ctl_find_id() hits this again */ diff --git a/sound/core/info.c b/sound/core/info.c index 3b8ccda04e1a..c4d57d0809ad 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -528,6 +528,8 @@ int snd_info_card_register(struct snd_card *card) void snd_info_card_id_change(struct snd_card *card) { guard(mutex)(&info_mutex); + if (card->shutdown) + return; if (card->proc_root_link) { proc_remove(card->proc_root_link); card->proc_root_link = NULL; @@ -555,7 +557,6 @@ void snd_info_card_disconnect(struct snd_card *card) if (card->proc_root) snd_info_clear_entries(card->proc_root); card->proc_root_link = NULL; - card->proc_root = NULL; } /* diff --git a/sound/core/init.c b/sound/core/init.c index 1bcb6a2e7550..d518cb838777 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -82,8 +82,24 @@ static int module_slot_match(struct module *module, int idx) } #if IS_ENABLED(CONFIG_SND_MIXER_OSS) -int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); -EXPORT_SYMBOL(snd_mixer_oss_notify_callback); +static DEFINE_MUTEX(mixer_oss_notify_mutex); +static int (*mixer_oss_notify_callback)(struct snd_card *card, int cmd); + +void snd_mixer_oss_notify(struct snd_card *card, int cmd) +{ + guard(mutex)(&mixer_oss_notify_mutex); + if (mixer_oss_notify_callback) + mixer_oss_notify_callback(card, cmd); +} +EXPORT_SYMBOL(snd_mixer_oss_notify); + +void snd_mixer_oss_set_notify(int (*callback)(struct snd_card *, int)) +{ + /* Wait for callbacks, including a final module_put(), to return. */ + guard(mutex)(&mixer_oss_notify_mutex); + mixer_oss_notify_callback = callback; +} +EXPORT_SYMBOL(snd_mixer_oss_set_notify); #endif static int check_empty_slot(struct module *module, int slot) @@ -216,11 +232,13 @@ static void __snd_card_release(struct device *dev, void *data) * is added automatically. In that way, the resource disconnection is assured * at first, then released in the expected order. * - * If an error happens at the probe before snd_card_register() is called and - * there have been other devres resources, you'd need to free the card manually - * 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. + * Caveat: if an error happens at the probe before snd_card_register() is + * called and there have been other devres resources that are referred at the + * card-level destructor call, you'd still need to free the card manually; + * otherwise it may lead to UAF due to devres call orders. Instead of a manual + * snd_card_free() call, you can use the automatic clean-up for the card + * pointer via __free(snd_card_free), too. In this case, don't forget to do + * NULL-clear the card pointer before the successful return! * * Return: zero if successful, or a negative error code */ @@ -250,30 +268,6 @@ int snd_devm_card_new(struct device *parent, int idx, const char *xid, } EXPORT_SYMBOL_GPL(snd_devm_card_new); -/** - * snd_card_free_on_error - a small helper for handling devm probe errors - * @dev: the managed device object - * @ret: the return code from the probe callback - * - * 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) -{ - struct snd_card *card; - - if (!ret) - return 0; - card = devres_find(dev, __snd_card_release, NULL, NULL); - if (card) - snd_card_free(card); - return ret; -} -EXPORT_SYMBOL_GPL(snd_card_free_on_error); - static int snd_card_init(struct snd_card *card, struct device *parent, int idx, const char *xid, struct module *module, size_t extra_size) @@ -356,7 +350,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, err = snd_info_card_create(card); if (err < 0) { dev_err(parent, "unable to create card info\n"); - goto __error_ctl; + goto __error; } #ifdef CONFIG_SND_DEBUG @@ -365,16 +359,16 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #endif #ifdef CONFIG_SND_CTL_DEBUG card->value_buf = kmalloc_obj(*card->value_buf); - if (!card->value_buf) - return -ENOMEM; + if (!card->value_buf) { + err = -ENOMEM; + goto __error; + } #endif return 0; - __error_ctl: - snd_device_free_all(card); __error: - put_device(&card->card_dev); - return err; + snd_card_free(card); + return err; } /** @@ -530,8 +524,7 @@ void snd_card_disconnect(struct snd_card *card) /* at this point, they cannot respond to any calls except release() */ #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); #endif /* notify all devices that we are disconnected */ @@ -557,6 +550,8 @@ void snd_card_disconnect(struct snd_card *card) clear_bit(card->number, snd_cards_lock); } + /* order card->shutdown store against power_ref read */ + smp_mb(); snd_power_sync_ref(card); } EXPORT_SYMBOL(snd_card_disconnect); @@ -587,8 +582,7 @@ static int snd_card_do_free(struct snd_card *card) card->releasing = true; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_FREE); #endif snd_device_free_all(card); kfree(card->components); @@ -932,8 +926,7 @@ int snd_card_register(struct snd_card *card) return err; #if IS_ENABLED(CONFIG_SND_MIXER_OSS) - if (snd_mixer_oss_notify_callback) - snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); #endif return 0; } @@ -1174,12 +1167,14 @@ EXPORT_SYMBOL(snd_card_file_remove); int snd_power_ref_and_wait(struct snd_card *card) { snd_power_ref(card); - if (snd_power_get_state(card) == SNDRV_CTL_POWER_D0) - return 0; - wait_event_cmd(card->power_sleep, - card->shutdown || - snd_power_get_state(card) == SNDRV_CTL_POWER_D0, - snd_power_unref(card), snd_power_ref(card)); + /* order power_ref increment against card->shutdown read */ + smp_mb__after_atomic(); + if (snd_power_get_state(card) != SNDRV_CTL_POWER_D0) { + wait_event_cmd(card->power_sleep, + card->shutdown || + snd_power_get_state(card) == SNDRV_CTL_POWER_D0, + snd_power_unref(card), snd_power_ref(card)); + } if (card->shutdown) { snd_power_unref(card); return -ENODEV; diff --git a/sound/core/jack.c b/sound/core/jack.c index 0f04018181ac..a81a64b3bfe7 100644 --- a/sound/core/jack.c +++ b/sound/core/jack.c @@ -38,6 +38,7 @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { }; #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static void snd_jack_add_debugfs(struct snd_jack *jack); static void snd_jack_remove_debugfs(struct snd_jack *jack); static int snd_jack_dev_disconnect(struct snd_device *device) @@ -68,6 +69,9 @@ static int snd_jack_dev_free(struct snd_device *device) struct snd_card *card = device->card; struct snd_jack_kctl *jack_kctl, *tmp_jack_kctl; + /* Remove debugfs while the controls are still linked. */ + snd_jack_remove_debugfs(jack); + list_for_each_entry_safe(jack_kctl, tmp_jack_kctl, &jack->kctl_list, list) { list_del_init(&jack_kctl->list); snd_ctl_remove(card, jack_kctl->kctl); @@ -85,11 +89,10 @@ static int snd_jack_dev_free(struct snd_device *device) } #ifdef CONFIG_SND_JACK_INPUT_DEV -static int snd_jack_dev_register(struct snd_device *device) +static int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) { - struct snd_jack *jack = device->device_data; - struct snd_card *card = device->card; - int err, i; + int i; snprintf(jack->name, sizeof(jack->name), "%s %s", card->shortname, jack->id); @@ -117,14 +120,32 @@ static int snd_jack_dev_register(struct snd_device *device) input_set_capability(jack->input_dev, EV_KEY, jack->key[i]); } - err = input_register_device(jack->input_dev); - if (err == 0) - jack->registered = 1; - - return err; + return input_register_device(jack->input_dev); +} +#else +static inline int snd_jack_input_dev_register(struct snd_card *card, + struct snd_jack *jack) +{ + return 0; } #endif /* CONFIG_SND_JACK_INPUT_DEV */ +static int snd_jack_dev_register(struct snd_device *device) +{ + struct snd_card *card = device->card; + struct snd_jack *jack = device->device_data; + int err; + + err = snd_jack_input_dev_register(card, jack); + if (err < 0) + return err; + + snd_jack_add_debugfs(jack); + + jack->registered = true; + return 0; +} + #ifdef CONFIG_SND_JACK_INJECTION_DEBUG static void snd_jack_inject_report(struct snd_jack_kctl *jack_kctl, int status) { @@ -346,6 +367,9 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, char *tname; int i; + if (jack_kctl->jack_debugfs_root) + return 0; + /* Don't create injection interface for Phantom jacks */ if (strstr(jack_kctl->kctl->id.name, "Phantom")) return 0; @@ -384,6 +408,14 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ + struct snd_jack_kctl *jack_kctl; + + list_for_each_entry(jack_kctl, &jack->kctl_list, list) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { struct snd_jack_kctl *jack_kctl; @@ -400,6 +432,10 @@ static int snd_jack_debugfs_add_inject_node(struct snd_jack *jack, return 0; } +static void snd_jack_add_debugfs(struct snd_jack *jack) +{ +} + static void snd_jack_remove_debugfs(struct snd_jack *jack) { } @@ -420,7 +456,8 @@ static void snd_jack_kctl_add(struct snd_jack *jack, struct snd_jack_kctl *jack_ { jack_kctl->jack = jack; list_add_tail(&jack_kctl->list, &jack->kctl_list); - snd_jack_debugfs_add_inject_node(jack, jack_kctl); + if (jack->registered) + snd_jack_debugfs_add_inject_node(jack, jack_kctl); } static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const char *name, unsigned int mask) @@ -450,7 +487,7 @@ static struct snd_jack_kctl * snd_jack_kctl_new(struct snd_card *card, const cha return jack_kctl; error: - snd_ctl_free_one(kctl); + snd_ctl_remove(card, kctl); return NULL; } @@ -501,9 +538,7 @@ int snd_jack_new(struct snd_card *card, const char *id, int type, int err; static const struct snd_device_ops ops = { .dev_free = snd_jack_dev_free, -#ifdef CONFIG_SND_JACK_INPUT_DEV .dev_register = snd_jack_dev_register, -#endif /* CONFIG_SND_JACK_INPUT_DEV */ .dev_disconnect = snd_jack_dev_disconnect, }; diff --git a/sound/core/misc.c b/sound/core/misc.c index 066fb9ecdcdc..6fe199e76e83 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -183,11 +183,13 @@ void snd_refcount_init(struct snd_refcount *ref) { atomic_set(&ref->count, 0); init_waitqueue_head(&ref->waiter); + spin_lock_init(&ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_init); void snd_refcount_put(struct snd_refcount *ref) { + guard(spinlock_irqsave)(&ref->lock); if (atomic_dec_and_test(&ref->count)) wake_up(&ref->waiter); } @@ -195,6 +197,7 @@ EXPORT_SYMBOL_GPL(snd_refcount_put); void snd_refcount_sync(struct snd_refcount *ref) { - wait_event(ref->waiter, !atomic_read(&ref->count)); + guard(spinlock_irq)(&ref->lock); + wait_event_lock_irq(ref->waiter, !atomic_read(&ref->count), ref->lock); } EXPORT_SYMBOL_GPL(snd_refcount_sync); diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index c533d767c29a..a51bc68473e6 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -1272,6 +1272,7 @@ static void snd_mixer_oss_build(struct snd_mixer_oss *mixer) }; unsigned int idx; + guard(mutex)(&mixer->reg_mutex); for (idx = 0; idx < ARRAY_SIZE(table); idx++) snd_mixer_oss_build_input(mixer, &table[idx], 0, 0); if (mixer->mask_recsrc) { @@ -1308,13 +1309,22 @@ static int snd_mixer_oss_free1(void *private) static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) { struct snd_mixer_oss *mixer; + int err; if (cmd == SND_MIXER_OSS_NOTIFY_REGISTER) { - int idx, err; + int idx; + + /* Module loading and card registration can both notify us. */ + if (card->mixer_oss) + return 0; mixer = kzalloc_objs(*mixer, 2); if (mixer == NULL) return -ENOMEM; + if (!try_module_get(THIS_MODULE)) { + kfree(mixer); + return -ENODEV; + } mutex_init(&mixer->reg_mutex); err = snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIXER, card, 0, @@ -1324,6 +1334,7 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) "unable to register OSS mixer device %i:%i\n", card->number, 0); kfree(mixer); + module_put(THIS_MODULE); return err; } mixer->oss_dev_alloc = 1; @@ -1357,7 +1368,10 @@ static int snd_mixer_oss_notify_handler(struct snd_card *card, int cmd) if (cmd == SND_MIXER_OSS_NOTIFY_DISCONNECT) return 0; snd_mixer_oss_proc_done(mixer); - return snd_mixer_oss_free1(mixer); + err = snd_mixer_oss_free1(mixer); + if (!err) + module_put(THIS_MODULE); + return err; } return 0; } @@ -1366,12 +1380,12 @@ static int __init alsa_mixer_oss_init(void) { struct snd_card *card; int idx; - - snd_mixer_oss_notify_callback = snd_mixer_oss_notify_handler; + + snd_mixer_oss_set_notify(snd_mixer_oss_notify_handler); for (idx = 0; idx < SNDRV_CARDS; idx++) { card = snd_card_ref(idx); if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_REGISTER); + snd_mixer_oss_notify(card, SND_MIXER_OSS_NOTIFY_REGISTER); snd_card_unref(card); } } @@ -1380,17 +1394,8 @@ static int __init alsa_mixer_oss_init(void) static void __exit alsa_mixer_oss_exit(void) { - struct snd_card *card; - int idx; - - snd_mixer_oss_notify_callback = NULL; - for (idx = 0; idx < SNDRV_CARDS; idx++) { - card = snd_card_ref(idx); - if (card) { - snd_mixer_oss_notify_handler(card, SND_MIXER_OSS_NOTIFY_FREE); - snd_card_unref(card); - } - } + /* Live adapters hold module references and prevent normal unload. */ + snd_mixer_oss_set_notify(NULL); } module_init(alsa_mixer_oss_init) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index bd19328dca9e..e8df27d09b97 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -3151,6 +3151,12 @@ static void register_oss_dsp(struct snd_pcm *pcm, int index) static int snd_pcm_oss_register_minor(struct snd_pcm *pcm) { + if (pcm->oss.registered) + return 0; + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + pcm->oss.registered = true; + pcm->oss.reg = 0; if (dsp_map[pcm->card->number] == (int)pcm->device) { char name[128]; @@ -3205,8 +3211,14 @@ static int snd_pcm_oss_disconnect_minor(struct snd_pcm *pcm) static int snd_pcm_oss_unregister_minor(struct snd_pcm *pcm) { + /* BUILD-state and creation-error cleanup need not have registered us. */ + if (!pcm->oss.registered) + return 0; + snd_pcm_oss_disconnect_minor(pcm); snd_pcm_oss_proc_done(pcm); + pcm->oss.registered = false; + module_put(THIS_MODULE); return 0; } diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 41c2cab7a52c..35ece386d1a2 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -853,8 +853,10 @@ static int snd_pcm_free(struct snd_pcm *pcm) { if (!pcm) return 0; - if (!pcm->internal) - pcm_call_notify(pcm, n_unregister); + if (!pcm->internal) { + scoped_guard(mutex, ®ister_mutex) + pcm_call_notify(pcm, n_unregister); + } if (pcm->private_free) pcm->private_free(pcm); snd_pcm_lib_preallocate_free_for_all(pcm); @@ -957,6 +959,7 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, init_waitqueue_head(&runtime->sleep); init_waitqueue_head(&runtime->tsleep); + snd_refcount_init(&runtime->refcnt); __snd_pcm_set_state(runtime, SNDRV_PCM_STATE_OPEN); mutex_init(&runtime->buffer_mutex); diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index 55ecf87586c4..c54cb07304ad 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -219,8 +219,8 @@ static int snd_pcm_status_user_compat64(struct snd_pcm_substream *substream, .audio_tstamp_data = status.audio_tstamp_data, .audio_tstamp_sec = status.audio_tstamp_sec, .audio_tstamp_nsec = status.audio_tstamp_nsec, - .driver_tstamp_sec = status.audio_tstamp_sec, - .driver_tstamp_nsec = status.audio_tstamp_nsec, + .driver_tstamp_sec = status.driver_tstamp_sec, + .driver_tstamp_nsec = status.driver_tstamp_nsec, .audio_tstamp_accuracy = status.audio_tstamp_accuracy, }; @@ -252,7 +252,7 @@ static int snd_pcm_ioctl_hw_params_compat(struct snd_pcm_substream *substream, return -EFAULT; if (refine) { - err = snd_pcm_hw_refine(substream, data); + err = snd_pcm_hw_refine_with_lock(substream, data); if (err < 0) return err; err = fixup_unreferenced_params(substream, data); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 4d665b4148d7..e8f6a9da4a4c 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2009,8 +2009,8 @@ static int wait_for_avail(struct snd_pcm_substream *substream, case SNDRV_PCM_STATE_DRAINING: if (is_playback) err = -EPIPE; - else - avail = 0; /* indicate draining */ + else + avail = snd_pcm_avail(substream); goto _endloop; case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: @@ -2208,13 +2208,17 @@ static int pcm_sanity_check(struct snd_pcm_substream *substream) return 0; } -static int pcm_accessible_state(struct snd_pcm_runtime *runtime) +static int pcm_accessible_state(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; + switch (runtime->state) { case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: case SNDRV_PCM_STATE_PAUSED: return 0; + case SNDRV_PCM_STATE_DRAINING: + return substream->stream == SNDRV_PCM_STREAM_CAPTURE ? 0 : -EBADFD; case SNDRV_PCM_STATE_XRUN: return -EPIPE; case SNDRV_PCM_STATE_SUSPENDED: @@ -2320,7 +2324,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, nonblock = !!(substream->f_flags & O_NONBLOCK); snd_pcm_stream_lock_irq(substream); - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; @@ -2348,7 +2352,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, if (!avail) { if (!is_playback && runtime->state == SNDRV_PCM_STATE_DRAINING) { - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + snd_pcm_drain_done(substream); goto _end_unlock; } if (nonblock) { @@ -2388,7 +2392,7 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, atomic_dec(&runtime->buffer_accessing); if (err < 0) goto _end_unlock; - err = pcm_accessible_state(runtime); + err = pcm_accessible_state(substream); if (err < 0) goto _end_unlock; appl_ptr += frames; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 6d32c12fb79b..35152fae74ca 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -580,29 +580,6 @@ int snd_pcm_hw_refine(struct snd_pcm_substream *substream, } EXPORT_SYMBOL(snd_pcm_hw_refine); -static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params __user * _params) -{ - int err; - struct snd_pcm_hw_params *params __free(kfree) = - memdup_user(_params, sizeof(*params)); - - if (IS_ERR(params)) - return PTR_ERR(params); - - err = snd_pcm_hw_refine(substream, params); - if (err < 0) - return err; - - err = fixup_unreferenced_params(substream, params); - if (err < 0) - return err; - - if (copy_to_user(_params, params, sizeof(*params))) - return -EFAULT; - return 0; -} - static int period_to_usecs(struct snd_pcm_runtime *runtime) { int usecs; @@ -778,11 +755,48 @@ EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); #define is_oss_stream(substream) false #endif +static void pcm_qos_req_clear(struct snd_pcm_substream *substream) +{ + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); +} + +static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) +{ + if (usecs < 0) { + pcm_qos_req_clear(substream); + return; + } + + if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) + cpu_latency_qos_update_request(&substream->latency_pm_qos_req, + usecs); + else + cpu_latency_qos_add_request(&substream->latency_pm_qos_req, + usecs); +} + +static int hw_params_state_check(struct snd_pcm_substream *substream) +{ + guard(pcm_stream_lock_irq)(substream); + switch (substream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + case SNDRV_PCM_STATE_SETUP: + case SNDRV_PCM_STATE_PREPARED: + if (!is_oss_stream(substream) && + atomic_read(&substream->mmap_count)) + return -EBADFD; + return 0; + default: + return -EBADFD; + } +} + static int snd_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_pcm_runtime *runtime; - int err, usecs; + int err; unsigned int bits; snd_pcm_uframes_t frames; @@ -792,20 +806,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - scoped_guard(pcm_stream_lock_irq, substream) { - switch (runtime->state) { - case SNDRV_PCM_STATE_OPEN: - case SNDRV_PCM_STATE_SETUP: - case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && - atomic_read(&substream->mmap_count)) - err = -EBADFD; - break; - default: - err = -EBADFD; - break; - } - } + err = hw_params_state_check(substream); if (err) goto unlock; @@ -889,12 +890,6 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, snd_pcm_timer_resolution_change(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_SETUP); - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); - usecs = period_to_usecs(runtime); - if (usecs >= 0) - cpu_latency_qos_add_request(&substream->latency_pm_qos_req, - usecs); err = 0; _error: if (err) { @@ -932,6 +927,57 @@ static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream, return err; } +/* + * call snd_pcm_hw_refine() after buffer lock and sanity check; + * this one should be used for ioctls and other external paths instead of + * direct call of snd_pcm_hw_refine() + */ +static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime; + int err; + + if (PCM_RUNTIME_CHECK(substream)) + return -ENXIO; + runtime = substream->runtime; + err = snd_pcm_buffer_access_lock(runtime); + if (err < 0) + return err; + err = hw_params_state_check(substream); + if (err) + goto unlock; + + err = snd_pcm_hw_refine(substream, params); + + unlock: + snd_pcm_buffer_access_unlock(runtime); + return err; +} + +static int snd_pcm_hw_refine_user(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params __user * _params) +{ + int err; + struct snd_pcm_hw_params *params __free(kfree) = + memdup_user(_params, sizeof(*params)); + + if (IS_ERR(params)) + return PTR_ERR(params); + + err = snd_pcm_hw_refine_with_lock(substream, params); + if (err < 0) + return err; + + err = fixup_unreferenced_params(substream, params); + if (err < 0) + return err; + + if (copy_to_user(_params, params, sizeof(*params))) + return -EFAULT; + return 0; +} + static int do_hw_free(struct snd_pcm_substream *substream) { int result = 0; @@ -971,7 +1017,7 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream) goto unlock; result = do_hw_free(substream); snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN); - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); unlock: snd_pcm_buffer_access_unlock(runtime); return result; @@ -1191,8 +1237,8 @@ static int snd_pcm_status_user32(struct snd_pcm_substream *substream, .audio_tstamp_data = status64.audio_tstamp_data, .audio_tstamp_sec = status64.audio_tstamp_sec, .audio_tstamp_nsec = status64.audio_tstamp_nsec, - .driver_tstamp_sec = status64.audio_tstamp_sec, - .driver_tstamp_nsec = status64.audio_tstamp_nsec, + .driver_tstamp_sec = status64.driver_tstamp_sec, + .driver_tstamp_nsec = status64.driver_tstamp_nsec, .audio_tstamp_accuracy = status64.audio_tstamp_accuracy, }; @@ -2005,6 +2051,7 @@ static int snd_pcm_do_prepare(struct snd_pcm_substream *substream, err = substream->ops->prepare(substream); if (err < 0) return err; + pcm_qos_req_update(substream, period_to_usecs(substream->runtime)); return snd_pcm_do_reset(substream, state); } @@ -2055,6 +2102,25 @@ static int snd_pcm_prepare(struct snd_pcm_substream *substream, (snd_pcm_state_t)f_flags); } +static int snd_pcm_update_linked_hw_ptr(struct snd_pcm_substream *substream) +{ + struct snd_pcm_substream *s; + + /* Keep group membership stable while locking each stream separately. */ + guard(rwsem_read)(&snd_pcm_link_rwsem); + snd_pcm_group_for_each_entry(s, substream) { + int err; + + guard(pcm_stream_lock_irq)(s); + if (!snd_pcm_running(s)) + continue; + err = snd_pcm_update_hw_ptr(s); + if (err < 0) + return err; + } + return 0; +} + /* * drain ioctl */ @@ -2164,7 +2230,13 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, /* resume pause */ if (runtime->state == SNDRV_PCM_STATE_PAUSED) snd_pcm_pause(substream, false); + snd_pcm_stream_unlock_irq(substream); + + result = snd_pcm_update_linked_hw_ptr(substream); + if (result < 0) + return result; + snd_pcm_stream_lock_irq(substream); /* pre-start/stop - all running streams are changed to DRAINING state */ result = snd_pcm_action(&snd_pcm_action_drain_init, substream, ACTION_ARG_IGNORE); @@ -2179,9 +2251,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, for (;;) { long tout; struct snd_pcm_runtime *to_check; - unsigned int drain_rate; - snd_pcm_uframes_t drain_bufsz; - bool drain_no_period_wakeup; if (signal_pending(current)) { result = -ERESTARTSYS; @@ -2196,45 +2265,35 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, runtime = s->runtime; if (runtime->state == SNDRV_PCM_STATE_DRAINING) { to_check = runtime; + /* pin the draining substream */ + snd_refcount_get(&to_check->refcnt); + + init_wait_entry(&wait, 0); + prepare_to_wait(&to_check->sleep, &wait, + TASK_INTERRUPTIBLE); + if (to_check->no_period_wakeup) { + tout = MAX_SCHEDULE_TIMEOUT; + } else { + tout = 100; + if (to_check->rate) { + long t = to_check->buffer_size * + 1100 / to_check->rate; + tout = max(t, tout); + } + tout = msecs_to_jiffies(tout); + } break; } } snd_pcm_group_unref(group, substream); if (!to_check) break; /* all drained */ - /* - * Cache the runtime fields needed after unlock. - * A concurrent close() on the linked stream may free - * its runtime via snd_pcm_detach_substream() once we - * release the stream lock below. - */ - drain_no_period_wakeup = to_check->no_period_wakeup; - drain_rate = to_check->rate; - drain_bufsz = to_check->buffer_size; - init_wait_entry(&wait, 0); - prepare_to_wait(&to_check->sleep, &wait, TASK_INTERRUPTIBLE); snd_pcm_stream_unlock_irq(substream); - if (drain_no_period_wakeup) - tout = MAX_SCHEDULE_TIMEOUT; - else { - tout = 100; - if (drain_rate) { - long t = drain_bufsz * 1100 / drain_rate; - tout = max(t, tout); - } - tout = msecs_to_jiffies(tout); - } tout = schedule_timeout(tout); - snd_pcm_stream_lock_irq(substream); - group = snd_pcm_stream_group_ref(substream); - snd_pcm_group_for_each_entry(s, substream) { - if (s->runtime == to_check) { - finish_wait(&to_check->sleep, &wait); - break; - } - } - snd_pcm_group_unref(group, substream); + finish_wait(&to_check->sleep, &wait); + /* unpin the draining substream again now */ + snd_refcount_put(&to_check->refcnt); if (card->shutdown) { result = -ENODEV; @@ -2267,23 +2326,26 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream, static int snd_pcm_drop(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime; - int result = 0; + int result; if (PCM_RUNTIME_CHECK(substream)) return -ENXIO; runtime = substream->runtime; - if (runtime->state == SNDRV_PCM_STATE_OPEN || - runtime->state == SNDRV_PCM_STATE_DISCONNECTED) - return -EBADFD; + scoped_guard(pcm_stream_lock_irq, substream) { + if (runtime->state == SNDRV_PCM_STATE_OPEN || + runtime->state == SNDRV_PCM_STATE_DISCONNECTED) + return -EBADFD; - guard(pcm_stream_lock_irq)(substream); - /* resume pause */ - if (runtime->state == SNDRV_PCM_STATE_PAUSED) - snd_pcm_pause(substream, false); + /* resume pause */ + if (runtime->state == SNDRV_PCM_STATE_PAUSED) + snd_pcm_pause(substream, false); - snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); - /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + result = snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP); + /* runtime->control->appl_ptr = runtime->status->hw_ptr; */ + } + + snd_pcm_sync_stop(substream, true); return result; } @@ -2776,27 +2838,23 @@ static int snd_pcm_hw_constraints_complete(struct snd_pcm_substream *substream) return 0; } -static void pcm_release_private(struct snd_pcm_substream *substream) -{ - if (snd_pcm_stream_linked(substream)) - snd_pcm_unlink(substream); -} - void snd_pcm_release_substream(struct snd_pcm_substream *substream) { substream->ref_count--; if (substream->ref_count > 0) return; + snd_pcm_unlink(substream); + snd_pcm_drop(substream); if (substream->hw_opened) { + snd_refcount_sync(&substream->runtime->refcnt); if (substream->runtime->state != SNDRV_PCM_STATE_OPEN) do_hw_free(substream); substream->ops->close(substream); substream->hw_opened = 0; } - if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) - cpu_latency_qos_remove_request(&substream->latency_pm_qos_req); + pcm_qos_req_clear(substream); if (substream->pcm_release) { substream->pcm_release(substream); substream->pcm_release = NULL; @@ -2872,8 +2930,6 @@ static int snd_pcm_open_file(struct file *file, return -ENOMEM; } pcm_file->substream = substream; - if (substream->ref_count == 1) - substream->pcm_release = pcm_release_private; file->private_data = pcm_file; return 0; @@ -2930,7 +2986,7 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) mutex_lock(&pcm->open_mutex); while (1) { err = snd_pcm_open_file(file, pcm, stream); - if (err >= 0) + if (!err) break; if (err == -EAGAIN) { if (file->f_flags & O_NONBLOCK) { @@ -2999,7 +3055,7 @@ static int do_pcm_hwsync(struct snd_pcm_substream *substream) switch (substream->runtime->state) { case SNDRV_PCM_STATE_DRAINING: if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - return -EBADFD; + return 0; fallthrough; case SNDRV_PCM_STATE_RUNNING: return snd_pcm_update_hw_ptr(substream); @@ -3063,11 +3119,15 @@ static snd_pcm_sframes_t rewind_appl_ptr(struct snd_pcm_substream *substream, static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3076,17 +3136,22 @@ static snd_pcm_sframes_t snd_pcm_rewind(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, snd_pcm_uframes_t frames) { + struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t ret; if (frames == 0) return 0; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { ret = do_pcm_hwsync(substream); if (!ret) @@ -3095,20 +3160,26 @@ static snd_pcm_sframes_t snd_pcm_forward(struct snd_pcm_substream *substream, } if (ret >= 0) snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); + atomic_dec(&runtime->buffer_accessing); return ret; } static int snd_pcm_delay(struct snd_pcm_substream *substream, snd_pcm_sframes_t *delay) { + struct snd_pcm_runtime *runtime = substream->runtime; int err; + /* Keep the DMA buffer alive through the sync below. */ + if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) + return -EBUSY; scoped_guard(pcm_stream_lock_irq, substream) { err = do_pcm_hwsync(substream); if (delay && !err) *delay = snd_pcm_calc_delay(substream); } snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); + atomic_dec(&runtime->buffer_accessing); return err; } @@ -4188,7 +4259,7 @@ static int snd_pcm_hw_refine_old_user(struct snd_pcm_substream *substream, if (IS_ERR(oparams)) return PTR_ERR(oparams); snd_pcm_hw_convert_from_old_params(params, oparams); - err = snd_pcm_hw_refine(substream, params); + err = snd_pcm_hw_refine_with_lock(substream, params); if (err < 0) return err; diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 2617bb5b4faf..630d9fc8e9da 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -36,6 +36,13 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ +/* upper bound for draining the output buffer */ +#define SNDRV_RAWMIDI_DRAIN_TIMEOUT (10 * HZ) +/* interval at which drain progress is re-checked */ +#define SNDRV_RAWMIDI_DRAIN_POLL (HZ / 5) +/* polls without progress before the drain is considered stalled */ +#define SNDRV_RAWMIDI_DRAIN_STALLS 5 + static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -208,14 +215,14 @@ static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) static inline void snd_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); } static void snd_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, int up) { - if (!substream->opened) + if (!substream->opened || substream->disconnected) return; substream->ops->trigger(substream, up); if (!up) @@ -246,31 +253,74 @@ int snd_rawmidi_drop_output(struct snd_rawmidi_substream *substream) } EXPORT_SYMBOL(snd_rawmidi_drop_output); +static bool output_drained(struct snd_rawmidi_runtime *runtime) +{ + return runtime->avail >= runtime->buffer_size; +} + int snd_rawmidi_drain_output(struct snd_rawmidi_substream *substream) { - int err = 0; - long timeout; struct snd_rawmidi_runtime *runtime; + size_t avail, prev_avail; + unsigned int stalls = 0; + unsigned long deadline; + long timeout, wait; + bool done; + int err = 0; scoped_guard(spinlock_irq, &substream->lock) { runtime = substream->runtime; - if (!substream->opened || !runtime || !runtime->buffer) + if (!substream->opened || !runtime || !runtime->buffer || + substream->disconnected) return -EINVAL; snd_rawmidi_buffer_ref(runtime); runtime->drain = 1; + prev_avail = runtime->avail; + } + + /* + * Wait for the device to consume the buffer. Rather than always + * sleeping for the whole timeout, sample the free space and stop + * early once it has not increased for a second. A substream that + * is still making progress is given as long as it needs, within the + * same overall limit as before. + */ + deadline = jiffies + SNDRV_RAWMIDI_DRAIN_TIMEOUT; + for (;;) { + /* signed difference, so this is safe across a jiffies wrap */ + wait = (long)(deadline - jiffies); + if (wait <= 0) { + timeout = 0; + break; + } + wait = min_t(long, SNDRV_RAWMIDI_DRAIN_POLL, wait); + timeout = wait_event_interruptible_timeout(runtime->sleep, + output_drained(runtime), wait); + scoped_guard(spinlock_irq, &substream->lock) { + avail = runtime->avail; + done = output_drained(runtime); + } + if (done || signal_pending(current)) + break; + if (avail == prev_avail) { + if (++stalls >= SNDRV_RAWMIDI_DRAIN_STALLS) { + timeout = 0; + break; + } + } else { + stalls = 0; + prev_avail = avail; + } } - timeout = wait_event_interruptible_timeout(runtime->sleep, - (runtime->avail >= runtime->buffer_size), - 10*HZ); - scoped_guard(spinlock_irq, &substream->lock) { if (signal_pending(current)) err = -ERESTARTSYS; if (runtime->avail < runtime->buffer_size && !timeout) { - rmidi_warn(substream->rmidi, - "rawmidi drain error (avail = %li, buffer_size = %li)\n", - (long)runtime->avail, (long)runtime->buffer_size); + dev_warn_ratelimited(substream->rmidi->dev, + "rawmidi drain error (avail = %li, buffer_size = %li)\n", + (long)runtime->avail, + (long)runtime->buffer_size); err = -EIO; } runtime->drain = 0; @@ -537,7 +587,7 @@ static void close_substream(struct snd_rawmidi *rmidi, if (--substream->use_count) return; - if (cleanup) { + if (cleanup && !substream->disconnected) { if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) snd_rawmidi_input_trigger(substream, 0); else { @@ -557,6 +607,9 @@ static void close_substream(struct snd_rawmidi *rmidi, substream->opened = 0; substream->append = 0; } + /* Drain input work queued after the earlier cancellation. */ + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + cancel_work_sync(&substream->runtime->event_work); substream->ops->close(substream); if (substream->runtime->private_free) substream->runtime->private_free(substream); @@ -1151,7 +1204,7 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, struct snd_rawmidi_runtime *runtime; guard(spinlock_irqsave)(&substream->lock); - if (!substream->opened) + if (!substream->opened || substream->disconnected) return -EBADFD; runtime = substream->runtime; if (!runtime || !runtime->buffer) { @@ -2063,8 +2116,13 @@ static int snd_rawmidi_dev_disconnect(struct snd_device *device) struct snd_rawmidi_substream *s; list_for_each_entry(s, &rmidi->streams[dir].substreams, list) { - if (s->runtime) + scoped_guard(spinlock_irq, &s->lock) + s->disconnected = true; + if (s->runtime) { + s->ops->trigger(s, 0); + cancel_work_sync(&s->runtime->event_work); wake_up(&s->runtime->sleep); + } } } diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index e4f58cb985d4..0d606159103e 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -27,14 +27,17 @@ config SND_SEQ_DUMMY will be called snd-seq-dummy. config SND_SEQUENCER_OSS - tristate "OSS Sequencer API" + tristate "OSS Sequencer API (deprecated)" depends on SND_OSSEMUL select SND_SEQ_MIDI_EVENT help Say Y here to enable OSS sequencer emulation (both /dev/sequencer and /dev/music interfaces). - Many programs still use the OSS API, so say Y. + The OSS sequencer API is almost nowhere used, hence this option will + be dropped soon. + Use user-space wrapper (alsa-oss) or CUSE daemon (oss-seq-emu) + instead of the kernel-side emulation. To compile this driver as a module, choose M here: the module will be called snd-seq-oss. diff --git a/sound/core/seq/oss/seq_oss.c b/sound/core/seq/oss/seq_oss.c index 2835576040ed..64a7e62029cc 100644 --- a/sound/core/seq/oss/seq_oss.c +++ b/sound/core/seq/oss/seq_oss.c @@ -67,6 +67,9 @@ static int __init alsa_seq_oss_init(void) { int rc; + pr_warn("seq-oss: kernel OSS sequencer emulation is deprecated\n"); + pr_warn("seq-oss: Use user-space solution (alsa-oss or oss-seq-emu) instead.\n"); + rc = register_device(); if (rc < 0) goto error; diff --git a/sound/core/seq/oss/seq_oss_device.h b/sound/core/seq/oss/seq_oss_device.h index 935cf3df0b30..571186db4fa5 100644 --- a/sound/core/seq/oss/seq_oss_device.h +++ b/sound/core/seq/oss/seq_oss_device.h @@ -72,6 +72,7 @@ struct seq_oss_devinfo { int cseq; /* sequencer client number */ int port; /* sequencer port number */ int queue; /* sequencer queue number */ + bool initialized; struct snd_seq_addr addr; /* address of this device */ diff --git a/sound/core/seq/oss/seq_oss_event.c b/sound/core/seq/oss/seq_oss_event.c index 122735862044..cf493f6c983b 100644 --- a/sound/core/seq/oss/seq_oss_event.c +++ b/sound/core/seq/oss/seq_oss_event.c @@ -425,6 +425,9 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data struct seq_oss_devinfo *dp = (struct seq_oss_devinfo *)private_data; union evrec *rec; + if (!dp || !smp_load_acquire(&dp->initialized)) + return 0; /* ignored */ + if (ev->type != SNDRV_SEQ_EVENT_ECHO) return snd_seq_oss_midi_input(ev, direct, private_data); @@ -433,8 +436,12 @@ snd_seq_oss_event_input(struct snd_seq_event *ev, int direct, void *private_data rec = (union evrec*)&ev->data; if (rec->s.code == SEQ_SYNCTIMER) { + struct seq_oss_writeq *writeq = READ_ONCE(dp->writeq); + + if (!writeq) + return 0; /* sync echo back */ - snd_seq_oss_writeq_wakeup(dp->writeq, rec->t.time); + snd_seq_oss_writeq_wakeup(writeq, rec->t.time); } else { /* echo back event */ diff --git a/sound/core/seq/oss/seq_oss_init.c b/sound/core/seq/oss/seq_oss_init.c index 1aece46c8b06..6474a76a3cce 100644 --- a/sound/core/seq/oss/seq_oss_init.c +++ b/sound/core/seq/oss/seq_oss_init.c @@ -258,6 +258,7 @@ snd_seq_oss_open(struct file *file, int level) client_table[dp->index] = dp; num_clients++; + smp_store_release(&dp->initialized, true); return 0; @@ -376,7 +377,7 @@ delete_seq_queue(int queue) /* - * free device informations - private_free callback of port + * free device information - private_free callback of port */ static void free_devinfo(void *private) @@ -403,6 +404,7 @@ snd_seq_oss_release(struct seq_oss_devinfo *dp) client_table[dp->index] = NULL; num_clients--; + smp_store_release(&dp->initialized, false); snd_seq_oss_reset(dp); diff --git a/sound/core/seq/oss/seq_oss_midi.c b/sound/core/seq/oss/seq_oss_midi.c index 70f94df65144..b456bd37d87b 100644 --- a/sound/core/seq/oss/seq_oss_midi.c +++ b/sound/core/seq/oss/seq_oss_midi.c @@ -211,6 +211,11 @@ snd_seq_oss_midi_check_exit_port(int client, int port) mdev = find_slot(client, port); if (mdev) { scoped_guard(spinlock_irqsave, ®ister_lock) { + /* Only one callback may release the device. */ + if (midi_devs[mdev->seq_device] != mdev) { + snd_use_lock_free(&mdev->use_lock); + return 0; + } midi_devs[mdev->seq_device] = NULL; } snd_use_lock_free(&mdev->use_lock); diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 239809ce48d7..0711a9e23b5f 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -95,23 +95,18 @@ static inline int snd_seq_write_pool_allocated(struct snd_seq_client *client) return snd_seq_total_cells(client->pool) > 0; } -/* return pointer to client structure for specified id; call under RCU read-lock */ -static struct snd_seq_client *__clientptr(int clientid) +/* return pointer to client structure for specified id; + * the caller must guarantee the client's lifetime by itself, as neither RCU + * nor a use_lock reference is taken here. + */ +static struct snd_seq_client *clientptr(int clientid) { if (clientid < 0 || clientid >= SNDRV_SEQ_MAX_CLIENTS) { pr_debug("ALSA: seq: oops. Trying to get pointer to client %d\n", clientid); return NULL; } - return rcu_dereference_check(clienttab[clientid], - lockdep_is_held(&clients_lock)); -} - -/* return pointer to client structure for specified id */ -static struct snd_seq_client *clientptr(int clientid) -{ - guard(rcu)(); - return __clientptr(clientid); + return rcu_dereference_protected(clienttab[clientid], true); } static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) @@ -124,7 +119,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) return NULL; } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); if (clienttablock[clientid]) @@ -159,7 +154,7 @@ static struct snd_seq_client *client_use_ptr(int clientid, bool load_module) } } scoped_guard(rcu) { - client = __clientptr(clientid); + client = rcu_dereference(clienttab[clientid]); if (client) return snd_seq_client_ref(client); } @@ -485,11 +480,11 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, if (err < 0) { if (cell) snd_seq_fifo_cell_putback(fifo, cell); - if (err == -EAGAIN && result > 0) - err = 0; } - return (err < 0) ? err : result; + if (result > 0) + return result; + return err < 0 ? err : 0; } diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 80110501da6f..57aaa39afb9d 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -25,7 +25,9 @@ struct snd_seq_port_info32 { u32 kernel; /* reserved for kernel use (must be NULL) */ u32 flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned int cmd, @@ -40,7 +42,9 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (copy_from_user(data, data32, sizeof(*data32)) || get_user(data->flags, &data32->flags) || - get_user(data->time_queue, &data32->time_queue)) + get_user(data->time_queue, &data32->time_queue) || + get_user(data->direction, &data32->direction) || + get_user(data->ump_group, &data32->ump_group)) return -EFAULT; data->kernel = NULL; @@ -50,9 +54,12 @@ static int snd_seq_call_port_info_ioctl(struct snd_seq_client *client, unsigned if (err < 0) return err; - if (copy_to_user(data32, data, sizeof(*data32)) || + if (copy_to_user(data32, data, + offsetof(struct snd_seq_port_info32, kernel)) || put_user(data->flags, &data32->flags) || - put_user(data->time_queue, &data32->time_queue)) + put_user(data->time_queue, &data32->time_queue) || + put_user(data->direction, &data32->direction) || + put_user(data->ump_group, &data32->ump_group)) return -EFAULT; return err; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index eb67eb0eeb14..b582d034437f 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -248,17 +248,9 @@ static void clear_subscriber_list(struct snd_seq_client *client, } /* delete port data */ -static int port_delete(struct snd_seq_client *client, - struct snd_seq_client_port *port) +static void port_free(struct snd_seq_client *client, + struct snd_seq_client_port *port) { - /* set closing flag and wait for all port access are gone */ - port->closing = 1; - /* the port has already been unlinked from the client's port list; - * wait for a grace period so that RCU readers still traversing the - * list can no longer take a new use_lock reference, then drain the - * outstanding references before freeing - */ - synchronize_rcu(); snd_use_lock_sync(&port->use_lock); /* clear subscribers info */ @@ -272,6 +264,20 @@ static int port_delete(struct snd_seq_client *client, snd_BUG_ON(port->c_dest.count != 0); kfree(port); +} + +static int port_delete(struct snd_seq_client *client, + struct snd_seq_client_port *port) +{ + /* set closing flag and wait for all port access are gone */ + port->closing = 1; + /* the port has already been unlinked from the client's port list; + * wait for a grace period so that RCU readers still traversing the + * list can no longer take a new use_lock reference, then drain the + * outstanding references before freeing + */ + synchronize_rcu(); + port_free(client, port); return 0; } @@ -302,18 +308,25 @@ int snd_seq_delete_port(struct snd_seq_client *client, int port) int snd_seq_delete_all_ports(struct snd_seq_client *client) { struct snd_seq_client_port *port, *tmp; + LIST_HEAD(deleted); - /* unlink and delete each port; port_delete() waits for an RCU grace - * period before draining the port, so concurrent lockless readers can - * no longer take a new use_lock reference on it + /* unlink all ports first and wait for a single RCU grace period + * before draining and freeing them, so concurrent lockless readers + * can no longer take a new use_lock reference on any of them */ guard(mutex)(&client->ports_mutex); list_for_each_entry_safe(port, tmp, &client->ports_list_head, list) { list_del_rcu(&port->list); client->num_ports--; + port->closing = 1; + list_add_tail(&port->delete_list, &deleted); snd_seq_system_client_ev_port_exit(port->addr.client, port->addr.port); - port_delete(client, port); } + if (list_empty(&deleted)) + return 0; + synchronize_rcu(); + list_for_each_entry_safe(port, tmp, &deleted, delete_list) + port_free(client, port); return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 12ad86bf1489..ef1787d6c2dd 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -49,6 +49,7 @@ struct snd_seq_client_port { struct module *owner; /* owner of this port */ char name[64]; /* port name */ struct list_head list; /* port list */ + struct list_head delete_list; /* used by snd_seq_delete_all_ports() */ snd_use_lock_t use_lock; /* subscribers */ diff --git a/sound/core/timer.c b/sound/core/timer.c index 679b26435670..905176bf6948 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -2173,9 +2173,11 @@ static long snd_utimer_ioctl(struct file *file, unsigned int ioctl, unsigned lon } static const struct file_operations snd_utimer_fops = { + .owner = THIS_MODULE, .llseek = noop_llseek, .release = snd_utimer_release, .unlocked_ioctl = snd_utimer_ioctl, + .compat_ioctl = snd_utimer_ioctl, }; static int snd_utimer_start(struct snd_timer *t) diff --git a/sound/core/timer_compat.c b/sound/core/timer_compat.c index 25ee81c1668b..5bab00466de7 100644 --- a/sound/core/timer_compat.c +++ b/sound/core/timer_compat.c @@ -99,6 +99,7 @@ static long __snd_timer_user_ioctl_compat(struct file *file, unsigned int cmd, case SNDRV_TIMER_IOCTL_PAUSE: case SNDRV_TIMER_IOCTL_PAUSE_OLD: case SNDRV_TIMER_IOCTL_NEXT_DEVICE: + case SNDRV_TIMER_IOCTL_CREATE: return __snd_timer_user_ioctl(file, cmd, (unsigned long)argp, true); case SNDRV_TIMER_IOCTL_GPARAMS32: return snd_timer_user_gparams_compat(file, argp); diff --git a/sound/core/ump.c b/sound/core/ump.c index 3d1a2ed3b476..f6482a9bfe61 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -66,7 +66,7 @@ static void safe_copy_string(void *dst, size_t max_dst_size, if (!max_dst_size--) return; - for (s = src; max_dst_size && *s && max_src_size--; s++) { + for (s = src; max_dst_size && max_src_size-- && *s; s++) { if (!isascii(*s) || !isprint(*s)) continue; *d++ = *s; diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 85cc67de6330..0a7972d91ec5 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -323,7 +323,7 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, cc->cc_data_lsb); reset_rpn(cc); - return 1; + return 8; } /* convert to a MIDI 1.0 Channel Voice message */ diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index cd3dcaaf9f72..f43144ba2797 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -287,6 +287,8 @@ EXPORT_SYMBOL(_snd_ctl_add_follower); * * Adds the multiple follower kcontrols with the given names. * Returns 0 for success or a negative error code. + * + * Note that this function takes card->controls_rwsem lock internally. */ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, const char * const *list) @@ -294,6 +296,7 @@ int snd_ctl_add_followers(struct snd_card *card, struct snd_kcontrol *master, struct snd_kcontrol *follower; int err; + guard(rwsem_write)(&card->controls_rwsem); for (; *list; list++) { follower = snd_ctl_find_id_mixer(card, *list); if (follower) { @@ -463,6 +466,7 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); /** * snd_ctl_add_vmaster_hook - Add a hook to a vmaster control + * @card: the card instance * @kcontrol: vmaster kctl element * @hook: the hook function * @private_data: the private_data pointer to be saved @@ -470,13 +474,17 @@ EXPORT_SYMBOL(snd_ctl_make_virtual_master); * Adds the given hook to the vmaster control element so that it's called * at each time when the value is changed. * + * Takes the card's controls_rwsem lock internally. + * * Return: Zero. */ -int snd_ctl_add_vmaster_hook(struct snd_kcontrol *kcontrol, +int snd_ctl_add_vmaster_hook(struct snd_card *card, struct snd_kcontrol *kcontrol, void (*hook)(void *private_data, int), void *private_data) { struct link_master *master = snd_kcontrol_chip(kcontrol); + + guard(rwsem_write)(&card->controls_rwsem); master->hook = hook; master->hook_private_data = private_data; return 0; diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c index 81cb1f59f703..0aaa64b189da 100644 --- a/sound/drivers/aloop.c +++ b/sound/drivers/aloop.c @@ -1073,6 +1073,18 @@ static unsigned int get_cable_index(struct snd_pcm_substream *substream) return !substream->stream; } +/* + * In notify mode the playback side may change format, rate, channels and, + * with a sound timer, the period size. A running capture that no longer + * matches is stopped by loopback_check_format(). + * call in loopback->cable_lock + */ +static bool loopback_playback_unconstrained(struct loopback_pcm *dpcm) +{ + return dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK && + get_notify(dpcm); +} + static int rule_format(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { @@ -1082,6 +1094,8 @@ static int rule_format(struct snd_pcm_hw_params *params, snd_mask_none(&m); scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; m.bits[0] = (u_int32_t)cable->hw.formats; m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); } @@ -1096,6 +1110,8 @@ static int rule_rate(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.rate_min; t.max = cable->hw.rate_max; } @@ -1112,6 +1128,8 @@ static int rule_channels(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.channels_min; t.max = cable->hw.channels_max; } @@ -1128,6 +1146,8 @@ static int rule_period_bytes(struct snd_pcm_hw_params *params, struct snd_interval t; scoped_guard(mutex, &dpcm->loopback->cable_lock) { + if (loopback_playback_unconstrained(dpcm)) + return 0; t.min = cable->hw.period_bytes_min; t.max = cable->hw.period_bytes_max; } @@ -1355,7 +1375,7 @@ static const struct loopback_ops loopback_snd_timer_ops = { .dpcm_info = loopback_snd_timer_dpcm_info, }; -static int loopback_open(struct snd_pcm_substream *substream) +static int __loopback_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct loopback *loopback = substream->private_data; @@ -1364,7 +1384,6 @@ static int loopback_open(struct snd_pcm_substream *substream) int err = 0; int dev = get_cable_index(substream); - guard(mutex)(&loopback->cable_lock); dpcm = kzalloc_obj(*dpcm); if (!dpcm) return -ENOMEM; @@ -1459,6 +1478,14 @@ static int loopback_open(struct snd_pcm_substream *substream) return err; } +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + + guard(mutex)(&loopback->cable_lock); + return __loopback_open(substream); +} + static int loopback_close(struct snd_pcm_substream *substream) { struct loopback *loopback = substream->private_data; diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c index 3f6bfee29986..6b0a06e5c876 100644 --- a/sound/drivers/dummy.c +++ b/sound/drivers/dummy.c @@ -1063,11 +1063,13 @@ static int snd_dummy_probe(struct platform_device *devptr) break; } } + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) { - if (pcm_substreams[dev] < 1) - pcm_substreams[dev] = 1; - if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) - pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); if (err < 0) return err; diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c index 6de03d72a908..fcf2b686f9f5 100644 --- a/sound/drivers/mpu401/mpu401_uart.c +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -155,20 +155,20 @@ static void snd_mpu401_uart_timer(struct timer_list *t) scoped_guard(spinlock_irqsave, &mpu->timer_lock) { /*mpu->mode |= MPU401_MODE_TIMER;*/ - mod_timer(&mpu->timer, 1 + jiffies); + if (mpu->timer_invoked) + mod_timer(&mpu->timer, 1 + jiffies); } if (mpu->rmidi) _snd_mpu401_uart_interrupt(mpu); } /* - * initialize the timer callback if not programmed yet + * arm the timer if it is not already active */ static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input) { guard(spinlock_irqsave)(&mpu->timer_lock); if (mpu->timer_invoked == 0) { - timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mod_timer(&mpu->timer, 1 + jiffies); } mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER : @@ -474,6 +474,7 @@ static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi) struct snd_mpu401 *mpu = rmidi->private_data; if (mpu->irq >= 0) free_irq(mpu->irq, (void *) mpu); + timer_shutdown_sync(&mpu->timer); release_and_free_resource(mpu->res); kfree(mpu); } @@ -528,6 +529,7 @@ int snd_mpu401_uart_new(struct snd_card *card, int device, spin_lock_init(&mpu->input_lock); spin_lock_init(&mpu->output_lock); spin_lock_init(&mpu->timer_lock); + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); mpu->hardware = hardware; mpu->irq = -1; mpu->rmidi = rmidi; diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c index 5d5281e4deb7..393f132a438b 100644 --- a/sound/drivers/pcmtest.c +++ b/sound/drivers/pcmtest.c @@ -280,39 +280,35 @@ static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_ru fill_block_pattern_n(v_iter, runtime); } +static void fill_random_block(char *buf, size_t buf_size, size_t pos, + size_t count) +{ + while (count) { + size_t chunk = min(count, buf_size - pos); + + get_random_bytes(buf + pos, chunk); + count -= chunk; + pos = 0; + } +} + static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { unsigned int channels = runtime->channels; - // Remaining space in all channel buffers - size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + size_t pos = v_iter->buf_pos / channels; unsigned int i; - for (i = 0; i < channels; i++) { - if (v_iter->b_rw <= bytes_remain) { - //b_rw - count of bytes must be written for all channels at each timer tick - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - v_iter->b_rw / channels); - } else { - // Write to the end of buffer and start from the beginning of it - get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), - bytes_remain / channels); - get_random_bytes(runtime->dma_area + v_iter->chan_block * i, - (v_iter->b_rw - bytes_remain) / channels); - } - } + for (i = 0; i < channels; i++) + fill_random_block(runtime->dma_area + v_iter->chan_block * i, + v_iter->chan_block, pos, + v_iter->b_rw / channels); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) { - size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; - - if (v_iter->b_rw <= in_cur_block) { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); - } else { - get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); - get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); - } + fill_random_block(runtime->dma_area, runtime->dma_bytes, + v_iter->buf_pos, v_iter->b_rw); inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); } @@ -349,6 +345,7 @@ static void timer_timeout(struct timer_list *data) v_iter = timer_container_of(v_iter, data, timer_instance); substream = v_iter->substream; + guard(pcm_stream_lock_irqsave)(substream); if (v_iter->suspend) return; @@ -362,7 +359,7 @@ static void timer_timeout(struct timer_list *data) v_iter->period_pos += v_iter->b_rw; if (v_iter->period_pos >= v_iter->period_bytes) { v_iter->period_pos %= v_iter->period_bytes; - snd_pcm_period_elapsed(substream); + snd_pcm_period_elapsed_under_stream_lock(substream); } if (!v_iter->suspend) @@ -449,24 +446,6 @@ static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substr return bytes_to_frames(substream->runtime, v_iter->buf_pos); } -static int snd_pcmtst_free(struct pcmtst *pcmtst) -{ - if (!pcmtst) - return 0; - kfree(pcmtst); - return 0; -} - -// These callbacks are required, but empty - all freeing occurs in pdev_remove -static int snd_pcmtst_dev_free(struct snd_device *device) -{ - return 0; -} - -static void pcmtst_pdev_release(struct device *dev) -{ -} - static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; @@ -566,40 +545,9 @@ static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) return err; } -static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, - struct pcmtst **r_pcmtst) -{ - struct pcmtst *pcmtst; - int err; - static const struct snd_device_ops ops = { - .dev_free = snd_pcmtst_dev_free, - }; - - pcmtst = kzalloc_obj(*pcmtst); - if (!pcmtst) - return -ENOMEM; - pcmtst->card = card; - pcmtst->pdev = pdev; - - err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); - if (err < 0) - goto _err_free_chip; - - err = snd_pcmtst_new_pcm(pcmtst); - if (err < 0) - goto _err_free_chip; - - *r_pcmtst = pcmtst; - return 0; - -_err_free_chip: - snd_pcmtst_free(pcmtst); - return err; -} - static int pcmtst_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct pcmtst *pcmtst; int err; @@ -607,10 +555,16 @@ static int pcmtst_probe(struct platform_device *pdev) if (err) return err; - err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, + sizeof(*pcmtst), &card); if (err < 0) return err; - err = snd_pcmtst_create(card, pdev, &pcmtst); + + pcmtst = card->private_data; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_pcmtst_new_pcm(pcmtst); if (err < 0) return err; @@ -623,27 +577,19 @@ static int pcmtst_probe(struct platform_device *pdev) return err; platform_set_drvdata(pdev, pcmtst); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void pdev_remove(struct platform_device *pdev) -{ - struct pcmtst *pcmtst = platform_get_drvdata(pdev); - - snd_pcmtst_free(pcmtst); -} +#define SND_PCMTEST_DRIVER "pcmtest" -static struct platform_device pcmtst_pdev = { - .name = "pcmtest", - .dev.release = pcmtst_pdev_release, -}; +static struct platform_device *pcmtst_pdev; static struct platform_driver pcmtst_pdrv = { .probe = pcmtst_probe, - .remove = pdev_remove, .driver = { - .name = "pcmtest", + .name = SND_PCMTEST_DRIVER, }, }; @@ -755,12 +701,14 @@ static int __init mod_init(void) err = init_debug_files(buf_allocated); if (err) goto err_free_patterns; - err = platform_device_register(&pcmtst_pdev); - if (err) + pcmtst_pdev = platform_device_register_simple(SND_PCMTEST_DRIVER, -1, NULL, 0); + if (IS_ERR(pcmtst_pdev)) { + err = PTR_ERR(pcmtst_pdev); goto err_clear_debug; + } err = platform_driver_register(&pcmtst_pdrv); if (err) { - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); goto err_clear_debug; } @@ -779,7 +727,7 @@ static void __exit mod_exit(void) free_pattern_buffers(); platform_driver_unregister(&pcmtst_pdrv); - platform_device_unregister(&pcmtst_pdev); + platform_device_unregister(pcmtst_pdev); } MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing"); diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c index 766206c6ca75..8ba5e35af4a1 100644 --- a/sound/drivers/serial-generic.c +++ b/sound/drivers/serial-generic.c @@ -183,10 +183,7 @@ static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *subst { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode, up); } static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream) @@ -230,10 +227,7 @@ static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *subs { struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; - if (up) - set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); - else - clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + assign_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode, up); if (up) snd_serial_generic_tx_wakeup(drvdata); diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c index 216d1fceb6e7..1c523525fad2 100644 --- a/sound/firewire/bebob/bebob_hwdep.c +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -8,7 +8,7 @@ /* * This codes give three functionality. * - * 1.get firewire node infomation + * 1.get firewire node information * 2.get notification about starting/stopping stream * 3.lock/unlock stream */ diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 0f8fb62f021d..896091c1144f 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -18,6 +18,7 @@ #include <linux/types.h> #include <linux/io.h> #include <linux/pci.h> +#include <linux/dma-mapping.h> #include <asm/io.h> #include <sound/core.h> #include <sound/hda_codec.h> @@ -1060,6 +1061,8 @@ enum dsp_download_state { * CA0132 specific */ +#define AE5_STRIP_MAX_LEDS 100 + struct ca0132_spec { struct hda_gen_spec gen; @@ -1162,6 +1165,13 @@ struct ca0132_spec { * Renames PlayEnhancement and CrystalVoice too. */ bool use_alt_controls; + +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + /* AE-5 WS2812 strip control */ + struct mutex ae5_strip_mutex; + u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS]; + int ae5_strip_cur_num_leds; +#endif }; /* @@ -6846,6 +6856,15 @@ static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) } +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +static int ae5_add_strip_controls(struct hda_codec *codec); +#else +static inline int ae5_add_strip_controls(struct hda_codec *codec) +{ + return 0; +} +#endif + /* * Add headphone gain enumerated control for the AE-5. This switches between * three modes, low, medium, and high. When non-headphone outputs are selected, @@ -7149,6 +7168,10 @@ static int ca0132_build_controls(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_AE5: + err = ae5_add_strip_controls(codec); + if (err < 0) + return err; + fallthrough; case QUIRK_AE7: err = ae5_add_headphone_gain_enum(codec); if (err < 0) @@ -8316,8 +8339,367 @@ static void sbz_setup_defaults(struct hda_codec *codec) } /* - * Setup default parameters for the Sound BlasterX AE-5 DSP. + * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport + */ +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) +#define AE5_STRIP_DMA_SIZE 0x8000 +#define AE5_STRIP_TOTAL_WORDS (AE5_STRIP_DMA_SIZE / sizeof(u32)) +#define AE5_STRIP_PREAMBLE_WORDS 5 +#define AE5_STRIP_WORDS_PER_LED 4 +#define AE5_STRIP_RESET_GAP_WORDS 83 + +#define CA0113_SERIALIZER_BASE(spec, ch) ((spec)->mem_base + 0x42c + (ch) * 0x40) +#define CA0113_SERIALIZER_CLK(spec, ch) ((spec)->mem_base + 0x43c + (ch) * 0x40) +#define CA0113_SERIALIZER_OUT(spec, ch) ((spec)->mem_base + 0x454 + (ch) * 0x40) + +static void ae5_strip_encode_24(u32 u, u32 *words) +{ + /* + * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word). + * Hardware audio serializer transmits MSB first (bits 31 down to 0). + * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word) + * is placed at n = 29 - bit_idx * 4: + * Bit 0 -> 0b100 << n (375ns high, 1042ns low): BIT(n + 2) + * Bit 1 -> 0b111 << n (1083ns high, 333ns low): BIT(n + 2) | BIT(n + 1) | BIT(n) + */ + int i8 = 23; + int i, bit_idx; + + for (i = 0; i < 4; i++) { + u32 out = 0; + + for (bit_idx = 0; bit_idx < 6; bit_idx++) { + int bit = (u >> (i8 - bit_idx)) & 1; + int n = 29 - bit_idx * 4; + + out |= (bit ? (BIT(2) | BIT(1) | BIT(0)) : BIT(2)) << n; + } + words[i] = out; + i8 -= 6; + } +} + +static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct hdac_stream *s, *dsp = NULL, *first = NULL; + + guard(spinlock_irqsave)(&bus->reg_lock); + list_for_each_entry(s, &bus->stream_list, list) { + if (s->direction != SNDRV_PCM_STREAM_PLAYBACK) + continue; + if (s->opened || s->running || s->locked) + continue; + if (!first) + first = s; + /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */ + if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id) + dsp = s; + } + + s = dsp ? dsp : first; + if (s) + s->opened = 1; + + return s; +} + +/* + * Caller must hold spec->ae5_strip_mutex. */ +static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds) +{ + struct ca0132_spec *spec = codec->spec; + struct hdac_bus *bus = &codec->bus->core; + struct snd_dma_buffer dmab; + struct hdac_stream *hstr = NULL; + unsigned int format, stream_tag; + u32 enc_words[4]; + __le32 *dst; + int word_idx = 0; + int frame_words; + int led, w, i; + int retries = 20; /* ~100ms max */ + + if (!spec || !spec->mem_base) + return -ENODEV; + if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + lockdep_assert_held(&spec->ae5_strip_mutex); + + CLASS(snd_hda_power_pm, pm)(codec); + if (pm.err < 0) + return pm.err; + + /* + * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda + * HDAUDIO_STREAM_FORMAT. + */ + format = snd_hdac_stream_format(2, 24, 44100); + + for (;;) { + hstr = ae5_strip_find_stream(codec); + if (!hstr) { + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: no free azx stream after retries\n"); + return -EBUSY; + } + usleep_range(5000, 10000); + continue; + } + + stream_tag = snd_hdac_dsp_prepare(hstr, format, AE5_STRIP_DMA_SIZE, &dmab); + if ((int)stream_tag > 0) + break; + + /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */ + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + if (--retries <= 0) { + codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n", + (int)stream_tag); + return (int)stream_tag; + } + usleep_range(5000, 10000); + } + + /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */ + memset(dmab.area, 0, AE5_STRIP_DMA_SIZE); + dst = (__le32 *)dmab.area; + + /* Words per frame: preamble + (num_leds * words_per_led) + reset gap */ + frame_words = AE5_STRIP_PREAMBLE_WORDS + (num_leds * AE5_STRIP_WORDS_PER_LED) + + AE5_STRIP_RESET_GAP_WORDS; + + while (word_idx + frame_words <= AE5_STRIP_TOTAL_WORDS) { + /* Preamble per Windows CtxHda FUN_00042944 */ + for (w = 0; w < AE5_STRIP_PREAMBLE_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + /* LEDs */ + for (led = 0; led < num_leds; led++) { + ae5_strip_encode_24(grb_colors[led], enc_words); + for (w = 0; w < AE5_STRIP_WORDS_PER_LED; w++) { + *dst++ = cpu_to_le32(enc_words[w]); + word_idx++; + } + } + /* Zero reset gap (> 1.8 ms; WS2812 requires > 50 us low) */ + for (w = 0; w < AE5_STRIP_RESET_GAP_WORDS; w++) { + *dst++ = 0; + word_idx++; + } + } + while (word_idx < AE5_STRIP_TOTAL_WORDS) { + *dst++ = 0; + word_idx++; + } + /* Ensure DMA buffer writes are committed before triggering the HDA stream */ + wmb(); + + /* 2. Configure CA0113 BAR2 hardware registers */ + /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ + writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); + + /* Base serializer initialization (CtxHdb 0x14ba4) */ + writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400); + for (i = 0; i < 4; i++) { + writel(readl(CA0113_SERIALIZER_BASE(spec, i)) & ~1, + CA0113_SERIALIZER_BASE(spec, i)); + writel(0, CA0113_SERIALIZER_CLK(spec, i)); + } + writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408); + writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c); + writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410); + + /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_CLK(spec, i)) | 0x33, + CA0113_SERIALIZER_CLK(spec, i)); + + /* BAR2 + 0x100 format & channel enables: + * - base 0x70f + * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58) + * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4) + * - bits 4..7 = 0xf (stage 1 enables) + * - bits 12..15 = 0xf (stage 2 enables) + * => 0x3000ff8f + */ + writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100); + + /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94) */ + for (i = 0; i < 4; i++) + writel(readl(CA0113_SERIALIZER_OUT(spec, i)) | 1, + CA0113_SERIALIZER_OUT(spec, i)); + + /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */ + writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4), + spec->mem_base + 0x104); + + /* Enable CA0113 MMIO GPIO 0 & 1 */ + ca0113_mmio_gpio_set(codec, 0, true); + ca0113_mmio_gpio_set(codec, 1, true); + + codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n", + num_leds, stream_tag, readl(spec->mem_base + 0x104)); + + /* 3. Trigger stream transmission on HDA link */ + snd_hdac_dsp_trigger(hstr, true); + /* + * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64 + * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete + * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes + * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep. + */ + usleep_range(15000, 20000); + snd_hdac_dsp_trigger(hstr, false); + codec_dbg(codec, "AE5 strip: trigger complete\n"); + + /* Teardown: clear 0x104 pin mux back to 0 */ + writel(0x00000000, spec->mem_base + 0x104); + + snd_hdac_dsp_cleanup(hstr, &dmab); + scoped_guard(spinlock_irq, &bus->reg_lock) + hstr->opened = 0; + + return 0; +} + +static int ae5_strip_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = AE5_STRIP_MAX_LEDS * 3; + return 0; +} + +static int ae5_strip_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u32 grb = spec->ae5_strip_cur_colors[i]; + + ucontrol->value.bytes.data[i * 3 + 0] = (grb >> 8) & 0xff; + ucontrol->value.bytes.data[i * 3 + 1] = (grb >> 16) & 0xff; + ucontrol->value.bytes.data[i * 3 + 2] = grb & 0xff; + } + return 0; +} + +static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + u32 colors[AE5_STRIP_MAX_LEDS]; + int i, num_leds, change, ret; + + guard(mutex)(&spec->ae5_strip_mutex); + for (i = 0; i < AE5_STRIP_MAX_LEDS; i++) { + u8 r = ucontrol->value.bytes.data[i * 3 + 0]; + u8 g = ucontrol->value.bytes.data[i * 3 + 1]; + u8 b = ucontrol->value.bytes.data[i * 3 + 2]; + + colors[i] = ((u32)g << 16) | ((u32)r << 8) | (u32)b; + } + change = memcmp(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)) != 0; + num_leds = spec->ae5_strip_cur_num_leds; + ret = ae5_strip_send_frame(codec, colors, num_leds); + if (ret < 0) + return ret; + if (change) + memcpy(spec->ae5_strip_cur_colors, colors, + sizeof(spec->ae5_strip_cur_colors)); + return change; +} + +static int ae5_strip_num_leds_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = AE5_STRIP_MAX_LEDS; + return 0; +} + +static int ae5_strip_num_leds_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + guard(mutex)(&spec->ae5_strip_mutex); + ucontrol->value.integer.value[0] = spec->ae5_strip_cur_num_leds; + return 0; +} + +static int ae5_strip_num_leds_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 1 || val > AE5_STRIP_MAX_LEDS) + return -EINVAL; + + guard(mutex)(&spec->ae5_strip_mutex); + if (spec->ae5_strip_cur_num_leds == val) + return 0; + ret = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, val); + if (ret < 0) + return ret; + spec->ae5_strip_cur_num_leds = val; + return 1; +} + +static const struct snd_kcontrol_new ae5_strip_ctls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_ctl_info, + .get = ae5_strip_ctl_get, + .put = ae5_strip_ctl_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "AE-5 LED Strip Count", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = ae5_strip_num_leds_info, + .get = ae5_strip_num_leds_get, + .put = ae5_strip_num_leds_put, + }, +}; + +static int ae5_add_strip_controls(struct hda_codec *codec) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(ae5_strip_ctls); i++) { + err = snd_hda_ctl_add(codec, 0, + snd_ctl_new1(&ae5_strip_ctls[i], codec)); + if (err < 0) + return err; + } + return 0; +} +#endif /* CONFIG_SND_HDA_DSP_LOADER */ + static void ae5_setup_defaults(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -8741,6 +9123,10 @@ static void ca0132_init_chip(struct hda_codec *codec) unsigned int on; mutex_init(&spec->chipio_mutex); +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_init(&spec->ae5_strip_mutex); + spec->ae5_strip_cur_num_leds = 10; +#endif /* * The Windows driver always does this upon startup, which seems to @@ -9627,6 +10013,9 @@ static void ca0132_free(struct hda_codec *codec) if (spec->mem_base) pci_iounmap(codec->bus->pci, spec->mem_base); #endif +#if IS_ENABLED(CONFIG_SND_HDA_DSP_LOADER) + mutex_destroy(&spec->ae5_strip_mutex); +#endif kfree(spec->spec_init_verbs); kfree(codec->spec); codec->spec = NULL; diff --git a/sound/hda/codecs/cirrus/cs8409-tables.c b/sound/hda/codecs/cirrus/cs8409-tables.c index b9ec8fb8eab7..d0cef8492c99 100644 --- a/sound/hda/codecs/cirrus/cs8409-tables.c +++ b/sound/hda/codecs/cirrus/cs8409-tables.c @@ -594,7 +594,7 @@ const struct hda_quirk cs8409_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x0BA8, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAA, "Odin", CS8409_ODIN), SND_PCI_QUIRK(0x1028, 0x0BAE, "Odin", CS8409_ODIN), - SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK), + SND_PCI_QUIRK(0x1028, 0x0BB2, "Warlock MLK", CS8409_WARLOCK_MLK_DELAYED_JD), SND_PCI_QUIRK(0x1028, 0x0BB3, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB4, "Warlock MLK", CS8409_WARLOCK_MLK), SND_PCI_QUIRK(0x1028, 0x0BB5, "Warlock N3 15 TGL-U Nuvoton EC", CS8409_WARLOCK), @@ -625,6 +625,7 @@ const struct hda_model_fixup cs8409_models[] = { { .id = CS8409_BULLSEYE, .name = "bullseye" }, { .id = CS8409_WARLOCK, .name = "warlock" }, { .id = CS8409_WARLOCK_MLK, .name = "warlock mlk" }, + { .id = CS8409_WARLOCK_MLK_DELAYED_JD, .name = "warlock mlk delayed jd" }, { .id = CS8409_WARLOCK_MLK_DUAL_MIC, .name = "warlock mlk dual mic" }, { .id = CS8409_CYBORG, .name = "cyborg" }, { .id = CS8409_DOLPHIN, .name = "dolphin" }, @@ -652,6 +653,12 @@ const struct hda_fixup cs8409_fixups[] = { .chained = true, .chain_id = CS8409_FIXUPS, }, + [CS8409_WARLOCK_MLK_DELAYED_JD] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, [CS8409_WARLOCK_MLK_DUAL_MIC] = { .type = HDA_FIXUP_PINS, .v.pins = cs8409_cs42l42_pincfgs, diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index c43ff3ef75b6..12ee71d7a35c 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -56,6 +56,9 @@ static int cs8409_parse_auto_config(struct hda_codec *codec) } static void cs8409_disable_i2c_clock_worker(struct work_struct *work); +static void cs8409_jack_detect_worker(struct work_struct *work); +static void cs42l42_run_jack_detect(struct sub_codec *cs42l42); +static int cs8409_i2c_read(struct sub_codec *scodec, unsigned int addr); static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) { @@ -69,6 +72,7 @@ static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) codec->power_save_node = 1; mutex_init(&spec->i2c_mux); INIT_DELAYED_WORK(&spec->i2c_clk_work, cs8409_disable_i2c_clock_worker); + INIT_DELAYED_WORK(&spec->jack_detect_work, cs8409_jack_detect_worker); snd_hda_gen_spec_init(&spec->gen); return spec; @@ -116,6 +120,28 @@ static void cs8409_disable_i2c_clock_worker(struct work_struct *work) } /* + * cs8409_jack_detect_worker - Perform initial jack detection once hardware is settled + * + * Defer initial jack detection. On ChromeOS and possibly other platforms this + * delay is required for jack detection to work as expected. + */ +static void cs8409_jack_detect_worker(struct work_struct *work) +{ + struct cs8409_spec *spec = container_of(work, struct cs8409_spec, jack_detect_work.work); + struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; + + if (spec->init_done && spec->build_ctrl_done && !cs42l42->hp_jack_in) { + int reg_ts_status = cs8409_i2c_read(cs42l42, CS42L42_TSRS_PLUG_STATUS); + + /* error case */ + if (reg_ts_status < 0) + return; + + cs42l42_run_jack_detect(cs42l42); + } +} + +/* * cs8409_enable_i2c_clock - Enable I2C clocks * @codec: the codec instance * Enable I2C clocks. @@ -951,6 +977,8 @@ static void cs8409_remove(struct hda_codec *codec) { struct cs8409_spec *spec = codec->spec; + /* Disable jack detect work */ + disable_delayed_work_sync(&spec->jack_detect_work); /* Cancel i2c clock disable timer, and disable clock if left enabled */ cancel_delayed_work_sync(&spec->i2c_clk_work); cs8409_disable_i2c_clock(codec); @@ -1020,6 +1048,9 @@ static int cs8409_cs42l42_suspend(struct hda_codec *codec) cs8409_enable_ur(codec, 0); + /* Cancel jack detect work first as it accesses CS42L42 over I2C */ + cancel_delayed_work_sync(&spec->jack_detect_work); + for (i = 0; i < spec->num_scodecs; i++) cs42l42_suspend(spec->scodecs[i]); @@ -1153,6 +1184,9 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; spec->speaker_pdn_gpio = CS8409_CYBORG_SPEAKER_PDN; break; + case CS8409_WARLOCK_MLK_DELAYED_JD: + spec->delay_jack_detect = 1; + fallthrough; case CS8409_WARLOCK_MLK: case CS8409_WARLOCK_MLK_DUAL_MIC: spec->scodecs[CS8409_CODEC0]->full_scale_vol = CS42L42_FULL_SCALE_VOL_0DB; @@ -1197,8 +1231,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, cs8409_cs42l42_hw_init(codec); spec->init_done = 1; if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; case HDA_FIXUP_ACT_BUILD: spec->build_ctrl_done = 1; @@ -1208,8 +1247,13 @@ void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, * Run immediately after init. */ if (spec->init_done && spec->build_ctrl_done - && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) - cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + && !spec->scodecs[CS8409_CODEC0]->hp_jack_in) { + if (spec->delay_jack_detect) + schedule_delayed_work(&spec->jack_detect_work, + msecs_to_jiffies(CS8409_JACK_DETECT_DELAY_MS)); + else + cs42l42_run_jack_detect(spec->scodecs[CS8409_CODEC0]); + } break; default: break; diff --git a/sound/hda/codecs/cirrus/cs8409.h b/sound/hda/codecs/cirrus/cs8409.h index be1714a84fff..dacc1083b9d4 100644 --- a/sound/hda/codecs/cirrus/cs8409.h +++ b/sound/hda/codecs/cirrus/cs8409.h @@ -247,6 +247,7 @@ enum cs8409_coefficient_index_registers { #define CS8409_CS42L42_AMIC_PIN_NID CS8409_PIN_ASP1_RECEIVER_A #define CS8409_CS42L42_DMIC_PIN_NID CS8409_PIN_DMIC1_IN #define CS8409_CS42L42_DMIC_ADC_PIN_NID CS8409_PIN_DMIC1 +#define CS8409_JACK_DETECT_DELAY_MS 100 /* Dolphin */ @@ -266,6 +267,7 @@ enum { CS8409_BULLSEYE, CS8409_WARLOCK, CS8409_WARLOCK_MLK, + CS8409_WARLOCK_MLK_DELAYED_JD, CS8409_WARLOCK_MLK_DUAL_MIC, CS8409_CYBORG, CS8409_FIXUPS, @@ -339,12 +341,14 @@ struct cs8409_spec { unsigned int i2c_clck_enabled; unsigned int dev_addr; struct delayed_work i2c_clk_work; + struct delayed_work jack_detect_work; unsigned int playback_started:1; unsigned int capture_started:1; unsigned int init_done:1; unsigned int build_ctrl_done:1; unsigned int speaker_muted:1; + unsigned int delay_jack_detect:1; /* verb exec op override */ int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index 9bfdbf5c9032..a141c94eee13 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -302,6 +302,7 @@ enum { CXT_FIXUP_HP_SPECTRE, CXT_FIXUP_HP_GATE_MIC, CXT_FIXUP_MUTE_LED_GPIO, + CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED, CXT_FIXUP_HP_ELITEONE_OUT_DIS, CXT_FIXUP_HP_ZBOOK_MUTE_LED, CXT_FIXUP_HEADSET_MIC, @@ -312,6 +313,7 @@ enum { CXT_FIXUP_HP_A_U, CXT_FIXUP_ACER_SWIFT_HP, CXT_FIXUP_HUAWEI_MATEBOOK_HP, + CXT_FIXUP_MATEBOOK14_SN6140, }; /* for hda_fixup_thinkpad_acpi() */ @@ -808,6 +810,13 @@ static void cxt_fixup_hp_zbook_mute_led(struct hda_codec *codec, cxt_setup_mute_led(codec, 0x10, 0x20); } +static void cxt_fixup_acer_aspire_go_mute_led(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action == HDA_FIXUP_ACT_PRE_PROBE) + cxt_setup_mute_led(codec, 0, 0x10); +} + static void cxt_fixup_hp_a_u(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -817,6 +826,67 @@ static void cxt_fixup_hp_a_u(struct hda_codec *codec, cxt_setup_gpio_unmute(codec, 0x2); } +/* Huawei MateBook 14 (SN6140): the speaker output follows the headphone + * pin's connection select in hardware. Both pins must be routed to the + * shared DAC 0x10, which carries the playback stream; the generic + * parser mutes that DAC when no headphone is plugged, silencing the + * speaker. Suppress the auto-mute, bind the speaker pin to the shared + * DAC, remove its mute switch and keep it unmuted; the auto-mute hook + * re-applies the routing after every output update. + */ +static void cxt_mb14_route_unmute(struct hda_codec *codec) +{ + /* route both pins to the shared DAC 0x10, enable the speaker + * EAPD and unmute the DAC at a moderate gain; the vmaster + * volume takes over once userspace starts + */ + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL, 0); + snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_EAPD_BTLENABLE, 0x02); + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK | HDA_AMP_MUTE, 0x40); +} + +static void cxt_mb14_automute(struct hda_codec *codec) +{ + snd_hda_gen_update_outputs(codec); + cxt_mb14_route_unmute(codec); +} + +static void cxt_fixup_matebook14_sn6140(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t dac_0x10[] = { 0x10 }; + + spec->gen.suppress_auto_mute = 1; + spec->gen.automute_hook = cxt_mb14_automute; + /* Bind the speaker pin to the shared DAC 0x10. + */ + snd_hda_override_conn_list(codec, 0x17, 1, dac_0x10); + return; + } + if (action == HDA_FIXUP_ACT_PROBE) { + unsigned int caps; + + /* Remove the mute switch on the shared DAC so that + * userspace (WirePlumber) cannot mute it when the + * headphone route becomes unavailable (e.g. after + * resume). Must be done here: the codec regmap is not + * available at pre-probe time. + */ + caps = query_amp_caps(codec, 0x10, HDA_OUTPUT); + snd_hda_override_amp_caps(codec, 0x10, HDA_OUTPUT, + caps & ~AC_AMPCAP_MUTE); + return; + } + if (action != HDA_FIXUP_ACT_INIT) + return; + cxt_mb14_route_unmute(codec); +} + /* ThinkPad X200 & co with cxt5051 */ static const struct hda_pintbl cxt_pincfg_lenovo_x200[] = { { 0x16, 0x042140ff }, /* HP (seq# overridden) */ @@ -1007,6 +1077,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_mute_led_gpio, }, + [CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_acer_aspire_go_mute_led, + }, [CXT_FIXUP_HP_ELITEONE_OUT_DIS] = { .type = HDA_FIXUP_FUNC, .v.func = cxt_fixup_update_pinctl, @@ -1062,6 +1136,10 @@ static const struct hda_fixup cxt_fixups[] = { { } }, }, + [CXT_FIXUP_MATEBOOK14_SN6140] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt_fixup_matebook14_sn6140, + }, }; static const struct hda_quirk cxt5045_fixups[] = { @@ -1112,6 +1190,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x054c, "Acer Aspire 3830TG", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x054f, "Acer Aspire 4830T", CXT_FIXUP_ASPIRE_DMIC), SND_PCI_QUIRK(0x1025, 0x136d, "Acer Swift SF314", CXT_FIXUP_ACER_SWIFT_HP), + SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), @@ -1164,6 +1243,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x17aa, 0x397b, "Lenovo S205", CXT_FIXUP_STEREO_DMIC), SND_PCI_QUIRK_VENDOR(0x17aa, "Thinkpad/Ideapad", CXT_FIXUP_LENOVO_XPAD_ACPI), SND_PCI_QUIRK(0x19e5, 0x3289, "Huawei Matebook", CXT_FIXUP_HUAWEI_MATEBOOK_HP), + SND_PCI_QUIRK(0x19e5, 0x329e, "Huawei MateBook 14", CXT_FIXUP_MATEBOOK14_SN6140), SND_PCI_QUIRK(0x1c06, 0x2011, "Lemote A1004", CXT_PINCFG_LEMOTE_A1004), SND_PCI_QUIRK(0x1c06, 0x2012, "Lemote A1205", CXT_PINCFG_LEMOTE_A1205), SND_PCI_QUIRK(0x1d05, 0x3012, "MECHREVO Wujie 15X Pro", CXT_FIXUP_HEADSET_MIC), diff --git a/sound/hda/codecs/hdmi/eld.c b/sound/hda/codecs/hdmi/eld.c index 1464fd1c675b..fc6fe1b0873c 100644 --- a/sound/hda/codecs/hdmi/eld.c +++ b/sound/hda/codecs/hdmi/eld.c @@ -155,9 +155,11 @@ void snd_hdmi_write_eld_info(struct hdmi_eld *eld, e->aud_synch_delay = val; else if (!strcmp(name, "speakers")) e->spk_alloc = val; - else if (!strcmp(name, "sad_count")) + else if (!strcmp(name, "sad_count")) { + if (val > ELD_MAX_SAD) + continue; e->sad_count = val; - else if (!strncmp(name, "sad", 3)) { + } else if (!strncmp(name, "sad", 3)) { sname = name + 4; n = name[3] - '0'; if (name[4] >= '0' && name[4] <= '9') { diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index 1e7a05c87733..355fd3e73183 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -983,7 +983,8 @@ static int hdmi_read_pin_conn(struct hda_codec *codec, int pin_idx) snd_hda_set_dev_select(codec, pin_nid, dev_id); if (spec->intel_hsw_fixup) { - conns = spec->num_cvts; + /* spec->cvt_nids[] only has ARRAY_SIZE(spec->cvt_nids) slots */ + conns = min_t(int, spec->num_cvts, ARRAY_SIZE(spec->cvt_nids)); memcpy(per_pin->mux_nids, spec->cvt_nids, sizeof(hda_nid_t) * conns); } else { @@ -1671,7 +1672,7 @@ int snd_hda_hdmi_generic_pcm_prepare(struct hda_pcm_stream *hinfo, * After S3, the audio driver restores pin:cvt selections * but this can happen before gfx is ready and such selection * is overlooked by HW. Thus multiple pins can share a same - * default convertor and mute control will affect each other, + * default converter and mute control will affect each other, * which can cause a resumed audio playback become silent * after S3. */ diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 1623cf926c71..e3b3e88dbf8a 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1754,6 +1754,25 @@ static void alc295_fixup_hp_pavilion_mute_led_1b(struct hda_codec *codec, spec->mute_led_nid = 0x1b; } +/* + * HP 245 14 inch G10 Notebook PC (SSID 103c:8ba4): the speaker mute LED is + * controlled through the HP_Mute_LED DMI string (NID 0x1a), same as the + * ALC269_FIXUP_HP_MUTE_LED fallback, but the microphone mute LED is wired to + * COEF 0x35 bits 2-3 with an inverted sense. + */ +static void alc236_fixup_hp_mute_led_micmute_coef_inv(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) + spec->micmute_led_polarity = 1; + + alc269_fixup_hp_mute_led(codec, fix, action); + alc236_fixup_hp_coef_micmute_led(codec, fix, action); +} + static void alc233_fixup_lenovo_coef_micmute_led(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -1840,6 +1859,48 @@ struct alc298_samsung_amp_desc { unsigned short init_seq[2][2]; }; +/* + * The MSI GE66 Raider 11UE drives its speakers through a Realtek ALC1316 smart + * amplifier, reached over the ALC298 coefficient tunnel. Left unconfigured the + * amplifier's silence detection mutes the speaker path after about 0.2 s of + * quiet and takes about 0.4 s to reopen, so the start of every sound that + * follows a pause is lost. Headphones do not pass through the amplifier and are + * unaffected. + * + * Tunnel layout for ALC286/ALC298: coefficient 0x22 selects the amplifier, 0x23 + * carries the 16-bit register, 0x24 and 0x25 the value's high and low halves, + * and 0x26 commits. Bit 4 of the commit word is a self-clearing go bit. + */ +static void alc298_fixup_alc1316_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + static const unsigned short init_seq[][2] = { + { 0x0000, 0x01 }, { 0xc001, 0x14 }, { 0xc003, 0x00 }, { 0xc004, 0x11 }, + { 0xc005, 0x00 }, { 0xc006, 0x00 }, { 0xc007, 0x11 }, { 0xc008, 0x11 }, + { 0xc009, 0x00 }, { 0xc730, 0x06 }, { 0xc612, 0x16 }, { 0xc00f, 0xf7 }, + { 0xc00a, 0xd8 }, { 0xc00b, 0xb7 }, { 0xc60b, 0xff }, { 0xc60c, 0x37 }, + { 0xc605, 0xf0 }, { 0xc020, 0x00 }, { 0xc022, 0xd6 }, { 0xc023, 0x00 }, + { 0xc025, 0xd6 }, { 0xc602, 0x07 }, { 0xc603, 0x07 }, { 0xd101, 0x00 }, + { 0xc093, 0x80 }, { 0xc090, 0x87 }, { 0xc091, 0x11 }, { 0xc614, 0x20 }, + { 0xc615, 0x0a }, { 0xc616, 0x02 }, { 0xc617, 0x00 }, { 0xc050, 0x80 }, + { 0xc051, 0x7f }, { 0xc052, 0x00 }, { 0xc053, 0x70 }, { 0xc054, 0x00 }, + { 0xc0a2, 0x01 }, { 0xc09d, 0x80 }, { 0xc09c, 0x71 } + }; + int i; + + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x22, 0x0010); + + for (i = 0; i < ARRAY_SIZE(init_seq); i++) { + alc_write_coef_idx(codec, 0x23, init_seq[i][0]); + alc_write_coef_idx(codec, 0x24, 0x0000); + alc_write_coef_idx(codec, 0x25, init_seq[i][1]); + alc_write_coef_idx(codec, 0x26, 0xb031); + } +} + static void alc298_fixup_samsung_amp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -2767,6 +2828,19 @@ static void alc275_fixup_gpio4_off(struct hda_codec *codec, } } +static void alc256_fixup_asus_aio_gpio2_low(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + struct alc_spec *spec = codec->spec; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + spec->gpio_mask |= 0x04; + spec->gpio_dir |= 0x04; + spec->gpio_data &= ~0x04; + } +} + /* Quirk for Thinkpad X1 7th and 8th Gen * The following fixed routing needed * DAC1 (NID 0x02) -> Speaker (NID 0x14); some eq applied secretly @@ -3433,6 +3507,28 @@ static void alc287_fixup_legion_16ithg6_speakers(struct hda_codec *cdc, const st comp_generic_fixup(cdc, action, "i2c", "CLSA0101", "-%s:00-cs35l41-hda.%d", 2); } +static void alc287_fixup_yoga_slim7_carbon_speakers(struct hda_codec *cdc, + const struct hda_fixup *fix, int action) +{ + /* + * The bass speakers are driven by two CS35L41 amps fed over I2S. The codec only + * clocks the I2S bus while pin 0x17 is enabled, but the BIOS marks it unconnected. + * Keep the pin on DAC 0x02 with the other speakers: DAC 0x06 gets powered down + * with stereo streams, which stops the clock and the amps fail to power up. + */ + static const struct hda_pintbl pincfgs[] = { + { 0x17, 0x90170121 }, + { } + }; + static const hda_nid_t conn[] = { 0x02 }; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + snd_hda_apply_pincfgs(cdc, pincfgs); + snd_hda_override_conn_list(cdc, 0x17, ARRAY_SIZE(conn), conn); + } + comp_generic_fixup(cdc, action, "i2c", "CLSA0102", "-%s:00-cs35l41-hda.%d", 2); +} + static void alc285_fixup_asus_ga403u(struct hda_codec *cdc, const struct hda_fixup *fix, int action) { /* @@ -3537,6 +3633,27 @@ static void alc233_fixup_no_audio_jack(struct hda_codec *codec, alc_process_coef_fw(codec, alc233_fixup_no_audio_jack_coefs); } +static const struct coef_fw alc256_asus_speaker_coefs[] = { + WRITE_COEF(0x10, 0x7f20), WRITE_COEF(0x16, 0x0c50), WRITE_COEF(0x35, 0x8d6a), + WRITE_COEF(0x37, 0xfe06), WRITE_COEF(0x57, 0x7f7f), + {} +}; + +static void alc256_fixup_asus_speaker_coefs(struct hda_codec *codec, + const struct hda_fixup *fix, + int action) +{ + /* + * The internal speakers of the ASUS ExpertBook PM3606CHA stay silent + * with the default COEF values. These are the values set by the + * Windows driver. They are applied on every init so that they are + * restored after resume, too. + */ + if (action != HDA_FIXUP_ACT_INIT) + return; + alc_process_coef_fw(codec, alc256_asus_speaker_coefs); +} + static void alc256_fixup_mic_no_presence_and_resume(struct hda_codec *codec, const struct hda_fixup *fix, int action) @@ -3661,6 +3778,28 @@ static void alc287_fixup_yoga9_14iap7_bass_spk_pin(struct hda_codec *codec, } } +/* + * The Lenovo Xiaoxin 14 GT (SSID 17aa:3912) uses the same bass-speaker + * pin wiring as its 0x390d/0x3911/0x391a siblings, but on this board the + * codec's vendor processing coefficients (node 0x20) are left at their + * power-on default instead of being programmed the way the vendor + * (Windows) driver programs them. That leaves audible hiss/crackle on + * both the headphone jack and the internal speakers, independent of + * volume, regardless of which pin/DAC routing is used. + * + * This single coefficient write was captured from the vendor driver's + * own init sequence (via a VFIO pass-through of the physical codec to a + * Windows VM) and confirmed to eliminate the headphone jack noise. + */ +static void alc287_fixup_lenovo_xiaoxin_14gt_hp_noise(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_write_coef_idx(codec, 0x36, 0x77d7); +} + static void alc_fixup_yoga_pro7_audio(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -4165,6 +4304,7 @@ enum { ALC256_FIXUP_ASUS_HEADSET_MODE, ALC256_FIXUP_ASUS_MIC, ALC256_FIXUP_ASUS_AIO_GPIO2, + ALC256_FIXUP_ASUS_AIO_GPIO2_LOW, ALC233_FIXUP_ASUS_MIC_NO_PRESENCE, ALC233_FIXUP_EAPD_COEF_AND_MIC_NO_PRESENCE, ALC233_FIXUP_LENOVO_MULTI_CODECS, @@ -4181,6 +4321,7 @@ enum { ALC255_FIXUP_DUMMY_LINEOUT_VERB, ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, + ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS, ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC295_FIXUP_HP_PAVILION_X360, @@ -4193,6 +4334,7 @@ enum { ALC294_FIXUP_ASUS_I2C_HEADSET_MIC, ALC294_FIXUP_ASUS_SPI_HEADSET_MIC, ALC294_FIXUP_ASUS_SPK, + ALC294_FIXUP_ASUS_Q423SA, ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, ALC285_FIXUP_LENOVO_PC_BEEP_IN_NOISE, ALC255_FIXUP_ACER_HEADSET_MIC, @@ -4237,10 +4379,12 @@ enum { ALC236_FIXUP_HP_MUTE_LED_COEFBIT2, ALC236_FIXUP_HP_GPIO_LED, ALC236_FIXUP_HP_MUTE_LED, + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV, ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF, ALC236_FIXUP_HP_15_FD0XXX, ALC236_FIXUP_LENOVO_INV_DMIC, ALC298_FIXUP_SAMSUNG_AMP, + ALC298_FIXUP_MSI_GE66_ALC1316_AMP, ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, ALC298_FIXUP_LG_GRAM_STYLE_14, @@ -4285,11 +4429,13 @@ enum { ALC295_FIXUP_HP_OMEN, ALC285_FIXUP_HP_SPECTRE_X360, ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED, ALC623_FIXUP_LENOVO_THINKSTATION_P340, ALC255_FIXUP_ACER_HEADPHONE_AND_MIC, ALC236_FIXUP_HP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS, ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + ALC287_FIXUP_LEGION_S7_15IMH05_AMP, ALC287_FIXUP_YOGA7_14ITL_SPEAKERS, ALC298_FIXUP_LENOVO_C940_DUET7, ALC287_FIXUP_LENOVO_YOGA_BOOK_9I, @@ -4317,9 +4463,11 @@ enum { ALC295_FIXUP_FRAMEWORK_LAPTOP_MIC_NO_PRESENCE, ALC295_FIXUP_FRAMEWORK_LAPTOP_LIMIT_INT_MIC_BOOST, ALC287_FIXUP_LEGION_16ITHG6, + ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK, ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, ALC287_FIXUP_YOGA9_14IMH9_BASS_SPK_PIN, + ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE, ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS, ALC236_FIXUP_DELL_DUAL_CODECS, ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, @@ -4338,9 +4486,12 @@ enum { ALC245_FIXUP_HP_X360_MUTE_LEDS, ALC287_FIXUP_THINKPAD_I2S_SPK, ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_THINKPAD, + ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD, ALC2XX_FIXUP_HEADSET_MIC, ALC289_FIXUP_DELL_CS35L41_SPI_2, ALC256_FIXUP_ACER_SFG16_MICMUTE_LED, + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED, ALC256_FIXUP_HEADPHONE_AMP_VOL, ALC245_FIXUP_HP_SPECTRE_X360_EU0XXX, ALC245_FIXUP_HP_SPECTRE_X360_16_AA0XXX, @@ -4375,8 +4526,9 @@ enum { ALC245_FIXUP_BASS_HP_DAC, ALC245_FIXUP_ACER_MICMUTE_LED, ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED, - ALC236_FIXUP_HP_DMIC, ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO, + ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS, + ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE, ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX, ALC287_FIXUP_ACER_MICMUTE_LED, ALC236_FIXUP_DELL_HP_POP_NOISE, @@ -4388,6 +4540,8 @@ enum { ALC285_LENOVO_DAC_RENAME, ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1, ALC256_FIXUP_IPASON_SMARTBOOK_S1, + ALC256_FIXUP_ASUS_SPEAKER_COEFS, + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -4437,6 +4591,53 @@ static void alc700_fixup_intel_hades_canyon(struct hda_codec *codec, alc_update_coef_idx(codec, 0x1b, 0x0400, 0x0400); } +static const struct hda_verb legion_amp_verbs[] = { + /* 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, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + + /* right speaker */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, + { 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, 0x2 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, + {} +}; + +static void alc287_fixup_legion_s7_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + snd_hda_sequence_write(codec, legion_amp_verbs); + /* + * The amplifiers accept the setup only a few ms after the first + * write (8 ms is the measured minimum on the test machine), so + * keep a margin. + */ + msleep(20); + snd_hda_sequence_write(codec, legion_amp_verbs); +} + static const struct hda_fixup alc269_fixups[] = { [ALC298_FIXUP_RAZER_BLADE16_2025_PINS] = { .type = HDA_FIXUP_PINS, @@ -4834,6 +5035,24 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC255_FIXUP_MIC_MUTE_LED }, + [ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + {0x12, 0x90a60130}, + {0x13, 0x40000000}, + {0x14, 0x90170110}, + {0x18, 0x411111f0}, + {0x19, 0x04a11140}, /* use as headset mic, without its own jack detect */ + {0x1a, 0x411111f0}, + {0x1b, 0x90170112}, + {0x1d, 0x40759a05}, + {0x1e, 0x411111f0}, + {0x21, 0x04211020}, + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_huawei_mbx_stereo, @@ -5442,6 +5661,10 @@ static const struct hda_fixup alc269_fixups[] = { /* Set up GPIO2 for the speaker amp */ .v.func = alc_fixup_gpio4, }, + [ALC256_FIXUP_ASUS_AIO_GPIO2_LOW] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_aio_gpio2_low, + }, [ALC233_FIXUP_ASUS_MIC_NO_PRESENCE] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -5669,6 +5892,17 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_ASUS_HEADSET_MIC }, + [ALC294_FIXUP_ASUS_Q423SA] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + /* Restore bit 10 lost after reboot from Windows */ + { 0x20, AC_VERB_SET_COEF_INDEX, 0x1b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4e4b }, + { } + }, + .chained = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC295_FIXUP_CHROME_BOOK] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_chromebook, @@ -6033,6 +6267,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_vref, }, + [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc236_fixup_hp_mute_led_micmute_coef_inv, + }, [ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO] = { .type = HDA_FIXUP_FUNC, .v.func = alc236_fixup_hp_mute_led_micmute_gpio, @@ -6055,6 +6293,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_hp_pavilion_mute_led_1b, }, + [ALC298_FIXUP_MSI_GE66_ALC1316_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc298_fixup_alc1316_amp, + }, [ALC298_FIXUP_SAMSUNG_AMP] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_samsung_amp, @@ -6435,6 +6677,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_ideapad_acpi, + .chained = true, + .chain_id = ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP, + }, [ALC287_FIXUP_LENOVO_YOGA_PRO7] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_yoga_pro7_audio, @@ -6486,41 +6734,13 @@ static const struct hda_fixup alc269_fixups[] = { }, [ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS] = { .type = HDA_FIXUP_VERBS, - //.v.verbs = legion_15imhg05_coefs, - .v.verbs = (const struct hda_verb[]) { - // set left speaker Legion 7i. - { 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, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - - // set right speaker Legion 7i. - { 0x20, AC_VERB_SET_COEF_INDEX, 0x24 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x42 }, - - { 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, 0x2 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0x0 }, - { 0x20, AC_VERB_SET_PROC_COEF, 0xb020 }, - {} - }, + .v.verbs = legion_amp_verbs, + .chained = true, + .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, + }, + [ALC287_FIXUP_LEGION_S7_15IMH05_AMP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_legion_s7_amp, .chained = true, .chain_id = ALC287_FIXUP_LEGION_15IMHG05_AUTOMUTE, }, @@ -6713,6 +6933,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc287_fixup_legion_16ithg6_speakers, }, + [ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_yoga_slim7_carbon_speakers, + }, [ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { @@ -6787,6 +7011,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, + [ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_lenovo_xiaoxin_14gt_hp_noise, + .chained = true, + .chain_id = ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN, + }, [ALC295_FIXUP_DELL_INSPIRON_TOP_SPEAKERS] = { .type = HDA_FIXUP_FUNC, .v.func = alc295_fixup_dell_inspiron_top_speakers, @@ -6886,6 +7116,18 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2_THINKPAD_ACPI, }, + [ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_four, + .chained = true, + .chain_id = ALC285_FIXUP_THINKPAD_NO_BASS_SPK_HEADSET_JACK, + }, + [ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc287_fixup_tb_vmaster_led, + .chained = true, + .chain_id = ALC287_FIXUP_CS35L41_I2C_4_THINKPAD_ACPI, + }, [ALC2XX_FIXUP_HEADSET_MIC] = { .type = HDA_FIXUP_FUNC, .v.func = alc2xx_fixup_headset_mic, @@ -6900,6 +7142,13 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_fixup_acer_sfg16_micmute_led, }, + [ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_acer_sfg16_micmute_led, + .chained = true, + .chained_before = true, + .chain_id = ALC2XX_FIXUP_HEADSET_MIC, + }, [ALC256_FIXUP_HEADPHONE_AMP_VOL] = { .type = HDA_FIXUP_FUNC, .v.func = alc256_decrease_headphone_amp_val, @@ -7084,13 +7333,6 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC287_FIXUP_CS35L41_I2C_2, }, - [ALC236_FIXUP_HP_DMIC] = { - .type = HDA_FIXUP_PINS, - .v.pins = (const struct hda_pintbl[]) { - { 0x12, 0x90a60160 }, /* use as internal mic */ - { } - }, - }, [ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -7101,6 +7343,20 @@ static const struct hda_fixup alc269_fixups[] = { { } } }, + [ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x14, 0x90170111 }, /* tweeters (top speakers) */ + { 0x19, 0x03a1113c }, /* headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE + }, + [ALC256_FIXUP_HONOR_DRA_XX_HEADSET_MODE] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc_fixup_headset_mode, + }, [ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX] = { .type = HDA_FIXUP_FUNC, .v.func = cs35l41_fixup_i2c_two, @@ -7174,6 +7430,21 @@ static const struct hda_fixup alc269_fixups[] = { { } }, }, + [ALC256_FIXUP_ASUS_SPEAKER_COEFS] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc256_fixup_asus_speaker_coefs, + .chained = true, + .chain_id = ALC256_FIXUP_ASUS_MIC_NO_PRESENCE, + }, + [ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a19150 }, /* headset mic jack, no presence detect */ + { } + }, + .chained = true, + .chain_id = ALC245_FIXUP_BASS_HP_DAC + }, }; static const struct hda_quirk alc269_fixup_tbl[] = { @@ -7230,7 +7501,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1025, 0x160e, "Acer PT316-51S", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1616, "Acer Aspire A515-57", ALC256_FIXUP_ACER_COLDBOOT), SND_PCI_QUIRK(0x1025, 0x161f, "Acer S40-54", ALC256_FIXUP_ACER_MIC_NO_PRESENCE), - SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), + /* + * Shared SSID: A315-44P needs GPIO3 micmute LED; AG15-42P needs + * the headset pin remap (BIOS leaves 0x19 as 0x411111f0). One + * entry applies both. GPIO3 is unused on AG15. + */ + SND_PCI_QUIRK(0x1025, 0x1640, "Acer Aspire A315-44P / AG15-42P", + ALC256_FIXUP_ACER_HEADSET_MICMUTE_LED), SND_PCI_QUIRK(0x1025, 0x166c, "Acer Predator PH16-71", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x1679, "Acer Nitro 16 AN16-41", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1025, 0x169a, "Acer Swift SFG16", ALC256_FIXUP_ACER_SFG16_MICMUTE_LED), @@ -7491,6 +7768,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8812, "HP Spectre x360 15-eb1xxx", ALC285_FIXUP_HP_SPECTRE_X360_EB1), SND_PCI_QUIRK(0x103c, 0x881d, "HP 250 G8 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x881e, "HP Laptop 15s-du3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x8820, "HP ENVY Laptop 13-ba1xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8846, "HP EliteBook 850 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8847, "HP EliteBook x360 830 G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x884b, "HP EliteBook 840 Aero G8 Notebook PC", ALC285_FIXUP_HP_GPIO_LED), @@ -7502,6 +7780,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8870, "HP ZBook Fury 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x8873, "HP ZBook Studio 15.6 Inch G8 Mobile Workstation PC", ALC285_FIXUP_HP_GPIO_AMP_INIT), SND_PCI_QUIRK(0x103c, 0x887a, "HP Laptop 15s-eq2xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), + SND_PCI_QUIRK(0x103c, 0x89fc, "HP Laptop 15s-eq3xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x887c, "HP Laptop 14s-fq1xxx", ALC236_FIXUP_HP_MUTE_LED_COEFBIT2), SND_PCI_QUIRK(0x103c, 0x888a, "HP ENVY x360 Convertible 15-eu0xxx", ALC245_FIXUP_HP_X360_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x888d, "HP ZBook Power 15.6 inch G8 Mobile Workstation PC", ALC236_FIXUP_HP_GPIO_LED), @@ -7622,6 +7901,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b92, "HP", ALC245_FIXUP_CS35L41_SPI_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b96, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b97, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), + SND_PCI_QUIRK(0x103c, 0x8ba4, "HP 245 14 inch G10 Notebook PC", + ALC236_FIXUP_HP_MUTE_LED_MICMUTE_COEF_INV), SND_PCI_QUIRK(0x103c, 0x8ba9, "HP Omen 16-wd0xxx", ALC245_FIXUP_HP_MUTE_LED_V1_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb1, "HP Victus 15-fa1xxx (MB 8BB1)", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), SND_PCI_QUIRK(0x103c, 0x8bb3, "HP Slim OMEN", ALC287_FIXUP_CS35L41_I2C_2), @@ -7643,7 +7924,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8be5, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be6, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8be7, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8be8, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8be9, "HP Envy x360 2-in-1 Laptop 15-fh0xxx", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), SND_PCI_QUIRK(0x103c, 0x8bf0, "HP", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8bfd, "HP Pavilion 15-eg3xxx", ALC287_FIXUP_HP_GPIO_LED), @@ -7722,12 +8003,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8d9c, "HP 17 Turbine OmniBook 7 DIS", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8d9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8d9e, "HP 17 Turbine OmniBook X DIS", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8d9f, "HP 14 Cadet (x360)", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8da0, "HP 16 Clipper OmniBook 7(X360)", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da1, "HP 16 Clipper OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da7, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8da8, "HP 16 Piston OmniBook X", ALC245_FIXUP_HP_ENVY_X360_15_FH0XXX), - SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_DMIC), + SND_PCI_QUIRK(0x103c, 0x8dc9, "HP Laptop 15-fc0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8dd4, "HP EliteStudio 8 AIO", ALC274_FIXUP_HP_AIO_BIND_DACS), SND_PCI_QUIRK(0x103c, 0x8dd7, "HP Laptop 15-fd0xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8de8, "HP Gemtree", ALC245_FIXUP_TAS2781_SPI_2), @@ -7820,7 +8101,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x10a1, "ASUS UX391UA", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x10a4, "ASUS TP3407SA", ALC287_FIXUP_TAS2781_I2C), SND_PCI_QUIRK(0x1043, 0x10c0, "ASUS X540SA", ALC256_FIXUP_ASUS_MIC), - SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS Vivobook S14 S5406SA", ALC2XX_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x10c4, "ASUS S5406SA/Q423SA", ALC294_FIXUP_ASUS_Q423SA), SND_PCI_QUIRK(0x1043, 0x10d0, "ASUS X540LA/X540LJ", ALC255_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1043, 0x10d3, "ASUS K6500ZC", ALC294_FIXUP_ASUS_SPK), SND_PCI_QUIRK(0x1043, 0x1154, "ASUS TP3607SH", ALC287_FIXUP_TAS2781_I2C), @@ -7867,7 +8148,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1663, "ASUS GU603ZI/ZJ/ZQ/ZU/ZV", ALC285_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x1664, "ASUS ExpertBook PM3406CHA", ALC256_FIXUP_ASUS_PM3406CHA), SND_PCI_QUIRK(0x1043, 0x1683, "ASUS UM3402YAR", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x16a3, "ASUS UX3402VA", ALC294_FIXUP_ASUS_SPI_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x16b2, "ASUS GU603", ALC289_FIXUP_ASUS_GA401), SND_PCI_QUIRK(0x1043, 0x16d3, "ASUS UX5304VA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), @@ -7950,6 +8231,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3500, "ASUS E5402WHA", ALC256_FIXUP_ASUS_AIO_GPIO2_LOW), + SND_PCI_QUIRK(0x1043, 0x3501, "ASUS PM3606CHA", ALC256_FIXUP_ASUS_SPEAKER_COEFS), SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), @@ -8031,6 +8314,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), + SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", + ALC298_FIXUP_MSI_GE66_ALC1316_AMP), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK), @@ -8206,7 +8491,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x38d5, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x38d6, "Lenovo Yoga Pro 9 16IMH9", ALC287_FIXUP_TAS2781_I2C), HDA_CODEC_QUIRK(0x17aa, 0x3855, "Legion 7 16ITHG6", ALC287_FIXUP_LEGION_16ITHG6), - SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3811, "Legion S7 15IMH05", ALC287_FIXUP_LEGION_S7_15IMH05_AMP), SND_PCI_QUIRK(0x17aa, 0x3813, "Legion 7i 15IMHG05", ALC287_FIXUP_LEGION_15IMHG05_SPEAKERS), 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), @@ -8216,6 +8501,12 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3827, "Ideapad S740", ALC285_FIXUP_IDEAPAD_S740_COEF), SND_PCI_QUIRK(0x17aa, 0x3834, "Lenovo IdeaPad Slim 9i 14ITL5", ALC287_FIXUP_YOGA7_14ITL_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x383d, "Legion Y9000X 2019", ALC285_FIXUP_LEGION_Y9000X_SPEAKERS), + SND_PCI_QUIRK(0x17aa, 0x3842, "ThinkBook X G2 IAH", ALC287_FIXUP_MG_RTKC_CSAMP_CS35L41_I2C_4_THINKPAD), + /* IdeaPad Pro 5 14IAH10 shares PCI SSID 17aa:3843 with Yoga Book 9i; + * use codec SSID to distinguish them + */ + HDA_CODEC_QUIRK(0x17aa, 0x3914, "Lenovo IdeaPad Pro 5 14IAH10", + ALC287_FIXUP_IDEAPAD_BASS_SPK_AMP_LED), SND_PCI_QUIRK(0x17aa, 0x3843, "Lenovo Yoga 9i / Yoga Book 9i", ALC287_FIXUP_LENOVO_YOGA_BOOK_9I), /* Yoga Pro 7 14IMH9 shares PCI SSID 17aa:3847 with Legion 7 16ACHG6; * use codec SSID to distinguish them @@ -8230,6 +8521,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { 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), + HDA_CODEC_QUIRK(0x17aa, 0x3856, "Lenovo Yoga Slim 7 Carbon 14ACN6", ALC287_FIXUP_YOGA_SLIM7_CARBON_SPEAKERS), SND_PCI_QUIRK(0x17aa, 0x3862, "Lenovo IdeaPad Slim 3 15ABR8", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x17aa, 0x3865, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3866, "Lenovo 13X", ALC287_FIXUP_CS35L41_I2C_2), @@ -8243,6 +8535,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3874, "Legion 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), + /* Lenovo ThinkBook Plus G3 IAP shares PCI SSID 17aa:3891 with Lenovo Yoga + * Pro 7 14AHP9; use codec SSID to distinguish them. + */ + HDA_CODEC_QUIRK(0x17aa, 0x3875, "ThinkBook Plus G3 IAP", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3877, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3878, "Lenovo Legion 7 Slim 16ARHA7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x387d, "Yoga S780-16 pro Quad AAC", ALC287_FIXUP_TAS2781_I2C), @@ -8309,7 +8605,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x3907, "Legion Pro 7i 16IAX10H / Y9000P IAX10", ALC287_FIXUP_LENOVO_LEGION_AW88399), SND_PCI_QUIRK(0x17aa, 0x390d, "Lenovo Yoga Pro 7 14ASP10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN_HEADSET), SND_PCI_QUIRK(0x17aa, 0x3911, "Lenovo Yoga Pro 7 14IAH10", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), - SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), + SND_PCI_QUIRK(0x17aa, 0x3912, "Lenovo Xiaoxin 14 GT", ALC287_FIXUP_LENOVO_XIAOXIN_14GT_HP_NOISE), SND_PCI_QUIRK(0x17aa, 0x3913, "Lenovo 145", ALC236_FIXUP_LENOVO_INV_DMIC), SND_PCI_QUIRK(0x17aa, 0x391f, "Yoga S990-16 pro Quad YC Quad", ALC287_FIXUP_TXNW2781_I2C), SND_PCI_QUIRK(0x17aa, 0x3920, "Yoga S990-16 pro Quad VECO Quad", ALC287_FIXUP_YOGA9_SPEAKER2_TO_DAC1), @@ -8359,6 +8655,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), + SND_PCI_QUIRK(0x19e5, 0x3e13, "Huawei MACHR-WX9", ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS), SND_PCI_QUIRK(0x1a58, 0x2023, "Razer Blade 16 (2025)", ALC298_FIXUP_RAZER_BLADE16_2025_PINS), SND_PCI_QUIRK(0x1a58, 0x300e, "Razer Blade 16 (2025)", @@ -8400,11 +8697,13 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xb020, "Minisforum AI X1 Pro", ALC245_FIXUP_MINISFORUM_JACK_DETECT), - SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", + ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), @@ -8419,6 +8718,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x2782, 0x1701, "Infinix Y4 Max", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1705, "MEDION E15433", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x1707, "Vaio VJFE-ADL", ALC298_FIXUP_SPK_VOLUME), + SND_PCI_QUIRK(0x2782, 0x1718, "Acer Aspire AL16-31P", ALC269VC_FIXUP_INFINIX_Y4_MAX), SND_PCI_QUIRK(0x2782, 0x4900, "MEDION E15443", ALC233_FIXUP_MEDION_MTL_SPK), SND_PCI_QUIRK(0x2782, 0xa128, "Positivo N15RPE-S", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2782, 0xa212, "Lunnen Ground 14", ALC269VC_FIXUP_LUNNEN_GROUND_14), @@ -8616,6 +8916,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC298_FIXUP_HUAWEI_MBX_STEREO, .name = "huawei-mbx-stereo"}, {.id = ALC256_FIXUP_MEDION_HEADSET_NO_PRESENCE, .name = "alc256-medion-headset"}, {.id = ALC298_FIXUP_SAMSUNG_AMP, .name = "alc298-samsung-amp"}, + {.id = ALC298_FIXUP_MSI_GE66_ALC1316_AMP, .name = "alc298-alc1316-amp"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS, .name = "alc298-samsung-amp-v2-2-amps"}, {.id = ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS, .name = "alc298-samsung-amp-v2-4-amps"}, {.id = ALC256_FIXUP_SAMSUNG_HEADPHONE_VERY_QUIET, .name = "alc256-samsung-headphone"}, diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index eff7c83b9c59..c1c7277b6754 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -198,6 +198,24 @@ static void alc897_fixup_lenovo_headset_mode(struct hda_codec *codec, } } +static void alc897_fixup_limit_linein_boost(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_PROBE) + return; + + snd_hda_override_amp_caps(codec, 0x1a, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x2f << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + snd_hda_override_amp_caps(codec, 0x08, HDA_INPUT, + (0x00 << AC_AMPCAP_OFFSET_SHIFT) | + (0x01 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x02 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); +} + static const struct coef_fw alc668_coefs[] = { WRITE_COEF(0x01, 0xbebe), WRITE_COEF(0x02, 0xaaaa), WRITE_COEF(0x03, 0x0), WRITE_COEF(0x04, 0x0180), WRITE_COEF(0x06, 0x0), WRITE_COEF(0x07, 0x0f80), @@ -326,6 +344,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN3, ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, + ALC897_FIXUP_LIMIT_LINEIN_BOOST, }; static const struct hda_fixup alc662_fixups[] = { @@ -787,6 +806,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_gpio4, }, + [ALC897_FIXUP_LIMIT_LINEIN_BOOST] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_limit_linein_boost, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -857,6 +880,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1b35, 0x1234, "CZC ET26", ALC662_FIXUP_CZC_ET26), SND_PCI_QUIRK(0x1b35, 0x2206, "CZC P10T", ALC662_FIXUP_CZC_P10T), SND_PCI_QUIRK(0x1c6c, 0x1239, "Compaq N14JP6-V2", ALC897_FIXUP_HP_HSMIC_VERB), + SND_PCI_QUIRK(0x1e0b, 0x1297, "DECENTA MATX03", ALC897_FIXUP_LIMIT_LINEIN_BOOST), SND_PCI_QUIRK(0x1e63, 0x6d9a, "F+ FLAPTOP r", ALC897_FIXUP_HP_HSMIC_VERB), SND_PCI_QUIRK(0x1f66, 0x0202, "Beelink SER6 Max 6900", ALC897_FIXUP_BEELINK_SER6_AMP), diff --git a/sound/hda/codecs/realtek/alc882.c b/sound/hda/codecs/realtek/alc882.c index fd466b6985f0..800c3d6f5134 100644 --- a/sound/hda/codecs/realtek/alc882.c +++ b/sound/hda/codecs/realtek/alc882.c @@ -372,7 +372,7 @@ static const struct hda_fixup alc882_fixups[] = { [ALC883_FIXUP_ACER_EAPD] = { .type = HDA_FIXUP_VERBS, .v.verbs = (const struct hda_verb[]) { - /* eanable EAPD on Acer laptops */ + /* enable EAPD on Acer laptops */ { 0x20, AC_VERB_SET_COEF_INDEX, 0x07 }, { 0x20, AC_VERB_SET_PROC_COEF, 0x3050 }, { } diff --git a/sound/hda/codecs/side-codecs/Kconfig b/sound/hda/codecs/side-codecs/Kconfig index f90f1dfcec68..b7e2a6fa4258 100644 --- a/sound/hda/codecs/side-codecs/Kconfig +++ b/sound/hda/codecs/side-codecs/Kconfig @@ -22,6 +22,7 @@ config SND_HDA_SCODEC_AW88399_I2C depends on I2C depends on ACPI depends on SND_SOC + imply SERIAL_MULTI_INSTANTIATE select SND_HDA_SCODEC_AW88399 select SND_SOC_AW88399_LIB help diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 62296be45c55..d50c3d8f5303 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1907,7 +1907,6 @@ err: hw_cfg->valid = false; hw_cfg->gpio1.valid = false; hw_cfg->gpio2.valid = false; - acpi_dev_put(cs35l41->dacpi); return ret; } @@ -1928,10 +1927,8 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i cs35l41->dacpi = adev; struct device *physdev __free(put_device) = get_device(acpi_get_first_physical_node(adev)); - if (!physdev) { - acpi_dev_put(adev); + if (!physdev) return -ENODEV; - } sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR(sub)) @@ -1986,8 +1983,10 @@ 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) - return dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + if (ret) { + dev_err_probe(cs35l41->dev, ret, "Platform not supported\n"); + goto err_acpi; + } if (IS_ERR(cs35l41->reset_gpio)) { ret = PTR_ERR(cs35l41->reset_gpio); @@ -2074,6 +2073,7 @@ err_pm: err: if (cs35l41_safe_reset(cs35l41->regmap, cs35l41->hw_cfg.bst_type)) gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); +err_acpi: gpiod_put(cs35l41->reset_gpio); gpiod_put(cs35l41->cs_gpio); acpi_dev_put(cs35l41->dacpi); diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c index fdf406e92fca..59b82238f2b8 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_i2c.c @@ -23,6 +23,8 @@ static int cs35l41_hda_i2c_probe(struct i2c_client *clt) device_name = "CLSA0100"; else if (strstr(dev_name(&clt->dev), "CLSA0101")) device_name = "CLSA0101"; + else if (strstr(dev_name(&clt->dev), "CLSA0102")) + device_name = "CLSA0102"; else if (strstr(dev_name(&clt->dev), "CSC3551")) device_name = "CSC3551"; else @@ -45,6 +47,7 @@ static const struct i2c_device_id cs35l41_hda_i2c_id[] = { static const struct acpi_device_id cs35l41_acpi_hda_match[] = { {"CLSA0100", 0 }, {"CLSA0101", 0 }, + {"CLSA0102", 0 }, {"CSC3551", 0 }, {} }; diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 416d7bf3e289..c50c7e689533 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -85,6 +85,8 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8C51", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, + { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, + { "103C8F07", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, { "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "10431433", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "10431463", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, @@ -126,6 +128,8 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "10433A60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, { "17AA3865", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3866", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3845", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, + { "17AA3846", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA386E", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 2, -1, 0, 0, 0 }, { "17AA386F", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, { "17AA3874", 2, EXTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, -1, -1, 0, 0, 0 }, @@ -439,6 +443,33 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy return 0; } +/* + * Device CLSA0102 (Lenovo Yoga Slim 7 Carbon 14ACN6) has the same ACPI layout as CLSA010(0/1): + * no _DSD, both amps in one node, reset on GPIO index 0. It has no speaker ID GPIO, the + * interrupt GPIO sits at index 2. The speakers use an external VSPK supply switched by GPIO1 + * (the Windows driver sets ExternalVspkControl and keeps the boost converter off). + */ +static int lenovo_yoga_slim7_carbon_no_acpi(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 = -ENOENT; + hw_cfg->spk_pos = cs35l41->index; + hw_cfg->bst_type = CS35L41_EXT_BOOST; + hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; + hw_cfg->gpio1.valid = true; + hw_cfg->gpio2.func = CS35L41_INTERRUPT; + hw_cfg->gpio2.valid = true; + hw_cfg->valid = true; + + return 0; +} + static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, const char *hid) { @@ -463,6 +494,7 @@ struct cs35l41_prop_model { static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CLSA0100", NULL, lenovo_legion_no_acpi }, { "CLSA0101", NULL, lenovo_legion_no_acpi }, + { "CLSA0102", NULL, lenovo_yoga_slim7_carbon_no_acpi }, { "CSC3551", "10251826", generic_dsd_config }, { "CSC3551", "1025182C", generic_dsd_config }, { "CSC3551", "10251844", generic_dsd_config }, @@ -511,6 +543,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8C6A", hp_i2c_int_2amp_dual_spkid }, { "CSC3551", "103C8CDD", generic_dsd_config }, { "CSC3551", "103C8CDE", generic_dsd_config }, + { "CSC3551", "103C8E60", generic_dsd_config }, + { "CSC3551", "103C8F07", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, { "CSC3551", "10431433", generic_dsd_config }, { "CSC3551", "10431463", generic_dsd_config }, @@ -551,6 +585,8 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "10433A40", generic_dsd_config }, { "CSC3551", "10433A50", generic_dsd_config }, { "CSC3551", "10433A60", generic_dsd_config }, + { "CSC3551", "17AA3845", generic_dsd_config }, + { "CSC3551", "17AA3846", generic_dsd_config }, { "CSC3551", "17AA3865", generic_dsd_config }, { "CSC3551", "17AA3866", generic_dsd_config }, { "CSC3551", "17AA386E", generic_dsd_config }, diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b..44855f593daf 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -335,10 +335,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } cd->cali_dat_sz_per_dev = TAS2563_CAL_DATA_SIZE * TASDEV_CALIB_N; + cd->total_sz = 0; /* extra byte for each device is the device number */ - cd->total_sz = (cd->cali_dat_sz_per_dev + 1) * p->ndev; - data = cd->data = devm_kzalloc(p->dev, cd->total_sz, + data = cd->data = devm_kzalloc(p->dev, + (cd->cali_dat_sz_per_dev + 1) * p->ndev, GFP_KERNEL); if (!data) return -ENOMEM; @@ -352,7 +353,6 @@ static int tas2563_save_calibration(struct tas2781_hda *h) if (ret < 0 || ret >= sizeof(var8) - 1) { dev_err(p->dev, "%s: Read %s failed\n", __func__, var8); - cd->total_sz = 0; return -EINVAL; } /* @@ -370,7 +370,11 @@ static int tas2563_save_calibration(struct tas2781_hda *h) dev_warn(p->dev, "Dev %d: Caldat[%d] read failed %ld\n", i, j, status); - cd->total_sz = 0; + return -EINVAL; + } + if (*(uint32_t *)&data[offset] == 0) { + dev_warn(p->dev, + "Dev %d: Caldat[%d] is zero\n", i, j); return -EINVAL; } bedata = cpu_to_be32(*(uint32_t *)&data[offset]); @@ -379,12 +383,7 @@ static int tas2563_save_calibration(struct tas2781_hda *h) } } - if (cd->total_sz != offset) { - dev_err(p->dev, "%s: tot_size(%lu) and offset(%u) mismatch\n", - __func__, cd->total_sz, offset); - cd->total_sz = 0; - return -EINVAL; - } + cd->total_sz = offset; r->r0_reg = TAS2563_CAL_R0; r->invr0_reg = TAS2563_CAL_INVR0; diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index ee3bd21adc36..9a8ba43667f7 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -361,7 +361,7 @@ static unsigned int stac_vref_led_power_filter(struct hda_codec *codec, return snd_hda_gen_path_power_filter(codec, nid, power_state); } -/* update mute-LED accoring to the master switch */ +/* update mute-LED according to the master switch */ static void stac_update_led_status(struct hda_codec *codec, bool muted) { struct sigmatel_spec *spec = codec->spec; @@ -1710,7 +1710,7 @@ static const struct hda_pintbl ref92hd73xx_pin_configs[] = { { 0x11, 0x01014030 }, // CD in { 0x12, 0x02319040 }, - // Digial Mic ins + // Digital Mic ins { 0x13, 0x90a000f0 }, { 0x14, 0x90a000f0 }, // Digital outs @@ -3817,7 +3817,7 @@ static const struct hda_pintbl dell_3st_pin_configs[] = { static void stac927x_fixup_ref_no_jd(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - /* no jack detecion for ref-no-jd model */ + /* no jack detection for ref-no-jd model */ if (action == HDA_FIXUP_ACT_PRE_PROBE) codec->no_jack_detect = 1; } diff --git a/sound/hda/codecs/via.c b/sound/hda/codecs/via.c index 807312cffd35..0641fc14b346 100644 --- a/sound/hda/codecs/via.c +++ b/sound/hda/codecs/via.c @@ -933,6 +933,7 @@ static const struct hda_fixup via_fixups[] = { static const struct hda_quirk vt2002p_fixups[] = { SND_PCI_QUIRK(0x1043, 0x13f7, "Asus B23E", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x1487, "Asus G75", VIA_FIXUP_ASUS_G75), + SND_PCI_QUIRK(0x1043, 0x1c33, "Asus X55A", VIA_FIXUP_POWER_SAVE), SND_PCI_QUIRK(0x1043, 0x8532, "Asus X202E", VIA_FIXUP_INTMIC_BOOST), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo", VIA_FIXUP_POWER_SAVE), {} diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 6a728a773556..957feace0f58 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -89,8 +89,12 @@ static int hda_codec_driver_probe(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct module *owner = dev->driver->owner; struct hda_codec_driver *driver = hda_codec_to_driver(codec); + bool probed = false; int err; + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_attach)) return -EINVAL; @@ -119,7 +123,8 @@ static int hda_codec_driver_probe(struct device *dev) err = driver->ops->probe(codec, codec->preset); if (err < 0) - goto error_module_put; + goto error_module; + probed = true; err = snd_hda_codec_build_pcms(codec); if (err < 0) goto error_module; @@ -138,7 +143,9 @@ static int hda_codec_driver_probe(struct device *dev) return 0; error_module: - if (driver->ops->remove) + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: module_put(owner); @@ -154,6 +161,10 @@ static int hda_codec_driver_remove(struct device *dev) struct hda_codec *codec = dev_to_hda_codec(dev); struct hda_codec_driver *driver = hda_codec_to_driver(codec); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(&codec->core); + cancel_delayed_work_sync(&codec->jackpoll_work); + if (codec->bus->core.ext_ops) { if (WARN_ON(!codec->bus->core.ext_ops->hdev_detach)) return -EINVAL; diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 5d9b53bd64ea..31e5fad78b0f 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -757,11 +757,15 @@ static void codec_release_pcms(struct hda_codec *codec) */ void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + 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->core.registered = 0; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = false; + spin_unlock_irq(&bus->core.reg_lock); } snd_hda_codec_disconnect_pcms(codec); @@ -808,6 +812,8 @@ void snd_hda_codec_display_power(struct hda_codec *codec, bool enable) */ void snd_hda_codec_register(struct hda_codec *codec) { + struct hda_bus *bus = codec->bus; + if (codec->core.registered) return; if (device_is_registered(hda_codec_dev(codec))) { @@ -815,7 +821,9 @@ void snd_hda_codec_register(struct hda_codec *codec) 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->core.registered = 1; + spin_lock_irq(&bus->core.reg_lock); + codec->core.registered = true; + spin_unlock_irq(&bus->core.reg_lock); } } EXPORT_SYMBOL_GPL(snd_hda_codec_register); @@ -921,7 +929,6 @@ snd_hda_codec_device_init(struct hda_bus *bus, unsigned int codec_addr, snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16); snd_array_init(&codec->cvt_setups, sizeof(struct hda_cvt_setup), 8); snd_array_init(&codec->spdif_out, sizeof(struct hda_spdif_out), 16); - snd_array_init(&codec->jacktbl, sizeof(struct hda_jack_tbl), 16); snd_array_init(&codec->verbs, sizeof(struct hda_verb *), 8); INIT_LIST_HEAD(&codec->conn_list); INIT_LIST_HEAD(&codec->pcm_list_head); @@ -2018,7 +2025,7 @@ int snd_hda_add_vmaster_hook(struct hda_codec *codec, if (!hook->hook || !hook->sw_kctl) return 0; hook->codec = codec; - snd_ctl_add_vmaster_hook(hook->sw_kctl, vmaster_hook, hook); + snd_ctl_add_vmaster_hook(codec->card, hook->sw_kctl, vmaster_hook, hook); return 0; } EXPORT_SYMBOL_GPL(snd_hda_add_vmaster_hook); @@ -3040,6 +3047,7 @@ void snd_hda_codec_shutdown(struct hda_codec *codec) codec->jackpoll_interval = 0; /* don't poll any longer */ cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hdac_device_disable_unsol(&codec->core); list_for_each_entry(cpcm, &codec->pcm_list_head, list) snd_pcm_suspend_all(cpcm->pcm); @@ -3774,7 +3782,7 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); /** - * snd_hda_multi_out_analog_prepare - Preapre the analog outputs. + * snd_hda_multi_out_analog_prepare - Prepare the analog outputs. * @codec: the HDA codec * @mout: hda_multi_out object * @stream_tag: stream tag to assign diff --git a/sound/hda/common/hda_jack.h b/sound/hda/common/hda_jack.h index e9b9970c59ed..a06b315e4b05 100644 --- a/sound/hda/common/hda_jack.h +++ b/sound/hda/common/hda_jack.h @@ -44,6 +44,7 @@ struct hda_jack_tbl { int type; int button_state; struct snd_jack *jack; + struct hda_jack_tbl *next; }; struct hda_jack_keymap { diff --git a/sound/hda/common/jack.c b/sound/hda/common/jack.c index 1d6b0f0e6f27..6b8aa1a6c008 100644 --- a/sound/hda/common/jack.c +++ b/sound/hda/common/jack.c @@ -76,12 +76,11 @@ static u32 read_pin_sense(struct hda_codec *codec, hda_nid_t nid, int dev_id) struct hda_jack_tbl * snd_hda_jack_tbl_get_mst(struct hda_codec *codec, hda_nid_t nid, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid && jack->dev_id == dev_id) return jack; return NULL; @@ -98,12 +97,11 @@ struct hda_jack_tbl * snd_hda_jack_tbl_get_from_tag(struct hda_codec *codec, unsigned char tag, int dev_id) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!tag || !jack) + if (!tag) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->tag == tag && jack->dev_id == dev_id) return jack; return NULL; @@ -113,12 +111,11 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_tbl_get_from_tag); static struct hda_jack_tbl * any_jack_tbl_get_from_nid(struct hda_codec *codec, hda_nid_t nid) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - if (!nid || !jack) + if (!nid) return NULL; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid == nid) return jack; return NULL; @@ -142,12 +139,19 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) if (jack) return jack; - jack = snd_array_new(&codec->jacktbl); + jack = kzalloc_obj(*jack); if (!jack) return NULL; jack->nid = nid; jack->dev_id = dev_id; jack->jack_dirty = 1; + if (!codec->jacktbl) + codec->jacktbl = jack; + else + codec->jacktbl_last->next = jack; + codec->jacktbl_last = jack; + codec->jacktbl_used++; + if (existing_nid_jack) { jack->tag = existing_nid_jack->tag; @@ -158,7 +162,7 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) */ jack->jack_detect = existing_nid_jack->jack_detect; } else { - jack->tag = codec->jacktbl.used; + jack->tag = codec->jacktbl_used; } return jack; @@ -166,10 +170,9 @@ snd_hda_jack_tbl_new(struct hda_codec *codec, hda_nid_t nid, int dev_id) void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { if (!codec->bus->shutdown && jack->jack) snd_device_disconnect(codec->card, jack->jack); } @@ -177,22 +180,31 @@ void snd_hda_jack_tbl_disconnect(struct hda_codec *codec) void snd_hda_jack_tbl_clear(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack, *jack_next; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack_next) { struct hda_jack_callback *cb, *next; - /* free jack instances manually when clearing/reconfiguring */ - if (!codec->bus->shutdown && jack->jack) - snd_device_free(codec->card, jack->jack); + jack_next = jack->next; + + if (jack->jack) { + /* fingers away from stale data */ + jack->jack->private_data = NULL; + jack->jack->private_free = NULL; + /* free jack instances manually when clearing/reconfiguring */ + if (!codec->bus->shutdown) + snd_device_free(codec->card, jack->jack); + } for (cb = jack->callback; cb; cb = next) { next = cb->next; kfree(cb); } + kfree(jack); } - snd_array_free(&codec->jacktbl); + + codec->jacktbl = codec->jacktbl_last = NULL; + codec->jacktbl_used = 0; } #define get_jack_plug_state(sense) !!(sense & AC_PINSENSE_PRESENCE) @@ -238,10 +250,9 @@ static void jack_detect_update(struct hda_codec *codec, */ void snd_hda_jack_set_dirty_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i; + struct hda_jack_tbl *jack; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack->jack_dirty = 1; } @@ -389,12 +400,15 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_detect_enable); int snd_hda_jack_set_gating_jack(struct hda_codec *codec, hda_nid_t gated_nid, hda_nid_t gating_nid) { - struct hda_jack_tbl *gated = snd_hda_jack_tbl_new(codec, gated_nid, 0); - struct hda_jack_tbl *gating = - snd_hda_jack_tbl_new(codec, gating_nid, 0); + struct hda_jack_tbl *gated, *gating; WARN_ON(codec->dp_mst); + if (!snd_hda_jack_tbl_new(codec, gated_nid, 0) || + !snd_hda_jack_tbl_new(codec, gating_nid, 0)) + return -ENOMEM; + gated = snd_hda_jack_tbl_get_mst(codec, gated_nid, 0); + gating = snd_hda_jack_tbl_get_mst(codec, gating_nid, 0); if (!gated || !gating) return -EINVAL; @@ -475,19 +489,17 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_set_button_state); void snd_hda_jack_report_sync(struct hda_codec *codec) { struct hda_jack_tbl *jack; - int i, state; + int state; /* update all jacks at first */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) jack_detect_update(codec, jack); /* report the updated jacks; it's done after updating all jacks * to make sure that all gating jacks properly have been set */ - jack = codec->jacktbl.list; - for (i = 0; i < codec->jacktbl.used; i++, jack++) + for (jack = codec->jacktbl; jack; jack = jack->next) if (jack->nid) { if (!jack->jack || jack->block_report) continue; @@ -755,10 +767,10 @@ EXPORT_SYMBOL_GPL(snd_hda_jack_unsol_event); */ void snd_hda_jack_poll_all(struct hda_codec *codec) { - struct hda_jack_tbl *jack = codec->jacktbl.list; - int i, changes = 0; + struct hda_jack_tbl *jack; + int changes = 0; - for (i = 0; i < codec->jacktbl.used; i++, jack++) { + for (jack = codec->jacktbl; jack; jack = jack->next) { unsigned int old_sense; if (!jack->nid || !jack->jack_dirty || jack->phantom_jack) continue; diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c index 505cc97e0ee9..04b261dde22e 100644 --- a/sound/hda/controllers/acpi.c +++ b/sound/hda/controllers/acpi.c @@ -266,6 +266,38 @@ static void hda_acpi_shutdown(struct platform_device *pdev) azx_stop_chip(chip); } +static int hda_acpi_runtime_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + /* enable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) | + STATESTS_INT_MASK); + + azx_stop_chip(chip); + azx_enter_link_reset(chip); + } + + return 0; +} + +static int hda_acpi_runtime_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct azx *chip = card->private_data; + + if (chip && chip->running) { + azx_init_chip(chip, 1); + /* disable controller wake up event */ + azx_writew(chip, WAKEEN, azx_readw(chip, WAKEEN) & + ~STATESTS_INT_MASK); + } + + return 0; +} + static int hda_acpi_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); @@ -274,6 +306,12 @@ static int hda_acpi_suspend(struct device *dev) rc = pm_runtime_force_suspend(dev); if (rc < 0) return rc; + /* + * Runtime PM is never enabled for this device, so the force_suspend + * above does not reach the runtime callback; stop the controller + * explicitly. + */ + hda_acpi_runtime_suspend(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); return 0; @@ -287,6 +325,8 @@ static int hda_acpi_resume(struct device *dev) rc = pm_runtime_force_resume(dev); if (rc < 0) return rc; + /* See hda_acpi_suspend(): re-init the controller explicitly. */ + hda_acpi_runtime_resume(dev); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; @@ -294,6 +334,7 @@ static int hda_acpi_resume(struct device *dev) static const struct dev_pm_ops hda_acpi_pm = { SYSTEM_SLEEP_PM_OPS(hda_acpi_suspend, hda_acpi_resume) + RUNTIME_PM_OPS(hda_acpi_runtime_suspend, hda_acpi_runtime_resume, NULL) }; static const struct hda_data nvidia_hda_data = { diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 24015c73a67f..6b80b59b8743 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -1184,7 +1184,7 @@ static const struct dev_pm_ops azx_pm = { }; -static int azx_probe_continue(struct azx *chip); +static int azx_probe_continue(struct azx *chip, bool free_on_error); #ifdef SUPPORT_VGA_SWITCHEROO static struct pci_dev *get_bound_vga(struct pci_dev *pci); @@ -1211,8 +1211,10 @@ static void azx_vs_set_state(struct pci_dev *pci, if (!disabled) { dev_info(chip->card->dev, "Start delayed initialization\n"); - if (azx_probe_continue(chip) < 0) + if (azx_probe_continue(chip, false) < 0) { + hda->init_failed = 1; dev_err(chip->card->dev, "initialization error\n"); + } } } else { dev_info(chip->card->dev, "%s via vga_switcheroo\n", @@ -1251,8 +1253,6 @@ static bool azx_vs_can_switch(struct pci_dev *pci) struct hda_intel *hda = container_of(chip, struct hda_intel, chip); wait_for_completion(&hda->probe_wait); - if (hda->init_failed) - return false; if (chip->disabled || !hda->probe_continued) return true; if (snd_hda_lock_devices(&chip->bus)) @@ -1380,7 +1380,6 @@ static void azx_free(struct azx *chip) * actually stop the chip) to allow GPU to suspend via * vga_switcheroo, and print a warning. */ - dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); pm_runtime_disable(&pci->dev); pm_runtime_set_suspended(&pci->dev); pm_runtime_enable(&pci->dev); @@ -1738,7 +1737,12 @@ static void azx_check_snoop_available(struct azx *chip) static void azx_probe_work(struct work_struct *work) { struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work); - azx_probe_continue(&hda->chip); + + if (azx_probe_continue(&hda->chip, false) < 0) { + hda->init_failed = 1; + complete_all(&hda->probe_wait); + snd_card_disconnect(hda->chip.card); + } } static int default_bdl_pos_adj(struct azx *chip) @@ -2356,7 +2360,7 @@ static const unsigned int azx_max_codecs[AZX_NUM_DRIVERS] = { [AZX_DRIVER_TERA] = 1, }; -static int azx_probe_continue(struct azx *chip) +static int azx_probe_continue(struct azx *chip, bool free_on_error) { struct hda_intel *hda = container_of(chip, struct hda_intel, chip); struct hdac_bus *bus = azx_bus(chip); @@ -2447,8 +2451,12 @@ static int azx_probe_continue(struct azx *chip) out_free: if (err < 0) { - pci_set_drvdata(pci, NULL); - snd_card_free(chip->card); + if (hda->vga_switcheroo_registered) + dev_warn(&pci->dev, "GPU sound probed, but not operational: please add a quirk to driver_denylist\n"); + if (free_on_error) { + pci_set_drvdata(pci, NULL); + snd_card_free(chip->card); + } return err; } @@ -2481,12 +2489,17 @@ static void azx_remove(struct pci_dev *pci) static void azx_shutdown(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); + struct hda_intel *hda; struct azx *chip; if (!card) return; chip = card->private_data; - if (chip && chip->running) + if (!chip) + return; + hda = container_of(chip, struct hda_intel, chip); + cancel_delayed_work_sync(&hda->probe_work); + if (chip->running) __azx_shutdown_chip(chip, true); } diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 81498f1e413e..02462bd035e7 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -21,7 +21,7 @@ static const struct hdac_bus_ops default_ops = { }; /** - * snd_hdac_bus_init - initialize a HD-audio bas bus + * snd_hdac_bus_init - initialize a HD-audio bus device * @bus: the pointer to bus object * @dev: device pointer * @ops: bus verb operators @@ -121,11 +121,15 @@ int snd_hdac_bus_exec_verb_unlocked(struct hdac_bus *bus, unsigned int addr, break; /* process pending verbs */ err = bus->ops->get_response(bus, addr, &tmp); - if (err) + if (err) { + snd_hdac_bus_reset_response_counter(bus, addr); break; + } } if (!err && res) { err = bus->ops->get_response(bus, addr, res); + if (err) + snd_hdac_bus_reset_response_counter(bus, addr); trace_hda_get_response(bus, addr, *res); } return err; @@ -180,18 +184,40 @@ 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->registered) + if (!codec || !codec->registered || codec->unsol_disabled) + continue; + if (!codec->dev.driver) continue; - spin_unlock_irq(&bus->reg_lock); drv = drv_to_hdac_driver(codec->dev.driver); - if (drv->unsol_event) - drv->unsol_event(codec, res); + if (!drv->unsol_event) + continue; + spin_unlock_irq(&bus->reg_lock); + drv->unsol_event(codec, res); spin_lock_irq(&bus->reg_lock); } spin_unlock_irq(&bus->reg_lock); } /** + * snd_hdac_device_disable_unsol - block and flush unsol events for the codec + * @codec: the HDA core device + * + * Stop dispatching the unsolicited events to the given codec, and wait for + * the pending unsol event handler to finish. Called at unbinding the codec + * driver before releasing the driver resources. + */ +void snd_hdac_device_disable_unsol(struct hdac_device *codec) +{ + struct hdac_bus *bus = codec->bus; + + spin_lock_irq(&bus->reg_lock); + codec->unsol_disabled = true; + spin_unlock_irq(&bus->reg_lock); + flush_work(&bus->unsol_work); +} +EXPORT_SYMBOL_GPL(snd_hdac_device_disable_unsol); + +/** * snd_hdac_bus_add_device - Add a codec to bus * @bus: HDA core bus * @codec: HDA core device to add diff --git a/sound/hda/core/component.c b/sound/hda/core/component.c index 04755903880e..83b31186b027 100644 --- a/sound/hda/core/component.c +++ b/sound/hda/core/component.c @@ -70,10 +70,7 @@ void snd_hdac_display_power(struct hdac_bus *bus, unsigned int idx, bool enable) dev_dbg(bus->dev, "display power %s\n", str_enable_disable(enable)); guard(mutex)(&bus->lock); - if (enable) - set_bit(idx, &bus->display_power_status); - else - clear_bit(idx, &bus->display_power_status); + assign_bit(idx, &bus->display_power_status, enable); if (!acomp || !acomp->ops) return; diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 78855ac357c6..a7acd3418ae8 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -396,6 +396,25 @@ int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, } EXPORT_SYMBOL_GPL(snd_hdac_bus_get_response); +/** + * snd_hdac_bus_reset_response_counter - reset the pending response count + * @bus: HD-audio core bus + * @addr: codec address + * + * Drop the number of commands that are still waiting for a response from + * the given codec address. This is called when a verb is known to be dead, + * e.g. after a response timeout, so that a lost response won't leave the + * counter non-zero forever and block all later verbs to the same codec. + */ +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, unsigned int addr) +{ + if (addr >= HDA_MAX_CODECS) + return; + + guard(spinlock_irq)(&bus->reg_lock); + bus->rirb.cmds[addr] = 0; +} + #define HDAC_MAX_CAPS 10 /** * snd_hdac_bus_parse_capabilities - parse capability structure @@ -781,9 +800,6 @@ EXPORT_SYMBOL_GPL(snd_hdac_bus_free_stream_pages); */ void snd_hdac_bus_link_power(struct hdac_device *codec, bool enable) { - if (enable) - set_bit(codec->addr, &codec->bus->codec_powered); - else - clear_bit(codec->addr, &codec->bus->codec_powered); + assign_bit(codec->addr, &codec->bus->codec_powered, enable); } EXPORT_SYMBOL_GPL(snd_hdac_bus_link_power); diff --git a/sound/hda/core/device.c b/sound/hda/core/device.c index a45f61e12c1a..6187ffaedbfa 100644 --- a/sound/hda/core/device.c +++ b/sound/hda/core/device.c @@ -572,14 +572,12 @@ EXPORT_SYMBOL_GPL(snd_hdac_power_up); /** * snd_hdac_power_down - power down the codec * @codec: the codec object - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down(struct hdac_device *codec) +void snd_hdac_power_down(struct hdac_device *codec) { struct device *dev = &codec->dev; - return pm_runtime_put_autosuspend(dev); + pm_runtime_put_autosuspend(dev); } EXPORT_SYMBOL_GPL(snd_hdac_power_down); @@ -624,14 +622,11 @@ int snd_hdac_keep_power_up(struct hdac_device *codec) * * Like snd_hdac_power_up_pm(), this function is used in a recursive * code path like init code which may be called by PM suspend/resume again. - * - * Returns zero if successful, or a negative error code. */ -int snd_hdac_power_down_pm(struct hdac_device *codec) +void snd_hdac_power_down_pm(struct hdac_device *codec) { if (atomic_dec_if_positive(&codec->in_pm) < 0) - return snd_hdac_power_down(codec); - return 0; + snd_hdac_power_down(codec); } EXPORT_SYMBOL_GPL(snd_hdac_power_down_pm); #endif diff --git a/sound/hda/core/local.h b/sound/hda/core/local.h index 5f03b203c416..b9e58395decb 100644 --- a/sound/hda/core/local.h +++ b/sound/hda/core/local.h @@ -16,6 +16,8 @@ int snd_hdac_bus_add_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_remove_device(struct hdac_bus *bus, struct hdac_device *codec); void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); +void snd_hdac_bus_reset_response_counter(struct hdac_bus *bus, + unsigned int addr); int snd_hdac_bus_exec_verb(struct hdac_bus *bus, unsigned int addr, unsigned int cmd, unsigned int *res); diff --git a/sound/i2c/other/ak4113.c b/sound/i2c/other/ak4113.c index 6ef5fffda8d7..9405d985a87d 100644 --- a/sound/i2c/other/ak4113.c +++ b/sound/i2c/other/ak4113.c @@ -130,7 +130,7 @@ void snd_ak4113_reinit(struct ak4113 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4113_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4114.c b/sound/i2c/other/ak4114.c index 71efb29b6c7c..4c3dbbf8c316 100644 --- a/sound/i2c/other/ak4114.c +++ b/sound/i2c/other/ak4114.c @@ -135,7 +135,7 @@ void snd_ak4114_reinit(struct ak4114 *chip) scoped_guard(mutex, &chip->reinit_mutex) { ak4114_init_regs(chip); } - /* bring up statistics / event queing */ + /* bring up statistics / event queuing */ if (atomic_dec_and_test(&chip->wq_processing)) schedule_delayed_work(&chip->work, HZ / 10); } diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index 9dd36b82a6ac..e7410f0f8660 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -147,7 +147,7 @@ EXPORT_SYMBOL(snd_akm4xxx_reset); * Volume conversion table for non-linear volumes * from -63.5dB (mute) to 0dB step 0.5dB * - * Used for AK4524/AK4620 input/ouput attenuation, AK4528, and + * Used for AK4524/AK4620 input/output attenuation, AK4528, and * AK5365 input attenuation */ static const unsigned char vol_cvt_datt[128] = { diff --git a/sound/isa/galaxy/galaxy.c b/sound/isa/galaxy/galaxy.c index b0f1562d0fc0..aa31744ed582 100644 --- a/sound/isa/galaxy/galaxy.c +++ b/sound/isa/galaxy/galaxy.c @@ -478,11 +478,11 @@ static void snd_galaxy_free(struct snd_card *card) galaxy_set_config(galaxy, galaxy->config); } -static int __snd_galaxy_probe(struct device *dev, unsigned int n) +static int snd_galaxy_probe(struct device *dev, unsigned int n) { struct snd_galaxy *galaxy; struct snd_wss *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; u8 type; int err; @@ -595,14 +595,10 @@ static int __snd_galaxy_probe(struct device *dev, unsigned int n) return err; dev_set_drvdata(dev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_galaxy_probe(struct device *dev, unsigned int n) -{ - return snd_card_free_on_error(dev, __snd_galaxy_probe(dev, n)); -} - static struct isa_driver snd_galaxy_driver = { .match = snd_galaxy_match, .probe = snd_galaxy_probe, diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c index abaa3ed3ab5c..8a6db40ff893 100644 --- a/sound/isa/opti9xx/opti92x-ad1848.c +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -378,7 +378,7 @@ static int snd_opti9xx_configure(struct snd_opti9xx *chip, */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); /* - * This bit sets OPTI931 to automaticaly select FM + * This bit sets OPTI931 to automatically select FM * or digital input signal. */ snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index cd3a63c7c2a7..f75533fe03a4 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -572,14 +572,14 @@ static void snd_sc6000_free(struct snd_card *card) sc6000_setup_board(card->dev, sc6000->vport, 0); } -static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) +static int snd_sc6000_probe(struct device *devptr, unsigned int dev) { static const int possible_irqs[] = { 5, 7, 9, 10, 11, -1 }; static const int possible_dmas[] = { 1, 3, 0, -1 }; int err; int xirq = irq[dev]; int xdma = dma[dev]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; @@ -696,14 +696,10 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) return err; dev_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sc6000_probe(struct device *devptr, unsigned int dev) -{ - return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); -} - #ifdef CONFIG_PM static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, pm_message_t state) diff --git a/sound/mips/hal2.c b/sound/mips/hal2.c index 2beb6a0dc3b7..6decbaa5800e 100644 --- a/sound/mips/hal2.c +++ b/sound/mips/hal2.c @@ -375,7 +375,7 @@ static void hal2_setup_dac(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECTX); /* Setup the HAL2 for playback */ hal2_set_dac_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECTX); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -400,7 +400,7 @@ static void hal2_setup_adc(struct snd_hal2 *hal2) hal2_i_clearbit16(hal2, H2I_DMA_PORT_EN, H2I_DMA_PORT_EN_CODECR); /* Setup the HAL2 for record */ hal2_set_adc_rate(hal2); - /* Set endianess */ + /* Set endianness */ hal2_i_clearbit16(hal2, H2I_DMA_END, H2I_DMA_END_CODECR); /* Set DMA bus */ hal2_i_setbit16(hal2, H2I_DMA_DRV, (1 << pbus->pbusnr)); @@ -837,30 +837,24 @@ static int hal2_create(struct snd_card *card, struct snd_hal2 **rchip) static int hal2_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_hal2 *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = hal2_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_pcm_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = hal2_mixer_create(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI HAL2 Audio"); strscpy(card->shortname, "SGI HAL2 Audio"); @@ -869,24 +863,16 @@ static int hal2_probe(struct platform_device *pdev) SGI_HPCDMA_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void hal2_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver hal2_driver = { .probe = hal2_probe, - .remove = hal2_remove, .driver = { .name = "sgihal2", } diff --git a/sound/mips/sgio2audio.c b/sound/mips/sgio2audio.c index 497d84cff12f..25c1871f72c1 100644 --- a/sound/mips/sgio2audio.c +++ b/sound/mips/sgio2audio.c @@ -742,7 +742,8 @@ static struct { /* ALSA driver */ -static int snd_sgio2audio_free(struct snd_sgio2audio *chip) +static int snd_sgio2audio_free(struct snd_sgio2audio *chip, + unsigned int irq_count) { int i; @@ -752,7 +753,7 @@ static int snd_sgio2audio_free(struct snd_sgio2audio *chip) writeq(0, &mace->perif.audio.control); /* release IRQ's */ - for (i = 0; i < ARRAY_SIZE(snd_sgio2_isr_table); i++) + for (i = 0; i < irq_count; i++) free_irq(snd_sgio2_isr_table[i].irq, &chip->channel[snd_sgio2_isr_table[i].idx]); @@ -768,7 +769,7 @@ static int snd_sgio2audio_dev_free(struct snd_device *device) { struct snd_sgio2audio *chip = device->device_data; - return snd_sgio2audio_free(chip); + return snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); } static const struct snd_device_ops ops = { @@ -819,7 +820,7 @@ static int snd_sgio2audio_create(struct snd_card *card, 0, snd_sgio2_isr_table[i].desc, &chip->channel[snd_sgio2_isr_table[i].idx])) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, i); printk(KERN_ERR "sgio2audio: cannot allocate irq %d\n", snd_sgio2_isr_table[i].irq); return -EBUSY; @@ -843,13 +844,13 @@ static int snd_sgio2audio_create(struct snd_card *card, /* initialize the AD1843 codec */ err = ad1843_init(&chip->ad1843); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); if (err < 0) { - snd_sgio2audio_free(chip); + snd_sgio2audio_free(chip, ARRAY_SIZE(snd_sgio2_isr_table)); return err; } *rchip = chip; @@ -858,30 +859,24 @@ static int snd_sgio2audio_create(struct snd_card *card, static int snd_sgio2audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_sgio2audio *chip; int err; - err = snd_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_sgio2audio_create(card, &chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_pcm(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } err = snd_sgio2audio_new_mixer(chip); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } strscpy(card->driver, "SGI O2 Audio"); strscpy(card->shortname, "SGI O2 Audio"); @@ -891,24 +886,16 @@ static int snd_sgio2audio_probe(struct platform_device *pdev) MACEISA_AUDIO3_MERR_IRQ); err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } + platform_set_drvdata(pdev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static void snd_sgio2audio_remove(struct platform_device *pdev) -{ - struct snd_card *card = platform_get_drvdata(pdev); - - snd_card_free(card); -} - static struct platform_driver sgio2audio_driver = { .probe = snd_sgio2audio_probe, - .remove = snd_sgio2audio_remove, .driver = { .name = "sgio2audio", } diff --git a/sound/mips/snd-n64.c b/sound/mips/snd-n64.c index f17e63f2ff5a..da889570cebc 100644 --- a/sound/mips/snd-n64.c +++ b/sound/mips/snd-n64.c @@ -273,6 +273,15 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { .close = n64audio_pcm_close, }; +static void n64audio_free(struct snd_card *card) +{ + struct n64audio *priv = card->private_data; + + if (priv->ring_base) + dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, + priv->ring_base_dma); +} + /* * The target device is embedded and RAM-constrained. We save RAM * by initializing in __init code that gets dropped late in boot. @@ -280,14 +289,14 @@ static const struct snd_pcm_ops n64audio_pcm_ops = { */ static int __init n64audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pcm *pcm; struct n64audio *priv; int err, irq; - err = snd_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, - SNDRV_DEFAULT_STR1, - THIS_MODULE, sizeof(*priv), &card); + err = snd_devm_card_new(&pdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(*priv), &card); if (err < 0) return err; @@ -296,29 +305,24 @@ static int __init n64audio_probe(struct platform_device *pdev) spin_lock_init(&priv->chan.lock); priv->card = card; + card->private_free = n64audio_free; priv->ring_base = dma_alloc_coherent(card->dev, 32 * 1024, &priv->ring_base_dma, - GFP_DMA|GFP_KERNEL); - if (!priv->ring_base) { - err = -ENOMEM; - goto fail_card; - } + GFP_KERNEL); + if (!priv->ring_base) + return -ENOMEM; priv->mi_reg_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(priv->mi_reg_base)) { - err = PTR_ERR(priv->mi_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->mi_reg_base)) + return PTR_ERR(priv->mi_reg_base); priv->ai_reg_base = devm_platform_ioremap_resource(pdev, 1); - if (IS_ERR(priv->ai_reg_base)) { - err = PTR_ERR(priv->ai_reg_base); - goto fail_dma_alloc; - } + if (IS_ERR(priv->ai_reg_base)) + return PTR_ERR(priv->ai_reg_base); err = snd_pcm_new(card, "N64 Audio", 0, 1, 0, &pcm); if (err < 0) - goto fail_dma_alloc; + return err; pcm->private_data = priv; strscpy(pcm->name, "N64 Audio"); @@ -331,28 +335,19 @@ static int __init n64audio_probe(struct platform_device *pdev) strscpy(card->longname, "N64 Audio"); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - err = -EINVAL; - goto fail_dma_alloc; - } + if (irq < 0) + return -EINVAL; + if (devm_request_irq(&pdev->dev, irq, n64audio_isr, - IRQF_SHARED, "N64 Audio", priv)) { - err = -EBUSY; - goto fail_dma_alloc; - } + IRQF_SHARED, "N64 Audio", priv)) + return -EBUSY; err = snd_card_register(card); if (err < 0) - goto fail_dma_alloc; + return err; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -fail_dma_alloc: - dma_free_coherent(card->dev, 32 * 1024, priv->ring_base, priv->ring_base_dma); - -fail_card: - snd_card_free(card); - return err; } static struct platform_driver n64audio_driver = { diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c index e25a78dd1bf2..339311e5d807 100644 --- a/sound/oss/dmasound/dmasound_q40.c +++ b/sound/oss/dmasound/dmasound_q40.c @@ -49,9 +49,8 @@ static int Q40SetFormat(int format); static int Q40SetVolume(int volume); static void Q40PlayNextFrame(int index); static void Q40Play(void); -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); -static void Q40Interrupt(void); +static irqreturn_t q40_audio_interrupt(int irq, void *dummy); +static void q40_frame_done(void); /*** Mid level stuff *********************************************************/ @@ -372,8 +371,8 @@ static void Q40Free(void *ptr, unsigned int size) static int __init Q40IrqInit(void) { /* Register interrupt handler. */ - if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "DMA sound", Q40Interrupt)) + if (request_irq(Q40_IRQ_SAMPLE, q40_audio_interrupt, 0, + "DMA sound", q40_frame_done)) return 0; return(1); @@ -384,7 +383,7 @@ static int __init Q40IrqInit(void) static void Q40IrqCleanUp(void) { master_outb(0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + free_irq(Q40_IRQ_SAMPLE, q40_frame_done); } #endif /* MODULE */ @@ -403,7 +402,6 @@ static void Q40PlayNextFrame(int index) u_char *start; u_long size; u_char speed; - int error; /* used by Q40Play() if all doubts whether there really is something * to be played are already wiped out. @@ -420,15 +418,6 @@ static void Q40PlayNextFrame(int index) speed=(dmasound.hard.speed==10000 ? 0 : 1); master_outb( 0,SAMPLE_ENABLE_REG); - free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); - if (dmasound.soft.stereo) - error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, - "Q40 sound", Q40Interrupt); - else - error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, - "Q40 sound", Q40Interrupt); - if (error && printk_ratelimit()) - pr_err("Couldn't register sound interrupt\n"); master_outb( speed, SAMPLE_RATE_REG); master_outb( 1,SAMPLE_CLEAR_REG); @@ -456,31 +445,35 @@ static void Q40Play(void) spin_unlock_irqrestore(&dmasound.lock, flags); } -static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) -{ - spin_lock(&dmasound.lock); - if (q40_sc>1){ - *DAC_LEFT=*q40_pp++; - *DAC_RIGHT=*q40_pp++; - q40_sc -=2; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); - spin_unlock(&dmasound.lock); - return IRQ_HANDLED; -} -static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) +static irqreturn_t q40_audio_interrupt(int irq, void *dummy) { + bool frame_done = false; + spin_lock(&dmasound.lock); - if (q40_sc>0){ - *DAC_LEFT=*q40_pp; - *DAC_RIGHT=*q40_pp++; - q40_sc --; - master_outb(1,SAMPLE_CLEAR_REG); - }else Q40Interrupt(); + if (dmasound.hard.stereo) { + if (q40_sc > 1) { + *DAC_LEFT = *q40_pp++; + *DAC_RIGHT = *q40_pp++; + q40_sc -= 2; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } + } else if (q40_sc > 0) { + *DAC_LEFT = *q40_pp; + *DAC_RIGHT = *q40_pp++; + q40_sc--; + master_outb(1, SAMPLE_CLEAR_REG); + } else { + frame_done = true; + } spin_unlock(&dmasound.lock); + if (frame_done) + q40_frame_done(); return IRQ_HANDLED; } -static void Q40Interrupt(void) + +static void q40_frame_done(void) { if (!write_sq.active) { /* playing was interrupted and sq_reset() has already cleared diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c index a9625aedf2a4..6cdd09f5855b 100644 --- a/sound/parisc/harmony.c +++ b/sound/parisc/harmony.c @@ -897,24 +897,24 @@ static int __init snd_harmony_probe(struct parisc_device *padev) { int err; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_harmony *h; - err = snd_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&padev->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_harmony_create(card, padev, &h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_pcm_init(h); if (err < 0) - goto free_and_ret; + return err; err = snd_harmony_mixer_init(h); if (err < 0) - goto free_and_ret; + return err; strscpy(card->driver, "harmony"); strscpy(card->shortname, "Harmony"); @@ -923,27 +923,17 @@ snd_harmony_probe(struct parisc_device *padev) err = snd_card_register(card); if (err < 0) - goto free_and_ret; + return err; parisc_set_drvdata(padev, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -free_and_ret: - snd_card_free(card); - return err; -} - -static void __exit -snd_harmony_remove(struct parisc_device *padev) -{ - snd_card_free(parisc_get_drvdata(padev)); } static struct parisc_driver snd_harmony_driver __refdata = { .name = "harmony", .id_table = snd_harmony_devtable, .probe = snd_harmony_probe, - .remove = __exit_p(snd_harmony_remove), }; static int __init diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 0bb65be021d9..207ce083593f 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2960,7 +2960,7 @@ int snd_ac97_tune_hardware(struct snd_ac97 *ac97, { int result; - /* quirk overriden? */ + /* quirk overridden? */ if (override && strcmp(override, "-1") && strcmp(override, "default")) { result = apply_quirk_str(ac97, override); if (result < 0) diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c index 64cc39dd2008..8577a2e0510c 100644 --- a/sound/pci/ac97/ac97_patch.c +++ b/sound/pci/ac97/ac97_patch.c @@ -430,7 +430,7 @@ static int patch_yamaha_ymf753(struct snd_ac97 * ac97) /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com. This chip has nonstandard and extended behaviour with regard to its S/PDIF output. The AC'97 spec states that the S/PDIF signal is to be output at pin 48. - The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48. + The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48. By default, no output pin is selected, and the S/PDIF signal is not output. There is also a bit to mute S/PDIF output in a vendor-specific register. */ diff --git a/sound/pci/ad1889.c b/sound/pci/ad1889.c index f4ec404c0d15..42a8fa3f4fdc 100644 --- a/sound/pci/ad1889.c +++ b/sound/pci/ad1889.c @@ -837,12 +837,12 @@ snd_ad1889_create(struct snd_card *card, struct pci_dev *pci) } static int -__snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_ad1889_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { int err; static int devno; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ad1889 *chip; /* (1) */ @@ -892,17 +892,12 @@ __snd_ad1889_probe(struct pci_dev *pci, /* (7) */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ devno++; return 0; } -static int snd_ad1889_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ad1889_probe(pci, pci_id)); -} - static const struct pci_device_id snd_ad1889_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_ANALOG_DEVICES, PCI_DEVICE_ID_AD1889JS) }, { 0, }, diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index f07c5f4c2380..8295eee9b977 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -2086,10 +2086,10 @@ static int snd_ali_create(struct snd_card *card, return 0; } -static int __snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_ali_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ali *codec; int err; @@ -2129,15 +2129,10 @@ static int __snd_ali_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_ali_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ali_probe(pci, pci_id)); -} - static struct pci_driver ali5451_driver = { .name = KBUILD_MODNAME, .id_table = snd_ali_ids, diff --git a/sound/pci/als300.c b/sound/pci/als300.c index 00f0720169c3..5832f4d38c4e 100644 --- a/sound/pci/als300.c +++ b/sound/pci/als300.c @@ -675,7 +675,7 @@ static int snd_als300_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_als300 *chip; int err, chip_type; @@ -696,7 +696,7 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_als300_create(card, pci, chip_type); if (err < 0) - goto error; + return err; strscpy(card->driver, "ALS300"); if (chip->chip_type == DEVICE_ALS300_PLUS) @@ -711,15 +711,12 @@ static int snd_als300_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver als300_driver = { diff --git a/sound/pci/als4000.c b/sound/pci/als4000.c index 6396aa6c3bf0..12bb868fb9ce 100644 --- a/sound/pci/als4000.c +++ b/sound/pci/als4000.c @@ -798,11 +798,11 @@ static void snd_card_als4000_free( struct snd_card *card ) snd_als4000_free_gameport(acard); } -static int __snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_als4000_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_card_als4000 *acard; unsigned long iobase; struct snd_sb *chip; @@ -918,16 +918,11 @@ static int __snd_card_als4000_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_als4000_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_als4000_probe(pci, pci_id)); -} - static int snd_als4000_suspend(struct device *dev) { struct snd_card *card = dev_get_drvdata(dev); diff --git a/sound/pci/atiixp.c b/sound/pci/atiixp.c index b738295b41e5..e1d36cd7945b 100644 --- a/sound/pci/atiixp.c +++ b/sound/pci/atiixp.c @@ -1550,10 +1550,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp *chip; int err; @@ -1598,15 +1598,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/atiixp_modem.c b/sound/pci/atiixp_modem.c index 8aaeb197ce45..4984a345b4b2 100644 --- a/sound/pci/atiixp_modem.c +++ b/sound/pci/atiixp_modem.c @@ -1182,10 +1182,10 @@ static int snd_atiixp_init(struct snd_card *card, struct pci_dev *pci) } -static int __snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_atiixp_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct atiixp_modem *chip; int err; @@ -1225,15 +1225,10 @@ static int __snd_atiixp_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_atiixp_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_atiixp_probe(pci, pci_id)); -} - static struct pci_driver atiixp_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_atiixp_ids, diff --git a/sound/pci/au88x0/au88x0.c b/sound/pci/au88x0/au88x0.c index bb02945793f0..87a24f0c86e7 100644 --- a/sound/pci/au88x0/au88x0.c +++ b/sound/pci/au88x0/au88x0.c @@ -192,10 +192,10 @@ snd_vortex_create(struct snd_card *card, struct pci_dev *pci) // constructor -- see "Constructor" sub-section static int -__snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; vortex_t *chip; int err; @@ -303,18 +303,13 @@ __snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) return err; // (7) pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; vortex_connect_default(chip, 1); vortex_enable_int(chip); return 0; } -static int -snd_vortex_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vortex_probe(pci, pci_id)); -} - // pci_driver definition static struct pci_driver vortex_driver = { .name = KBUILD_MODNAME, diff --git a/sound/pci/au88x0/au88x0.h b/sound/pci/au88x0/au88x0.h index 6cbb2bc4a048..f97a0303da3e 100644 --- a/sound/pci/au88x0/au88x0.h +++ b/sound/pci/au88x0/au88x0.h @@ -28,7 +28,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/au88x0/au88x0_mpu401.c b/sound/pci/au88x0/au88x0_mpu401.c index 164f6b7039ab..dff4bf80ab96 100644 --- a/sound/pci/au88x0/au88x0_mpu401.c +++ b/sound/pci/au88x0/au88x0_mpu401.c @@ -22,7 +22,7 @@ /* Vortex MPU401 defines. */ #define MIDI_CLOCK_DIV 0x61 -/* Standart MPU401 defines. */ +/* Standard MPU401 defines. */ #define MPU401_RESET 0xff #define MPU401_ENTER_UART 0x3f #define MPU401_ACK 0xfe diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c index 60a87322eae4..513249b9bb78 100644 --- a/sound/pci/aw2/aw2-alsa.c +++ b/sound/pci/aw2/aw2-alsa.c @@ -251,7 +251,7 @@ static int snd_aw2_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct aw2 *chip; int err; @@ -273,7 +273,7 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (3) Create main component */ err = snd_aw2_create(card, pci); if (err < 0) - goto error; + return err; /* initialize mutex */ mutex_init(&chip->mtx); @@ -292,17 +292,14 @@ static int snd_aw2_probe(struct pci_dev *pci, /* (6) Register card instance */ err = snd_card_register(card); if (err < 0) - goto error; + return err; /* (7) Set PCI driver data */ pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* open callback */ diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c index c84f1a45194f..6857a0ec2924 100644 --- a/sound/pci/aw2/aw2-saa7146.c +++ b/sound/pci/aw2/aw2-saa7146.c @@ -147,7 +147,7 @@ void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ @@ -204,7 +204,7 @@ void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip, unsigned long dw_page, dw_limit; /* Configure DMA for substream - Configuration informations: ALSA has allocated continuous memory + Configuration information: ALSA has allocated continuous memory pages. So we don't need to use MMU of saa7146. */ diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index ccca82417657..8cf94c41a54c 100644 --- a/sound/pci/azt3328.c +++ b/sound/pci/azt3328.c @@ -2403,10 +2403,10 @@ snd_azf3328_create(struct snd_card *card, } static int -__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_azf3328 *chip; struct snd_opl3 *opl3; int err; @@ -2492,16 +2492,11 @@ __snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) snd_azf3328_gameport(chip, dev); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_azf3328_probe(pci, pci_id)); -} - static inline void snd_azf3328_suspend_regs(const struct snd_azf3328 *chip, unsigned long io_addr, unsigned count, u32 *saved_regs) diff --git a/sound/pci/bt87x.c b/sound/pci/bt87x.c index 383def1f2af7..db8f60261a0f 100644 --- a/sound/pci/bt87x.c +++ b/sound/pci/bt87x.c @@ -798,11 +798,11 @@ static int snd_bt87x_detect_card(struct pci_dev *pci) return SND_BT87X_BOARD_UNKNOWN; } -static int __snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_bt87x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_bt87x *chip; int err; enum snd_bt87x_boardid boardid; @@ -878,16 +878,11 @@ static int __snd_bt87x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ ++dev; return 0; } -static int snd_bt87x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_bt87x_probe(pci, pci_id)); -} - /* default entries for all Bt87x cards - it's not exported */ /* driver_data is set to 0 to call detection */ static const struct pci_device_id snd_bt87x_default_ids[] = { diff --git a/sound/pci/ca0106/ca0106_main.c b/sound/pci/ca0106/ca0106_main.c index cf923f551211..6429c2bfc310 100644 --- a/sound/pci/ca0106/ca0106_main.c +++ b/sound/pci/ca0106/ca0106_main.c @@ -1710,11 +1710,11 @@ static int snd_ca0106_midi(struct snd_ca0106 *chip, unsigned int channel) } -static int __snd_ca0106_probe(struct pci_dev *pci, +static int snd_ca0106_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ca0106 *chip; int i, err; @@ -1767,16 +1767,11 @@ static int __snd_ca0106_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_ca0106_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_ca0106_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_ca0106_suspend(struct device *dev) { diff --git a/sound/pci/cmipci.c b/sound/pci/cmipci.c index 9d9f784e3a8c..d120b7bbe024 100644 --- a/sound/pci/cmipci.c +++ b/sound/pci/cmipci.c @@ -3170,7 +3170,7 @@ static int snd_cmipci_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -3201,19 +3201,16 @@ static int snd_cmipci_probe(struct pci_dev *pci, err = snd_cmipci_create(card, pci, dev); if (err < 0) - goto error; + return err; err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } /* diff --git a/sound/pci/cs4281.c b/sound/pci/cs4281.c index f51f4bb63766..b831f1d50750 100644 --- a/sound/pci/cs4281.c +++ b/sound/pci/cs4281.c @@ -324,7 +324,7 @@ MODULE_PARM_DESC(dual_codec, "Secondary Codec ID (0 = disabled)."); #define BA0_SSCR_XLPSRC (1<<8) /* External SRC Loopback Mode */ #define BA0_SSCR_LPSRC (1<<7) /* SRC Loopback Mode */ #define BA0_SSCR_CDTX (1<<5) /* CD Transfer Data */ -#define BA0_SSCR_HVC (1<<3) /* Harware Volume Control Enable */ +#define BA0_SSCR_HVC (1<<3) /* Hardware Volume Control Enable */ #define BA0_FMLVC 0x0754 /* FM Synthesis Left Volume Control */ #define BA0_FMRVC 0x0758 /* FM Synthesis Right Volume Control */ @@ -1811,11 +1811,11 @@ static void snd_cs4281_opl3_command(struct snd_opl3 *opl3, unsigned short cmd, udelay(30); } -static int __snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs4281_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs4281 *chip; struct snd_opl3 *opl3; int err; @@ -1868,16 +1868,11 @@ static int __snd_cs4281_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs4281_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs4281_probe(pci, pci_id)); -} - /* * Power Management */ diff --git a/sound/pci/cs46xx/cs46xx.c b/sound/pci/cs46xx/cs46xx.c index 0cb7c2a2929f..a4a0b36290d9 100644 --- a/sound/pci/cs46xx/cs46xx.c +++ b/sound/pci/cs46xx/cs46xx.c @@ -55,7 +55,7 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_cs46xx *chip; int err; @@ -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) - goto error; + return err; card->private_data = chip; chip->accept_valid = mmap_valid[dev]; err = snd_cs46xx_pcm(chip, 0); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP err = snd_cs46xx_pcm_rear(chip, 1); if (err < 0) - goto error; + return err; err = snd_cs46xx_pcm_iec958(chip, 2); if (err < 0) - goto error; + return err; #endif err = snd_cs46xx_mixer(chip, 2); if (err < 0) - goto error; + return err; #ifdef CONFIG_SND_CS46XX_NEW_DSP if (chip->nr_ac97_codecs ==2) { err = snd_cs46xx_pcm_center_lfe(chip, 3); if (err < 0) - goto error; + return err; } #endif err = snd_cs46xx_midi(chip, 0); if (err < 0) - goto error; + return err; err = snd_cs46xx_start_dsp(chip); if (err < 0) - goto error; + return err; snd_cs46xx_gameport(chip); @@ -117,15 +117,12 @@ static int snd_card_cs46xx_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver cs46xx_driver = { diff --git a/sound/pci/cs46xx/cs46xx_lib.c b/sound/pci/cs46xx/cs46xx_lib.c index f6db08b75649..797a5f09cade 100644 --- a/sound/pci/cs46xx/cs46xx_lib.c +++ b/sound/pci/cs46xx/cs46xx_lib.c @@ -315,7 +315,7 @@ int snd_cs46xx_download(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(*src++, dst); dst += sizeof(u32); @@ -474,7 +474,7 @@ int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip, dst = chip->region.idx[bank+1].remap_addr + offset; len /= sizeof(u32); - /* writel already converts 32-bit value to right endianess */ + /* writel already converts 32-bit value to right endianness */ while (len-- > 0) { writel(0, dst); dst += sizeof(u32); @@ -3039,7 +3039,7 @@ static int snd_cs46xx_chip_init(struct snd_cs46xx *chip) #endif /* - * Assert the vaid frame signal so that we can start sending commands + * Assert the valid frame signal so that we can start sending commands * to the AC97 codec. */ snd_cs46xx_pokeBA0(chip, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index 6fd4f629d1d5..a77e6f5a14ad 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -393,7 +393,7 @@ int cs46xx_dsp_load_module (struct snd_cs46xx * chip, struct dsp_module_desc * m dev_dbg(chip->card->dev, "dsp_spos: module got no code segment\n"); } else { - if (ins->code.offset + code->size > DSP_CODE_BYTE_SIZE) { + if ((u64)ins->code.offset + code->size > DSP_CODE_BYTE_SIZE / 4) { dev_err(chip->card->dev, "dsp_spos: no space available in DSP\n"); return -ENOMEM; diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index fd19365026b4..71eee5cb478f 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -160,15 +160,15 @@ void cs46xx_dsp_remove_scb (struct snd_cs46xx *chip, struct dsp_scb_descriptor * { struct dsp_spos_instance * ins = chip->dsp_spos_instance; - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(scb->index < 0 || scb->index >= ins->nscb || (ins->scbs + scb->index) != scb)) return; #if 0 - /* can't remove a SCB with childs before - removing childs first */ + /* can't remove a SCB with children before + removing children first */ if (snd_BUG_ON(scb->sub_list_ptr != ins->the_null_scb || scb->next_scb_ptr != ins->the_null_scb)) goto _end; @@ -1656,7 +1656,7 @@ int cs46xx_dsp_disable_spdif_out (struct snd_cs46xx *chip) return -EBUSY; } - /* check integrety */ + /* check integrity */ if (snd_BUG_ON(!ins->asynch_tx_scb)) return -EINVAL; if (snd_BUG_ON(!ins->spdif_pcm_input_scb)) diff --git a/sound/pci/cs5535audio/cs5535audio.c b/sound/pci/cs5535audio/cs5535audio.c index 0ebf6c02b1ef..e09f7bc9e23a 100644 --- a/sound/pci/cs5535audio/cs5535audio.c +++ b/sound/pci/cs5535audio/cs5535audio.c @@ -283,11 +283,11 @@ static int snd_cs5535audio_create(struct snd_card *card, return 0; } -static int __snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_cs5535audio_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct cs5535audio *cs5535au; int err; @@ -329,16 +329,11 @@ static int __snd_cs5535audio_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_cs5535audio_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_cs5535audio_probe(pci, pci_id)); -} - static struct pci_driver cs5535audio_driver = { .name = KBUILD_MODNAME, .id_table = snd_cs5535audio_ids, diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index d8dd84d41c87..99319d75e963 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -56,7 +56,7 @@ static int ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ct_atc *atc; int err; @@ -67,8 +67,8 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) dev++; return -ENOENT; } - err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, - 0, &card); + err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); if (err) return err; if ((reference_rate != 48000) && (reference_rate != 44100)) { @@ -89,14 +89,14 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = ct_atc_create(card, pci, reference_rate, multiple, pci_id->driver_data, subsystem[dev], &atc); if (err < 0) - goto error; + return err; card->private_data = atc; /* Create alsa devices supported by this card */ err = ct_atc_create_alsa_devs(atc); if (err < 0) - goto error; + return err; strscpy(card->driver, "SB-XFi"); strscpy(card->shortname, "Creative X-Fi"); @@ -105,21 +105,13 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - -error: - snd_card_free(card); - return err; -} - -static void ct_card_remove(struct pci_dev *pci) -{ - snd_card_free(pci_get_drvdata(pci)); } #ifdef CONFIG_PM_SLEEP @@ -149,7 +141,6 @@ static struct pci_driver ct_driver = { .name = KBUILD_MODNAME, .id_table = ct_pci_dev_ids, .probe = ct_card_probe, - .remove = ct_card_remove, .driver = { .pm = CT_CARD_PM_OPS, }, diff --git a/sound/pci/echoaudio/echoaudio.c b/sound/pci/echoaudio/echoaudio.c index 214d68657707..4dcad05fd57e 100644 --- a/sound/pci/echoaudio/echoaudio.c +++ b/sound/pci/echoaudio/echoaudio.c @@ -270,7 +270,7 @@ static int pcm_open(struct snd_pcm_substream *substream, return -ENOMEM; pipe->index = -1; /* Not configured yet */ - /* Set up hw capabilities and contraints */ + /* Set up hw capabilities and constraints */ memcpy(&pipe->hw, &pcm_hardware_skel, sizeof(struct snd_pcm_hardware)); dev_dbg(chip->card->dev, "max_channels=%d\n", max_channels); pipe->constr.list = channels_list; @@ -1720,7 +1720,7 @@ static const struct snd_kcontrol_new snd_echo_vumeters = { -/*** Channels info - it exports informations about the number of channels ***/ +/*** Channels info - it exports information about the number of channels ***/ static int snd_echo_channels_info_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -1934,11 +1934,11 @@ static int snd_echo_create(struct snd_card *card, } /* constructor */ -static int __snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_echo_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct echoaudio *chip; char *dsp; int err; @@ -2097,17 +2097,11 @@ static int __snd_echo_probe(struct pci_dev *pci, dev_info(card->dev, "Card registered: %s\n", card->longname); pci_set_drvdata(pci, chip); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_echo_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_echo_probe(pci, pci_id)); -} - - static int snd_echo_suspend(struct device *dev) { struct echoaudio *chip = dev_get_drvdata(dev); diff --git a/sound/pci/echoaudio/echoaudio.h b/sound/pci/echoaudio/echoaudio.h index 7a5744bf3155..ac4507b7cc78 100644 --- a/sound/pci/echoaudio/echoaudio.h +++ b/sound/pci/echoaudio/echoaudio.h @@ -394,8 +394,8 @@ struct echoaudio { u16 digital_modes; /* Bitmask of supported modes * (see ECHOCAPS_HAS_DIGITAL_MODE_*) */ - u16 input_clock_types; /* Suppoted input clock types */ - u16 output_clock_types; /* Suppoted output clock types - + u16 input_clock_types; /* Supported input clock types */ + u16 output_clock_types; /* Supported output clock types - * Layla20 only */ u16 device_id, subdevice_id; u16 *dsp_code; /* Current DSP code loaded, diff --git a/sound/pci/emu10k1/emu10k1x.c b/sound/pci/emu10k1/emu10k1x.c index ed4630c8342b..03d277dd02d9 100644 --- a/sound/pci/emu10k1/emu10k1x.c +++ b/sound/pci/emu10k1/emu10k1x.c @@ -1447,11 +1447,11 @@ static int snd_emu10k1x_midi(struct emu10k1x *emu) return 0; } -static int __snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_emu10k1x_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct emu10k1x *chip; int err; @@ -1506,16 +1506,11 @@ static int __snd_emu10k1x_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_emu10k1x_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_emu10k1x_probe(pci, pci_id)); -} - // PCI IDs static const struct pci_device_id snd_emu10k1x_ids[] = { { PCI_VDEVICE(CREATIVE, 0x0006) }, /* Dell OEM version (EMU10K1) */ diff --git a/sound/pci/ens1370.c b/sound/pci/ens1370.c index 0b9eb55a2121..c2af7eab633c 100644 --- a/sound/pci/ens1370.c +++ b/sound/pci/ens1370.c @@ -2267,11 +2267,11 @@ static irqreturn_t snd_audiopci_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static int __snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_audiopci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct ensoniq *ensoniq; int err; @@ -2328,16 +2328,11 @@ static int __snd_audiopci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_audiopci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_audiopci_probe(pci, pci_id)); -} - static struct pci_driver ens137x_driver = { .name = KBUILD_MODNAME, .id_table = snd_audiopci_ids, diff --git a/sound/pci/es1938.c b/sound/pci/es1938.c index 217beb9376ac..a612368fd0b1 100644 --- a/sound/pci/es1938.c +++ b/sound/pci/es1938.c @@ -1683,11 +1683,11 @@ static int snd_es1938_mixer(struct es1938 *chip) } -static int __snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1938_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1938 *chip; struct snd_opl3 *opl3; int idx, err; @@ -1759,16 +1759,11 @@ static int __snd_es1938_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1938_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1938_probe(pci, pci_id)); -} - static struct pci_driver es1938_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1938_ids, diff --git a/sound/pci/es1968.c b/sound/pci/es1968.c index f04628c8cbb7..b0abbea2a992 100644 --- a/sound/pci/es1968.c +++ b/sound/pci/es1968.c @@ -2692,11 +2692,11 @@ static int snd_es1968_create(struct snd_card *card, /* */ -static int __snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_es1968_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct es1968 *chip; unsigned int i; int err; @@ -2795,16 +2795,11 @@ static int __snd_es1968_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_es1968_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_es1968_probe(pci, pci_id)); -} - static struct pci_driver es1968_driver = { .name = KBUILD_MODNAME, .id_table = snd_es1968_ids, diff --git a/sound/pci/fm801.c b/sound/pci/fm801.c index 9cc96b807dd7..507ea0fe22a9 100644 --- a/sound/pci/fm801.c +++ b/sound/pci/fm801.c @@ -1264,11 +1264,11 @@ static int snd_fm801_create(struct snd_card *card, return 0; } -static int __snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_fm801_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct fm801 *chip; struct snd_opl3 *opl3; int err; @@ -1325,16 +1325,11 @@ static int __snd_card_fm801_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_fm801_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_fm801_probe(pci, pci_id)); -} - static const unsigned char saved_regs[] = { FM801_PCM_VOL, FM801_I2S_VOL, FM801_FM_VOL, FM801_REC_SRC, FM801_PLY_CTRL, FM801_PLY_COUNT, FM801_PLY_BUF1, FM801_PLY_BUF2, diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 859bb87393b4..651d92663567 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2524,11 +2524,11 @@ static int snd_vt1724_create(struct snd_card *card, * */ -static int __snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_vt1724_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_ice1712 *ice; int pcm_dev = 0, err; const struct snd_ice1712_card_info *c; @@ -2663,16 +2663,11 @@ static int __snd_vt1724_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_vt1724_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id)); -} - #ifdef CONFIG_PM_SLEEP static int snd_vt1724_suspend(struct device *dev) { diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index e2ea9016a73e..339024182a26 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -3167,10 +3167,10 @@ static int check_default_spdif_aclink(struct pci_dev *pci) return 0; } -static int __snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0 *chip; int err; struct shortname_table *name; @@ -3244,13 +3244,9 @@ static int __snd_intel8x0_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); - return 0; -} + card = NULL; /* probe succeeded, don't release as error */ -static int snd_intel8x0_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0_probe(pci, pci_id)); + return 0; } static struct pci_driver intel8x0_driver = { diff --git a/sound/pci/intel8x0m.c b/sound/pci/intel8x0m.c index 27dbf6125427..38a15ebf94b2 100644 --- a/sound/pci/intel8x0m.c +++ b/sound/pci/intel8x0m.c @@ -1218,10 +1218,10 @@ static struct shortname_table { { 0 }, }; -static int __snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_intel8x0m_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct intel8x0m *chip; int err; struct shortname_table *name; @@ -1262,15 +1262,10 @@ static int __snd_intel8x0m_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_intel8x0m_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_intel8x0m_probe(pci, pci_id)); -} - static struct pci_driver intel8x0m_driver = { .name = KBUILD_MODNAME, .id_table = snd_intel8x0m_ids, diff --git a/sound/pci/korg1212/korg1212.c b/sound/pci/korg1212/korg1212.c index f4d8402f835f..bb442b556158 100644 --- a/sound/pci/korg1212/korg1212.c +++ b/sound/pci/korg1212/korg1212.c @@ -2216,7 +2216,7 @@ snd_korg1212_probe(struct pci_dev *pci, { static int dev; struct snd_korg1212 *korg1212; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -2234,7 +2234,7 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_korg1212_create(card, pci); if (err < 0) - goto error; + return err; strscpy(card->driver, "korg1212"); strscpy(card->shortname, "korg1212"); @@ -2245,14 +2245,11 @@ snd_korg1212_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver korg1212_driver = { diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c index 34a3ba17deb4..1f7ffca57fc9 100644 --- a/sound/pci/lola/lola.c +++ b/sound/pci/lola/lola.c @@ -641,11 +641,11 @@ static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) return 0; } -static int __lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lola *chip; int err; @@ -687,16 +687,11 @@ static int __lola_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int lola_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); -} - /* PCI IDs */ static const struct pci_device_id lola_ids[] = { { PCI_VDEVICE(DIGIGRAM, 0x0001) }, diff --git a/sound/pci/lx6464es/lx6464es.c b/sound/pci/lx6464es/lx6464es.c index 3b40ec071379..881f462fa929 100644 --- a/sound/pci/lx6464es/lx6464es.c +++ b/sound/pci/lx6464es/lx6464es.c @@ -981,7 +981,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct lx6464es *chip; int err; @@ -1003,7 +1003,7 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_lx6464es_create(card, pci); if (err < 0) { dev_err(card->dev, "error during snd_lx6464es_create\n"); - goto error; + return err; } strscpy(card->driver, "LX6464ES"); @@ -1020,16 +1020,13 @@ static int snd_lx6464es_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; dev_dbg(chip->card->dev, "initialization successful\n"); pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver lx6464es_driver = { diff --git a/sound/pci/lx6464es/lx_core.c b/sound/pci/lx6464es/lx_core.c index 9909f1067871..ae265dd19333 100644 --- a/sound/pci/lx6464es/lx_core.c +++ b/sound/pci/lx6464es/lx_core.c @@ -364,7 +364,7 @@ int lx_dsp_get_mac(struct lx6464es *chip) macmsb = lx_dsp_reg_read(chip, eReg_ADMACESMSB) & 0x00FFFFFF; maclsb = lx_dsp_reg_read(chip, eReg_ADMACESLSB) & 0x00FFFFFF; - /* todo: endianess handling */ + /* todo: endianness handling */ chip->mac_address[5] = ((u8 *)(&maclsb))[0]; chip->mac_address[4] = ((u8 *)(&maclsb))[1]; chip->mac_address[3] = ((u8 *)(&maclsb))[2]; diff --git a/sound/pci/lx6464es/lx_defs.h b/sound/pci/lx6464es/lx_defs.h index eca5367ba561..89e433d3f7a2 100644 --- a/sound/pci/lx6464es/lx_defs.h +++ b/sound/pci/lx6464es/lx_defs.h @@ -315,7 +315,7 @@ enum stream_flags { #define ED_XILINX_ERROR (ED_RT | 0x07) #define ED_COBRANET_ITF_NOT_RESPONDING (ED_RT | 0x08) -/* Complete BOARD error code for the invaid standard object class */ +/* Complete BOARD error code for the invalid standard object class */ #define EB_ISO (ERROR_VALUE | E_SOURCE_BOARD | \ E_CLASS_INVALID_STD_OBJECT) #define EB_INVALID_EFFECT (EB_ISO | 0x00) diff --git a/sound/pci/maestro3.c b/sound/pci/maestro3.c index dd45ffa171e1..2114f9f16a34 100644 --- a/sound/pci/maestro3.c +++ b/sound/pci/maestro3.c @@ -2629,10 +2629,10 @@ snd_m3_create(struct snd_card *card, struct pci_dev *pci, /* */ static int -__snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_m3 *chip; int err; @@ -2690,16 +2690,11 @@ __snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) #endif pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_m3_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_m3_probe(pci, pci_id)); -} - static struct pci_driver m3_driver = { .name = KBUILD_MODNAME, .id_table = snd_m3_ids, diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c index 6b096d654f9f..587785d5451b 100644 --- a/sound/pci/oxygen/oxygen_lib.c +++ b/sound/pci/oxygen/oxygen_lib.c @@ -572,7 +572,7 @@ static void oxygen_card_free(struct snd_card *card) mutex_destroy(&chip->mutex); } -static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, struct module *owner, const struct pci_device_id *ids, int (*get_model)(struct oxygen *chip, @@ -580,7 +580,7 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, ) ) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct oxygen *chip; const struct pci_device_id *pci_id; int err; @@ -695,18 +695,9 @@ static int __oxygen_pci_probe(struct pci_dev *pci, int index, char *id, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } - -int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, - struct module *owner, - const struct pci_device_id *ids, - int (*get_model)(struct oxygen *chip, - const struct pci_device_id *id)) -{ - return snd_card_free_on_error(&pci->dev, - __oxygen_pci_probe(pci, index, id, owner, ids, get_model)); -} EXPORT_SYMBOL(oxygen_pci_probe); static int oxygen_pci_suspend(struct device *dev) diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c index b716356010b8..75efbf59a675 100644 --- a/sound/pci/oxygen/oxygen_pcm.c +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -171,16 +171,14 @@ static int oxygen_open(struct snd_pcm_substream *substream, snd_pcm_set_sync(substream); chip->streams[channel] = substream; - guard(mutex)(&chip->mutex); - chip->pcm_active |= 1 << channel; - if (channel == PCM_SPDIF) { - chip->spdif_pcm_bits = chip->spdif_bits; - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active |= 1 << channel; + if (channel == PCM_SPDIF) + chip->spdif_pcm_bits = chip->spdif_bits; } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 1); return 0; } @@ -220,19 +218,16 @@ static int oxygen_close(struct snd_pcm_substream *substream) struct oxygen *chip = snd_pcm_substream_chip(substream); unsigned int channel = oxygen_substream_channel(substream); - guard(mutex)(&chip->mutex); - chip->pcm_active &= ~(1 << channel); - if (channel == PCM_SPDIF) { - chip->controls[CONTROL_SPDIF_PCM]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->controls[CONTROL_SPDIF_PCM]->id); - } - if (channel == PCM_SPDIF || channel == PCM_MULTICH) - oxygen_update_spdif_source(chip); + scoped_guard(mutex, &chip->mutex) { + chip->pcm_active &= ~(1 << channel); + if (channel == PCM_SPDIF || channel == PCM_MULTICH) + oxygen_update_spdif_source(chip); - chip->streams[channel] = NULL; + chip->streams[channel] = NULL; + } + if (channel == PCM_SPDIF) + snd_ctl_activate_id(chip->card, + &chip->controls[CONTROL_SPDIF_PCM]->id, 0); return 0; } diff --git a/sound/pci/pcxhr/pcxhr_core.c b/sound/pci/pcxhr/pcxhr_core.c index b3b9ab4f303e..f855d3bfad4b 100644 --- a/sound/pci/pcxhr/pcxhr_core.c +++ b/sound/pci/pcxhr/pcxhr_core.c @@ -802,7 +802,7 @@ static int pcxhr_prepair_pipe_start(struct pcxhr_mgr *mgr, err); return err; } - /* if the pipe couldn't be prepaired for start, + /* if the pipe couldn't be prepared for start, * retry it later */ if (rmh.stat[0] == 0) @@ -913,7 +913,7 @@ int pcxhr_set_pipe_state(struct pcxhr_mgr *mgr, int playback_mask, if (err) return err; if (state == 0) - break; /* success, all pipes prepaired */ + break; /* success, all pipes prepared */ mdelay(1); /* wait 1 millisecond and retry */ } } else { diff --git a/sound/pci/riptide/riptide.c b/sound/pci/riptide/riptide.c index 99c00e46ce23..0d9110558ce4 100644 --- a/sound/pci/riptide/riptide.c +++ b/sound/pci/riptide/riptide.c @@ -264,7 +264,7 @@ MODULE_PARM_DESC(opl3_port, "OPL3 port # for Riptide driver."); #define SEND_SI2S(p,b) sendcmd(p,PARM,SI2S,WORD2(b),RET(0)) #define EOB_STATUS 0x80000000 /* status flags : block boundary */ -#define EOS_STATUS 0x40000000 /* : stoppped */ +#define EOS_STATUS 0x40000000 /* : stopped */ #define EOC_STATUS 0x20000000 /* : stream end */ #define ERR_STATUS 0x10000000 #define EMPTY_STATUS 0x08000000 @@ -453,7 +453,7 @@ struct snd_riptide { int in_suspend; }; -struct sgd { /* scatter gather desriptor */ +struct sgd { /* scatter gather descriptor */ __le32 dwNextLink; __le32 dwSegPtrPhys; __le32 dwSegLen; @@ -2020,10 +2020,10 @@ static void snd_riptide_joystick_remove(struct pci_dev *pci) #endif static int -__snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_riptide *chip; unsigned short val; int err; @@ -2117,16 +2117,11 @@ __snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_card_riptide_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_riptide_probe(pci, pci_id)); -} - static struct pci_driver driver = { .name = KBUILD_MODNAME, .id_table = snd_riptide_ids, diff --git a/sound/pci/rme32.c b/sound/pci/rme32.c index 454a30a2c07e..7ea9a71aaab9 100644 --- a/sound/pci/rme32.c +++ b/sound/pci/rme32.c @@ -8,7 +8,7 @@ * Thanks to : Anders Torger <torger@ludd.luth.se>, * Henk Hesselink <henk@anda.nl> * for writing the digi96-driver - * and RME for all informations. + * and RME for all information. * * **************************************************************************** * @@ -1810,11 +1810,11 @@ static void snd_rme32_card_free(struct snd_card *card) } static int -__snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) +snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) { static int dev; struct rme32 *rme32; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) { @@ -1858,16 +1858,11 @@ __snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int -snd_rme32_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme32_probe(pci, pci_id)); -} - static struct pci_driver rme32_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme32_ids, diff --git a/sound/pci/rme96.c b/sound/pci/rme96.c index 892fcc598557..926783f0410e 100644 --- a/sound/pci/rme96.c +++ b/sound/pci/rme96.c @@ -2356,12 +2356,12 @@ static void snd_rme96_card_free(struct snd_card *card) } static int -__snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +snd_rme96_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; struct rme96 *rme96; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; u8 val; @@ -2420,16 +2420,11 @@ __snd_rme96_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_rme96_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_rme96_probe(pci, pci_id)); -} - static struct pci_driver rme96_driver = { .name = KBUILD_MODNAME, .id_table = snd_rme96_ids, diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 31cc2d91c8d2..7dce548b9f36 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -3664,7 +3664,7 @@ snd_hdsp_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) snd_iprintf(buffer, "\n"); - /* Informations about H9632 specific controls */ + /* Information about H9632 specific controls */ if (hdsp->io_type == H9632) { char *tmp; @@ -5328,7 +5328,7 @@ static int snd_hdsp_probe(struct pci_dev *pci, { static int dev; struct hdsp *hdsp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -5349,21 +5349,18 @@ static int snd_hdsp_probe(struct pci_dev *pci, hdsp->pci = pci; err = snd_hdsp_create(card, hdsp); if (err) - goto error; + return err; strscpy(card->shortname, "Hammerfall DSP"); sprintf(card->longname, "%s at 0x%lx, irq %d", hdsp->card_name, hdsp->port, hdsp->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdsp_driver = { diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index 95fd82bb1c6d..cc4cf43e5a5b 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -6796,7 +6796,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, { static int dev; struct hdspm *hdspm; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -6818,7 +6818,7 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_hdspm_create(card, hdspm); if (err < 0) - goto error; + return err; if (hdspm->io_type != MADIface) { snprintf(card->shortname, sizeof(card->shortname), "%s_%x", @@ -6837,16 +6837,12 @@ static int snd_hdspm_probe(struct pci_dev *pci, err = snd_card_register(card); if (err < 0) - goto error; + return err; pci_set_drvdata(pci, card); - + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver hdspm_driver = { diff --git a/sound/pci/rme9652/rme9652.c b/sound/pci/rme9652/rme9652.c index 3be30cb57a2e..46ed6b7a2c53 100644 --- a/sound/pci/rme9652/rme9652.c +++ b/sound/pci/rme9652/rme9652.c @@ -2472,7 +2472,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, { static int dev; struct snd_rme9652 *rme9652; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; if (dev >= SNDRV_CARDS) @@ -2494,7 +2494,7 @@ static int snd_rme9652_probe(struct pci_dev *pci, rme9652->pci = pci; err = snd_rme9652_create(card, rme9652, precise_ptr[dev]); if (err) - goto error; + return err; strscpy(card->shortname, rme9652->card_name); @@ -2502,14 +2502,11 @@ static int snd_rme9652_probe(struct pci_dev *pci, card->shortname, rme9652->port, rme9652->irq); err = snd_card_register(card); if (err) - goto error; + return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; - - error: - snd_card_free(card); - return err; } static struct pci_driver rme9652_driver = { diff --git a/sound/pci/sis7019.c b/sound/pci/sis7019.c index 55c1b11a2900..fe5bda5af6a5 100644 --- a/sound/pci/sis7019.c +++ b/sound/pci/sis7019.c @@ -1309,10 +1309,10 @@ static int sis_chip_create(struct snd_card *card, return 0; } -static int __snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sis7019_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sis7019 *sis; int rc; @@ -1361,15 +1361,10 @@ static int __snd_sis7019_probe(struct pci_dev *pci, return rc; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_sis7019_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sis7019_probe(pci, pci_id)); -} - static struct pci_driver sis7019_driver = { .name = KBUILD_MODNAME, .id_table = snd_sis7019_ids, diff --git a/sound/pci/sonicvibes.c b/sound/pci/sonicvibes.c index a885d544acd6..b5bb20440c6e 100644 --- a/sound/pci/sonicvibes.c +++ b/sound/pci/sonicvibes.c @@ -1362,11 +1362,11 @@ static int snd_sonicvibes_midi(struct sonicvibes *sonic, return 0; } -static int __snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_sonic_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct sonicvibes *sonic; struct snd_rawmidi *midi_uart; struct snd_opl3 *opl3; @@ -1430,16 +1430,11 @@ static int __snd_sonic_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_sonic_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_sonic_probe(pci, pci_id)); -} - static struct pci_driver sonicvibes_driver = { .name = KBUILD_MODNAME, .id_table = snd_sonic_ids, diff --git a/sound/pci/trident/trident_memory.c b/sound/pci/trident/trident_memory.c index 81f6348191dc..4ed0e7d68322 100644 --- a/sound/pci/trident/trident_memory.c +++ b/sound/pci/trident/trident_memory.c @@ -27,7 +27,7 @@ #if PAGE_SIZE == 4096 /* page size == SNDRV_TRIDENT_PAGE_SIZE */ #define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ -#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ +#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maximum aligned pages */ /* fill TLB entrie(s) corresponding to page with ptr */ #define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) /* fill TLB entrie(s) corresponding to page with silence pointer */ diff --git a/sound/pci/via82xx.c b/sound/pci/via82xx.c index 24ee1302f27d..c13fc554c0c8 100644 --- a/sound/pci/via82xx.c +++ b/sound/pci/via82xx.c @@ -1242,11 +1242,7 @@ static int snd_via8233_playback_open(struct snd_pcm_substream *substream) VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); chip->playback_volume[stream][1] = VIA_DXS_MAX_VOLUME - (dxs_init_volume & 31); - chip->dxs_controls[stream]->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | - SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 1); } return 0; } @@ -1333,10 +1329,7 @@ static int snd_via8233_playback_close(struct snd_pcm_substream *substream) stream = viadev->reg_offset / 0x10; if (chip->dxs_controls[stream]) { - chip->dxs_controls[stream]->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, - &chip->dxs_controls[stream]->id); + snd_ctl_activate_id(chip->card, &chip->dxs_controls[stream]->id, 0); } return snd_via82xx_pcm_close(substream); } @@ -2435,10 +2428,10 @@ static int check_dxs_list(struct pci_dev *pci, int revision) return VIA_DXS_48K; }; -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx *chip; int chip_type = 0, card_type; unsigned int i; @@ -2543,15 +2536,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_ids, diff --git a/sound/pci/via82xx_modem.c b/sound/pci/via82xx_modem.c index b32f84ac17cc..7934bc5d6463 100644 --- a/sound/pci/via82xx_modem.c +++ b/sound/pci/via82xx_modem.c @@ -1088,10 +1088,10 @@ static int snd_via82xx_create(struct snd_card *card, } -static int __snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_via82xx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct via82xx_modem *chip; int chip_type = 0, card_type; unsigned int i; @@ -1139,15 +1139,10 @@ static int __snd_via82xx_probe(struct pci_dev *pci, if (err < 0) return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int snd_via82xx_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_via82xx_probe(pci, pci_id)); -} - static struct pci_driver via82xx_modem_driver = { .name = KBUILD_MODNAME, .id_table = snd_via82xx_modem_ids, diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index b6459dbdb1b0..40f3716c478f 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -431,7 +431,7 @@ static int vx2_load_dsp(struct vx_core *vx, int index, const struct firmware *ds /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/pci/ymfpci/ymfpci.c b/sound/pci/ymfpci/ymfpci.c index d3fb047c9a45..5e8c7e318138 100644 --- a/sound/pci/ymfpci/ymfpci.c +++ b/sound/pci/ymfpci/ymfpci.c @@ -152,11 +152,11 @@ static inline int snd_ymfpci_create_gameport(struct snd_ymfpci *chip, int dev, i void snd_ymfpci_free_gameport(struct snd_ymfpci *chip) { } #endif /* SUPPORT_JOYSTICK */ -static int __snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) +static int snd_card_ymfpci_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) { static int dev; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct resource *fm_res = NULL; struct resource *mpu_res = NULL; struct snd_ymfpci *chip; @@ -344,16 +344,11 @@ static int __snd_card_ymfpci_probe(struct pci_dev *pci, return err; pci_set_drvdata(pci, card); + card = NULL; /* probe succeeded, don't release as error */ dev++; return 0; } -static int snd_card_ymfpci_probe(struct pci_dev *pci, - const struct pci_device_id *pci_id) -{ - return snd_card_free_on_error(&pci->dev, __snd_card_ymfpci_probe(pci, pci_id)); -} - static struct pci_driver ymfpci_driver = { .name = KBUILD_MODNAME, .id_table = snd_ymfpci_ids, diff --git a/sound/pcmcia/vx/vxp_ops.c b/sound/pcmcia/vx/vxp_ops.c index 4211e7239138..e52b61125629 100644 --- a/sound/pcmcia/vx/vxp_ops.c +++ b/sound/pcmcia/vx/vxp_ops.c @@ -265,7 +265,7 @@ static int vxp_load_dsp(struct vx_core *vx, int index, const struct firmware *fw /* * vx_test_and_ack - test and acknowledge interrupt * - * called from irq hander, too + * called from irq handler, too * * spinlock held! */ diff --git a/sound/ppc/powermac.c b/sound/ppc/powermac.c index e685d245883e..30f4cd1a6d32 100644 --- a/sound/ppc/powermac.c +++ b/sound/ppc/powermac.c @@ -39,18 +39,18 @@ static struct platform_device *device; static int snd_pmac_probe(struct platform_device *devptr) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_pmac *chip; char *name_ext; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) return err; err = snd_pmac_new(card, &chip); if (err < 0) - goto __error; + return err; card->private_data = chip; switch (chip->model) { @@ -61,7 +61,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_burgundy_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_DACA: strscpy(card->driver, "PMac DACA"); @@ -70,7 +70,7 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_daca_init(chip); if (err < 0) - goto __error; + return err; break; case PMAC_TUMBLER: case PMAC_SNAPPER: @@ -81,10 +81,10 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, chip->device_id, chip->subframe); err = snd_pmac_tumbler_init(chip); if (err < 0) - goto __error; + return err; err = snd_pmac_tumbler_post_init(); if (err < 0) - goto __error; + return err; break; case PMAC_AWACS: case PMAC_SCREAMER: @@ -101,17 +101,16 @@ static int snd_pmac_probe(struct platform_device *devptr) card->shortname, name_ext, chip->revision); err = snd_pmac_awacs_init(chip); if (err < 0) - goto __error; + return err; break; default: dev_err(&devptr->dev, "unsupported hardware %d\n", chip->model); - err = -EINVAL; - goto __error; + return -EINVAL; } err = snd_pmac_pcm_new(chip); if (err < 0) - goto __error; + return err; chip->initialized = 1; if (enable_beep) @@ -119,20 +118,11 @@ static int snd_pmac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto __error; + return err; platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -__error: - snd_card_free(card); - return err; -} - - -static void snd_pmac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); } #ifdef CONFIG_PM_SLEEP @@ -160,7 +150,6 @@ static SIMPLE_DEV_PM_OPS(snd_pmac_pm, snd_pmac_driver_suspend, snd_pmac_driver_r static struct platform_driver snd_pmac_driver = { .probe = snd_pmac_probe, - .remove = snd_pmac_remove, .driver = { .name = SND_PMAC_DRIVER, .pm = SND_PMAC_PM_OPS, diff --git a/sound/ppc/tumbler.c b/sound/ppc/tumbler.c index 139c7b25927a..7ba52e7a24d7 100644 --- a/sound/ppc/tumbler.c +++ b/sound/ppc/tumbler.c @@ -1134,7 +1134,7 @@ static long tumbler_find_device(const char *device, const char *platform, gp->active_val = 0x4; gp->inactive_val = 0x5; /* Here are some crude hacks to extract the GPIO polarity and - * open collector informations out of the do-platform script + * open collector information out of the do-platform script * as we don't yet have an interpreter for these things */ if (platform) diff --git a/sound/sh/sh_dac_audio.c b/sound/sh/sh_dac_audio.c index 2d9a2d5b824e..5896cce424af 100644 --- a/sound/sh/sh_dac_audio.c +++ b/sound/sh/sh_dac_audio.c @@ -236,12 +236,6 @@ static int snd_sh_dac_pcm(struct snd_sh_dac *chip, int device) /* END OF PCM INTERFACE */ -/* driver .remove -- destructor */ -static void snd_sh_dac_remove(struct platform_device *devptr) -{ - snd_card_free(platform_get_drvdata(devptr)); -} - /* free -- it has been defined by create */ static int snd_sh_dac_free(struct snd_sh_dac *chip) { @@ -341,10 +335,10 @@ static int snd_sh_dac_create(struct snd_card *card, static int snd_sh_dac_probe(struct platform_device *devptr) { struct snd_sh_dac *chip; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; - err = snd_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); + err = snd_devm_card_new(&devptr->dev, index, id, THIS_MODULE, 0, &card); if (err < 0) { dev_err(&devptr->dev, "cannot allocate the card\n"); return err; @@ -352,11 +346,11 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_sh_dac_create(card, devptr, &chip); if (err < 0) - goto probe_error; + return err; err = snd_sh_dac_pcm(chip, 0); if (err < 0) - goto probe_error; + return err; strscpy(card->driver, "snd_sh_dac"); strscpy(card->shortname, "SuperH DAC audio driver"); @@ -364,16 +358,13 @@ static int snd_sh_dac_probe(struct platform_device *devptr) err = snd_card_register(card); if (err < 0) - goto probe_error; + return err; dev_info(&devptr->dev, "ALSA driver for SuperH DAC audio\n"); platform_set_drvdata(devptr, card); + card = NULL; /* probe succeeded, don't release as error */ return 0; - -probe_error: - snd_card_free(card); - return err; } /* @@ -381,7 +372,6 @@ probe_error: */ static struct platform_driver sh_dac_driver = { .probe = snd_sh_dac_probe, - .remove = snd_sh_dac_remove, .driver = { .name = "dac_audio", }, diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c index 237b0b060457..5188c9d74c8b 100644 --- a/sound/soc/codecs/hda.c +++ b/sound/soc/codecs/hda.c @@ -218,6 +218,9 @@ static int hda_codec_probe(struct snd_soc_component *component) goto err; } + /* unsol events are still blocked until registered */ + codec->core.unsol_disabled = false; + ret = driver->ops->probe(codec, codec->preset); if (ret < 0) { dev_err(&hdev->dev, "codec init failed: %d\n", ret); @@ -276,6 +279,10 @@ static void hda_codec_remove(struct snd_soc_component *component) /* Don't allow any more runtime suspends */ pm_runtime_forbid(&hdev->dev); + /* stop asynchronous jack handling before freeing driver resources */ + snd_hdac_device_disable_unsol(hdev); + cancel_delayed_work_sync(&codec->jackpoll_work); + hda_codec_unregister_dais(codec, component); if (driver->ops->remove) diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c index 404706b62156..82eeaceb1dac 100644 --- a/sound/soc/codecs/tas675x.c +++ b/sound/soc/codecs/tas675x.c @@ -1379,10 +1379,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) int ret; if (direction == SNDRV_PCM_STREAM_CAPTURE) { - if (mute) - clear_bit(dai->id, &tas->active_capture_dais); - else - set_bit(dai->id, &tas->active_capture_dais); + assign_bit(dai->id, &tas->active_capture_dais, !mute); return 0; } @@ -1391,10 +1388,7 @@ static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction) * The TAS675x has two playback DAIs (main audio and LLP). * Only transition to SLEEP when ALL are muted. */ - if (mute) - clear_bit(dai->id, &tas->active_playback_dais); - else - set_bit(dai->id, &tas->active_playback_dais); + assign_bit(dai->id, &tas->active_playback_dais, !mute); /* Last playback stream */ if (mute && !READ_ONCE(tas->active_playback_dais)) { diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 4bc40f328f58..42b0e21f281c 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -475,6 +475,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->input = dma_buf_export(&exp_info_in); if (IS_ERR(task->input)) { ret = PTR_ERR(task->input); + task->input = NULL; return ret; } @@ -485,6 +486,7 @@ static int fsl_asrc_m2m_comp_task_create(struct snd_compr_stream *stream, task->output = dma_buf_export(&exp_info_out); if (IS_ERR(task->output)) { ret = PTR_ERR(task->output); + task->output = NULL; return ret; } diff --git a/sound/soc/sof/intel/hda-codec.c b/sound/soc/sof/intel/hda-codec.c index fd371850b0d6..91f7ca22a4a7 100644 --- a/sound/soc/sof/intel/hda-codec.c +++ b/sound/soc/sof/intel/hda-codec.c @@ -89,7 +89,7 @@ void hda_codec_jack_wake_enable(struct snd_sof_dev *sdev, bool enable) list_for_each_codec(codec, hbus) { /* only set WAKEEN when needed for HDaudio codecs */ mask |= BIT(codec->core.addr); - if (codec->jacktbl.used) + if (codec->jacktbl_used) val |= BIT(codec->core.addr); } } else { @@ -118,7 +118,7 @@ void hda_codec_jack_check(struct snd_sof_dev *sdev) * Wake up all jack-detecting codecs regardless whether an event * has been recorded in STATESTS */ - if (codec->jacktbl.used) + if (codec->jacktbl_used) pm_request_resume(&codec->core.dev); } EXPORT_SYMBOL_NS_GPL(hda_codec_jack_check, "SND_SOC_SOF_HDA_AUDIO_CODEC"); diff --git a/sound/sparc/cs4231.c b/sound/sparc/cs4231.c index d9e5cca94c73..0489eac165c9 100644 --- a/sound/sparc/cs4231.c +++ b/sound/sparc/cs4231.c @@ -1497,8 +1497,8 @@ static int cs4231_attach_begin(struct platform_device *op, return -ENOENT; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_cs4231), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cs4231), &card); if (err < 0) return err; @@ -1519,28 +1519,24 @@ static int cs4231_attach_finish(struct snd_card *card) err = snd_cs4231_pcm(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_mixer(card); if (err < 0) - goto out_err; + return err; err = snd_cs4231_timer(card); if (err < 0) - goto out_err; + return err; err = snd_card_register(card); if (err < 0) - goto out_err; + return err; dev_set_drvdata(&chip->op->dev, chip); dev++; return 0; - -out_err: - snd_card_free(card); - return err; } #ifdef SBUS_SUPPORT @@ -1771,7 +1767,7 @@ static int snd_cs4231_sbus_create(struct snd_card *card, static int cs4231_sbus_probe(struct platform_device *op) { struct resource *rp = &op->resource[0]; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1785,12 +1781,14 @@ static int cs4231_sbus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_sbus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + err = cs4231_attach_finish(card); + if (err < 0) + return err; + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -1968,7 +1966,7 @@ static int snd_cs4231_ebus_create(struct snd_card *card, static int cs4231_ebus_probe(struct platform_device *op) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; int err; err = cs4231_attach_begin(op, &card); @@ -1981,12 +1979,15 @@ static int cs4231_ebus_probe(struct platform_device *op) op->archdata.irqs[0]); err = snd_cs4231_ebus_create(card, op, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) + return err; + + err = cs4231_attach_finish(card); + if (err < 0) return err; - } - return cs4231_attach_finish(card); + card = NULL; /* probe succeeded, don't release as error */ + return 0; } #endif @@ -2004,13 +2005,6 @@ static int cs4231_probe(struct platform_device *op) return -ENODEV; } -static void cs4231_remove(struct platform_device *op) -{ - struct snd_cs4231 *chip = dev_get_drvdata(&op->dev); - - snd_card_free(chip->card); -} - static const struct of_device_id cs4231_match[] = { { .name = "SUNW,CS4231", @@ -2030,7 +2024,6 @@ static struct platform_driver cs4231_driver = { .of_match_table = cs4231_match, }, .probe = cs4231_probe, - .remove = cs4231_remove, }; module_platform_driver(cs4231_driver); diff --git a/sound/sparc/dbri.c b/sound/sparc/dbri.c index ccaa36525f8d..9fad4102dc70 100644 --- a/sound/sparc/dbri.c +++ b/sound/sparc/dbri.c @@ -2576,11 +2576,16 @@ static void snd_dbri_free(struct snd_dbri *dbri) (void *)dbri->dma, dbri->dma_dvma); } +static void dbri_card_free(struct snd_card *card) +{ + snd_dbri_free(card->private_data); +} + static int dbri_probe(struct platform_device *op) { struct snd_dbri *dbri; struct resource *rp; - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; static int dev; int irq; int err; @@ -2598,8 +2603,8 @@ static int dbri_probe(struct platform_device *op) return -ENODEV; } - err = snd_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, - sizeof(struct snd_dbri), &card); + err = snd_devm_card_new(&op->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dbri), &card); if (err < 0) return err; @@ -2609,21 +2614,20 @@ static int dbri_probe(struct platform_device *op) sprintf(card->longname, "%s at 0x%02lx:0x%016llx, irq %d", card->shortname, rp->flags & 0xffL, (unsigned long long)rp->start, irq); + card->private_free = dbri_card_free; err = snd_dbri_create(card, op, irq, dev); - if (err < 0) { - snd_card_free(card); + if (err < 0) return err; - } dbri = card->private_data; err = snd_dbri_pcm(card); if (err < 0) - goto _err; + return err; err = snd_dbri_mixer(card); if (err < 0) - goto _err; + return err; /* /proc file handling */ snd_dbri_proc(card); @@ -2631,27 +2635,15 @@ static int dbri_probe(struct platform_device *op) err = snd_card_register(card); if (err < 0) - goto _err; + return err; printk(KERN_INFO "audio%d at %p (irq %d) is DBRI(%c)+CS4215(%d)\n", dev, dbri->regs, dbri->irq, op->dev.of_node->name[9], dbri->mm.version); dev++; + card = NULL; /* probe succeeded, don't release as error */ return 0; - -_err: - snd_dbri_free(dbri); - snd_card_free(card); - return err; -} - -static void dbri_remove(struct platform_device *op) -{ - struct snd_card *card = dev_get_drvdata(&op->dev); - - snd_dbri_free(card->private_data); - snd_card_free(card); } static const struct of_device_id dbri_match[] = { @@ -2672,7 +2664,6 @@ static struct platform_driver dbri_sbus_driver = { .of_match_table = dbri_match, }, .probe = dbri_probe, - .remove = dbri_remove, }; module_platform_driver(dbri_sbus_driver); diff --git a/sound/synth/emux/emux_oss.c b/sound/synth/emux/emux_oss.c index 1145c7956afd..30e353e7323d 100644 --- a/sound/synth/emux/emux_oss.c +++ b/sound/synth/emux/emux_oss.c @@ -124,7 +124,7 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) arg->private_data = p; arg->addr.client = p->chset.client; arg->addr.port = p->chset.port; - p->oss_arg = arg; + WRITE_ONCE(p->oss_arg, arg); reset_port_mode(p, arg->seq_mode); @@ -141,16 +141,24 @@ snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure) static void reset_port_mode(struct snd_emux_port *port, int midi_mode) { + struct snd_seq_oss_arg *arg; + + /* OSS events can arrive before the open path has set oss_arg. */ + arg = READ_ONCE(port->oss_arg); + if (midi_mode) { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI; port->drum_flags = DEFAULT_DRUM_FLAGS; port->volume_atten = 0; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS; + if (arg) + arg->event_passing = + SNDRV_SEQ_OSS_PROCESS_KEYPRESS; } else { port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH; port->drum_flags = 0; port->volume_atten = 32; - port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; + if (arg) + arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS; } } diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c index 10104cb42165..da5a2d7ac107 100644 --- a/sound/usb/6fire/control.c +++ b/sound/usb/6fire/control.c @@ -20,6 +20,7 @@ #include "control.h" #include "comm.h" #include "chip.h" +#include "pcm.h" static const char * const opt_coax_texts[2] = { "Optical", "Coax" }; static const char * const line_phono_texts[2] = { "Line", "Phono" }; @@ -148,9 +149,14 @@ static int usb6fire_control_set_channels( if (ret < 0) return ret; - /* disable digital inputs and outputs */ - /* TODO: use spdif_x to enable/disable digital channels */ - ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); + /* + * digital (S/PDIF) input and output: bit 0 adds four slots (4-7) to + * the capture frame, which carry the subframes of the receiver; up to + * two receiver frames arrive per card frame; bit 1 enables the + * transmitter, which encodes the playback pair in slots 6/7 (pcm.c) + */ + ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, + (spdif_in ? 0x01 : 0x00) | (spdif_out ? 0x02 : 0x00)); if (ret < 0) return ret; @@ -160,14 +166,26 @@ static int usb6fire_control_set_channels( static int usb6fire_control_streaming_update(struct control_runtime *rt) { struct comm_runtime *comm_rt = rt->chip->comm; + int err = 0; + int ret; if (comm_rt) { + /* + * the digital pairs are enabled only while a stream runs; the + * streaming register is written even if this write fails. + * usb6fire_control_digital_thru_put() holds stream_mutex, so + * this write cannot run after set_channels() and before + * streaming starts in usb6fire_pcm_set_rate() + */ + if (!rt->usb_streaming) + err = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); if (!rt->usb_streaming && rt->digital_thru_switch) usb6fire_control_set_rate(rt, DIGITAL_THRU_ONLY_SAMPLERATE); - return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, + ret = comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, (rt->usb_streaming ? 0x01 : 0x00) | (rt->digital_thru_switch ? 0x08 : 0x00)); + return err < 0 ? err : ret; } return -EINVAL; } @@ -383,6 +401,12 @@ static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol, struct control_runtime *rt = snd_kcontrol_chip(kcontrol); int changed = 0; + /* + * with Digital Thru on, streaming_update() selects the altsetting and + * writes the clock register of an idle card; prepare does the same + * under stream_mutex + */ + guard(mutex)(&rt->chip->pcm->stream_mutex); if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) { rt->digital_thru_switch = ucontrol->value.integer.value[0]; usb6fire_control_streaming_update(rt); diff --git a/sound/usb/6fire/pcm.c b/sound/usb/6fire/pcm.c index 0285d79ace0f..f5dfb8c3096a 100644 --- a/sound/usb/6fire/pcm.c +++ b/sound/usb/6fire/pcm.c @@ -9,13 +9,34 @@ * Copyright: (C) Torsten Schenk */ +#include <linux/bitops.h> +#include <sound/asoundef.h> +#include <sound/control.h> + #include "pcm.h" #include "chip.h" #include "comm.h" #include "control.h" +/* + * Every USB frame is a 4 byte header followed by 32-bit slots, one per + * channel: 3 bytes of sample and a marker byte. Analog slots are marked + * 0x40. Up to 96 kHz (altsettings 1 and 2) the playback frame carries two + * more slots, 6/7, which carry the data for the S/PDIF transmitter in + * the FPGA. Their marker byte tells the transmitter how to frame the + * sample: 0x10 marks the left subframe and 0x00 the right one; 0x20 marks + * the start of the channel status block (left subframe, every 192 + * frames); 0x04 is the channel status bit of the subframe; 0x01 is the + * validity bit, set for non-audio data; 0x08 is the parity bit, which + * makes the number of ones in the sample, the validity and channel status + * bits and itself even. + * At altsetting 3 (176.4/192 kHz) the packets hold the analog slots only. + */ enum { - OUT_N_CHANNELS = 6, IN_N_CHANNELS = 4 + OUT_N_ANALOG = 6, N_SPDIF = 2, + OUT_N_CHANNELS = OUT_N_ANALOG + N_SPDIF, IN_N_CHANNELS = 4, + SPDIF_OUT_SLOT = OUT_N_ANALOG, + SPDIF_BLOCK_FRAMES = 192 }; /* keep next two synced with @@ -24,6 +45,15 @@ enum { static const int rates_in_packet_size[] = { 228, 228, 420, 420, 404, 404 }; static const int rates_out_packet_size[] = { 228, 228, 420, 420, 604, 604 }; static const int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000 }; +static const u8 rates_iec958_fs[] = { + IEC958_AES3_CON_FS_44100, IEC958_AES3_CON_FS_48000, + IEC958_AES3_CON_FS_88200, IEC958_AES3_CON_FS_96000, + IEC958_AES3_CON_FS_176400, IEC958_AES3_CON_FS_192000 }; +/* channel status bits that "IEC958 Playback Default" sets, bytes 0 and 1 */ +static const u8 spdif_ctl_mask[] = { + IEC958_AES0_NONAUDIO | IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES0_CON_EMPHASIS, + IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL }; static const int rates_alsaid[] = { SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000, SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000, @@ -71,6 +101,7 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) { int ret; struct control_runtime *ctrl_rt = rt->chip->control; + bool spdif; ctrl_rt->usb_streaming = false; ret = ctrl_rt->update_streaming(ctrl_rt); @@ -89,8 +120,9 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) return ret; } - ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_CHANNELS, IN_N_CHANNELS, - false, false); + spdif = rt->rate <= CONTROL_RATE_96KHZ; + ret = ctrl_rt->set_channels(ctrl_rt, OUT_N_ANALOG, IN_N_CHANNELS, + spdif, false); if (ret < 0) { dev_err(&rt->chip->dev->dev, "error initializing channels while setting samplerate %d.\n", @@ -104,16 +136,108 @@ static int usb6fire_pcm_set_rate(struct pcm_runtime *rt) dev_err(&rt->chip->dev->dev, "error starting streaming while setting samplerate %d.\n", rates[rt->rate]); + /* stream_stop() will not run; clear 0x02/0x03 here */ + ctrl_rt->usb_streaming = false; + ctrl_rt->update_streaming(ctrl_rt); return ret; } rt->in_n_analog = IN_N_CHANNELS; - rt->out_n_analog = OUT_N_CHANNELS; + rt->out_n_slots = spdif ? OUT_N_CHANNELS : OUT_N_ANALOG; rt->in_packet_size = rates_in_packet_size[rt->rate]; rt->out_packet_size = rates_out_packet_size[rt->rate]; + + /* + * consumer channel status, 24 bit samples; bytes 0 and 1 are taken + * from rt->spdif_bits at the start of every block + */ + memset(rt->spdif_status, 0, sizeof(rt->spdif_status)); + rt->spdif_status[3] = rates_iec958_fs[rt->rate]; + rt->spdif_status[4] = IEC958_AES4_CON_MAX_WORDLEN_24 | + IEC958_AES4_CON_WORDLEN_24_20; + rt->spdif_frame = 0; return 0; } +/* + * Return the marker byte of one playback slot whose 3 sample bytes are + * already in place. The marker is 0x40 for an analog slot and the S/PDIF + * framing for a digital slot. With "IEC958 Playback Switch" off, the + * function also clears the sample bytes of a digital slot. After the right + * subframe, the function advances the channel status position. + */ +static u8 usb6fire_pcm_marker(struct pcm_runtime *rt, int slot, + u8 *sample) +{ + bool right = slot == SPDIF_OUT_SLOT + 1; + unsigned int n = rt->spdif_frame; + u8 marker = right ? 0x00 : 0x10; + u8 status; + + if (slot < SPDIF_OUT_SLOT) + return 0x40; + if (!right && n == 0) { + /* a new block takes the bits of "IEC958 Playback Default" */ + u32 bits = READ_ONCE(rt->spdif_bits); + + rt->spdif_status[0] = bits & 0xff; + rt->spdif_status[1] = bits >> 8; + /* + * "IEC958 Playback Switch" off omits the block start flag and + * clears the sample bytes of the digital slots + */ + rt->spdif_on = READ_ONCE(rt->spdif_switch); + if (rt->spdif_on) + marker |= 0x20; + } + if (!rt->spdif_on) + memset(sample, 0, 3); + status = rt->spdif_status[n >> 3]; + /* byte 2 holds the channel number: 1 = A (left), 2 = B (right) */ + if ((n >> 3) == 2) + status |= right ? (2 << 4) : (1 << 4); + if (status & (1 << (n & 7))) + marker |= 0x04; + /* V = 1 for non-audio data (AC3, DTS), as the vendor driver does */ + if (rt->spdif_status[0] & IEC958_AES0_NONAUDIO) + marker |= 0x01; + /* P makes the number of ones in bits 0-27 (sample, V, U, C, P) even */ + if (parity8(sample[0] ^ sample[1] ^ sample[2] ^ (marker & 0x07))) + marker |= 0x08; + if (right) + rt->spdif_frame = (n + 1) % SPDIF_BLOCK_FRAMES; + return marker; +} + +/* more than OUT_N_ANALOG playback channels need a rate up to 96 kHz */ +static int usb6fire_pcm_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { .min = 1, .max = OUT_N_ANALOG, .integer = 1 }; + + if (r->min <= rates[CONTROL_RATE_96KHZ]) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS), &t); +} + +static int usb6fire_pcm_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = 0, .max = rates[CONTROL_RATE_96KHZ], .integer = 1 + }; + + if (c->min <= OUT_N_ANALOG) + return 0; + return snd_interval_refine(hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE), &t); +} + static struct pcm_substream *usb6fire_pcm_get_substream( struct snd_pcm_substream *alsa_sub) { @@ -264,19 +388,21 @@ static void usb6fire_pcm_playback(struct pcm_substream *sub, } for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { - /* at least 4 header bytes for valid packet. - * after that: 32 bits per sample for analog channels */ + /* + * at least 4 header bytes for valid packet. + * after that: 32 bits per slot + */ isoc = &urb->instance->iso_frame_desc[i]; if (isoc->length > 4) frame_count = (isoc->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); else frame_count = 0; dest++; /* skip leading 4 bytes of every frame */ for (frame = 0; frame < frame_count; frame++) { memcpy(dest, src, bytes_per_frame); src += alsa_rt->channels; - dest += rt->out_n_analog; + dest += rt->out_n_slots; sub->dma_off++; sub->period_off++; if (src == src_end) { @@ -301,6 +427,7 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) int channel; int i; u8 *dest; + unsigned int spdif_frame; if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING) return; @@ -344,10 +471,10 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) / (rt->in_n_analog << 2); frames = min_t(unsigned int, frames, (rt->out_packet_size - 4) - / (rt->out_n_analog << 2)); + / (rt->out_n_slots << 2)); isoc_out->offset = total_length; - isoc_out->length = frames * (rt->out_n_analog << 2) + 4; + isoc_out->length = frames * (rt->out_n_slots << 2) + 4; isoc_out->status = 0; total_length += isoc_out->length; } @@ -368,28 +495,38 @@ static void usb6fire_pcm_in_urb_handler(struct urb *usb_urb) if (period_elapsed) snd_pcm_period_elapsed(sub->instance); - /* setup the 4th byte of each sample (0x40 for analog channels) */ + /* + * set the 4th byte of each sample (0x40 for analog channels, + * S/PDIF framing for the digital pair) + */ + spdif_frame = rt->spdif_frame; dest = out_urb->buffer; for (i = 0; i < PCM_N_PACKETS_PER_URB; i++) { isoc_out = &out_urb->instance->iso_frame_desc[i]; if (isoc_out->length >= 4) { frame_count = (isoc_out->length - 4) - / (rt->out_n_analog << 2); + / (rt->out_n_slots << 2); *(dest++) = 0xaa; *(dest++) = 0xaa; *(dest++) = frame_count; *(dest++) = 0x00; for (frame = 0; frame < frame_count; frame++) for (channel = 0; - channel < rt->out_n_analog; + channel < rt->out_n_slots; channel++) { - dest += 3; /* skip sample data */ - *(dest++) = 0x40; + dest[3] = usb6fire_pcm_marker(rt, + channel, dest); + dest += 4; } } } - usb_submit_urb(out_urb->instance, GFP_ATOMIC); + /* + * the frames of an out urb that was not submitted never reach the + * transmitter; the next urb continues the block at the same position + */ + if (usb_submit_urb(out_urb->instance, GFP_ATOMIC) < 0) + rt->spdif_frame = spdif_frame; usb_submit_urb(in_urb->instance, GFP_ATOMIC); } @@ -409,6 +546,7 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub); struct pcm_substream *sub = NULL; struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; + int ret; if (rt->panic) return -EPIPE; @@ -420,6 +558,17 @@ static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) if (rt->rate < ARRAY_SIZE(rates)) alsa_rt->hw.rates = rates_alsaid[rt->rate]; alsa_rt->hw.channels_max = OUT_N_CHANNELS; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + usb6fire_pcm_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (ret < 0) + return ret; + ret = snd_pcm_hw_rule_add(alsa_rt, 0, SNDRV_PCM_HW_PARAM_RATE, + usb6fire_pcm_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret < 0) + return ret; sub = &rt->playback; } else if (alsa_sub->stream == SNDRV_PCM_STREAM_CAPTURE) { if (rt->rate < ARRAY_SIZE(rates)) @@ -489,7 +638,17 @@ static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) alsa_rt->rate); return -EINVAL; } - + } + /* the digital pair is not available at 176.4 and 192 kHz */ + if (rt->rate > CONTROL_RATE_96KHZ && + alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK && + alsa_rt->channels > OUT_N_ANALOG) { + dev_err(&rt->chip->dev->dev, + "%d channels not available at %d Hz.\n", + alsa_rt->channels, rates[rt->rate]); + return -EINVAL; + } + if (rt->stream_state == STREAM_DISABLED) { ret = usb6fire_pcm_set_rate(rt); if (ret) return ret; @@ -612,6 +771,128 @@ static void usb6fire_pcm_free(struct pcm_runtime *rt) kfree(rt); } +static int usb6fire_pcm_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int usb6fire_pcm_spdif_mask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = spdif_ctl_mask[0]; + ucontrol->value.iec958.status[1] = spdif_ctl_mask[1]; + return 0; +} + +static int usb6fire_pcm_spdif_default_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = READ_ONCE(rt->spdif_bits); + + memset(ucontrol->value.iec958.status, 0, + sizeof(ucontrol->value.iec958.status)); + ucontrol->value.iec958.status[0] = bits & 0xff; + ucontrol->value.iec958.status[1] = bits >> 8; + return 0; +} + +static int usb6fire_pcm_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + u32 bits = (ucontrol->value.iec958.status[0] & spdif_ctl_mask[0]) | + (ucontrol->value.iec958.status[1] & spdif_ctl_mask[1]) << 8; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_bits == bits) + return 0; + WRITE_ONCE(rt->spdif_bits, bits); + return 1; +} + +static int usb6fire_pcm_spdif_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = READ_ONCE(rt->spdif_switch); + return 0; +} + +static int usb6fire_pcm_spdif_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct pcm_runtime *rt = snd_kcontrol_chip(kcontrol); + bool on = ucontrol->value.integer.value[0]; + + /* the control core serializes puts; the urb handler uses READ_ONCE */ + if (rt->spdif_switch == on) + return 0; + WRITE_ONCE(rt->spdif_switch, on); + return 1; +} + +/* + * playback channel map: channel n goes to analog output n, which the + * manual of the card assigns to FL FR FC LFE RL RR for n = 1..6; a + * 1-channel stream is MONO, as in snd_pcm_std_chmaps, and plays on + * analog output 1 only; channels 7 and 8 are the S/PDIF pair and have + * no position + */ +static const struct snd_pcm_chmap_elem usb6fire_pcm_chmaps[] = { + { .channels = 1, .map = { SNDRV_CHMAP_MONO } }, + { .channels = 2, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 3, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC } }, + { .channels = 4, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } }, + { .channels = 5, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL } }, + { .channels = 6, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 7, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN } }, + { .channels = 8, .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR, + SNDRV_CHMAP_UNKNOWN, SNDRV_CHMAP_UNKNOWN } }, + { } +}; + +static const struct snd_kcontrol_new usb6fire_pcm_spdif_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_default_get, + .put = usb6fire_pcm_spdif_default_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = usb6fire_pcm_spdif_info, + .get = usb6fire_pcm_spdif_mask_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), + .info = snd_ctl_boolean_mono_info, + .get = usb6fire_pcm_spdif_switch_get, + .put = usb6fire_pcm_spdif_switch_put, + }, +}; + int usb6fire_pcm_init(struct sfire_chip *chip) { int i; @@ -630,6 +911,9 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->chip = chip; rt->stream_state = STREAM_DISABLED; rt->rate = ARRAY_SIZE(rates); + rt->spdif_switch = true; + rt->spdif_bits = IEC958_AES0_CON_NOT_COPYRIGHT | + IEC958_AES1_CON_GENERAL << 8; init_waitqueue_head(&rt->stream_wait_queue); mutex_init(&rt->stream_mutex); @@ -665,6 +949,22 @@ int usb6fire_pcm_init(struct sfire_chip *chip) rt->instance = pcm; chip->pcm = rt; + + /* on error, rt and the controls added so far are freed with the card */ + for (i = 0; i < ARRAY_SIZE(usb6fire_pcm_spdif_controls); i++) { + ret = snd_ctl_add(chip->card, + snd_ctl_new1(&usb6fire_pcm_spdif_controls[i], rt)); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add S/PDIF control.\n"); + return ret; + } + } + ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, + usb6fire_pcm_chmaps, OUT_N_CHANNELS, 0, NULL); + if (ret < 0) { + dev_err(&chip->dev->dev, "cannot add channel map.\n"); + return ret; + } return 0; error: diff --git a/sound/usb/6fire/pcm.h b/sound/usb/6fire/pcm.h index b586fe220fd1..f1b867b9c468 100644 --- a/sound/usb/6fire/pcm.h +++ b/sound/usb/6fire/pcm.h @@ -53,7 +53,12 @@ struct pcm_runtime { int in_packet_size; int out_packet_size; int in_n_analog; /* number of analog channels soundcard sends */ - int out_n_analog; /* number of analog channels soundcard receives */ + int out_n_slots; /* 32-bit slots per frame the soundcard receives */ + bool spdif_switch; /* "IEC958 Playback Switch" */ + bool spdif_on; /* copy of spdif_switch, read at the start of each block */ + unsigned int spdif_frame; /* position in the channel status block */ + u8 spdif_status[24]; /* IEC 60958 channel status of the S/PDIF output */ + u32 spdif_bits; /* status bytes 0 and 1, "IEC958 Playback Default" */ struct mutex stream_mutex; u8 stream_state; /* one of STREAM_XXX (pcm.c) */ diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e62794a87e73..2e842dcc73de 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,6 +11,7 @@ snd-usb-audio-y := card.o \ helper.o \ implicit.o \ mixer.o \ + mixer_evo.o \ mixer_quirks.o \ mixer_scarlett.o \ mixer_scarlett2.o \ diff --git a/sound/usb/caiaq/Makefile b/sound/usb/caiaq/Makefile index 9a99c17a2c1b..ffa5a7edf95c 100644 --- a/sound/usb/caiaq/Makefile +++ b/sound/usb/caiaq/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-usb-caiaq-y := device.o audio.o midi.o control.o +snd-usb-caiaq-y := device.o audio.o midi.o control.o lcd.o snd-usb-caiaq-$(CONFIG_SND_USB_CAIAQ_INPUT) += input.o obj-$(CONFIG_SND_USB_CAIAQ) += snd-usb-caiaq.o diff --git a/sound/usb/caiaq/device.c b/sound/usb/caiaq/device.c index f604846ec6e6..634dc36f2ead 100644 --- a/sound/usb/caiaq/device.c +++ b/sound/usb/caiaq/device.c @@ -22,6 +22,7 @@ #include "midi.h" #include "control.h" #include "input.h" +#include "lcd.h" MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); MODULE_DESCRIPTION("caiaq USB audio"); @@ -142,7 +143,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) return; } if (urb->actual_length < 1) - return; + goto resubmit; payload_len = urb->actual_length - 1; @@ -192,6 +193,7 @@ static void usb_ep1_command_reply_dispatch (struct urb* urb) break; } + resubmit: cdev->ep1_in_urb->actual_length = 0; ret = usb_submit_urb(cdev->ep1_in_urb, GFP_ATOMIC); if (ret < 0) @@ -212,6 +214,8 @@ int snd_usb_caiaq_send_command(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 1) len = EP1_BUFSIZE - 1; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+1, buffer, len); @@ -235,6 +239,8 @@ int snd_usb_caiaq_send_command_bank(struct snd_usb_caiaqdev *cdev, if (len > EP1_BUFSIZE - 2) len = EP1_BUFSIZE - 2; + guard(mutex)(&cdev->ep1_out_mutex); + if (buffer && len > 0) memcpy(cdev->ep1_out_buf+2, buffer, len); @@ -383,10 +389,9 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) } #endif - /* finally, register the card and all its sub-instances */ - ret = snd_card_register(cdev->chip.card); + ret = snd_usb_caiaq_lcd_init(cdev); if (ret < 0) { - dev_err(dev, "snd_card_register() returned %d\n", ret); + dev_err(dev, "Unable to set up LCD (ret=%d)\n", ret); return ret; } @@ -396,6 +401,13 @@ static int setup_card(struct snd_usb_caiaqdev *cdev) return ret; } + /* finally, register the card and all its sub-instances */ + ret = snd_card_register(cdev->chip.card); + if (ret < 0) { + dev_err(dev, "snd_card_register() returned %d\n", ret); + return ret; + } + return 0; } @@ -443,6 +455,8 @@ static int create_card(struct usb_device *usb_dev, cdev->chip.usb_id = USB_ID(le16_to_cpu(usb_dev->descriptor.idVendor), le16_to_cpu(usb_dev->descriptor.idProduct)); spin_lock_init(&cdev->spinlock); + spin_lock_init(&cdev->midi_lock); + mutex_init(&cdev->ep1_out_mutex); *cardp = card; return 0; diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h index 1c6f34693fa8..16f65e7867d1 100644 --- a/sound/usb/caiaq/device.h +++ b/sound/usb/caiaq/device.h @@ -2,6 +2,8 @@ #ifndef CAIAQ_DEVICE_H #define CAIAQ_DEVICE_H +#include <linux/mutex.h> + #include "../usbaudio.h" #define USB_VID_NATIVEINSTRUMENTS 0x17cc @@ -28,6 +30,8 @@ #define MODNAME "snd-usb-caiaq" +#define CAIAQ_LCD_FRAME_SIZE 1024 + #define EP1_CMD_GET_DEVICE_INFO 0x1 #define EP1_CMD_READ_ERP 0x2 #define EP1_CMD_READ_ANALOG 0x3 @@ -35,6 +39,7 @@ #define EP1_CMD_WRITE_IO 0x5 #define EP1_CMD_MIDI_READ 0x6 #define EP1_CMD_MIDI_WRITE 0x7 +#define EP1_CMD_LCD 0x8 #define EP1_CMD_AUDIO_PARAMS 0x9 #define EP1_CMD_AUTO_MSG 0xb #define EP1_CMD_DIMM_LEDS 0xc @@ -68,10 +73,12 @@ struct snd_usb_caiaqdev { unsigned char ep1_in_buf[EP1_BUFSIZE]; unsigned char ep1_out_buf[EP1_BUFSIZE]; + struct mutex ep1_out_mutex; /* protects ep1_out_buf */ unsigned char midi_out_buf[EP1_BUFSIZE]; struct caiaq_device_spec spec; - spinlock_t spinlock; + spinlock_t spinlock; /* for PCM audio */ + spinlock_t midi_lock; /* midi_x_stream, midi_out_active */ wait_queue_head_t ep1_wait_queue; wait_queue_head_t prepare_wait_queue; int spec_received, audio_parm_answer; @@ -98,6 +105,13 @@ struct snd_usb_caiaqdev { unsigned char control_state[256]; unsigned char ep8_out_buf[2]; + /* Kore LCD */ + struct mutex lcd_mutex; /* protects the lcd_* fields */ + unsigned char lcd_frame[CAIAQ_LCD_FRAME_SIZE]; /* shown frame */ + unsigned char lcd_contrast; + bool lcd_ready; /* controller set up */ + bool lcd_frame_valid; /* lcd_frame matches the display */ + /* Linux input */ #ifdef CONFIG_SND_USB_CAIAQ_INPUT struct input_dev *input_dev; diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 8d924330c54c..856235c3398e 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -41,11 +41,11 @@ static const unsigned short keycode_kore[] = { BTN_3, BTN_2, BTN_1, - BTN_8, - BTN_7, - BTN_6, BTN_5, - KEY_BRL_DOT4, /* touch sensitive knobs */ + BTN_6, + BTN_7, + BTN_8, + KEY_BRL_DOT4, /* touch sensitive knobs (Kore 2 order) */ KEY_BRL_DOT3, KEY_BRL_DOT2, KEY_BRL_DOT1, @@ -55,6 +55,21 @@ static const unsigned short keycode_kore[] = { KEY_BRL_DOT5 }; +/* index of the first touch sensor in keycode_kore */ +#define KORE_TOUCH_KEYS 24 + +/* the first Kore controller reports its touch sensors in this order */ +static const unsigned short keycode_kore1_touch[] = { + KEY_BRL_DOT4, + KEY_BRL_DOT8, + KEY_BRL_DOT3, + KEY_BRL_DOT7, + KEY_BRL_DOT2, + KEY_BRL_DOT6, + KEY_BRL_DOT1, + KEY_BRL_DOT5 +}; + #define MASCHINE_BUTTONS (42) #define MASCHINE_BUTTON(X) ((X) + BTN_MISC) #define MASCHINE_PADS (16) @@ -666,6 +681,12 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) input->absbit[BIT_WORD(ABS_MISC)] |= BIT_MASK(ABS_MISC); BUILD_BUG_ON(sizeof(cdev->keycode) < sizeof(keycode_kore)); memcpy(cdev->keycode, keycode_kore, sizeof(keycode_kore)); + BUILD_BUG_ON(KORE_TOUCH_KEYS + ARRAY_SIZE(keycode_kore1_touch) > + ARRAY_SIZE(keycode_kore)); + if (cdev->chip.usb_id == + USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER)) + memcpy(cdev->keycode + KORE_TOUCH_KEYS, keycode_kore1_touch, + sizeof(keycode_kore1_touch)); input->keycodemax = ARRAY_SIZE(keycode_kore); input_set_abs_params(input, ABS_HAT0X, 0, 999, 0, 10); input_set_abs_params(input, ABS_HAT0Y, 0, 999, 0, 10); diff --git a/sound/usb/caiaq/lcd.c b/sound/usb/caiaq/lcd.c new file mode 100644 index 000000000000..dd2a3b949a43 --- /dev/null +++ b/sound/usb/caiaq/lcd.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LCD support for the Native Instruments Kore controllers + * + * Copyright (c) 2026 Niko Huuskonen <niko.huuskonen.00@gmail.com> + * + * Both Kore controllers have a 128x64 pixel monochrome LCD behind an + * ST7565-style controller. The firmware passes EP1 packets with the + * command byte EP1_CMD_LCD on to that controller: + * + * EP1_CMD_LCD 0x00 <n> <n controller command bytes> + * EP1_CMD_LCD 0x01 <n> <n display RAM bytes> + * + * The display is exposed as an exclusive hwdep device. Each write carries + * a whole frame of CAIAQ_LCD_FRAME_SIZE bytes: 8 pages of 128 columns, + * one byte per column and page, with bit 0 as the top pixel of the page. + * Only the pages that changed since the previous frame are sent to the + * device. The contrast is an ALSA control; the backlight is the existing + * "LED lcd" control. + * + * The controller setup and the packet layout follow the USB traffic of + * the vendor software, as documented by the OpenKoreBridge project. + */ + +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/usb.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/hwdep.h> +#include <sound/pcm.h> + +#include "device.h" +#include "lcd.h" + +#define LCD_WIDTH 128 +#define LCD_PAGES (CAIAQ_LCD_FRAME_SIZE / LCD_WIDTH) +#define LCD_COLUMN_OFFSET 4 /* first visible controller column */ +#define LCD_CHUNK 32 /* display RAM bytes per packet */ +#define LCD_CONTRAST_MAX 0x3f +#define LCD_CONTRAST_DEFAULT 0x1c + +#define LCD_KIND_COMMAND 0x00 +#define LCD_KIND_DATA 0x01 + +static int lcd_send(struct snd_usb_caiaqdev *cdev, u8 kind, + const u8 *bytes, unsigned int len) +{ + u8 buf[2 + LCD_CHUNK]; + + if (WARN_ON(len > LCD_CHUNK)) + return -EINVAL; + + buf[0] = kind; + buf[1] = len; + memcpy(buf + 2, bytes, len); + return snd_usb_caiaq_send_command(cdev, EP1_CMD_LCD, buf, len + 2); +} + +static int lcd_command(struct snd_usb_caiaqdev *cdev, u8 cmd) +{ + return lcd_send(cdev, LCD_KIND_COMMAND, &cmd, 1); +} + +static int lcd_set_contrast(struct snd_usb_caiaqdev *cdev) +{ + const u8 cmd[] = { 0x81, cdev->lcd_contrast }; + + return lcd_send(cdev, LCD_KIND_COMMAND, cmd, sizeof(cmd)); +} + +/* controller setup, in the order used by the vendor software */ +static int lcd_setup(struct snd_usb_caiaqdev *cdev) +{ + static const u8 head[] = { + 0xe2, /* reset */ + 0xa1, /* reverse column direction */ + 0xc8, /* reverse row direction */ + 0xa2, /* 1/9 bias */ + 0x2c, 0x2e, 0x2f, /* power up in three steps */ + 0x27, /* regulator resistor ratio */ + }; + static const u8 tail[] = { + 0xa6, /* normal, non-inverted display */ + 0x88, 0xef, /* sent by the vendor software */ + 0xaf, /* display on */ + }; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(head); i++) { + ret = lcd_command(cdev, head[i]); + if (ret) + return ret; + } + + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + + for (i = 0; i < ARRAY_SIZE(tail); i++) { + ret = lcd_command(cdev, tail[i]); + if (ret) + return ret; + } + + return 0; +} + +static int lcd_write_page(struct snd_usb_caiaqdev *cdev, unsigned int page, + const u8 *data) +{ + unsigned int col; + int ret; + + for (col = 0; col < LCD_WIDTH; col += LCD_CHUNK) { + unsigned int addr = LCD_COLUMN_OFFSET + col; + const u8 column[] = { 0x10 | (addr >> 4), addr & 0x0f }; + + ret = lcd_command(cdev, 0xb0 | page); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_COMMAND, + column, sizeof(column)); + if (!ret) + ret = lcd_send(cdev, LCD_KIND_DATA, + data + col, LCD_CHUNK); + if (ret) + return ret; + } + + return 0; +} + +static long lcd_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + struct snd_usb_caiaqdev *cdev = hw->private_data; + unsigned int page; + int ret; + + if (count != CAIAQ_LCD_FRAME_SIZE) + return -EINVAL; + + u8 *frame __free(kfree) = memdup_user(buf, count); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + guard(mutex)(&cdev->lcd_mutex); + + if (!cdev->lcd_ready) { + ret = lcd_setup(cdev); + if (ret) + return ret; + cdev->lcd_ready = true; + cdev->lcd_frame_valid = false; + } + + for (page = 0; page < LCD_PAGES; page++) { + u8 *shown = cdev->lcd_frame + page * LCD_WIDTH; + const u8 *next = frame + page * LCD_WIDTH; + + if (cdev->lcd_frame_valid && !memcmp(shown, next, LCD_WIDTH)) + continue; + + ret = lcd_write_page(cdev, page, next); + if (ret) { + cdev->lcd_frame_valid = false; + return ret; + } + memcpy(shown, next, LCD_WIDTH); + } + cdev->lcd_frame_valid = true; + + return count; +} + +static int lcd_contrast_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = LCD_CONTRAST_MAX; + return 0; +} + +static int lcd_contrast_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&cdev->lcd_mutex); + ucontrol->value.integer.value[0] = cdev->lcd_contrast; + return 0; +} + +static int lcd_contrast_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_usb_caiaqdev *cdev = snd_kcontrol_chip(kcontrol); + long val = ucontrol->value.integer.value[0]; + int ret; + + if (val < 0 || val > LCD_CONTRAST_MAX) + return -EINVAL; + + guard(mutex)(&cdev->lcd_mutex); + if (val == cdev->lcd_contrast) + return 0; + + cdev->lcd_contrast = val; + if (cdev->lcd_ready) { + ret = lcd_set_contrast(cdev); + if (ret) + return ret; + } + + return 1; +} + +static const struct snd_kcontrol_new lcd_contrast_control = { + .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, + .name = "LCD Contrast", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = lcd_contrast_info, + .get = lcd_contrast_get, + .put = lcd_contrast_put, +}; + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev) +{ + struct snd_card *card = cdev->chip.card; + struct snd_hwdep *hw; + int ret; + + switch (cdev->chip.usb_id) { + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER): + case USB_ID(USB_VID_NATIVEINSTRUMENTS, USB_PID_KORECONTROLLER2): + break; + default: + return 0; + } + + mutex_init(&cdev->lcd_mutex); + cdev->lcd_contrast = LCD_CONTRAST_DEFAULT; + + ret = snd_hwdep_new(card, "Kore LCD", 0, &hw); + if (ret < 0) + return ret; + + strscpy(hw->name, "Kore LCD", sizeof(hw->name)); + hw->iface = SNDRV_HWDEP_IFACE_CAIAQ; + hw->private_data = cdev; + hw->exclusive = 1; + hw->ops.write = lcd_hwdep_write; + + return snd_ctl_add(card, snd_ctl_new1(&lcd_contrast_control, cdev)); +} diff --git a/sound/usb/caiaq/lcd.h b/sound/usb/caiaq/lcd.h new file mode 100644 index 000000000000..423464750278 --- /dev/null +++ b/sound/usb/caiaq/lcd.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef CAIAQ_LCD_H +#define CAIAQ_LCD_H + +int snd_usb_caiaq_lcd_init(struct snd_usb_caiaqdev *cdev); + +#endif /* CAIAQ_LCD_H */ diff --git a/sound/usb/caiaq/midi.c b/sound/usb/caiaq/midi.c index 18529484c8dc..b7d7d24937a7 100644 --- a/sound/usb/caiaq/midi.c +++ b/sound/usb/caiaq/midi.c @@ -79,6 +79,7 @@ static void snd_usb_caiaq_midi_output_trigger(struct snd_rawmidi_substream *subs { struct snd_usb_caiaqdev *cdev = substream->rmidi->private_data; + guard(spinlock_irqsave)(&cdev->midi_lock); if (up) { cdev->midi_out_substream = substream; if (!cdev->midi_out_active) @@ -151,6 +152,7 @@ void snd_usb_caiaq_midi_output_done(struct urb* urb) { struct snd_usb_caiaqdev *cdev = urb->context; + guard(spinlock_irqsave)(&cdev->midi_lock); cdev->midi_out_active = 0; if (urb->status != 0) return; diff --git a/sound/usb/card.h b/sound/usb/card.h index 8299ac241c60..f956bcf00f77 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -123,6 +123,7 @@ struct snd_usb_endpoint { unsigned int syncmaxsize; /* sync endpoint packet size */ unsigned int fill_max:1; /* fill max packet size always */ unsigned int tenor_fb_quirk:1; /* corrupted feedback data */ + unsigned int snap_fb_quirk:1; /* ignore implausible feedback */ unsigned int datainterval; /* log_2 of data packet interval */ unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */ unsigned char silence_value; diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 34832183a1f0..cc77eace7b59 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -550,6 +550,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, } /* + * Return the partner rate in the other base-rate family. + * See QUIRK_FLAG_SWAP_RATES. + */ +static unsigned int swap_base_rate(unsigned int rate) +{ + switch (rate) { + case 44100: + return 48000; + case 48000: + return 44100; + case 88200: + return 96000; + case 96000: + return 88200; + case 176400: + return 192000; + case 192000: + return 176400; + default: + return rate; + } +} + +/* * Try to set the given sample rate: * * Return 0 if the clock source is read-only, the actual rate on success, @@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, if (!writeable) return 0; + if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES) + rate = swap_base_rate(rate); + data = cpu_to_le32(rate); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 879d0451536e..9c844c55ad3f 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -1908,6 +1908,22 @@ static void snd_usb_handle_sync_urb(struct snd_usb_endpoint *ep, else f >>= -ep->freqshift; + if (unlikely(sender->snap_fb_quirk) && + (f < ep->freqn - ep->freqn / 128 || + f > ep->freqn + ep->freqn / 128)) { + /* + * Feedback deviates from nominal far beyond what real + * clock differences can explain. This firmware can get + * stuck at nominal+1 sample per frame, causing audible + * pops. Trust the nominal rate instead. + */ + dev_warn_ratelimited(&ep->chip->dev->dev, + "EP 0x%x: ignoring implausible feedback 0x%x.%04x (nominal 0x%x.%04x)\n", + ep->ep_num, f >> 16, f & 0xffff, + ep->freqn >> 16, ep->freqn & 0xffff); + f = ep->freqn; + } + if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) { /* * If the frequency looks valid, set it. diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 6dbaf30232f9..3e1f40d6b481 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -117,7 +117,8 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) } else { /* copy single chunk */ memcpy(runtime->dma_area + - line6pcm->in.pos_done * bytes_per_frame, fbuf, fsize); + line6pcm->in.pos_done * bytes_per_frame, fbuf, + frames * bytes_per_frame); } line6pcm->in.pos_done += frames; diff --git a/sound/usb/line6/pcm.c b/sound/usb/line6/pcm.c index 2932eaf157f4..d17baf7761c6 100644 --- a/sound/usb/line6/pcm.c +++ b/sound/usb/line6/pcm.c @@ -155,7 +155,7 @@ static int line6_buffer_acquire(struct snd_line6_pcm *line6pcm, /* Invoked multiple times in a row so allocate once only */ if (!test_and_set_bit(type, &pstr->opened) && !pstr->buffer) { pstr->buffer = - kmalloc(array3_size(line6pcm->line6->iso_buffers, + kzalloc(array3_size(line6pcm->line6->iso_buffers, LINE6_ISO_PACKETS, pkt_size), GFP_KERNEL); if (!pstr->buffer) @@ -173,6 +173,14 @@ static void line6_buffer_release(struct snd_line6_pcm *line6pcm, clear_bit(type, &pstr->opened); if (!pstr->opened) { line6_wait_clear_audio_urbs(line6pcm, pstr); + if (pstr == &line6pcm->in) { + /* prev_fbuf points into the capture buffer we are about + * to free; drop it while no capture URB is in flight. + */ + guard(spinlock_irqsave)(&line6pcm->in.lock); + line6pcm->prev_fbuf = NULL; + line6pcm->prev_fsize = 0; + } kfree(pstr->buffer); pstr->buffer = NULL; } @@ -554,7 +562,11 @@ int line6_init_pcm(struct usb_line6 *line6, line6pcm->max_packet_size_out = usb_maxpacket(line6->usbdev, usb_sndisocpipe(line6->usbdev, ep_write)); - if (!line6pcm->max_packet_size_in || !line6pcm->max_packet_size_out) { + /* reject max packet sizes smaller than bytes-per-frame; + * (the magic number 6 is taken from the playback case) + */ + if (line6pcm->max_packet_size_in < 6 || + line6pcm->max_packet_size_out < 6) { dev_err(line6pcm->line6->ifcdev, "cannot get proper max packet size\n"); return -EINVAL; diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 7e5b1b13360f..e3d157554061 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -113,6 +113,7 @@ struct snd_usb_midi { unsigned int opened[2]; unsigned char disconnected; unsigned char input_running; + unsigned char keep_input_running; struct snd_kcontrol *roland_load_ctl; }; @@ -1149,41 +1150,42 @@ static int substream_open(struct snd_rawmidi_substream *substream, int dir, int open) { struct snd_usb_midi *umidi = substream->rmidi->private_data; - struct snd_kcontrol *ctl; + struct snd_ctl_elem_id ctl_id; + bool activate_ctl = false; + bool active; guard(rwsem_read)(&umidi->disc_rwsem); if (umidi->disconnected) return open ? -ENODEV : 0; - guard(mutex)(&umidi->mutex); - if (open) { - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access |= - SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); - update_roland_altsetting(umidi); + scoped_guard(mutex, &umidi->mutex) { + if (open) { + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = false; + update_roland_altsetting(umidi); + } } - } - umidi->opened[dir]++; - if (umidi->opened[1]) - snd_usbmidi_input_start(&umidi->list); - } else { - umidi->opened[dir]--; - if (!umidi->opened[1]) - snd_usbmidi_input_stop(&umidi->list); - if (!umidi->opened[0] && !umidi->opened[1]) { - if (umidi->roland_load_ctl) { - ctl = umidi->roland_load_ctl; - ctl->vd[0].access &= - ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; - snd_ctl_notify(umidi->card, - SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + umidi->opened[dir]++; + if (umidi->opened[1]) + snd_usbmidi_input_start(&umidi->list); + } else { + umidi->opened[dir]--; + if (!umidi->opened[1] && !umidi->keep_input_running) + snd_usbmidi_input_stop(&umidi->list); + if (!umidi->opened[0] && !umidi->opened[1]) { + if (umidi->roland_load_ctl) { + ctl_id = umidi->roland_load_ctl->id; + activate_ctl = true; + active = true; + } } } } + if (activate_ctl) + snd_ctl_activate_id(umidi->card, &ctl_id, active); return 0; } @@ -1281,10 +1283,7 @@ static void snd_usbmidi_input_trigger(struct snd_rawmidi_substream *substream, { struct snd_usb_midi *umidi = substream->rmidi->private_data; - if (up) - set_bit(substream->number, &umidi->input_triggered); - else - clear_bit(substream->number, &umidi->input_triggered); + assign_bit(substream->number, &umidi->input_triggered, up); } static const struct snd_rawmidi_ops snd_usbmidi_output_ops = { @@ -2262,6 +2261,10 @@ static int snd_usbmidi_detect_roland(struct snd_usb_midi *umidi, struct usb_host_interface *hostif; u8 *cs_desc; + /* TD-11 becomes unusable if input isn't serviced continuously */ + if (umidi->usb_id == USB_ID(0x0582, 0x0151)) + umidi->keep_input_running = 1; + intf = umidi->iface; if (!intf) return -ENOENT; @@ -2469,7 +2472,7 @@ void snd_usbmidi_input_start(struct list_head *p) int i; umidi = list_entry(p, struct snd_usb_midi, list); - if (umidi->input_running || !umidi->opened[1]) + if (umidi->input_running || (!umidi->opened[1] && !umidi->keep_input_running)) return; for (i = 0; i < MIDI_MAX_ENDPOINTS; ++i) snd_usbmidi_input_start_ep(umidi, umidi->endpoints[i].in); @@ -2644,6 +2647,10 @@ int __snd_usbmidi_create(struct snd_card *card, if (err < 0) goto exit; + /* start input for devices that need continuous servicing */ + if (umidi->keep_input_running) + snd_usbmidi_input_start(&umidi->list); + usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ecaa8bc08d7c..a95f26a3cfa7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -304,8 +304,9 @@ static inline int mixer_ctrl_intf(struct usb_mixer_interface *mixer) return get_iface_desc(mixer->hostif)->bInterfaceNumber; } -static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* send a request for UAC1 feature & mixer unit */ +static int request_get_ctl_v1(struct usb_mixer_elem_info *cval, + u8 request, int validx, int *value_ret) { struct snd_usb_audio *chip = cval->head.mixer->chip; unsigned char buf[2]; @@ -317,6 +318,8 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, if (pm.err < 0) return -EIO; + validx += cval->idx_off; + while (timeout-- > 0) { idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), request, @@ -335,94 +338,84 @@ static int get_ctl_value_v1(struct usb_mixer_elem_info *cval, int request, return -EINVAL; } -static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* convert the given UAC1 wValue (ICN|OCN) to UAC2 MCN */ +static unsigned char to_mcn(const struct usb_mixer_elem_info *cval, + unsigned int validx) { - struct snd_usb_audio *chip = cval->head.mixer->chip; - /* enough space for one range */ - unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; - unsigned char *val; - int idx = 0, ret, val_size, size; - __u8 bRequest; + unsigned char m = (validx >> 8) & 0xff; /* 1-based input channel */ + unsigned char v = validx & 0xff; /* 1-based output channel */ - val_size = uac2_ctl_value_size(cval->val_type); + /* num_inputs * num_outputs is guaranteed to be < 256 */ + return (m - 1) * cval->num_outputs + (v - 1); +} - if (request == UAC_GET_CUR) { - bRequest = UAC2_CS_CUR; - size = val_size; - } else { - bRequest = UAC2_CS_RANGE; - size = sizeof(__u16) + 3 * val_size; - } +/* send a request for UAC2 feature & mixer unit */ +static int request_get_ctl_v2(struct usb_mixer_elem_info *cval, + u8 request, int validx, unsigned char *buf, + int size) +{ + struct snd_usb_audio *chip = cval->head.mixer->chip; + int idx, ret; - memset(buf, 0, sizeof(buf)); + CLASS(snd_usb_lock, pm)(chip); + if (pm.err) + return -EIO; - { - CLASS(snd_usb_lock, pm)(chip); - if (pm.err) - return -EIO; + validx += cval->idx_off; - idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); - ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest, - USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, - validx, idx, buf, size); - } + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); - if (ret < 0) { + memset(buf, 0, size); + idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8); + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + validx, idx, buf, size); + if (ret < 0) usb_audio_dbg(chip, "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", request, validx, idx, cval->val_type); - return ret; - } - - /* FIXME: how should we handle multiple triplets here? */ - switch (request) { - case UAC_GET_CUR: - val = buf; - break; - case UAC_GET_MIN: - val = buf + sizeof(__u16); - break; - case UAC_GET_MAX: - val = buf + sizeof(__u16) + val_size; - break; - case UAC_GET_RES: - val = buf + sizeof(__u16) + val_size * 2; - break; - default: - return -EINVAL; - } - - *value_ret = convert_signed_value(cval, - snd_usb_combine_bytes(val, val_size)); - - return 0; + return ret; } -static int get_ctl_value(struct usb_mixer_elem_info *cval, int request, - int validx, int *value_ret) +/* read the current value for UAC2 */ +static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, + int validx, int *value_ret) { - validx += cval->idx_off; + /* enough space for one value */ + unsigned char buf[sizeof(__u32)]; + int ret, val_size; - return (cval->head.mixer->protocol == UAC_VERSION_1) ? - get_ctl_value_v1(cval, request, validx, value_ret) : - get_ctl_value_v2(cval, request, validx, value_ret); + val_size = uac2_ctl_value_size(cval->val_type); + + ret = request_get_ctl_v2(cval, UAC2_CS_CUR, validx, buf, val_size); + if (ret < 0) + return ret; + + *value_ret = convert_signed_value(cval, + snd_usb_combine_bytes(buf, val_size)); + return 0; } +/* read the current value */ static int get_cur_ctl_value(struct usb_mixer_elem_info *cval, - int validx, int *value) + int validx, int *value_ret) { - return get_ctl_value(cval, UAC_GET_CUR, validx, value); + return (cval->head.mixer->protocol == UAC_VERSION_1) ? + request_get_ctl_v1(cval, UAC_GET_CUR, validx, value_ret) : + get_ctl_value_v2(cval, validx, value_ret); } /* channel = 0: master, 1 = first channel */ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, int channel, int *value) { - return get_ctl_value(cval, UAC_GET_CUR, - (cval->control << 8) | channel, - value); + return get_cur_ctl_value(cval, + (cval->control << 8) | channel, + value); } int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, @@ -430,7 +423,7 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, { int err; - if (cval->cached & BIT(channel)) { + if (test_bit(channel, cval->cached)) { *value = cval->cache_val[index]; return 0; } @@ -447,11 +440,86 @@ int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, cval->control, channel, err); return err; } - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = *value; return 0; } +/* extract the mixer min/max/res info from UAC1 feature / mixer unit */ +static int get_ctl_range_v1(struct usb_mixer_elem_info *cval, int validx) +{ + int last_valid_res; + + if (request_get_ctl_v1(cval, UAC_GET_MAX, validx, &cval->max) < 0 || + request_get_ctl_v1(cval, UAC_GET_MIN, validx, &cval->min) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get min/max values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) { + cval->res = 1; + return 0; + } + + last_valid_res = cval->res; + while (cval->res > 1) { + if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, + validx, cval->res / 2) < 0) + break; + cval->res /= 2; + } + if (request_get_ctl_v1(cval, UAC_GET_RES, validx, &cval->res) < 0) + cval->res = last_valid_res; + + return 0; +} + +/* extract the mixer min/max/res info from UAC2 feature / mixer unit */ +static int get_ctl_range_v2(struct usb_mixer_elem_info *cval, int validx) +{ + /* enough space for one range */ + unsigned char buf[sizeof(__u16) + 3 * sizeof(__u32)]; + unsigned char *val; + int val_size, size; + + val_size = uac2_ctl_value_size(cval->val_type); + size = sizeof(__u16) + 3 * val_size; + + if (request_get_ctl_v2(cval, UAC2_CS_RANGE, validx, buf, size) < 0) { + usb_audio_err(cval->head.mixer->chip, + "%d:%d: cannot get RANGE values for control %d (id %d)\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->control, cval->head.id); + return -EAGAIN; /* handled by the caller later again */ + } + + /* FIXME: how should we handle multiple triplets here? */ + val = buf + 2; + cval->min = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->max = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + val += val_size; + cval->res = convert_signed_value(cval, snd_usb_combine_bytes(val, val_size)); + return 0; +} + +/* extract the mixer min/max/res info */ +static int get_ctl_range(struct usb_mixer_elem_info *cval, int validx) +{ + switch (cval->head.mixer->protocol) { + case UAC_VERSION_1: + return get_ctl_range_v1(cval, validx); + case UAC_VERSION_2: + case UAC_VERSION_3: + return get_ctl_range_v2(cval, validx); + default: + return -EINVAL; + } +} + /* * set a mixer value */ @@ -478,6 +546,10 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, } request = UAC2_CS_CUR; + + /* correct wValue for UAC2 mixer control with MCN */ + if (cval->v2_mixer) + validx = (UAC2_MU_MIXER << 8) | to_mcn(cval, validx); } value_set = convert_bytes_value(cval, value_set); @@ -532,7 +604,7 @@ int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, value); if (err < 0) return err; - cval->cached |= BIT(channel); + set_bit(channel, cval->cached); cval->cache_val[index] = value; return 0; } @@ -577,20 +649,25 @@ static int check_matrix_bitmap(unsigned char *bmap, /* * add an alsa control element - * search and increment the index until an empty slot is found. + * when @look_for_empty is set, search and increment the index until an empty + * slot is found. * * if failed, give up and free the control instance. */ int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info) + bool is_std_info, + bool look_for_empty) { struct usb_mixer_interface *mixer = list->mixer; int err; - while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) - kctl->id.index++; + if (look_for_empty) { + while (snd_ctl_find_id(mixer->chip->card, &kctl->id)) + kctl->id.index++; + } + err = snd_ctl_add(mixer->chip->card, kctl); if (err < 0) { usb_audio_dbg(mixer->chip, "cannot add control (err = %d)\n", @@ -786,7 +863,7 @@ static int parse_term_uac2_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -809,7 +886,7 @@ static int parse_term_uac3_iterm_unit(struct mixer_build *state, return err; /* save input term properties after recursion, - * to ensure they are not overriden by the recursion calls + * to ensure they are not overridden by the recursion calls */ term->id = id; term->type = le16_to_cpu(d->wTerminalType); @@ -1344,32 +1421,11 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; } } - if (get_ctl_value(cval, UAC_GET_MAX, (cval->control << 8) | minchn, &cval->max) < 0 || - get_ctl_value(cval, UAC_GET_MIN, (cval->control << 8) | minchn, &cval->min) < 0) { - usb_audio_err(cval->head.mixer->chip, - "%d:%d: cannot get min/max values for control %d (id %d)\n", - cval->head.id, mixer_ctrl_intf(cval->head.mixer), - cval->control, cval->head.id); - return -EAGAIN; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, - &cval->res) < 0) { - cval->res = 1; - } else if (cval->head.mixer->protocol == UAC_VERSION_1) { - int last_valid_res = cval->res; - while (cval->res > 1) { - if (snd_usb_mixer_set_ctl_value(cval, UAC_SET_RES, - (cval->control << 8) | minchn, - cval->res / 2) < 0) - break; - cval->res /= 2; - } - if (get_ctl_value(cval, UAC_GET_RES, - (cval->control << 8) | minchn, &cval->res) < 0) - cval->res = last_valid_res; - } + ret = get_ctl_range(cval, (cval->control << 8) | minchn); + if (ret < 0) + return ret; + if (cval->res == 0) cval->res = 1; @@ -1398,7 +1454,7 @@ no_checks: * properly. */ if (ret) - cval->cached = 0; + bitmap_zero(cval->cached, MAX_CHANNELS + 1); cval->initialized = 1; } @@ -1480,6 +1536,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int ret; + guard(mutex)(&cval->head.mixer->lock); if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; @@ -1514,6 +1571,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int c, cnt, val, err; + guard(mutex)(&cval->head.mixer->lock); ucontrol->value.integer.value[0] = cval->min; if (cval->cmask) { cnt = 0; @@ -1544,10 +1602,12 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); - int max_val = get_max_exposed(cval); + int max_val; int c, cnt, val, oval, err; int changed = 0; + guard(mutex)(&cval->head.mixer->lock); + max_val = get_max_exposed(cval); if (cval->cmask) { cnt = 0; for (c = 0; c < MAX_CHANNELS; c++) { @@ -1595,6 +1655,7 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return filter_error(cval, err); @@ -2326,7 +2387,7 @@ static bool mixer_bitmap_overflow(struct uac_mixer_unit_descriptor *desc, */ static void build_mixer_unit_ctl(struct mixer_build *state, struct uac_mixer_unit_descriptor *desc, - int in_pin, int in_ch, int num_outs, + int in_ch, int num_outs, int unitid, struct usb_audio_term *iterm) { struct usb_mixer_elem_info *cval; @@ -2345,6 +2406,10 @@ static void build_mixer_unit_ctl(struct mixer_build *state, snd_usb_mixer_elem_init_std(&cval->head, state->mixer, unitid); cval->control = in_ch + 1; /* based on 1 */ + if (state->mixer->protocol == UAC_VERSION_2 || + state->mixer->protocol == UAC_VERSION_3) + cval->v2_mixer = true; + cval->num_outputs = num_outs; cval->val_type = USB_MIXER_S16; for (i = 0; i < num_outs; i++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); @@ -2417,24 +2482,13 @@ static int parse_audio_input_terminal(struct mixer_build *state, int unitid, /* * parse a mixer unit */ -static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, - void *raw_desc) +/* UAC1 mixer unit */ +static int parse_audio_mixer_unit_v1(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) { - struct uac_mixer_unit_descriptor *desc = raw_desc; struct usb_audio_term iterm; - int input_pins, num_ins, num_outs; - int pin, ich, err; - - err = uac_mixer_unit_get_channels(state, desc); - if (err < 0) { - usb_audio_err(state->chip, - "invalid MIXER UNIT descriptor %d\n", - unitid); - return err; - } - - num_outs = err; - input_pins = desc->bNrInPins; + int num_ins, pin, ich, och, err; num_ins = 0; ich = 0; @@ -2453,25 +2507,90 @@ static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, num_ins, num_outs)) break; for (; ich < num_ins; ich++) { - int och, ich_has_controls = 0; - for (och = 0; och < num_outs; och++) { __u8 *c = uac_mixer_unit_bmControls(desc, state->mixer->protocol); - if (check_matrix_bitmap(c, ich, och, num_outs)) { - ich_has_controls = 1; + if (check_matrix_bitmap(c, ich, och, num_outs)) break; - } } - if (ich_has_controls) - build_mixer_unit_ctl(state, desc, pin, ich, num_outs, + if (och < num_outs) + build_mixer_unit_ctl(state, desc, ich, num_outs, unitid, &iterm); } } return 0; } +/* UAC2/UAC3 mixer unit */ +static int parse_audio_mixer_unit_v2(struct mixer_build *state, int unitid, + int input_pins, int num_outs, + struct uac_mixer_unit_descriptor *desc) +{ + struct usb_audio_term iterm; + int pin, och, err; + + if (input_pins * num_outs > 256 || + mixer_bitmap_overflow(desc, state->mixer->protocol, + input_pins, num_outs)) { + usb_audio_err(state->chip, + "invalid channels for MIXER UNIT %d: input=%d, output=%d\n", + unitid, input_pins, num_outs); + return -EINVAL; + } + + for (pin = 0; pin < input_pins; pin++) { + err = parse_audio_unit(state, desc->baSourceID[pin]); + if (err < 0) + continue; + if (!num_outs) + continue; + err = check_input_term(state, desc->baSourceID[pin], &iterm); + if (err < 0) + return err; + + for (och = 0; och < num_outs; och++) { + __u8 *c = uac_mixer_unit_bmControls(desc, + state->mixer->protocol); + + if (check_matrix_bitmap(c, pin, och, num_outs)) + break; + } + if (och < num_outs) + build_mixer_unit_ctl(state, desc, pin, num_outs, + unitid, &iterm); + } + return 0; +} + +static int parse_audio_mixer_unit(struct mixer_build *state, int unitid, + void *raw_desc) +{ + struct uac_mixer_unit_descriptor *desc = raw_desc; + int num_outs; + + num_outs = uac_mixer_unit_get_channels(state, desc); + if (num_outs < 0) { + usb_audio_err(state->chip, + "invalid MIXER UNIT descriptor %d\n", + unitid); + return num_outs; + } + + switch (state->mixer->protocol) { + case UAC_VERSION_1: + default: + return parse_audio_mixer_unit_v1(state, unitid, + desc->bNrInPins, num_outs, + desc); + case UAC_VERSION_2: + case UAC_VERSION_3: + return parse_audio_mixer_unit_v2(state, unitid, + desc->bNrInPins, num_outs, + desc); + } +} + /* * Processing Unit / Extension Unit */ @@ -2483,6 +2602,7 @@ static int mixer_ctl_procunit_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int err, val; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.integer.value[0] = cval->min; @@ -2500,6 +2620,7 @@ static int mixer_ctl_procunit_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -2851,6 +2972,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { ucontrol->value.enumerated.item[0] = 0; @@ -2868,6 +2990,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); int val, oval, err; + guard(mutex)(&cval->head.mixer->lock); err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) return filter_error(cval, err); @@ -3140,6 +3263,7 @@ static void snd_usb_mixer_free(struct usb_mixer_interface *mixer) } usb_free_urb(mixer->rc_urb); kfree(mixer->rc_setup_packet); + mutex_destroy(&mixer->lock); kfree(mixer); } @@ -3550,7 +3674,7 @@ void snd_usb_mixer_notify_id(struct usb_mixer_interface *mixer, int unitid) info = mixer_elem_list_to_info(list); /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &list->kctl->id); } @@ -3617,7 +3741,7 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, __u8 channel = value & 0xff; unsigned int count = 0; - if (channel >= MAX_CHANNELS) { + if (channel > MAX_CHANNELS) { usb_audio_dbg(mixer->chip, "%s(): bogus channel number %d\n", __func__, channel); @@ -3649,9 +3773,9 @@ static void snd_usb_mixer_interrupt_v2(struct usb_mixer_interface *mixer, /* invalidate cache, so the value is read from the device */ if (!info->get_cur_broken) { if (channel) - info->cached &= ~BIT(channel); + clear_bit(channel, info->cached); else /* master channel */ - info->cached = 0; + bitmap_zero(info->cached, MAX_CHANNELS + 1); } snd_ctl_notify(mixer->chip->card, SNDRV_CTL_EVENT_MASK_VALUE, @@ -3784,6 +3908,8 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif) return -ENOMEM; } + mutex_init(&mixer->lock); + mixer->hostif = &usb_ifnum_to_if(chip->dev, ctrlif)->altsetting[0]; switch (get_iface_desc(mixer->hostif)->bInterfaceProtocol) { case UAC_VERSION_1: @@ -3887,7 +4013,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & BIT(c))) continue; - if (cval->cached & BIT(c + 1)) { + if (test_bit(c + 1, cval->cached)) { err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) @@ -3897,7 +4023,7 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list) } } else { /* master */ - if (cval->cached) + if (test_bit(0, cval->cached)) snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 037b446d8b6f..c40f832852be 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -18,6 +18,7 @@ struct usb_mixer_interface { struct usb_host_interface *hostif; struct list_head list; unsigned int ignore_ctl_error; + struct mutex lock; /* lock for feature unit callbacks */ /* UAC2 status interrupt endpoint; owned by mixer.c */ struct urb *urb; /* array[MAX_ID_ELEMS], indexed by unit id */ @@ -92,11 +93,13 @@ struct usb_mixer_elem_info { int min, max, res; int max_exposed; /* control API exposes the value in 0..max_exposed */ int dBmin, dBmax; - int cached; + DECLARE_BITMAP(cached, MAX_CHANNELS + 1); int cache_val[MAX_CHANNELS]; u8 initialized; u8 min_mute; u8 get_cur_broken; + u8 num_outputs; + bool v2_mixer; void *private_data; }; @@ -110,10 +113,11 @@ int snd_usb_mixer_set_ctl_value(struct usb_mixer_elem_info *cval, int snd_usb_mixer_add_list(struct usb_mixer_elem_list *list, struct snd_kcontrol *kctl, - bool is_std_info); + bool is_std_info, + bool look_for_empty); #define snd_usb_mixer_add_control(list, kctl) \ - snd_usb_mixer_add_list(list, kctl, true) + snd_usb_mixer_add_list(list, kctl, true, true) void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list, struct usb_mixer_interface *mixer, diff --git a/sound/usb/mixer_evo.c b/sound/usb/mixer_evo.c new file mode 100644 index 000000000000..c327b82c8d2d --- /dev/null +++ b/sound/usb/mixer_evo.c @@ -0,0 +1,505 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Audient EVO driver for ALSA + * Copyright (C) 2026 Christian Ruppert <arc@gmx.li> + * + * Based on the work of Ivan Hazucha for audient-evo-py. + * https://github.com/vanzaho/audient-evo-py/ + */ + + +#include <asm/byteorder.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "mixer_quirks.h" +#include "helper.h" +#include "mixer_evo.h" + + +enum snd_evo_type { + SND_EVO_TYPE_MONITOR = 0, + SND_EVO_TYPE_PHANTOM, + SND_EVO_TYPE_MUTE, + SND_EVO_TYPE_MUTE_OUT, + SND_EVO_TYPE_MIXER, + SND_EVO_TYPE_NCTYPES +}; + +static const struct snd_evo_ctrl_type { + snd_ctl_elem_type_t type; + bool invert; + union { + int integer; + } min; + union { + int integer; + } max; + u16 wValue; + u16 wIndex; +} snd_evo_ctypes[SND_EVO_TYPE_NCTYPES] = { + [SND_EVO_TYPE_MONITOR] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = 0, + .max.integer = 127, + .wValue = 0x0000, + .wIndex = 0x3800, + }, + [SND_EVO_TYPE_PHANTOM] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = false, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0000, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0200, + .wIndex = 0x3A00, + }, + [SND_EVO_TYPE_MUTE_OUT] = { + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .invert = true, + .min.integer = 0, + .max.integer = 1, + .wValue = 0x0100, + .wIndex = 0x3B00, + }, + [SND_EVO_TYPE_MIXER] = { + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .min.integer = -128 * 256, + .max.integer = 6 * 256, + .wValue = 0x0100, + .wIndex = 0x3C00, + }, +}; + +#define SND_EVO_FLAGS_DIRECTION (1L<<0) +#define SND_EVO_FLAGS_CAPTURE (1L<<0) +#define SND_EVO_FLAGS_PLAYBACK (0L<<0) + +struct snd_evo_ctrl { + char *basename; + enum snd_evo_type type; + unsigned int channels; + unsigned int hwchannels; + unsigned int val_offs; + unsigned long flags; +}; + +static const struct snd_evo_ctrl evo4_controls[] = { + { + .basename = "Master", + .type = SND_EVO_TYPE_MUTE_OUT, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic", + .type = SND_EVO_TYPE_MUTE, + .channels = 4, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Monitor", + .type = SND_EVO_TYPE_MONITOR, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_PLAYBACK, + }, + { + .basename = "Mic 1", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 2, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 4, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Master Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 6, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Left", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 8, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Loopback Right", + .type = SND_EVO_TYPE_MIXER, + .channels = 2, + .hwchannels = 2, + .val_offs = 10, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 1 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 0, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { + .basename = "Mic 2 Phantom", + .type = SND_EVO_TYPE_PHANTOM, + .channels = 1, + .hwchannels = 1, + .val_offs = 1, + .flags = SND_EVO_FLAGS_CAPTURE, + }, + { 0 }, /* sentinel */ +}; + +static const struct evo_devinfo { + u32 usb_id; + const struct snd_evo_ctrl *controls; +} evo_devinfo[] = { + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = evo4_controls, + }, + { + .usb_id = USB_ID(USB_AUDIENT_VID, USB_EVO4_PID), + .controls = NULL, + }, + { 0 } /* sentinel */ +}; + + +static inline unsigned long snd_evo_kctl_priv(enum snd_evo_type type, + u8 valoffs, u8 hwchannels) +{ + unsigned long type_l = type & 0xFF; + unsigned long valoffs_l = valoffs & 0xFF; + unsigned long hwchannels_l = hwchannels & 0xFF; + + return (hwchannels_l << 16) | (valoffs_l << 8) | type_l; +} + +static __always_inline +const struct snd_evo_ctrl_type *snd_evo_kctl_type(struct snd_kcontrol *kctl) +{ + return &snd_evo_ctypes[kctl->private_value & 0xFF]; +} + +static __always_inline u16 snd_evo_kctl_valoffs(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 8) & 0xFF; +} + +static __always_inline u16 snd_evo_kctl_hwchannels(struct snd_kcontrol *kctl) +{ + return (kctl->private_value >> 16) & 0xFF; +} + +static inline u16 snd_evo_kctl_channels(struct snd_kcontrol *kctl) +{ + struct usb_mixer_elem_info *info = kctl->private_data; + + return info->channels; +} + + +#define EVO_REQ_CUR (0x01) +#define EVO_REQTYPE_SET (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) +#define EVO_REQTYPE_GET (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) + +static inline int snd_evo_get_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_GET, + value, index, buf, len); +} + +static inline int snd_evo_set_cur(struct usb_mixer_interface *mixer, + u16 value, u16 index, + void *buf, size_t len) +{ + struct usb_device *const dev = mixer->chip->dev; + + return snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + EVO_REQ_CUR, EVO_REQTYPE_SET, + value, index, buf, len); +} + + +static int snd_ctl_evo_boolean_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u32 buf; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + ctl_val->value.integer.value[i] = le32_to_cpu(buf) ? 1 : 0; + + if (ctype->invert) + ctl_val->value.integer.value[i] ^= 0x1; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->invert + ? ctype->max.integer + : ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_boolean_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + u32 rbuf; + u32 buf = cpu_to_le32(ctl_val->value.integer.value[i] ? 1 : 0); + int ret; + + if (ctype->invert) + buf ^= 0x1; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_integer_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *const list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *const mixer = list->mixer; + + const unsigned int nchan = snd_evo_kctl_channels(kctl); + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int i; + + for (i = 0; i < hwchan; i++) { + u16 buf; + s16 val; + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + + val = le16_to_cpu(buf); + ctl_val->value.integer.value[i] = val; + } + + for (; i < nchan; i++) + ctl_val->value.integer.value[i] = ctype->min.integer; + + return 0; +} + +static int snd_ctl_evo_integer_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ctl_val) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl); + struct usb_mixer_interface *mixer = list->mixer; + + const unsigned int hwchan = snd_evo_kctl_hwchannels(kctl); + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + const u16 value = ctype->wValue + snd_evo_kctl_valoffs(kctl); + const u16 index = ctype->wIndex; + + int changed = 0; + int i; + + for (i = 0; i < hwchan; i++) { + s16 rbuf; + s16 buf = cpu_to_le16(ctl_val->value.integer.value[i]); + int ret; + + ret = snd_evo_get_cur(mixer, value + i, index, + &rbuf, sizeof(rbuf)); + if (ret < 0) + return ret; + + if (rbuf != buf) { + changed = 1; + + ret = snd_evo_set_cur(mixer, value + i, index, + &buf, sizeof(buf)); + if (ret < 0) + return ret; + } + } + + return changed; +} + +static int snd_ctl_evo_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + const struct snd_evo_ctrl_type *const ctype = snd_evo_kctl_type(kctl); + + uinfo->type = ctype->type; + uinfo->access = SNDRV_CTL_ELEM_ACCESS_READWRITE + | SNDRV_CTL_ELEM_ACCESS_VOLATILE; + uinfo->count = snd_evo_kctl_channels(kctl); + uinfo->value.integer.min = ctype->min.integer; + uinfo->value.integer.max = ctype->max.integer; + return 0; +} + + +static int snd_evo_add_ctrl(struct usb_mixer_interface *mixer, + const struct snd_evo_ctrl *ctrl) +{ + const struct snd_evo_ctrl_type *ctype = &snd_evo_ctypes[ctrl->type]; + char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct snd_kcontrol_new knew = { 0 }; + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + + knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + knew.name = name; + knew.info = snd_ctl_evo_info; + knew.private_value = snd_evo_kctl_priv(ctrl->type, ctrl->val_offs, + ctrl->hwchannels); + + switch (ctype->type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + knew.get = snd_ctl_evo_integer_get; + knew.put = snd_ctl_evo_integer_set; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + knew.get = snd_ctl_evo_boolean_get; + knew.put = snd_ctl_evo_boolean_set; + break; + default: + return -EINVAL; + } + + snprintf(name, sizeof(name), "%s %s %s", ctrl->basename, + ((ctrl->flags & SND_EVO_FLAGS_DIRECTION) + == SND_EVO_FLAGS_CAPTURE) ? "Capture" : "Playback", + (ctype->type == SNDRV_CTL_ELEM_TYPE_BOOLEAN) + ? "Switch" : "Volume"); + + elem = kzalloc_obj(*elem); + if (!elem) + return -ENOMEM; + + elem->head.mixer = mixer; + elem->channels = ctrl->channels; + + kctl = snd_ctl_new1(&knew, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + + kctl->private_free = snd_usb_mixer_elem_free; + + return snd_usb_mixer_add_control(&elem->head, kctl); +} + + +/* called from mixer_quirks.c */ +int snd_evo_controls_create(struct usb_mixer_interface *mixer) +{ + const struct evo_devinfo *info; + const struct snd_evo_ctrl *ctrl; + + for (info = evo_devinfo; info->controls; info++) + if (info->usb_id == mixer->chip->usb_id) + break; + + if (!info->controls) + return -ENODEV; + + for (ctrl = info->controls; ctrl->basename; ctrl++) { + const int ret = snd_evo_add_ctrl(mixer, ctrl); + + if (ret < 0) + return ret; + } + + return 0; +} diff --git a/sound/usb/mixer_evo.h b/sound/usb/mixer_evo.h new file mode 100644 index 000000000000..7fe7e197185c --- /dev/null +++ b/sound/usb/mixer_evo.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_MIXER_EVO_H +#define __USB_MIXER_EVO_H + +#include "mixer.h" + +#define USB_AUDIENT_VID (0x2708) +#define USB_EVO4_PID (0x0006) + +int snd_evo_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_EVO_H */ diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index f8e1f78039e1..98b4146216ae 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -505,6 +505,14 @@ static const struct usbmix_connector_map gigabyte_b450_connector_map[] = { {} }; +/* Audient EVO4: Rename FU 10 from 'EVO4 ' to 'Master' according to the + * ALSA naming convention + */ +static const struct usbmix_name_map audient_evo4_map[] = { + { 10, "Master Playback" }, + {} +}; + /* Audient iD14 MkI and MkII: FU 12 sits on the monitor mixer branch but is * traced through to the Speaker output terminal, so it is named "Speaker * Playback Volume". On MkII it controls only 4 of 6 playback channels. MkI @@ -633,6 +641,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .map = audient_id14_map, }, { + /* Audient EVO4 MkI */ + .id = USB_ID(0x2708, 0x0006), + .map = audient_evo4_map, + }, + { /* Audient iD14 MkII */ .id = USB_ID(0x2708, 0x0008), .map = audient_id14_map, diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index fc622eb95dc5..01210cc3fae7 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -38,6 +38,7 @@ #include "mixer_scarlett2.h" #include "mixer_us16x08.h" #include "mixer_s1810c.h" +#include "mixer_evo.h" #include "helper.h" #include "fcp.h" @@ -166,7 +167,7 @@ static int add_single_ctl_with_resume(struct usb_mixer_interface *mixer, } kctl->private_free = snd_usb_mixer_elem_free; /* don't use snd_usb_mixer_add_control() here, this is a special list element */ - return snd_usb_mixer_add_list(list, kctl, false); + return snd_usb_mixer_add_list(list, kctl, false, true); } /* @@ -4539,6 +4540,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) err = snd_fcp_init(mixer); break; + case USB_ID(USB_AUDIENT_VID, USB_EVO4_PID): /* Audient EVO 4 */ + err = snd_evo_controls_create(mixer); + break; + case USB_ID(0x041e, 0x323b): /* Creative Sound Blaster E1 */ err = snd_soundblaster_e1_switch_create(mixer); break; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c index 369968565c19..6808730ef047 100644 --- a/sound/usb/mixer_scarlett.c +++ b/sound/usb/mixer_scarlett.c @@ -292,7 +292,7 @@ static int forte_get_ctl_value(struct usb_mixer_elem_info *elem, int *value) /* Device may not support reading input controls. * Return cached value or default to avoid blocking module load. */ - if (elem->cached) + if (test_bit(0, elem->cached)) *value = elem->cache_val[0]; else *value = 0; /* Default: first option */ @@ -353,7 +353,7 @@ static int forte_input_gain_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -364,7 +364,7 @@ static int forte_input_gain_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -405,7 +405,7 @@ static int forte_ctl_enum_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -416,7 +416,7 @@ static int forte_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -454,7 +454,7 @@ static int forte_ctl_switch_put(struct snd_kcontrol *kctl, err = forte_set_ctl_value(elem, val); if (err < 0) return err; - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -465,7 +465,7 @@ static int forte_ctl_switch_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) forte_set_ctl_value(elem, *elem->cache_val); return 0; } @@ -532,7 +532,7 @@ static int scarlett_ctl_resume(struct usb_mixer_elem_list *list) int i; for (i = 0; i < elem->channels; i++) - if (elem->cached & (1 << i)) + if (test_bit(i, elem->cached)) snd_usb_set_cur_mix_value(elem, i, i, elem->cache_val[i]); return 0; @@ -692,7 +692,7 @@ static int scarlett_ctl_enum_resume(struct usb_mixer_elem_list *list) { struct usb_mixer_elem_info *elem = mixer_elem_list_to_info(list); - if (elem->cached) + if (test_bit(0, elem->cached)) snd_usb_set_cur_mix_value(elem, 0, 0, *elem->cache_val); return 0; } diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c index 14fb1ad764a7..18e8c2bd2678 100644 --- a/sound/usb/mixer_us16x08.c +++ b/sound/usb/mixer_us16x08.c @@ -189,6 +189,7 @@ static int snd_us16x08_route_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* route has no bias */ ucontrol->value.enumerated.item[0] = elem->cache_val[index]; @@ -204,6 +205,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, char buf[sizeof(route_msg)]; int val, val_org, err; + guard(mutex)(&elem->head.mixer->lock); /* get the new value (no bias for routes) */ val = ucontrol->value.enumerated.item[0]; @@ -236,7 +238,7 @@ static int snd_us16x08_route_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -258,6 +260,7 @@ static int snd_us16x08_master_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -272,6 +275,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0]; @@ -295,7 +299,7 @@ static int snd_us16x08_master_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -310,6 +314,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, val = ucontrol->value.integer.value[0]; + guard(mutex)(&elem->head.mixer->lock); /* prepare the message buffer from template */ switch (elem->head.id) { case SND_US16X08_ID_BYPASS: @@ -336,7 +341,7 @@ static int snd_us16x08_bus_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1; + set_bit(0, elem->cached); elem->cache_val[0] = val; return 1; } @@ -346,6 +351,7 @@ static int snd_us16x08_bus_get(struct snd_kcontrol *kcontrol, { struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); + guard(mutex)(&elem->head.mixer->lock); switch (elem->head.id) { case SND_US16X08_ID_BUSS_OUT: ucontrol->value.integer.value[0] = elem->cache_val[0]; @@ -368,6 +374,7 @@ static int snd_us16x08_channel_get(struct snd_kcontrol *kcontrol, struct usb_mixer_elem_info *elem = snd_kcontrol_chip(kcontrol); int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = elem->cache_val[index]; return 0; @@ -382,6 +389,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, int val, err; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -404,7 +412,7 @@ static int snd_us16x08_channel_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -428,6 +436,7 @@ static int snd_us16x08_comp_get(struct snd_kcontrol *kcontrol, int index = ucontrol->id.index; int val_idx = COMP_STORE_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); ucontrol->value.integer.value[0] = store->val[val_idx][index]; return 0; @@ -445,6 +454,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, int threshold, ratio, attack, release, gain, switch_on; int err; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -508,7 +518,7 @@ static int snd_us16x08_comp_put(struct snd_kcontrol *kcontrol, } store->val[val_idx][index] = val; - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -521,6 +531,7 @@ static int snd_us16x08_eqswitch_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_eq_store *store = elem->private_data; int index = ucontrol->id.index; + guard(mutex)(&elem->head.mixer->lock); /* get low switch from cache is enough, cause all bands are together */ val = store->val[EQ_STORE_BAND_IDX(elem->head.id)] [EQ_STORE_PARAM_IDX(elem->head.id)][index]; @@ -540,6 +551,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, int val, err = 0; int b_idx; + guard(mutex)(&elem->head.mixer->lock); /* new control value incl. bias*/ val = ucontrol->value.integer.value[0] + SND_US16X08_KCBIAS(kcontrol); @@ -567,7 +579,7 @@ static int snd_us16x08_eqswitch_put(struct snd_kcontrol *kcontrol, return err; } - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -582,6 +594,7 @@ static int snd_us16x08_eq_get(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = store->val[b_idx][p_idx][index]; ucontrol->value.integer.value[0] = val; @@ -601,6 +614,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, int b_idx = EQ_STORE_BAND_IDX(elem->head.id) - 1; int p_idx = EQ_STORE_PARAM_IDX(elem->head.id); + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -631,7 +645,7 @@ static int snd_us16x08_eq_put(struct snd_kcontrol *kcontrol, store->val[b_idx][p_idx][index] = val; /* store new value in EQ band cache */ - elem->cached |= 1 << index; + set_bit(index, elem->cached); elem->cache_val[index] = val; return 1; } @@ -729,6 +743,7 @@ static int snd_us16x08_meter_get(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; u8 meter_urb[64] = {0}; + guard(mutex)(&elem->head.mixer->lock); switch (kcontrol->private_value) { case 0: { char tmp[sizeof(mix_init_msg1)]; @@ -788,6 +803,7 @@ static int snd_us16x08_meter_put(struct snd_kcontrol *kcontrol, struct snd_us16x08_meter_store *store = elem->private_data; int val; + guard(mutex)(&elem->head.mixer->lock); val = ucontrol->value.integer.value[0]; /* sanity check */ @@ -1118,7 +1134,8 @@ static int add_new_ctl(struct usb_mixer_interface *mixer, strscpy(kctl->id.name, name, sizeof(kctl->id.name)); - err = snd_usb_mixer_add_control(&elem->head, kctl); + /* look_for_empty=false, as we want the fixed id.index as channel */ + err = snd_usb_mixer_add_list(&elem->head, kctl, true, false); if (err < 0) return err; @@ -1352,7 +1369,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) } for (i = 0; i < 8; i++) elem->cache_val[i] = i < 2 ? i : i + 2; - elem->cached = 0xff; + bitmap_set(elem->cached, 0, 8); /* create compressor mixer elements */ comp_store = snd_us16x08_create_comp_store(); @@ -1374,7 +1391,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) if (err < 0) return err; elem->cache_val[0] = master_controls[i].default_val; - elem->cached = 1; + set_bit(0, elem->cached); } /* add channel controls */ @@ -1394,7 +1411,7 @@ int snd_us16x08_controls_create(struct usb_mixer_interface *mixer) elem->cache_val[j] = channel_controls[i].default_val; } - elem->cached = 0xffff; + bitmap_set(elem->cached, 0, SND_US16X08_MAX_CHANNELS); } /* create eq store */ diff --git a/sound/usb/mixer_us16x08.h b/sound/usb/mixer_us16x08.h index 7b8583dd3b07..e98fe55265d1 100644 --- a/sound/usb/mixer_us16x08.h +++ b/sound/usb/mixer_us16x08.h @@ -60,7 +60,7 @@ #define SND_US16X08_ID_EQHIGHMIDLEVEL 0x03 #define SND_US16X08_ID_EQHIGHLEVEL 0x04 -/* EQ frequence IDs */ +/* EQ frequency IDs */ #define SND_US16X08_ID_EQLOWFREQ 0x11 #define SND_US16X08_ID_EQLOWMIDFREQ 0x12 #define SND_US16X08_ID_EQHIGHMIDFREQ 0x13 diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index dd02c6294edf..2da8cbe3e6de 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -1227,15 +1227,15 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S24_3LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x0120), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ - /* .product_name = "OCTO-CAPTURE", */ + /* .product_name = "OCTA-CAPTURE", */ QUIRK_DATA_COMPOSITE { { QUIRK_DATA_AUDIOFORMAT(0) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 10, .iface = 0, .altsetting = 1, @@ -1250,8 +1250,56 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, + { QUIRK_DATA_AUDIOFORMAT(1) { - .formats = SNDRV_PCM_FMTBIT_S32_LE, + .formats = SNDRV_PCM_FMTBIT_S24_3LE, .channels = 12, .iface = 1, .altsetting = 1, @@ -1266,9 +1314,57 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 12, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 4, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, + { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { - .out_cables = 0x0001, - .in_cables = 0x0001 + .out_cables = 0x0005, + .in_cables = 0x0003 } }, { QUIRK_DATA_IGNORE(3) }, @@ -1278,7 +1374,7 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { - /* only 44.1 kHz works at the moment */ + /* S32_LE; altsettings 1-4 match the device clock. */ USB_DEVICE(0x0582, 0x012f), QUIRK_DRIVER_INFO { /* .vendor_name = "Roland", */ @@ -1301,6 +1397,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 4, + .iface = 0, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 0, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x05, + .ep_attr = 0x05, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, + { QUIRK_DATA_AUDIOFORMAT(1) { .formats = SNDRV_PCM_FMTBIT_S32_LE, .channels = 6, @@ -1317,6 +1461,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), } }, { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 48000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 6, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_96000, + .rate_min = 96000, + .rate_max = 96000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 96000 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 2, + .iface = 1, + .altsetting = 4, + .altset_idx = 4, + .endpoint = 0x85, + .ep_attr = 0x25, + .rates = SNDRV_PCM_RATE_192000, + .rate_min = 192000, + .rate_max = 192000, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 192000 } + } + }, + { QUIRK_DATA_MIDI_FIXED_ENDPOINT(2) { .out_cables = 0x0001, .in_cables = 0x0001 @@ -3541,6 +3733,53 @@ YAMAHA_DEVICE(0x7010, "UB99"), }, { /* + * Pioneer DJ DDJ-SZ + * 10 channels playback & 10 channels capture @ 44.1kHz S24LE + */ + USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0191), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + { + QUIRK_DATA_AUDIOFORMAT(0) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 10, + .iface = 0, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x82, + .ep_idx = 1, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .nr_rates = 1, + .rate_table = (unsigned int[]) { 44100 } + } + }, + QUIRK_COMPOSITE_END + } + } +}, +{ + /* * Pioneer DJ DJM-750MK2 * 10 channels playback & 12 channels capture @ 48kHz S24LE */ diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index a54fabce7920..4fadb1fdcdea 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -3,6 +3,7 @@ */ #include <linux/cleanup.h> +#include <linux/delay.h> #include <linux/err.h> #include <linux/init.h> #include <linux/slab.h> @@ -871,6 +872,49 @@ static int snd_usb_cm1a_boot_quirk(struct usb_device *dev) } /* + * The Xiaomi audio connector ignores UAC2 volume changes after reconnecting + * until the device and configuration descriptors have been read again. + */ +#define XIAOMI_AUDIO_MAX_CONFIG_SIZE 1024 + +static void snd_usb_xiaomi_boot_quirk(struct usb_device *dev) +{ + u8 *buf __free(kfree) = kmalloc(XIAOMI_AUDIO_MAX_CONFIG_SIZE, GFP_KERNEL); + struct usb_config_descriptor *config; + unsigned int length; + int ret; + + if (!buf) + return; + + ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, buf, + USB_DT_DEVICE_SIZE); + if (ret != USB_DT_DEVICE_SIZE) { + dev_warn(&dev->dev, "device descriptor re-read failed: %d\n", ret); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, + USB_DT_CONFIG_SIZE); + if (ret != USB_DT_CONFIG_SIZE) { + dev_warn(&dev->dev, "configuration header re-read failed: %d\n", ret); + return; + } + + config = (struct usb_config_descriptor *)buf; + length = le16_to_cpu(config->wTotalLength); + if (length < USB_DT_CONFIG_SIZE || + length > XIAOMI_AUDIO_MAX_CONFIG_SIZE) { + dev_warn(&dev->dev, "unexpected configuration length: %u\n", length); + return; + } + + ret = usb_get_descriptor(dev, USB_DT_CONFIG, 0, buf, length); + if (ret != (int)length) + dev_warn(&dev->dev, "configuration descriptor re-read failed: %d\n", ret); +} + +/* * Some sound cards from Native Instruments are in fact compliant to the USB * audio standard of version 2 and other approved USB standards, even though * they come up as vendor-specific device when first connected. @@ -1606,22 +1650,60 @@ static int s1810c_skip_setting_quirk(struct snd_usb_audio *chip, return 0; } +static int hp_elite_x3_lap_dock_skip_setting_quirk(struct snd_usb_audio *chip, + int iface, int altno) +{ + /* + * 1:1 - Capture, S16_LE, 8000/32000/44100/48000Hz + * 2:1 - Playback, S16_LE, 48000Hz + * 2:2 - Playback, S24_3LE, 48000Hz + * + * 2:2 is broken because: + * - it doesn't accept SET_CUR(SAMPLE_RATE). QUIRK_FLAG_FIXED_RATE works + * around it, however... + * - it constantly produces severe harmonic distortion once the capture + * stream is also opened. The interface 2 must be closed and reopened + * to make it recover. IOW, simply closing the capture stream makes no + * difference. + * + * Considering that S24_3LE offers no additional benefit on small + * speakers compared to S16_LE, and 2:1 is always usable as an + * alternative, skip 2:2 to get rid of the trouble. + * + * Setting chip->setup to any non-default value disables the fixup and + * reenables 2:2 (in this case QUIRK_FLAG_FIXED_RATE is required). + */ + if (!chip->setup && iface == 2 && altno == 2) { + usb_audio_info(chip, + "%d:%d: skipping broken altsetting on HP Elite x3 Lap Dock\n", + iface, altno); + return 1; + } + + return 0; +} + int snd_usb_apply_interface_quirk(struct snd_usb_audio *chip, int iface, int altno) { - /* audiophile usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2003)) - return audiophile_skip_setting_quirk(chip, iface, altno); + switch (chip->usb_id) { + /* HP Elite x3 Lap Dock: skip broken altsets */ + case USB_ID(0x03f0, 0x0c56): + return hp_elite_x3_lap_dock_skip_setting_quirk(chip, iface, altno); /* quattro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2001)) + case USB_ID(0x0763, 0x2001): return quattro_skip_setting_quirk(chip, iface, altno); + /* audiophile usb: skip altsets incompatible with device_setup */ + case USB_ID(0x0763, 0x2003): + return audiophile_skip_setting_quirk(chip, iface, altno); /* fasttrackpro usb: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x0763, 0x2012)) + case USB_ID(0x0763, 0x2012): return fasttrackpro_skip_setting_quirk(chip, iface, altno); /* presonus studio 1810c: skip altsets incompatible with device_setup */ - if (chip->usb_id == USB_ID(0x194f, 0x010c)) + case USB_ID(0x194f, 0x010c): return s1810c_skip_setting_quirk(chip, iface, altno); + } return 0; } @@ -1702,6 +1784,9 @@ int snd_usb_apply_boot_quirk_once(struct usb_device *dev, switch (id) { case USB_ID(0x07fd, 0x0008): /* MOTU M Series, 1st hardware version */ return snd_usb_motu_m_series_boot_quirk(dev); + case USB_ID(0x2717, 0xd005): /* Xiaomi audio connector */ + snd_usb_xiaomi_boot_quirk(dev); + return 0; } /* Behringer devices may need explicit device descriptor read at boot */ @@ -1791,6 +1876,62 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs, subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0; } +/* + * The DDJ-SZ needs a vendor "arm" sequence before its capture path + * produces real audio; without it capture runs with no USB or ALSA error + * but delivers a hard zero on every channel. The sequence is replicated + * byte-for-byte from a USB capture of the Windows driver: each write is + * followed by a status read whose content is a fixed value regardless of + * what was written, but the read is replicated too, since it is unclear + * whether the device requires it to process the preceding write. + * + * This runs from snd_usb_set_format_quirk(), i.e. on every format setup + * rather than once per device. Re-arming is harmless in practice and + * keeps the device armed if it is reset behind our back. + */ +static void ddj_sz_arm_quirk(struct usb_device *dev) +{ + static const struct { + u16 value; + u16 index; + u8 read_len; + } cmds[] = { + { 0x0100, 0x8002, 6 }, + { 0x0200, 0x8002, 6 }, + { 0x0303, 0x8002, 6 }, + { 0x0403, 0x8002, 6 }, + { 0x050a, 0x8002, 6 }, + { 0x0000, 0x8003, 2 }, + }; + u8 buf[6]; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 3, + USB_DIR_OUT | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + cmds[i].value, cmds[i].index, NULL, 0); + if (err < 0) + goto err_out; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, + 0x0000, cmds[i].index, buf, + cmds[i].read_len); + if (err < 0) + goto err_out; + } + + return; + +err_out: + dev_warn(&dev->dev, + "DDJ-SZ: arm sequence step %u failed (%d), capture may be silent\n", + i, err); +} + static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs, u16 windex) { @@ -1880,6 +2021,75 @@ static int rme_digiface_set_format_quirk(struct snd_usb_substream *subs) return 0; } +#define ROLAND_CAPTURE_RATE_REQUEST 3 +#define ROLAND_CAPTURE_RATE_READ_VALUE 0x0001 +#define ROLAND_CAPTURE_RATE_WRITE_VALUE 0x0008 +#define ROLAND_CAPTURE_RATE_WRITE_PREFIX 0x40 +#define ROLAND_CAPTURE_RATE_RETRIES 40 +#define ROLAND_CAPTURE_RATE_POLL_MS 25 + +/* + * OCTA-CAPTURE and QUAD-CAPTURE use the same vendor request for their + * hardware clock. A read returns the 24-bit little-endian rate followed by + * a transition-status byte. A write carries 0x40 followed by the rate. + */ +static int roland_capture_read_rate(struct usb_device *dev, u32 *rate) +{ + u8 data[4]; + int err; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_READ_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) + return err < 0 ? err : -EIO; + + *rate = combine_triple(data); + return 0; +} + +static void roland_capture_set_rate(struct snd_usb_substream *subs) +{ + struct snd_usb_audio *chip = subs->stream->chip; + struct usb_device *dev = chip->dev; + u32 rate = subs->data_endpoint->cur_rate; + u32 current_rate; + u8 data[4]; + int err; + int i; + + /* Serialize playback and capture endpoint starts during a clock change. */ + guard(mutex)(&chip->mutex); + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + + data[0] = ROLAND_CAPTURE_RATE_WRITE_PREFIX; + data[1] = rate; + data[2] = rate >> 8; + data[3] = rate >> 16; + err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), + ROLAND_CAPTURE_RATE_REQUEST, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + ROLAND_CAPTURE_RATE_WRITE_VALUE, 0, + data, sizeof(data)); + if (err != sizeof(data)) { + usb_audio_warn(chip, "cannot set Roland sample rate to %u Hz: %d\n", + rate, err < 0 ? err : -EIO); + return; + } + + for (i = 0; i < ROLAND_CAPTURE_RATE_RETRIES; i++) { + err = roland_capture_read_rate(dev, ¤t_rate); + if (!err && current_rate == rate) + return; + msleep(ROLAND_CAPTURE_RATE_POLL_MS); + } + usb_audio_warn(chip, "Roland sample rate did not reach %u Hz\n", rate); +} + void snd_usb_set_format_quirk(struct snd_usb_substream *subs, const struct audioformat *fmt) { @@ -1903,6 +2113,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */ pioneer_djm_set_format_quirk(subs, 0x0086); break; + case USB_ID(0x08e4, 0x0191): /* Pioneer DDJ-SZ */ + ddj_sz_arm_quirk(subs->dev); + pioneer_djm_set_format_quirk(subs, 0x0082); + break; case USB_ID(0x0dba, 0x5000): mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */ break; @@ -1910,6 +2124,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs, case USB_ID(0x2a39, 0x3fa0): /* RME Digiface USB (alternate) */ rme_digiface_set_format_quirk(subs); break; + case USB_ID(0x0582, 0x0120): /* Roland OCTA-CAPTURE */ + case USB_ID(0x0582, 0x012f): /* Roland QUAD-CAPTURE */ + roland_capture_set_rate(subs); + break; } } @@ -1980,6 +2198,16 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep) ep->chip->usb_id == USB_ID(0x1852, 0x5034)) && /* T+A Dac8 */ ep->syncmaxsize == 4) ep->tenor_fb_quirk = 1; + + /* + * Mayflower ARC AMP DAC (MAG Technology USB front-end): the + * feedback can get stuck at nominal+1 sample per frame, causing + * pops every 1-2 sec until stream restart. Snap implausible + * feedback to nominal. + */ + if (ep->chip->usb_id == USB_ID(0x0493, 0x4170) && + ep->type == SND_USB_ENDPOINT_TYPE_SYNC) + ep->snap_fb_quirk = 1; } /* quirk applied after snd_usb_ctl_msg(); not applied during boot quirks */ @@ -2114,6 +2342,21 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, int stream) { switch (chip->usb_id) { + case USB_ID(0x0499, 0x5500): /* Yamaha 01V96i */ + /* + * The 01V96i declares its audio streaming interfaces as + * vendor-specific (class 0xff) and provides no class-specific + * endpoint descriptor. parse_uac_endpoint_attributes() therefore + * returns no attributes, and the driver never issues the SET_CUR + * sampling-frequency request the hardware needs to engage its USB + * stream: playback and capture PCMs report RUNNING, but the desk + * passes no audio. + * + * Force the attribute on so the stream is initialised the same way + * the Windows driver does it. + */ + fp->attributes |= UAC_EP_CS_ATTR_SAMPLE_RATE; + break; case USB_ID(0x0a92, 0x0053): /* AudioTrak Optoplay */ /* Optoplay sets the sample rate attribute although * it seems not supporting it in fact. @@ -2148,6 +2391,14 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, */ fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; break; + case USB_ID(0x0a12, 0x1244): /* Shanling UP4 */ + /* + * MaxPacketsOnly is set, but the device expects nominal-size + * packets. Filling 48 frames per packet at 44.1 kHz results + * in silence. No effect at 48 kHz, where both sizes match. + */ + fp->attributes &= ~UAC_EP_CS_ATTR_FILL_MAX; + break; case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */ /* mic works only when ep packet size is set to wMaxPacketSize */ fp->attributes |= UAC_EP_CS_ATTR_FILL_MAX; @@ -2238,6 +2489,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x0124, 0x0c21, /* Generic USB Headphone */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x03f0, 0x0c56, /* HP Elite x3 Lap Dock */ + QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x03f0, 0x654a, /* HP 320 FHD Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x041e, 0x3000, /* Creative SB Extigy */ @@ -2251,6 +2504,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_DISABLE_AUTOSUSPEND), DEVICE_FLG(0x045e, 0x070f, /* MS LifeChat LX-3000 Headset */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x046d, 0x0802, /* Logitech Webcam C200 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0807, /* Logitech Webcam C500 */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0808, /* Logitech Webcam C600 */ @@ -2271,6 +2526,10 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x046d, 0x08ca, /* Logitech Quickcam Fusion */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), + DEVICE_FLG(0x046d, 0x08e5, /* Logitech HD Pro Webcam c920 */ + QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x046d, 0x094c, /* Logitech Brio 100 */ + QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIC_RES_384), DEVICE_FLG(0x046d, 0x0991, /* Logitech QuickCam Pro */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_IGNORE_CTL_ERROR | QUIRK_FLAG_MIC_RES_384), @@ -2323,6 +2582,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x0644, 0x8044, /* Esoteric D-05X */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | QUIRK_FLAG_FORCE_IFACE_RESET), + DEVICE_FLG(0x0644, 0x8047, /* TASCAM US-16x08 */ + QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY | + QUIRK_FLAG_FORCE_IFACE_RESET), DEVICE_FLG(0x0644, 0x804a, /* TEAC UD-301 */ QUIRK_FLAG_ITF_USB_DSD_DAC | QUIRK_FLAG_CTL_MSG_DELAY | QUIRK_FLAG_IFACE_DELAY), @@ -2404,6 +2666,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), + DEVICE_FLG(0x1237, 0x17b8, /* rongyuan GS20 */ + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2556,8 +2820,12 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x3443, 0x930d, /* NexiGo N930W 60fps Webcam */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), + DEVICE_FLG(0x35d8, 0x011d, /* MOONDROP DAWN PRO2 */ + QUIRK_FLAG_DSD_RAW), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ + QUIRK_FLAG_SWAP_RATES), DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2685,6 +2953,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + QUIRK_STRING_ENTRY(SWAP_RATES), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 24436473931d..c6d59218d35f 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation; * Issue SET_CUR for the sample rate even when the clock already reports the * requested rate. A device advertising a single rate is otherwise never sent * the request at all, and some require it before streaming will start. + * QUIRK_FLAG_SWAP_RATES + * The device applies a requested sample rate to the other base-rate family + * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner + * rate in SET_CUR so that the device runs at the requested rate. */ enum { @@ -294,6 +298,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, + QUIRK_TYPE_SWAP_RATES = 33, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; @@ -332,5 +337,6 @@ enum { #define QUIRK_FLAG_MIXER_GET_CUR_OK QUIRK_FLAG(MIXER_GET_CUR_OK) #define QUIRK_FLAG_PLAYBACK_URB_FIXUP QUIRK_FLAG(PLAYBACK_URB_FIXUP) #define QUIRK_FLAG_ALWAYS_SET_RATE QUIRK_FLAG(ALWAYS_SET_RATE) +#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES) #endif /* __USBAUDIO_H */ diff --git a/sound/virtio/virtio_pcm.h b/sound/virtio/virtio_pcm.h index 368dafa5df3f..8837dfc12828 100644 --- a/sound/virtio/virtio_pcm.h +++ b/sound/virtio/virtio_pcm.h @@ -118,6 +118,8 @@ int virtsnd_pcm_msg_alloc(struct virtio_pcm_substream *vss, void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss); +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss); + int virtsnd_pcm_msg_send(struct virtio_pcm_substream *vss, unsigned long offset, unsigned long bytes); diff --git a/sound/virtio/virtio_pcm_msg.c b/sound/virtio/virtio_pcm_msg.c index a5c4e7027717..72103f606716 100644 --- a/sound/virtio/virtio_pcm_msg.c +++ b/sound/virtio/virtio_pcm_msg.c @@ -183,6 +183,25 @@ void virtsnd_pcm_msg_free(struct virtio_pcm_substream *vss) } /** + * virtsnd_pcm_msg_reset() - Reset the fill level of all I/O messages. + * @vss: VirtIO PCM substream. + * + * Messages that were only partially filled by the application when the + * substream was stopped are never sent to the device, so their length must be + * cleared before the substream is started again. + * + * Context: Any context. Takes and releases the VirtIO substream spinlock. + */ +void virtsnd_pcm_msg_reset(struct virtio_pcm_substream *vss) +{ + unsigned int i; + + guard(spinlock_irqsave)(&vss->lock); + for (i = 0; vss->msgs && i < vss->nmsgs; ++i) + vss->msgs[i]->length = 0; +} + +/** * virtsnd_pcm_msg_send() - Send asynchronous I/O messages. * @vss: VirtIO PCM substream. * @offset: starting position that has been updated diff --git a/sound/virtio/virtio_pcm_ops.c b/sound/virtio/virtio_pcm_ops.c index 1105e7ff3523..1091100a4c98 100644 --- a/sound/virtio/virtio_pcm_ops.c +++ b/sound/virtio/virtio_pcm_ops.c @@ -299,6 +299,7 @@ static int virtsnd_pcm_prepare(struct snd_pcm_substream *substream) vss->xfer_xrun = false; vss->suspended = false; vss->msg_count = 0; + virtsnd_pcm_msg_reset(vss); memset(&vss->pcm_indirect, 0, sizeof(vss->pcm_indirect)); vss->pcm_indirect.sw_buffer_size = diff --git a/sound/x86/intel_hdmi_audio.c b/sound/x86/intel_hdmi_audio.c index 91dfcf47e979..542fc35ec9b7 100644 --- a/sound/x86/intel_hdmi_audio.c +++ b/sound/x86/intel_hdmi_audio.c @@ -475,7 +475,8 @@ static void had_build_channel_allocation_map(struct snd_intelhad *intelhaddata) kfree(intelhaddata->chmap->chmap); intelhaddata->chmap->chmap = NULL; - chmap = kzalloc_obj(*chmap); + /* info->chmap must be zero-terminated; allocate a spare terminator entry */ + chmap = kzalloc_objs(*chmap, 2); if (!chmap) return; @@ -575,6 +576,12 @@ static int had_register_chmap_ctls(struct snd_intelhad *intelhaddata, intelhaddata->chmap->kctl->info = had_chmap_ctl_info; intelhaddata->chmap->kctl->get = had_chmap_ctl_get; intelhaddata->chmap->chmap = NULL; + + /* drop the inherited TLV read: it walks chmap without our mutex */ + intelhaddata->chmap->kctl->tlv.c = NULL; + intelhaddata->chmap->kctl->vd[0].access &= + ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return 0; } @@ -1634,9 +1641,9 @@ static void hdmi_lpe_audio_free(struct snd_card *card) * This function is called when the i915 driver creates the * hdmi-lpe-audio platform device. */ -static int __hdmi_lpe_audio_probe(struct platform_device *pdev) +static int hdmi_lpe_audio_probe(struct platform_device *pdev) { - struct snd_card *card; + struct snd_card *card __free(snd_card_free) = NULL; struct snd_intelhad_card *card_ctx; struct snd_intelhad *ctx; struct snd_pcm *pcm; @@ -1797,14 +1804,10 @@ static int __hdmi_lpe_audio_probe(struct platform_device *pdev) schedule_work(&ctx->hdmi_audio_wq); } + card = NULL; /* probe succeeded, don't release as error */ return 0; } -static int hdmi_lpe_audio_probe(struct platform_device *pdev) -{ - return snd_card_free_on_error(&pdev->dev, __hdmi_lpe_audio_probe(pdev)); -} - static const struct dev_pm_ops hdmi_lpe_audio_pm = { SYSTEM_SLEEP_PM_OPS(hdmi_lpe_audio_suspend, hdmi_lpe_audio_resume) }; diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 3dd8e1176b89..7b0e1e9ebf1b 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -1,3 +1,4 @@ +aloop-test global-timer mixer-test pcm-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index 8dab90ad22bb..afd64a679dc1 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,7 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/aloop-test.c b/tools/testing/selftests/alsa/aloop-test.c new file mode 100644 index 000000000000..a58b9fdbdc17 --- /dev/null +++ b/tools/testing/selftests/alsa/aloop-test.c @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Tests for the hw constraints between the two ends of an snd-aloop cable, + * with and without the "PCM Notify" control, and for the "PCM Slave" + * controls that report the playback side's parameters. + * + * Needs snd-aloop loaded with the default card id "Loopback". The tests use + * the cable between hw:Loopback,0,0 (playback) and hw:Loopback,1,0 (capture). + */ +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <alsa/asoundlib.h> +#include "kselftest_harness.h" + +#define FRAMES 1024 +#define MAX_CHANNELS 4 + +struct stream_params { + snd_pcm_access_t access; + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; +}; + +/* The playback params differ from the capture params in every field. */ +static const struct stream_params capture_params = { + SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_FORMAT_S16_LE, 2, 44100 +}; + +static const struct stream_params playback_params = { + SND_PCM_ACCESS_RW_NONINTERLEAVED, SND_PCM_FORMAT_S32_LE, 4, 96000 +}; + +struct probe_result { + unsigned int rate_min, rate_max; + unsigned int channels_min, channels_max; + bool format_ok; +}; + +/* Value events seen on the cable's controls */ +enum { + EV_ACTIVE = 1 << 0, + EV_FORMAT = 1 << 1, + EV_RATE = 1 << 2, + EV_CHANNELS = 1 << 3, + EV_ACCESS = 1 << 4, +}; + +FIXTURE(aloop) { + char play_name[32]; + char capt_name[32]; + snd_ctl_t *ctl; + bool saved_notify; + bool notify_saved; +}; + +/* The cable's controls are on the capture side device. */ +static void cable_ctl_id(snd_ctl_elem_value_t *value, const char *name) +{ + snd_ctl_elem_value_set_interface(value, SND_CTL_ELEM_IFACE_PCM); + snd_ctl_elem_value_set_name(value, name); + snd_ctl_elem_value_set_device(value, 1); + snd_ctl_elem_value_set_subdevice(value, 0); +} + +static long cable_ctl_get(snd_ctl_t *ctl, const char *name) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, name); + if (snd_ctl_elem_read(ctl, value) < 0) + return -1; + if (!strcmp(name, "PCM Slave Access Mode")) + return snd_ctl_elem_value_get_enumerated(value, 0); + return snd_ctl_elem_value_get_integer(value, 0); +} + +static int set_notify(snd_ctl_t *ctl, bool on) +{ + snd_ctl_elem_value_t *value; + + snd_ctl_elem_value_alloca(&value); + cable_ctl_id(value, "PCM Notify"); + snd_ctl_elem_value_set_boolean(value, 0, on); + return snd_ctl_elem_write(ctl, value); +} + +/* Read all pending control events and return the EV_* bits for the cable's controls. */ +static unsigned int read_events(snd_ctl_t *ctl) +{ + static const struct { + const char *name; + unsigned int bit; + } names[] = { + { "PCM Slave Active", EV_ACTIVE }, + { "PCM Slave Format", EV_FORMAT }, + { "PCM Slave Rate", EV_RATE }, + { "PCM Slave Channels", EV_CHANNELS }, + { "PCM Slave Access Mode", EV_ACCESS }, + }; + snd_ctl_event_t *event; + unsigned int seen = 0; + int i; + + snd_ctl_event_alloca(&event); + while (snd_ctl_read(ctl, event) > 0) { + if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM || + !(snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE) || + snd_ctl_event_elem_get_device(event) != 1 || + snd_ctl_event_elem_get_subdevice(event) != 0) + continue; + for (i = 0; i < ARRAY_SIZE(names); i++) + if (!strcmp(snd_ctl_event_elem_get_name(event), names[i].name)) + seen |= names[i].bit; + } + return seen; +} + +/* Open and configure a stream. snd_pcm_hw_params() also prepares it. */ +static int open_pcm(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, + const struct stream_params *p) +{ + unsigned int buffer_time = 100000; + snd_pcm_hw_params_t *hw; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(*pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_set_access(*pcm, hw, p->access); + if (err >= 0) + err = snd_pcm_hw_params_set_format(*pcm, hw, p->format); + if (err >= 0) + err = snd_pcm_hw_params_set_channels(*pcm, hw, p->channels); + if (err >= 0) + err = snd_pcm_hw_params_set_rate(*pcm, hw, p->rate, 0); + if (err >= 0) + err = snd_pcm_hw_params_set_buffer_time_near(*pcm, hw, &buffer_time, NULL); + if (err >= 0) + err = snd_pcm_hw_params(*pcm, hw); + if (err < 0) { + snd_pcm_close(*pcm); + *pcm = NULL; + } + return err; +} + +/* What a client probing the device sees, and whether it may use the given format */ +static int probe_pcm(const char *name, snd_pcm_stream_t stream, snd_pcm_format_t format, + struct probe_result *res) +{ + snd_pcm_hw_params_t *hw; + snd_pcm_t *pcm; + int err; + + snd_pcm_hw_params_alloca(&hw); + err = snd_pcm_open(&pcm, name, stream, 0); + if (err < 0) + return err; + err = snd_pcm_hw_params_any(pcm, hw); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_min(hw, &res->rate_min, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_rate_max(hw, &res->rate_max, NULL); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_min(hw, &res->channels_min); + if (err >= 0) + err = snd_pcm_hw_params_get_channels_max(hw, &res->channels_max); + if (err >= 0) + res->format_ok = !snd_pcm_hw_params_test_format(pcm, hw, format); + snd_pcm_close(pcm); + return err; +} + +static int start_playback(snd_pcm_t *pcm, const struct stream_params *p) +{ + static char silence[FRAMES * MAX_CHANNELS * 4]; + void *bufs[MAX_CHANNELS]; + snd_pcm_sframes_t written; + unsigned int i; + + if (p->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { + for (i = 0; i < p->channels; i++) + bufs[i] = silence + i * FRAMES * 4; + written = snd_pcm_writen(pcm, bufs, FRAMES); + } else { + written = snd_pcm_writei(pcm, silence, FRAMES); + } + if (written < 0) + return written; + if (snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED) + return snd_pcm_start(pcm); + return 0; +} + +FIXTURE_SETUP(aloop) { + char ctl_name[32]; + snd_pcm_t *pcm; + int card, err; + + card = snd_card_get_index("Loopback"); + if (card < 0) + SKIP(return, "No Loopback card, snd-aloop is probably not loaded"); + + sprintf(ctl_name, "hw:%d", card); + sprintf(self->play_name, "hw:%d,0,0", card); + sprintf(self->capt_name, "hw:%d,1,0", card); + + err = snd_pcm_open(&pcm, self->capt_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->capt_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + err = snd_pcm_open(&pcm, self->play_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + if (err == -EBUSY) + SKIP(return, "%s is in use", self->play_name); + ASSERT_EQ(err, 0); + snd_pcm_close(pcm); + + ASSERT_EQ(snd_ctl_open(&self->ctl, ctl_name, SND_CTL_NONBLOCK), 0); + ASSERT_EQ(snd_ctl_subscribe_events(self->ctl, 1), 0); + self->saved_notify = cable_ctl_get(self->ctl, "PCM Notify"); + self->notify_saved = true; +} + +FIXTURE_TEARDOWN(aloop) { + if (self->notify_saved) + set_notify(self->ctl, self->saved_notify); + if (self->ctl) + snd_ctl_close(self->ctl); +} + +/* Without notify, a playback opened while a capture is set up is pinned to its parameters. */ +TEST_F(aloop, playback_constrained_without_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, false), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, capture_params.rate); + EXPECT_EQ(res.rate_max, capture_params.rate); + EXPECT_EQ(res.channels_min, capture_params.channels); + EXPECT_EQ(res.channels_max, capture_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(capt); +} + +/* With notify, the playback side is free to pick other parameters. */ +TEST_F(aloop, playback_unconstrained_with_notify) { + struct probe_result res; + snd_pcm_t *capt; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + + ASSERT_EQ(probe_pcm(self->play_name, SND_PCM_STREAM_PLAYBACK, playback_params.format, + &res), 0); + EXPECT_LE(res.rate_min, capture_params.rate); + EXPECT_GE(res.rate_max, playback_params.rate); + EXPECT_LE(res.channels_min, capture_params.channels); + EXPECT_GE(res.channels_max, playback_params.channels); + EXPECT_TRUE(res.format_ok); + + snd_pcm_close(capt); +} + +/* + * With notify, starting a playback with different parameters stops the + * running capture, and the "PCM Slave" controls report the new parameters + * with a value event for each one that changed. + */ +TEST_F(aloop, params_change_stops_capture_with_notify) { + snd_pcm_t *capt, *play; + unsigned int events; + + /* + * The controls keep the last playback's parameters, and only notify on + * a change. Start a playback with the capture's parameters while no + * capture is open, so that every control changes below. + */ + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &capture_params), 0); + ASSERT_EQ(start_playback(play, &capture_params), 0); + snd_pcm_close(play); + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&capt, self->capt_name, SND_PCM_STREAM_CAPTURE, &capture_params), 0); + ASSERT_EQ(snd_pcm_start(capt), 0); + ASSERT_EQ(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 0); + read_events(self->ctl); + + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0) + TH_LOG("Playback refused other parameters while the capture is running"); + ASSERT_EQ(start_playback(play, &playback_params), 0); + + /* loopback_check_format() stops the capture from the playback's start trigger. */ + EXPECT_NE(snd_pcm_state(capt), SND_PCM_STATE_RUNNING); + + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Active"), 1); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Format"), playback_params.format); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Rate"), playback_params.rate); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Channels"), playback_params.channels); + EXPECT_EQ(cable_ctl_get(self->ctl, "PCM Slave Access Mode"), 1); + + events = read_events(self->ctl); + EXPECT_TRUE(events & EV_ACTIVE); + EXPECT_TRUE(events & EV_FORMAT); + EXPECT_TRUE(events & EV_RATE); + EXPECT_TRUE(events & EV_CHANNELS); + EXPECT_TRUE(events & EV_ACCESS); + + snd_pcm_close(play); + snd_pcm_close(capt); +} + +/* With notify, a capture opened second is still pinned to the playback's parameters. */ +TEST_F(aloop, capture_constrained_with_notify) { + struct probe_result res; + snd_pcm_t *play; + + ASSERT_EQ(set_notify(self->ctl, true), 0); + ASSERT_EQ(open_pcm(&play, self->play_name, SND_PCM_STREAM_PLAYBACK, &playback_params), 0); + + ASSERT_EQ(probe_pcm(self->capt_name, SND_PCM_STREAM_CAPTURE, capture_params.format, + &res), 0); + EXPECT_EQ(res.rate_min, playback_params.rate); + EXPECT_EQ(res.rate_max, playback_params.rate); + EXPECT_EQ(res.channels_min, playback_params.channels); + EXPECT_EQ(res.channels_max, playback_params.channels); + EXPECT_FALSE(res.format_ok); + + snd_pcm_close(play); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 0857d64c322a..966ef83354b9 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -28,7 +28,13 @@ #include "kselftest.h" #include "alsa-local.h" -#define TESTS_PER_CONTROL 7 +#define TESTS_PER_CONTROL 8 + +/* Suffixes of the SNDRV_CTL_NAME_IEC958() names, not exported to userspace */ +#define IEC958_DEFAULT "Default" +#define IEC958_CON_MASK "Con Mask" +#define IEC958_PRO_MASK "Pro Mask" +#define IEC958_MASK "Mask" struct card_data { snd_ctl_t *handle; @@ -46,9 +52,14 @@ struct ctl_data { snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *def_val; + snd_ctl_elem_value_t *snapshot; + bool snapshot_valid; + bool moved; int elem; int event_missing; int event_spurious; + int side_effects; + unsigned int ev_mask; struct card_data *card; struct ctl_data *next; }; @@ -153,6 +164,10 @@ static void find_controls(void) if (err < 0) ksft_exit_fail_msg("Out of memory\n"); + err = snd_ctl_elem_value_malloc(&ctl_data->snapshot); + if (err < 0) + ksft_exit_fail_msg("Out of memory\n"); + snd_ctl_elem_list_get_id(card_data->ctls, ctl, ctl_data->id); snd_ctl_elem_info_set_id(ctl_data->info, ctl_data->id); @@ -201,6 +216,20 @@ static void find_controls(void) snd_config_delete(config); } +/* The control on the same card that an event's numid refers to */ +static struct ctl_data *find_ctl_by_numid(struct card_data *card, + unsigned int numid) +{ + struct ctl_data *ctl; + + for (ctl = ctl_list; ctl != NULL; ctl = ctl->next) + if (ctl->card == card && + snd_ctl_elem_info_get_numid(ctl->info) == numid) + return ctl; + + return NULL; +} + /* * Block for up to timeout ms for an event, returns a negative value * on error, 0 for no event and 1 for an event. @@ -259,8 +288,21 @@ static int wait_for_event(struct ctl_data *ctl, int timeout) mask = snd_ctl_event_elem_get_mask(event); ev_id = snd_ctl_event_elem_get_numid(event); if (ev_id != snd_ctl_elem_info_get_numid(ctl->info)) { + struct ctl_data *other = find_ctl_by_numid(ctl->card, + ev_id); + + /* + * Remember that the driver announced this one. + * test_ctl_write_side_effects() uses that to tell a + * deliberate link from a silent register collision. + */ + if (other) + other->ev_mask |= mask; + ksft_print_msg("Event for unexpected ctl %s\n", snd_ctl_event_elem_get_name(event)); + /* The loop condition must not see the other control's mask */ + mask = 0; continue; } @@ -842,6 +884,251 @@ static bool test_ctl_write_valid_enumerated(struct ctl_data *ctl) return !fail; } +/* + * Find the read only mask control for an IEC958 value control. The two + * share device, subdevice and index but not always the interface. + */ +static struct ctl_data *find_iec958_mask_ctl(struct ctl_data *ctl, + const char *suffix) +{ + char name[64]; + int stem; + struct ctl_data *mask; + + stem = strlen(ctl->name) - strlen(IEC958_DEFAULT); + if (snprintf(name, sizeof(name), "%.*s%s", stem, ctl->name, suffix) >= + (int)sizeof(name)) + return NULL; + + for (mask = ctl_list; mask != NULL; mask = mask->next) { + if (mask->card != ctl->card) + continue; + if (snd_ctl_elem_info_get_type(mask->info) != + SND_CTL_ELEM_TYPE_IEC958) + continue; + if (snd_ctl_elem_info_is_inactive(mask->info)) + continue; + if (!snd_ctl_elem_info_is_readable(mask->info)) + continue; + if (snd_ctl_elem_id_get_device(mask->id) != + snd_ctl_elem_id_get_device(ctl->id)) + continue; + if (snd_ctl_elem_id_get_subdevice(mask->id) != + snd_ctl_elem_id_get_subdevice(ctl->id)) + continue; + if (snd_ctl_elem_id_get_index(mask->id) != + snd_ctl_elem_id_get_index(ctl->id)) + continue; + if (strcmp(mask->name, name) == 0) + return mask; + } + + return NULL; +} + +/* + * Read the bits the device says it implements. Bit 0 of the first status + * byte picks which mask applies, some devices publish only a plain Mask. + */ +static bool read_iec958_mask(struct ctl_data *ctl, + const snd_aes_iec958_t *cur, + snd_aes_iec958_t *mask) +{ + int err; + struct ctl_data *mask_ctl; + snd_ctl_elem_value_t *val; + + if (!strend(ctl->name, IEC958_DEFAULT)) + return false; + + if (cur->status[0] & IEC958_AES0_PROFESSIONAL) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_PRO_MASK); + else + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_CON_MASK); + if (!mask_ctl) + mask_ctl = find_iec958_mask_ctl(ctl, IEC958_MASK); + if (!mask_ctl) + return false; + + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_set_id(val, mask_ctl->id); + + err = snd_ctl_elem_read(mask_ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + mask_ctl->name, snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(val, mask); + + return true; +} + +/* + * Throw away the events from a write we are not checking, one left behind + * would make a missing notification look like a notification we got. + */ +static int drop_events(struct ctl_data *ctl) +{ + int err; + + do { + err = wait_for_event(ctl, 0); + } while (err > 0); + + return err; +} + +/* + * Toggle every bit the device advertises, one at a time. Each one starts + * from the value we read since a driver can look at the rest of the block + * when it stores a bit, and bit 0 of the first status byte selects the + * layout the others are read in so leave that one alone. + */ +static bool test_ctl_write_valid_iec958_mask(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val, + const snd_aes_iec958_t *mask) +{ + int err, j; + unsigned int byte; + unsigned char bit; + bool fail = false, found = false; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val; + snd_ctl_elem_value_alloca(&val); + + snd_ctl_elem_value_copy(val, orig_val); + + for (byte = 0; byte < sizeof(mask->status); byte++) { + for (j = 0; j < 8; j++) { + bit = 1 << j; + + if (byte == 0 && bit == IEC958_AES0_PROFESSIONAL) + continue; + if (!(mask->status[byte] & bit)) + continue; + found = true; + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[byte] ^= bit; + snd_ctl_elem_value_set_iec958(val, &iec958); + + err = write_and_verify(ctl, val, NULL); + if (err != 0) { + ksft_print_msg("%s failed to set advertised status[%u] 0x%02x\n", + ctl->name, byte, bit); + fail = true; + } + } + } + + if (!found) + ksft_print_msg("%s implements no settable status bits\n", + ctl->name); + + return !fail; +} + +/* + * With nothing advertised all we can do is try non audio, and the device + * need not implement even that. Write it once and ignore the result, then + * compare against what came back so the value cannot fail, which leaves the + * notification counted by event_missing. + */ +static bool test_ctl_write_valid_iec958_guess(struct ctl_data *ctl, + snd_ctl_elem_value_t *orig_val) +{ + int err; + snd_aes_iec958_t iec958; + snd_ctl_elem_value_t *val, *read_val, *w_val; + snd_ctl_elem_value_alloca(&val); + snd_ctl_elem_value_alloca(&read_val); + snd_ctl_elem_value_alloca(&w_val); + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + iec958.status[0] ^= IEC958_AES0_NONAUDIO; + snd_ctl_elem_value_copy(val, orig_val); + snd_ctl_elem_value_set_iec958(val, &iec958); + + /* Writing can modify the value so keep a copy to write from */ + snd_ctl_elem_value_copy(w_val, val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_set_id(read_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, read_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + /* Put it back where we found it, then drop the events from both */ + snd_ctl_elem_value_copy(w_val, orig_val); + err = snd_ctl_elem_write(ctl->card->handle, w_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed: %s\n", + snd_strerror(err)); + return false; + } + + err = drop_events(ctl); + if (err < 0) { + ksft_print_msg("drop_events() failed for %s: %d\n", + ctl->name, err); + return false; + } + + if (snd_ctl_elem_value_compare(val, read_val)) { + ksft_print_msg("%s does not implement status[0] 0x%02x\n", + ctl->name, IEC958_AES0_NONAUDIO); + + /* Expect what came back, the notification is still checked */ + return write_and_verify(ctl, val, read_val) == 0; + } + + return write_and_verify(ctl, val, NULL) == 0; +} + +/* + * Write the channel status bits and check that userspace is told about it. + * Where a mask was found every bit in it has to stick, otherwise we fall + * back to one guessed bit the device is free to ignore. + */ +static bool test_ctl_write_valid_iec958(struct ctl_data *ctl) +{ + int err; + snd_aes_iec958_t iec958, mask; + snd_ctl_elem_value_t *orig_val; + snd_ctl_elem_value_alloca(&orig_val); + + /* + * The bytes past the ones a driver implements are compared too, + * so start from a read rather than building a value here. + */ + snd_ctl_elem_value_set_id(orig_val, ctl->id); + err = snd_ctl_elem_read(ctl->card->handle, orig_val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed: %s\n", + snd_strerror(err)); + return false; + } + + snd_ctl_elem_value_get_iec958(orig_val, &iec958); + + if (!read_iec958_mask(ctl, &iec958, &mask)) { + ksft_print_msg("%s falling back to non audio\n", ctl->name); + return test_ctl_write_valid_iec958_guess(ctl, orig_val); + } + + return test_ctl_write_valid_iec958_mask(ctl, orig_val, &mask); +} + static void test_ctl_write_valid(struct ctl_data *ctl) { bool pass; @@ -878,6 +1165,10 @@ static void test_ctl_write_valid(struct ctl_data *ctl) pass = test_ctl_write_valid_enumerated(ctl); break; + case SND_CTL_ELEM_TYPE_IEC958: + pass = test_ctl_write_valid_iec958(ctl); + break; + default: /* No tests for this yet */ ksft_test_result_skip("write_valid.%s.%d\n", @@ -892,6 +1183,202 @@ static void test_ctl_write_valid(struct ctl_data *ctl) ctl->card->card_name, ctl->elem); } +/* + * Build the smallest or the largest value the control offers. The smallest + * clears the control's register field and the largest sets its top bit, which + * is the bit a mask one bit too wide puts in its neighbour. + */ +static bool set_limit_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val, + bool max) +{ + int i, count = snd_ctl_elem_info_get_count(ctl->info); + + snd_ctl_elem_value_set_id(val, ctl->id); + + switch (snd_ctl_elem_info_get_type(ctl->info)) { + case SND_CTL_ELEM_TYPE_BOOLEAN: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_boolean(val, i, max); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer(val, i, max ? + snd_ctl_elem_info_get_max(ctl->info) : + snd_ctl_elem_info_get_min(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_INTEGER64: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_integer64(val, i, max ? + snd_ctl_elem_info_get_max64(ctl->info) : + snd_ctl_elem_info_get_min64(ctl->info)); + return true; + + case SND_CTL_ELEM_TYPE_ENUMERATED: + for (i = 0; i < count; i++) + snd_ctl_elem_value_set_enumerated(val, i, max ? + snd_ctl_elem_info_get_items(ctl->info) - 1 : 0); + return true; + + default: + /* Nothing sensible to write for the rest */ + return false; + } +} + +/* Note every control on the card that no longer reads as it did */ +static void find_moved_ctls(struct ctl_data *ctl, snd_ctl_elem_value_t *val) +{ + struct ctl_data *other; + int err; + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid) + continue; + + /* + * The buffer is shared and compare() looks at all of it, so + * clear what the last control left in the slots this one + * does not use. + */ + snd_ctl_elem_value_clear(val); + snd_ctl_elem_value_set_id(val, other->id); + err = snd_ctl_elem_read(ctl->card->handle, val); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + if (snd_ctl_elem_value_compare(other->snapshot, val)) + other->moved = true; + } +} + +/* + * Write one control and look for others on the same card that moved with it. + * A driver that links two controls on purpose tells userspace about both, so + * only an unannounced change is counted. That is what a control whose + * register mask covers bits belonging to its neighbour looks like from here. + */ +static void test_ctl_write_side_effects(struct ctl_data *ctl) +{ + struct ctl_data *other; + snd_ctl_elem_value_t *min_val, *max_val, *read_val; + int err; + + snd_ctl_elem_value_alloca(&min_val); + snd_ctl_elem_value_alloca(&max_val); + snd_ctl_elem_value_alloca(&read_val); + + /* Without a readable default there is nothing to put back */ + if (snd_ctl_elem_info_is_inactive(ctl->info) || + !snd_ctl_elem_info_is_writable(ctl->info) || + !snd_ctl_elem_info_is_readable(ctl->info) || + !set_limit_value(ctl, min_val, false) || + !set_limit_value(ctl, max_val, true)) { + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + return; + } + + /* Drain first, a stale event would look like the driver announced it */ + drop_events(ctl); + + /* + * Record what the rest of the card reads as. A volatile control can + * move on its own so there is nothing to compare it against. + */ + for (other = ctl_list; other != NULL; other = other->next) { + other->snapshot_valid = false; + other->moved = false; + other->ev_mask = 0; + + if (other == ctl || other->card != ctl->card) + continue; + if (!snd_ctl_elem_info_is_readable(other->info) || + snd_ctl_elem_info_is_volatile(other->info)) + continue; + + snd_ctl_elem_value_clear(other->snapshot); + snd_ctl_elem_value_set_id(other->snapshot, other->id); + err = snd_ctl_elem_read(ctl->card->handle, other->snapshot); + if (err < 0) { + ksft_print_msg("snd_ctl_elem_read() failed for %s: %s\n", + other->name, snd_strerror(err)); + continue; + } + + other->snapshot_valid = true; + } + + /* + * Compare against the snapshot after each write, before anything is + * put back. Restoring the control we wrote goes through the same + * mask, so doing it first would hide the change we are looking for. + */ + err = snd_ctl_elem_write(ctl->card->handle, min_val); + if (err >= 0) { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + err = snd_ctl_elem_write(ctl->card->handle, max_val); + } + if (err < 0) { + ksft_print_msg("snd_ctl_elem_write() failed for %s: %s\n", + ctl->name, snd_strerror(err)); + } else { + drop_events(ctl); + find_moved_ctls(ctl, read_val); + } + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->moved) + continue; + + if (other->ev_mask & SND_CTL_EVENT_MASK_VALUE) { + ksft_print_msg("Writing %s changed %s, the driver said so\n", + ctl->name, other->name); + } else { + ksft_print_msg("Writing %s silently changed %s\n", + ctl->name, other->name); + ctl->side_effects++; + } + } + + /* + * The control we wrote goes back first so its mask stops moving the + * rest. A plain write keeps this out of the event counters, they + * belong to the tests that check them. + */ + snd_ctl_elem_write(ctl->card->handle, ctl->def_val); + + for (other = ctl_list; other != NULL; other = other->next) { + if (!other->snapshot_valid || + !snd_ctl_elem_info_is_writable(other->info)) + continue; + + snd_ctl_elem_value_clear(read_val); + snd_ctl_elem_value_set_id(read_val, other->id); + if (snd_ctl_elem_read(ctl->card->handle, read_val) < 0) + continue; + + if (snd_ctl_elem_value_compare(other->snapshot, read_val)) + snd_ctl_elem_write(ctl->card->handle, other->snapshot); + } + + /* Our own restores queue events, the next test must not see them */ + drop_events(ctl); + + if (err < 0) + ksft_test_result_skip("write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); + else + ksft_test_result(!ctl->side_effects, + "write_side_effects.%s.%d\n", + ctl->card->card_name, ctl->elem); +} + static bool test_ctl_write_invalid_value(struct ctl_data *ctl, snd_ctl_elem_value_t *val) { @@ -1135,6 +1622,7 @@ int main(void) test_ctl_name(ctl); test_ctl_write_default(ctl); test_ctl_write_valid(ctl); + test_ctl_write_side_effects(ctl); test_ctl_write_invalid(ctl); test_ctl_event_missing(ctl); test_ctl_event_spurious(ctl); diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index 1a9ff010cb11..07aeaa777649 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -51,6 +51,9 @@ FIXTURE_SETUP(timer_f) { self->utimer_info->resolution = (NANO / FRAME_RATE * PERIOD_SIZE); timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { @@ -156,6 +159,9 @@ TEST(wrong_timers_test) { }; timer_dev_fd = open("/dev/snd/timer", O_RDONLY); + if (timer_dev_fd < 0 && + (errno == ENOENT || errno == ENODEV || errno == ENXIO)) + SKIP(return, "ALSA timer device unavailable"); ASSERT_GE(timer_dev_fd, 0); utimer_fd = ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, &wrong_timer); |
