summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2026-04-13 07:18:36 +0200
committerTakashi Iwai <tiwai@suse.de>2026-04-13 07:18:36 +0200
commitf365e47bfbe388b2dde411f8a016065274eee02f (patch)
tree9b41ea2f46dc9d5646d807e02297518826b6fd77
parent52521e8398839105ef8eb22b3f0993f9b0d11a57 (diff)
parent713e0f011178a2896e46db3244093454708066e2 (diff)
downloadlinux-f365e47bfbe388b2dde411f8a016065274eee02f.tar.gz
linux-f365e47bfbe388b2dde411f8a016065274eee02f.zip
Merge branch 'for-next' into for-linus
Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--Documentation/sound/alsa-configuration.rst7
-rw-r--r--include/sound/core.h3
-rw-r--r--include/sound/gus.h8
-rw-r--r--include/sound/hda_codec.h15
-rw-r--r--include/sound/hda_verbs.h12
-rw-r--r--include/sound/pcm.h4
-rw-r--r--include/sound/tea6330t.h1
-rw-r--r--include/sound/timer.h1
-rw-r--r--sound/aoa/aoa.h2
-rw-r--r--sound/aoa/codecs/onyx.c41
-rw-r--r--sound/aoa/codecs/tas.c1
-rw-r--r--sound/aoa/fabrics/layout.c28
-rw-r--r--sound/aoa/soundbus/i2sbus/core.c12
-rw-r--r--sound/aoa/soundbus/i2sbus/pcm.c71
-rw-r--r--sound/core/Makefile1
-rw-r--r--sound/core/compress_offload.c21
-rw-r--r--sound/core/control.c76
-rw-r--r--sound/core/control_trace.h55
-rw-r--r--sound/core/init.c8
-rw-r--r--sound/core/oss/pcm_oss.c44
-rw-r--r--sound/core/pcm_compat.c10
-rw-r--r--sound/core/pcm_native.c29
-rw-r--r--sound/core/seq/oss/seq_oss_rw.c6
-rw-r--r--sound/core/seq/seq_ump_convert.c35
-rw-r--r--sound/core/sound.c7
-rw-r--r--sound/core/timer.c29
-rw-r--r--sound/firewire/fireworks/fireworks_command.c5
-rw-r--r--sound/hda/codecs/analog.c18
-rw-r--r--sound/hda/codecs/ca0132.c14
-rw-r--r--sound/hda/codecs/cirrus/cs420x.c11
-rw-r--r--sound/hda/codecs/cirrus/cs421x.c11
-rw-r--r--sound/hda/codecs/cirrus/cs8409.c24
-rw-r--r--sound/hda/codecs/cmedia.c7
-rw-r--r--sound/hda/codecs/conexant.c14
-rw-r--r--sound/hda/codecs/generic.c2
-rw-r--r--sound/hda/codecs/hdmi/intelhdmi.c2
-rw-r--r--sound/hda/codecs/realtek/alc269.c27
-rw-r--r--sound/hda/codecs/realtek/alc662.c17
-rw-r--r--sound/hda/codecs/realtek/realtek.c31
-rw-r--r--sound/hda/codecs/realtek/realtek.h2
-rw-r--r--sound/hda/codecs/senarytech.c83
-rw-r--r--sound/hda/codecs/side-codecs/cs35l41_hda_property.c6
-rw-r--r--sound/hda/codecs/sigmatel.c10
-rw-r--r--sound/hda/common/codec.c86
-rw-r--r--sound/hda/common/hda_local.h1
-rw-r--r--sound/hda/common/proc.c100
-rw-r--r--sound/hda/controllers/intel.c13
-rw-r--r--sound/i2c/other/ak4xxx-adda.c34
-rw-r--r--sound/i2c/tea6330t.c41
-rw-r--r--sound/isa/es1688/es1688.c50
-rw-r--r--sound/isa/gus/gus_dma.c33
-rw-r--r--sound/isa/gus/gus_main.c36
-rw-r--r--sound/isa/gus/gus_pcm.c7
-rw-r--r--sound/isa/gus/gus_reset.c62
-rw-r--r--sound/isa/gus/gus_timer.c14
-rw-r--r--sound/isa/gus/gus_uart.c47
-rw-r--r--sound/isa/gus/gusclassic.c21
-rw-r--r--sound/isa/gus/gusextreme.c57
-rw-r--r--sound/isa/gus/gusmax.c28
-rw-r--r--sound/isa/gus/interwave.c178
-rw-r--r--sound/isa/msnd/msnd.c72
-rw-r--r--sound/isa/msnd/msnd.h3
-rw-r--r--sound/isa/msnd/msnd_pinnacle.c95
-rw-r--r--sound/isa/msnd/msnd_pinnacle_mixer.c4
-rw-r--r--sound/isa/sc6000.c193
-rw-r--r--sound/isa/sscape.c327
-rw-r--r--sound/pci/asihpi/asihpi.c36
-rw-r--r--sound/pci/ctxfi/ctatc.c34
-rw-r--r--sound/pci/ctxfi/ctdaio.c5
-rw-r--r--sound/pci/ctxfi/ctdaio.h2
-rw-r--r--sound/pci/ctxfi/ctvmem.h2
-rw-r--r--sound/usb/6fire/chip.c17
-rw-r--r--sound/usb/card.c44
-rw-r--r--sound/usb/format.c4
-rw-r--r--sound/usb/midi.c12
-rw-r--r--sound/usb/midi2.c6
-rw-r--r--sound/usb/mixer.c245
-rw-r--r--sound/usb/mixer.h4
-rw-r--r--sound/usb/mixer_quirks.c59
-rw-r--r--sound/usb/mixer_s1810c.c2
-rw-r--r--sound/usb/mixer_scarlett2.c2
-rw-r--r--sound/usb/qcom/qc_audio_offload.c2
-rw-r--r--sound/usb/quirks-table.h113
-rw-r--r--sound/usb/quirks.c82
-rw-r--r--sound/usb/stream.c8
-rw-r--r--sound/usb/usbaudio.h12
-rw-r--r--sound/usb/usx2y/us144mkii.c6
-rw-r--r--tools/testing/selftests/alsa/utimer-test.c10
88 files changed, 2333 insertions, 687 deletions
diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst
index 55b845d38236..f75f08763941 100644
--- a/Documentation/sound/alsa-configuration.rst
+++ b/Documentation/sound/alsa-configuration.rst
@@ -2376,6 +2376,13 @@ quirk_flags
Skip the probe-time interface setup (usb_set_interface,
init_pitch, init_sample_rate); redundant with
snd_usb_endpoint_prepare() at stream-open time
+ * bit 27: ``mixer_playback_linear_vol``
+ Set linear volume mapping for devices where the playback volume
+ control value is mapped to voltage (instead of dB) level linearly.
+ In short: ``x(raw) = (raw - raw_min) / (raw_max - raw_min)``;
+ ``V(x) = k * x``; ``dB(x) = 20 * log10(x)``. Overrides bit 24
+ * bit 28: ``mixer_capture_linear_vol``
+ Similar to bit 27 but for capture streams. Overrides bit 25
This module supports multiple devices, autoprobe and hotplugging.
diff --git a/include/sound/core.h b/include/sound/core.h
index 64327e971122..4093ec82a0a1 100644
--- a/include/sound/core.h
+++ b/include/sound/core.h
@@ -133,6 +133,9 @@ struct snd_card {
#ifdef CONFIG_SND_DEBUG
struct dentry *debugfs_root; /* debugfs root for card */
#endif
+#ifdef CONFIG_SND_CTL_DEBUG
+ struct snd_ctl_elem_value *value_buf; /* buffer for kctl->put() verification */
+#endif
#ifdef CONFIG_PM
unsigned int power_state; /* power state */
diff --git a/include/sound/gus.h b/include/sound/gus.h
index 321ae93625eb..3feb42627de1 100644
--- a/include/sound/gus.h
+++ b/include/sound/gus.h
@@ -536,6 +536,7 @@ int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
struct snd_gf1_dma_block * block,
int atomic,
int synth);
+void snd_gf1_dma_suspend(struct snd_gus_card *gus);
/* gus_volume.c */
@@ -552,6 +553,8 @@ struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, i
void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice);
int snd_gf1_start(struct snd_gus_card * gus);
int snd_gf1_stop(struct snd_gus_card * gus);
+int snd_gf1_suspend(struct snd_gus_card *gus);
+int snd_gf1_resume(struct snd_gus_card *gus);
/* gus_mixer.c */
@@ -572,6 +575,8 @@ int snd_gus_create(struct snd_card *card,
int effect,
struct snd_gus_card ** rgus);
int snd_gus_initialize(struct snd_gus_card * gus);
+int snd_gus_suspend(struct snd_gus_card *gus);
+int snd_gus_resume(struct snd_gus_card *gus);
/* gus_irq.c */
@@ -583,6 +588,8 @@ void snd_gus_irq_profile_init(struct snd_gus_card *gus);
/* gus_uart.c */
int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device);
+void snd_gf1_uart_suspend(struct snd_gus_card *gus);
+void snd_gf1_uart_resume(struct snd_gus_card *gus);
/* gus_dram.c */
int snd_gus_dram_write(struct snd_gus_card *gus, char __user *ptr,
@@ -593,5 +600,6 @@ int snd_gus_dram_read(struct snd_gus_card *gus, char __user *ptr,
/* gus_timer.c */
void snd_gf1_timers_init(struct snd_gus_card *gus);
void snd_gf1_timers_done(struct snd_gus_card *gus);
+void snd_gf1_timers_resume(struct snd_gus_card *gus);
#endif /* __SOUND_GUS_H */
diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h
index 5d9f0ef228af..24581080e26a 100644
--- a/include/sound/hda_codec.h
+++ b/include/sound/hda_codec.h
@@ -336,6 +336,17 @@ snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags,
return snd_hdac_codec_write(&codec->core, nid, flags, verb, parm);
}
+/* sync after write */
+static inline int
+snd_hda_codec_write_sync(struct hda_codec *codec, hda_nid_t nid, int flags,
+ unsigned int verb, unsigned int parm)
+{
+ /* use snd_hda_codec_read() for writing;
+ * the returned value is usually discarded
+ */
+ return snd_hdac_codec_read(&codec->core, nid, flags, verb, parm);
+}
+
#define snd_hda_param_read(codec, nid, param) \
snd_hdac_read_parm(&(codec)->core, nid, param)
#define snd_hda_get_sub_nodes(codec, nid, start_nid) \
@@ -470,6 +481,10 @@ void snd_hda_unlock_devices(struct hda_bus *bus);
void snd_hda_bus_reset(struct hda_bus *bus);
void snd_hda_bus_reset_codecs(struct hda_bus *bus);
+void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask,
+ unsigned int dir, unsigned int data,
+ unsigned int delay);
+
int snd_hda_codec_set_name(struct hda_codec *codec, const char *name);
/*
diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h
index 006d358acce2..6066954409aa 100644
--- a/include/sound/hda_verbs.h
+++ b/include/sound/hda_verbs.h
@@ -56,7 +56,12 @@ enum {
#define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d
#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */
#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f
-/* f10-f1a: GPIO */
+/* f10-f1a: GPI/GPO/GPIO */
+#define AC_VERB_GET_GPI_DATA 0x0f10
+#define AC_VERB_GET_GPI_WAKE_MASK 0x0f11
+#define AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK 0x0f12
+#define AC_VERB_GET_GPI_STICKY_MASK 0x0f13
+#define AC_VERB_GET_GPO_DATA 0x0f14
#define AC_VERB_GET_GPIO_DATA 0x0f15
#define AC_VERB_GET_GPIO_MASK 0x0f16
#define AC_VERB_GET_GPIO_DIRECTION 0x0f17
@@ -99,6 +104,11 @@ enum {
#define AC_VERB_SET_DIGI_CONVERT_2 0x70e
#define AC_VERB_SET_DIGI_CONVERT_3 0x73e
#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f
+#define AC_VERB_SET_GPI_DATA 0x710
+#define AC_VERB_SET_GPI_WAKE_MASK 0x711
+#define AC_VERB_SET_SPI_UNSOLICITED_RSP_MASK 0x712
+#define AC_VERB_SET_GPI_STICKY_MASK 0x713
+#define AC_VERB_SET_GPO_DATA 0x714
#define AC_VERB_SET_GPIO_DATA 0x715
#define AC_VERB_SET_GPIO_MASK 0x716
#define AC_VERB_SET_GPIO_DIRECTION 0x717
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index a7860c047503..76fc33dce537 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -729,6 +729,10 @@ static inline void __snd_pcm_set_state(struct snd_pcm_runtime *runtime,
runtime->status->state = state; /* copy for mmap */
}
+void snd_pcm_set_state(struct snd_pcm_substream *substream,
+ snd_pcm_state_t state);
+snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream);
+
/**
* bytes_to_samples - Unit conversion of the size from bytes to samples
* @runtime: PCM runtime instance
diff --git a/include/sound/tea6330t.h b/include/sound/tea6330t.h
index 1c77b78f6533..3a34033d2aa3 100644
--- a/include/sound/tea6330t.h
+++ b/include/sound/tea6330t.h
@@ -12,5 +12,6 @@
int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer);
int snd_tea6330t_update_mixer(struct snd_card *card, struct snd_i2c_bus *bus,
int equalizer, int fader);
+int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus);
#endif /* __SOUND_TEA6330T_H */
diff --git a/include/sound/timer.h b/include/sound/timer.h
index 760e132cc0cd..83bafe70cf33 100644
--- a/include/sound/timer.h
+++ b/include/sound/timer.h
@@ -102,6 +102,7 @@ struct snd_timer_instance {
unsigned int slave_id;
struct list_head open_list;
struct list_head active_list;
+ struct list_head master_list;
struct list_head ack_list;
struct list_head slave_list_head;
struct list_head slave_active_head;
diff --git a/sound/aoa/aoa.h b/sound/aoa/aoa.h
index badff9f7cd54..b92593f170ca 100644
--- a/sound/aoa/aoa.h
+++ b/sound/aoa/aoa.h
@@ -48,7 +48,7 @@ struct aoa_codec {
u32 connected;
/* data the fabric can associate with this structure */
- void *fabric_data;
+ const void *fabric_data;
/* private! */
struct list_head list;
diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c
index 04961c456d2c..4fb593e88fb1 100644
--- a/sound/aoa/codecs/onyx.c
+++ b/sound/aoa/codecs/onyx.c
@@ -32,6 +32,7 @@
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
+#include <sound/asoundef.h>
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
@@ -514,8 +515,36 @@ static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
return 1;
}
+static int onyx_set_spdif_pcm_rate(struct onyx *onyx, unsigned int rate)
+{
+ u8 dig_info3, fs;
+
+ switch (rate) {
+ case 32000:
+ fs = IEC958_AES3_CON_FS_32000;
+ break;
+ case 44100:
+ fs = IEC958_AES3_CON_FS_44100;
+ break;
+ case 48000:
+ fs = IEC958_AES3_CON_FS_48000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &dig_info3))
+ return -EBUSY;
+ dig_info3 = (dig_info3 & ~IEC958_AES3_CON_FS) | fs;
+ if (onyx_write_register(onyx, ONYX_REG_DIG_INFO3, dig_info3))
+ return -EBUSY;
+
+ return 0;
+}
+
static const struct snd_kcontrol_new onyx_spdif_ctrl = {
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = onyx_spdif_info,
@@ -695,9 +724,9 @@ static int onyx_prepare(struct codec_info_item *cii,
case 32000:
case 44100:
case 48000:
- /* these rates are ok for all outputs */
- /* FIXME: program spdif channel control bits here so that
- * userspace doesn't have to if it only plays pcm! */
+ if (onyx->codec.connected & 2)
+ return onyx_set_spdif_pcm_rate(onyx,
+ substream->runtime->rate);
return 0;
default:
/* got some rate that the digital output can't do,
@@ -980,10 +1009,12 @@ static int onyx_i2c_probe(struct i2c_client *client)
onyx->codec.node = of_node_get(node);
if (aoa_codec_register(&onyx->codec)) {
- goto fail;
+ goto fail_put;
}
printk(KERN_DEBUG PFX "created and attached onyx instance\n");
return 0;
+ fail_put:
+ of_node_put(onyx->codec.node);
fail:
kfree(onyx);
return -ENODEV;
diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c
index 13da2b159ad0..25214d3da65d 100644
--- a/sound/aoa/codecs/tas.c
+++ b/sound/aoa/codecs/tas.c
@@ -872,6 +872,7 @@ static int tas_i2c_probe(struct i2c_client *client)
return 0;
fail:
mutex_destroy(&tas->mtx);
+ of_node_put(tas->codec.node);
kfree(tas);
return -EINVAL;
}
diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c
index c18b55305294..c3ebb6de4789 100644
--- a/sound/aoa/fabrics/layout.c
+++ b/sound/aoa/fabrics/layout.c
@@ -55,7 +55,7 @@ struct codec_connection {
struct codec_connect_info {
char *name;
- struct codec_connection *connections;
+ const struct codec_connection *connections;
};
#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0)
@@ -116,7 +116,7 @@ MODULE_ALIAS("aoa-device-id-35");
MODULE_ALIAS("aoa-device-id-44");
/* onyx with all but microphone connected */
-static struct codec_connection onyx_connections_nomic[] = {
+static const struct codec_connection onyx_connections_nomic[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
.codec_bit = 0,
@@ -133,7 +133,7 @@ static struct codec_connection onyx_connections_nomic[] = {
};
/* onyx on machines without headphone */
-static struct codec_connection onyx_connections_noheadphones[] = {
+static const struct codec_connection onyx_connections_noheadphones[] = {
{
.connected = CC_SPEAKERS | CC_LINEOUT |
CC_LINEOUT_LABELLED_HEADPHONE,
@@ -157,7 +157,7 @@ static struct codec_connection onyx_connections_noheadphones[] = {
};
/* onyx on machines with real line-out */
-static struct codec_connection onyx_connections_reallineout[] = {
+static const struct codec_connection onyx_connections_reallineout[] = {
{
.connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
.codec_bit = 0,
@@ -174,7 +174,7 @@ static struct codec_connection onyx_connections_reallineout[] = {
};
/* tas on machines without line out */
-static struct codec_connection tas_connections_nolineout[] = {
+static const struct codec_connection tas_connections_nolineout[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE,
.codec_bit = 0,
@@ -191,7 +191,7 @@ static struct codec_connection tas_connections_nolineout[] = {
};
/* tas on machines with neither line out nor line in */
-static struct codec_connection tas_connections_noline[] = {
+static const struct codec_connection tas_connections_noline[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE,
.codec_bit = 0,
@@ -204,7 +204,7 @@ static struct codec_connection tas_connections_noline[] = {
};
/* tas on machines without microphone */
-static struct codec_connection tas_connections_nomic[] = {
+static const struct codec_connection tas_connections_nomic[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
.codec_bit = 0,
@@ -217,7 +217,7 @@ static struct codec_connection tas_connections_nomic[] = {
};
/* tas on machines with everything connected */
-static struct codec_connection tas_connections_all[] = {
+static const struct codec_connection tas_connections_all[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
.codec_bit = 0,
@@ -233,7 +233,7 @@ static struct codec_connection tas_connections_all[] = {
{} /* terminate array by .connected == 0 */
};
-static struct codec_connection toonie_connections[] = {
+static const struct codec_connection toonie_connections[] = {
{
.connected = CC_SPEAKERS | CC_HEADPHONE,
.codec_bit = 0,
@@ -241,7 +241,7 @@ static struct codec_connection toonie_connections[] = {
{} /* terminate array by .connected == 0 */
};
-static struct codec_connection topaz_input[] = {
+static const struct codec_connection topaz_input[] = {
{
.connected = CC_DIGITALIN,
.codec_bit = 0,
@@ -249,7 +249,7 @@ static struct codec_connection topaz_input[] = {
{} /* terminate array by .connected == 0 */
};
-static struct codec_connection topaz_output[] = {
+static const struct codec_connection topaz_output[] = {
{
.connected = CC_DIGITALOUT,
.codec_bit = 1,
@@ -257,7 +257,7 @@ static struct codec_connection topaz_output[] = {
{} /* terminate array by .connected == 0 */
};
-static struct codec_connection topaz_inout[] = {
+static const struct codec_connection topaz_inout[] = {
{
.connected = CC_DIGITALIN,
.codec_bit = 0,
@@ -772,7 +772,7 @@ static int check_codec(struct aoa_codec *codec,
{
const u32 *ref;
char propname[32];
- struct codec_connection *cc;
+ const struct codec_connection *cc;
/* if the codec has a 'codec' node, we require a reference */
if (of_node_name_eq(codec->node, "codec")) {
@@ -895,7 +895,7 @@ static void layout_notify(void *data)
static void layout_attached_codec(struct aoa_codec *codec)
{
- struct codec_connection *cc;
+ const struct codec_connection *cc;
struct snd_kcontrol *ctl;
int headphones, lineout;
struct layout_dev *ldev = layout_device;
diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c
index f974b96e98cd..833c44c0a950 100644
--- a/sound/aoa/soundbus/i2sbus/core.c
+++ b/sound/aoa/soundbus/i2sbus/core.c
@@ -84,6 +84,7 @@ static void i2sbus_release_dev(struct device *dev)
for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++)
free_irq(i2sdev->interrupts[i], i2sdev);
i2sbus_control_remove_dev(i2sdev->control, i2sdev);
+ of_node_put(i2sdev->sound.ofdev.dev.of_node);
mutex_destroy(&i2sdev->lock);
kfree(i2sdev);
}
@@ -147,7 +148,6 @@ static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index,
}
/* Returns 1 if added, 0 for otherwise; don't return a negative value! */
-/* FIXME: look at device node refcounting */
static int i2sbus_add_dev(struct macio_dev *macio,
struct i2sbus_control *control,
struct device_node *np)
@@ -178,8 +178,9 @@ static int i2sbus_add_dev(struct macio_dev *macio,
i = 0;
for_each_child_of_node(np, child) {
if (of_node_name_eq(child, "sound")) {
+ of_node_put(sound);
i++;
- sound = child;
+ sound = of_node_get(child);
}
}
if (i == 1) {
@@ -205,6 +206,7 @@ static int i2sbus_add_dev(struct macio_dev *macio,
}
}
}
+ of_node_put(sound);
/* for the time being, until we can handle non-layout-id
* things in some fabric, refuse to attach if there is no
* layout-id property or we haven't been forced to attach.
@@ -219,7 +221,7 @@ static int i2sbus_add_dev(struct macio_dev *macio,
mutex_init(&dev->lock);
spin_lock_init(&dev->low_lock);
dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask;
- dev->sound.ofdev.dev.of_node = np;
+ dev->sound.ofdev.dev.of_node = of_node_get(np);
dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask;
dev->sound.ofdev.dev.parent = &macio->ofdev.dev;
dev->sound.ofdev.dev.release = i2sbus_release_dev;
@@ -327,6 +329,7 @@ static int i2sbus_add_dev(struct macio_dev *macio,
for (i=0;i<3;i++)
release_and_free_resource(dev->allocated_resource[i]);
mutex_destroy(&dev->lock);
+ of_node_put(dev->sound.ofdev.dev.of_node);
kfree(dev);
return 0;
}
@@ -405,6 +408,9 @@ static int i2sbus_resume(struct macio_dev* dev)
int err, ret = 0;
list_for_each_entry(i2sdev, &control->list, item) {
+ if (list_empty(&i2sdev->sound.codec_list))
+ continue;
+
/* reset i2s bus format etc. */
i2sbus_pcm_prepare_both(i2sdev);
diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c
index aff99003d833..63004ece94f9 100644
--- a/sound/aoa/soundbus/i2sbus/pcm.c
+++ b/sound/aoa/soundbus/i2sbus/pcm.c
@@ -165,17 +165,16 @@ static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
* currently in use (if any). */
hw->rate_min = 5512;
hw->rate_max = 192000;
- /* if the other stream is active, then we can only
- * support what it is currently using.
- * FIXME: I lied. This comment is wrong. We can support
- * anything that works with the same serial format, ie.
- * when recording 24 bit sound we can well play 16 bit
- * sound at the same time iff using the same transfer mode.
+ /* If the other stream is already prepared, keep this stream
+ * on the same duplex format and rate.
+ *
+ * i2sbus_pcm_prepare() still programs one shared transport
+ * configuration for both directions, so mixed duplex formats
+ * are not supported here.
*/
if (other->active) {
- /* FIXME: is this guaranteed by the alsa api? */
hw->formats &= pcm_format_to_bits(i2sdev->format);
- /* see above, restrict rates to the one we already have */
+ /* Restrict rates to the one already in use. */
hw->rate_min = i2sdev->rate;
hw->rate_max = i2sdev->rate;
}
@@ -283,6 +282,23 @@ void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev)
}
#endif
+static void i2sbus_pcm_clear_active(struct i2sbus_dev *i2sdev, int in)
+{
+ struct pcm_info *pi;
+
+ guard(mutex)(&i2sdev->lock);
+
+ get_pcm_info(i2sdev, in, &pi, NULL);
+ pi->active = 0;
+}
+
+static inline int i2sbus_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, int in)
+{
+ i2sbus_pcm_clear_active(snd_pcm_substream_chip(substream), in);
+ return 0;
+}
+
static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
@@ -291,14 +307,27 @@ static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
get_pcm_info(i2sdev, in, &pi, NULL);
if (pi->dbdma_ring.stopping)
i2sbus_wait_for_stop(i2sdev, pi);
+ i2sbus_pcm_clear_active(i2sdev, in);
return 0;
}
+static int i2sbus_playback_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ return i2sbus_hw_params(substream, params, 0);
+}
+
static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream)
{
return i2sbus_hw_free(substream, 0);
}
+static int i2sbus_record_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ return i2sbus_hw_params(substream, params, 1);
+}
+
static int i2sbus_record_hw_free(struct snd_pcm_substream *substream)
{
return i2sbus_hw_free(substream, 1);
@@ -335,7 +364,6 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
return -EINVAL;
runtime = pi->substream->runtime;
- pi->active = 1;
if (other->active &&
((i2sdev->format != runtime->format)
|| (i2sdev->rate != runtime->rate)))
@@ -383,6 +411,9 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
/* set stop command */
command->command = cpu_to_le16(DBDMA_STOP);
+ cii = list_first_entry(&i2sdev->sound.codec_list,
+ struct codec_info_item, list);
+
/* ok, let's set the serial format and stuff */
switch (runtime->format) {
/* 16 bit formats */
@@ -390,13 +421,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
case SNDRV_PCM_FORMAT_U16_BE:
/* FIXME: if we add different bus factors we need to
* do more here!! */
- bi.bus_factor = 0;
- list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
- bi.bus_factor = cii->codec->bus_factor;
- break;
- }
- if (!bi.bus_factor)
- return -ENODEV;
+ bi.bus_factor = cii->codec->bus_factor;
input_16bit = 1;
break;
case SNDRV_PCM_FORMAT_S32_BE:
@@ -410,10 +435,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
return -EINVAL;
}
/* we assume all sysclocks are the same! */
- list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
- bi.sysclock_factor = cii->codec->sysclock_factor;
- break;
- }
+ bi.sysclock_factor = cii->codec->sysclock_factor;
if (clock_and_divisors(bi.sysclock_factor,
bi.bus_factor,
@@ -450,9 +472,11 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
/* early exit if already programmed correctly */
/* not locking these is fine since we touch them only in this function */
- if (in_le32(&i2sdev->intfregs->serial_format) == sfr
- && in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
+ if (in_le32(&i2sdev->intfregs->serial_format) == sfr &&
+ in_le32(&i2sdev->intfregs->data_word_sizes) == dws) {
+ pi->active = 1;
return 0;
+ }
/* let's notify the codecs about clocks going away.
* For now we only do mastering on the i2s cell... */
@@ -490,6 +514,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
if (cii->codec->switch_clock)
cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
+ pi->active = 1;
return 0;
}
@@ -734,6 +759,7 @@ static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
static const struct snd_pcm_ops i2sbus_playback_ops = {
.open = i2sbus_playback_open,
.close = i2sbus_playback_close,
+ .hw_params = i2sbus_playback_hw_params,
.hw_free = i2sbus_playback_hw_free,
.prepare = i2sbus_playback_prepare,
.trigger = i2sbus_playback_trigger,
@@ -802,6 +828,7 @@ static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
static const struct snd_pcm_ops i2sbus_record_ops = {
.open = i2sbus_record_open,
.close = i2sbus_record_close,
+ .hw_params = i2sbus_record_hw_params,
.hw_free = i2sbus_record_hw_free,
.prepare = i2sbus_record_prepare,
.trigger = i2sbus_record_trigger,
diff --git a/sound/core/Makefile b/sound/core/Makefile
index 31a0623cc89d..fdd3bb6e81a9 100644
--- a/sound/core/Makefile
+++ b/sound/core/Makefile
@@ -23,6 +23,7 @@ snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o
# for trace-points
CFLAGS_pcm_lib.o := -I$(src)
CFLAGS_pcm_native.o := -I$(src)
+CFLAGS_control.o := -I$(src)
snd-pcm-dmaengine-y := pcm_dmaengine.o
diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c
index fdba6e4b25fd..fd63d219bf86 100644
--- a/sound/core/compress_offload.c
+++ b/sound/core/compress_offload.c
@@ -41,13 +41,6 @@
#define COMPR_CODEC_CAPS_OVERFLOW
#endif
-/* TODO:
- * - add substream support for multiple devices in case of
- * SND_DYNAMIC_MINORS is not used
- * - Multiple node representation
- * driver should be able to register multiple nodes
- */
-
struct snd_compr_file {
unsigned long caps;
struct snd_compr_stream stream;
@@ -190,9 +183,21 @@ snd_compr_tstamp32_from_64(struct snd_compr_tstamp *tstamp32,
static int snd_compr_update_tstamp(struct snd_compr_stream *stream,
struct snd_compr_tstamp64 *tstamp)
{
+ int ret;
+
if (!stream->ops->pointer)
return -ENOTSUPP;
- stream->ops->pointer(stream, tstamp);
+
+ switch (stream->runtime->state) {
+ case SNDRV_PCM_STATE_OPEN:
+ return -EBADFD;
+ default:
+ break;
+ }
+
+ ret = stream->ops->pointer(stream, tstamp);
+ if (ret != 0)
+ return ret;
pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset,
tstamp->copied_total);
if (stream->direction == SND_COMPRESS_PLAYBACK)
diff --git a/sound/core/control.c b/sound/core/control.c
index 934e84e93838..374e703d15a9 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -19,6 +19,13 @@
#include <sound/info.h>
#include <sound/control.h>
+#ifdef CONFIG_SND_CTL_DEBUG
+#define CREATE_TRACE_POINTS
+#include "control_trace.h"
+#else
+#define trace_snd_ctl_put(card, kctl, iname, expected, actual)
+#endif
+
// Max allocation size for user controls.
static int max_user_ctl_alloc_size = 8 * 1024 * 1024;
module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444);
@@ -1264,6 +1271,72 @@ static int snd_ctl_elem_read_user(struct snd_card *card,
return result;
}
+#if IS_ENABLED(CONFIG_SND_CTL_DEBUG)
+
+static const char *const snd_ctl_elem_iface_names[] = {
+ [SNDRV_CTL_ELEM_IFACE_CARD] = "CARD",
+ [SNDRV_CTL_ELEM_IFACE_HWDEP] = "HWDEP",
+ [SNDRV_CTL_ELEM_IFACE_MIXER] = "MIXER",
+ [SNDRV_CTL_ELEM_IFACE_PCM] = "PCM",
+ [SNDRV_CTL_ELEM_IFACE_RAWMIDI] = "RAWMIDI",
+ [SNDRV_CTL_ELEM_IFACE_TIMER] = "TIMER",
+ [SNDRV_CTL_ELEM_IFACE_SEQUENCER] = "SEQUENCER",
+};
+
+static int snd_ctl_put_verify(struct snd_card *card, struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *control)
+{
+ struct snd_ctl_elem_value *original = card->value_buf;
+ struct snd_ctl_elem_info info;
+ const char *iname;
+ int ret, retcmp;
+
+ memset(original, 0, sizeof(*original));
+ memset(&info, 0, sizeof(info));
+
+ ret = kctl->info(kctl, &info);
+ if (ret)
+ return ret;
+
+ ret = kctl->get(kctl, original);
+ if (ret)
+ return ret;
+
+ ret = kctl->put(kctl, control);
+ if (ret < 0)
+ return ret;
+
+ /* Sanitize the new value (control->value) before comparing. */
+ fill_remaining_elem_value(control, &info, 0);
+
+ /* With known state for both new and original, do the comparison. */
+ retcmp = memcmp(&original->value, &control->value, sizeof(original->value));
+ if (retcmp)
+ retcmp = 1;
+
+ iname = snd_ctl_elem_iface_names[kctl->id.iface];
+ trace_snd_ctl_put(&kctl->id, iname, card->number, ret, retcmp);
+
+ return ret;
+}
+
+static int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *control, unsigned int access)
+{
+ if ((access & SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK) ||
+ (access & SNDRV_CTL_ELEM_ACCESS_VOLATILE))
+ return kctl->put(kctl, control);
+
+ return snd_ctl_put_verify(card, kctl, control);
+}
+#else
+static inline int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *control, unsigned int access)
+{
+ return kctl->put(kctl, control);
+}
+#endif
+
static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
struct snd_ctl_elem_value *control)
{
@@ -1300,7 +1373,8 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,
false);
}
if (!result)
- result = kctl->put(kctl, control);
+ result = snd_ctl_put(card, kctl, control, vd->access);
+
if (result < 0) {
up_write(&card->controls_rwsem);
return result;
diff --git a/sound/core/control_trace.h b/sound/core/control_trace.h
new file mode 100644
index 000000000000..d30e654b0860
--- /dev/null
+++ b/sound/core/control_trace.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM snd_ctl
+
+#if !defined(_TRACE_SND_CTL_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SND_CTL_H
+
+#include <linux/tracepoint.h>
+#include <uapi/sound/asound.h>
+
+TRACE_EVENT(snd_ctl_put,
+
+ TP_PROTO(struct snd_ctl_elem_id *id, const char *iname, unsigned int card,
+ int expected, int actual),
+
+ TP_ARGS(id, iname, card, expected, actual),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, numid)
+ __string(iname, iname)
+ __string(kname, id->name)
+ __field(unsigned int, index)
+ __field(unsigned int, device)
+ __field(unsigned int, subdevice)
+ __field(unsigned int, card)
+ __field(int, expected)
+ __field(int, actual)
+ ),
+
+ TP_fast_assign(
+ __entry->numid = id->numid;
+ __assign_str(iname);
+ __assign_str(kname);
+ __entry->index = id->index;
+ __entry->device = id->device;
+ __entry->subdevice = id->subdevice;
+ __entry->card = card;
+ __entry->expected = expected;
+ __entry->actual = actual;
+ ),
+
+ TP_printk("%s: expected=%d, actual=%d for ctl numid=%d, iface=%s, name='%s', index=%d, device=%d, subdevice=%d, card=%d\n",
+ __entry->expected == __entry->actual ? "success" : "fail",
+ __entry->expected, __entry->actual, __entry->numid,
+ __get_str(iname), __get_str(kname), __entry->index,
+ __entry->device, __entry->subdevice, __entry->card)
+);
+
+#endif /* _TRACE_SND_CTL_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE control_trace
+#include <trace/define_trace.h>
diff --git a/sound/core/init.c b/sound/core/init.c
index 2f1bd9cbdbed..0c316189e947 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -363,6 +363,11 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
card->debugfs_root = debugfs_create_dir(dev_name(&card->card_dev),
sound_debugfs_root);
#endif
+#ifdef CONFIG_SND_CTL_DEBUG
+ card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL);
+ if (!card->value_buf)
+ return -ENOMEM;
+#endif
return 0;
__error_ctl:
@@ -587,6 +592,9 @@ static int snd_card_do_free(struct snd_card *card)
snd_device_free_all(card);
if (card->private_free)
card->private_free(card);
+#ifdef CONFIG_SND_CTL_DEBUG
+ kfree(card->value_buf);
+#endif
if (snd_info_card_free(card) < 0) {
dev_warn(card->dev, "unable to free card info\n");
/* Not fatal error */
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
index d4fd4dfc7fc3..a140a0d9abb8 100644
--- a/sound/core/oss/pcm_oss.c
+++ b/sound/core/oss/pcm_oss.c
@@ -1227,14 +1227,16 @@ static int snd_pcm_oss_capture_position_fixup(struct snd_pcm_substream *substrea
snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel)
{
struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_state_t state;
int ret;
while (1) {
- if (runtime->state == SNDRV_PCM_STATE_XRUN ||
- runtime->state == SNDRV_PCM_STATE_SUSPENDED) {
+ state = snd_pcm_get_state(substream);
+ if (state == SNDRV_PCM_STATE_XRUN ||
+ state == SNDRV_PCM_STATE_SUSPENDED) {
#ifdef OSS_DEBUG
pcm_dbg(substream->pcm,
"pcm_oss: write: recovering from %s\n",
- runtime->state == SNDRV_PCM_STATE_XRUN ?
+ state == SNDRV_PCM_STATE_XRUN ?
"XRUN" : "SUSPEND");
#endif
ret = snd_pcm_oss_prepare(substream);
@@ -1249,7 +1251,7 @@ snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const
break;
/* test, if we can't store new data, because the stream */
/* has not been started */
- if (runtime->state == SNDRV_PCM_STATE_PREPARED)
+ if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED)
return -EAGAIN;
}
return ret;
@@ -1259,20 +1261,22 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_sframes_t delay;
+ snd_pcm_state_t state;
int ret;
while (1) {
- if (runtime->state == SNDRV_PCM_STATE_XRUN ||
- runtime->state == SNDRV_PCM_STATE_SUSPENDED) {
+ state = snd_pcm_get_state(substream);
+ if (state == SNDRV_PCM_STATE_XRUN ||
+ state == SNDRV_PCM_STATE_SUSPENDED) {
#ifdef OSS_DEBUG
pcm_dbg(substream->pcm,
"pcm_oss: read: recovering from %s\n",
- runtime->state == SNDRV_PCM_STATE_XRUN ?
+ state == SNDRV_PCM_STATE_XRUN ?
"XRUN" : "SUSPEND");
#endif
ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
if (ret < 0)
break;
- } else if (runtime->state == SNDRV_PCM_STATE_SETUP) {
+ } else if (state == SNDRV_PCM_STATE_SETUP) {
ret = snd_pcm_oss_prepare(substream);
if (ret < 0)
break;
@@ -1285,7 +1289,7 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p
frames, in_kernel);
mutex_lock(&runtime->oss.params_lock);
if (ret == -EPIPE) {
- if (runtime->state == SNDRV_PCM_STATE_DRAINING) {
+ if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_DRAINING) {
ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
if (ret < 0)
break;
@@ -1301,15 +1305,16 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p
#ifdef CONFIG_SND_PCM_OSS_PLUGINS
snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames)
{
- struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_state_t state;
int ret;
while (1) {
- if (runtime->state == SNDRV_PCM_STATE_XRUN ||
- runtime->state == SNDRV_PCM_STATE_SUSPENDED) {
+ state = snd_pcm_get_state(substream);
+ if (state == SNDRV_PCM_STATE_XRUN ||
+ state == SNDRV_PCM_STATE_SUSPENDED) {
#ifdef OSS_DEBUG
pcm_dbg(substream->pcm,
"pcm_oss: writev: recovering from %s\n",
- runtime->state == SNDRV_PCM_STATE_XRUN ?
+ state == SNDRV_PCM_STATE_XRUN ?
"XRUN" : "SUSPEND");
#endif
ret = snd_pcm_oss_prepare(substream);
@@ -1322,7 +1327,7 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void
/* test, if we can't store new data, because the stream */
/* has not been started */
- if (runtime->state == SNDRV_PCM_STATE_PREPARED)
+ if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED)
return -EAGAIN;
}
return ret;
@@ -1330,21 +1335,22 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void
snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames)
{
- struct snd_pcm_runtime *runtime = substream->runtime;
+ snd_pcm_state_t state;
int ret;
while (1) {
- if (runtime->state == SNDRV_PCM_STATE_XRUN ||
- runtime->state == SNDRV_PCM_STATE_SUSPENDED) {
+ state = snd_pcm_get_state(substream);
+ if (state == SNDRV_PCM_STATE_XRUN ||
+ state == SNDRV_PCM_STATE_SUSPENDED) {
#ifdef OSS_DEBUG
pcm_dbg(substream->pcm,
"pcm_oss: readv: recovering from %s\n",
- runtime->state == SNDRV_PCM_STATE_XRUN ?
+ state == SNDRV_PCM_STATE_XRUN ?
"XRUN" : "SUSPEND");
#endif
ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL);
if (ret < 0)
break;
- } else if (runtime->state == SNDRV_PCM_STATE_SETUP) {
+ } else if (state == SNDRV_PCM_STATE_SETUP) {
ret = snd_pcm_oss_prepare(substream);
if (ret < 0)
break;
diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c
index e71f393d3b01..5313f50f17da 100644
--- a/sound/core/pcm_compat.c
+++ b/sound/core/pcm_compat.c
@@ -430,11 +430,13 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream,
if (!boundary)
boundary = 0x7fffffff;
scoped_guard(pcm_stream_lock_irq, substream) {
- /* FIXME: we should consider the boundary for the sync from app */
- if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL))
- control->appl_ptr = scontrol.appl_ptr;
- else
+ if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) {
+ err = pcm_lib_apply_appl_ptr(substream, scontrol.appl_ptr);
+ if (err < 0)
+ return err;
+ } else {
scontrol.appl_ptr = control->appl_ptr % boundary;
+ }
if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN))
control->avail_min = scontrol.avail_min;
else
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 5a64453da728..a541bb235cfa 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -618,13 +618,32 @@ static int period_to_usecs(struct snd_pcm_runtime *runtime)
return usecs;
}
-static void snd_pcm_set_state(struct snd_pcm_substream *substream,
- snd_pcm_state_t state)
+/**
+ * snd_pcm_set_state - Set the PCM runtime state with stream lock
+ * @substream: PCM substream
+ * @state: state to set
+ */
+void snd_pcm_set_state(struct snd_pcm_substream *substream,
+ snd_pcm_state_t state)
{
guard(pcm_stream_lock_irq)(substream);
if (substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED)
__snd_pcm_set_state(substream->runtime, state);
}
+EXPORT_SYMBOL_GPL(snd_pcm_set_state);
+
+/**
+ * snd_pcm_get_state - Read the PCM runtime state with stream lock
+ * @substream: PCM substream
+ *
+ * Return: the current PCM state
+ */
+snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream)
+{
+ guard(pcm_stream_lock_irqsave)(substream);
+ return substream->runtime->state;
+}
+EXPORT_SYMBOL_GPL(snd_pcm_get_state);
static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream,
int event)
@@ -1761,6 +1780,9 @@ static int snd_pcm_suspend(struct snd_pcm_substream *substream)
* snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm
* @pcm: the PCM instance
*
+ * Takes and releases pcm->open_mutex to serialize against
+ * concurrent open/close while walking the substreams.
+ *
* After this call, all streams are changed to SUSPENDED state.
*
* Return: Zero if successful (or @pcm is %NULL), or a negative error code.
@@ -1773,8 +1795,9 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm)
if (! pcm)
return 0;
+ guard(mutex)(&pcm->open_mutex);
+
for_each_pcm_substream(pcm, stream, substream) {
- /* FIXME: the open/close code should lock this as well */
if (!substream->runtime)
continue;
diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c
index 8a142fd54a19..307ef98c44c7 100644
--- a/sound/core/seq/oss/seq_oss_rw.c
+++ b/sound/core/seq/oss/seq_oss_rw.c
@@ -101,9 +101,9 @@ snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count,
break;
}
fmt = (*(unsigned short *)rec.c) & 0xffff;
- /* FIXME the return value isn't correct */
- return snd_seq_oss_synth_load_patch(dp, rec.s.dev,
- fmt, buf, 0, count);
+ err = snd_seq_oss_synth_load_patch(dp, rec.s.dev,
+ fmt, buf, 0, count);
+ return err < 0 ? err : count;
}
if (ev_is_long(&rec)) {
/* extended code */
diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
index db2f169cae11..ff4ee26adad1 100644
--- a/sound/core/seq/seq_ump_convert.c
+++ b/sound/core/seq/seq_ump_convert.c
@@ -841,7 +841,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
unsigned char index = event->data.control.param & 0x7f;
unsigned char val = event->data.control.value & 0x7f;
struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
- int ret;
+ int ret = 0;
/* process special CC's (bank/rpn/nrpn) */
switch (index) {
@@ -851,47 +851,54 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
cc->cc_rpn_msb = val;
if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
reset_rpn(cc);
- return ret;
+ break;
case UMP_CC_RPN_LSB:
ret = fill_rpn(cc, data, channel, true);
cc->rpn_set = 1;
cc->cc_rpn_lsb = val;
if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
reset_rpn(cc);
- return ret;
+ break;
case UMP_CC_NRPN_MSB:
ret = fill_rpn(cc, data, channel, true);
cc->nrpn_set = 1;
cc->cc_nrpn_msb = val;
- return ret;
+ break;
case UMP_CC_NRPN_LSB:
ret = fill_rpn(cc, data, channel, true);
cc->nrpn_set = 1;
cc->cc_nrpn_lsb = val;
- return ret;
+ break;
case UMP_CC_DATA:
cc->cc_data_msb_set = 1;
cc->cc_data_msb = val;
- return fill_rpn(cc, data, channel, false);
+ ret = fill_rpn(cc, data, channel, false);
+ break;
case UMP_CC_BANK_SELECT:
cc->bank_set = 1;
cc->cc_bank_msb = val;
- return 0; // skip
+ ret = 0; // skip
+ break;
case UMP_CC_BANK_SELECT_LSB:
cc->bank_set = 1;
cc->cc_bank_lsb = val;
- return 0; // skip
+ ret = 0; // skip
+ break;
case UMP_CC_DATA_LSB:
cc->cc_data_lsb_set = 1;
cc->cc_data_lsb = val;
- return fill_rpn(cc, data, channel, false);
+ ret = fill_rpn(cc, data, channel, false);
+ break;
+ default:
+ data->cc.status = status;
+ data->cc.channel = channel;
+ data->cc.index = index;
+ data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
+ ret = 1;
+ break;
}
- data->cc.status = status;
- data->cc.channel = channel;
- data->cc.index = index;
- data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
- return 1;
+ return ret;
}
/* convert one-parameter control event to MIDI 2.0 UMP */
diff --git a/sound/core/sound.c b/sound/core/sound.c
index 93436db24710..8d05fe0d263b 100644
--- a/sound/core/sound.c
+++ b/sound/core/sound.c
@@ -216,9 +216,16 @@ static int snd_find_free_minor(int type, struct snd_card *card, int dev)
case SNDRV_DEVICE_TYPE_RAWMIDI:
case SNDRV_DEVICE_TYPE_PCM_PLAYBACK:
case SNDRV_DEVICE_TYPE_PCM_CAPTURE:
+ if (snd_BUG_ON(!card))
+ return -EINVAL;
+ minor = SNDRV_MINOR(card->number, type + dev);
+ break;
case SNDRV_DEVICE_TYPE_COMPRESS:
if (snd_BUG_ON(!card))
return -EINVAL;
+ if (dev < 0 ||
+ dev >= SNDRV_MINOR_HWDEP - SNDRV_MINOR_COMPRESS)
+ return -EINVAL;
minor = SNDRV_MINOR(card->number, type + dev);
break;
default:
diff --git a/sound/core/timer.c b/sound/core/timer.c
index 6a70df7ae019..820901d503af 100644
--- a/sound/core/timer.c
+++ b/sound/core/timer.c
@@ -129,6 +129,9 @@ static LIST_HEAD(snd_timer_list);
/* list of slave instances */
static LIST_HEAD(snd_timer_slave_list);
+/* list of open master instances that can accept slave links */
+static LIST_HEAD(snd_timer_master_list);
+
/* lock for slave active lists */
static DEFINE_SPINLOCK(slave_active_lock);
@@ -161,6 +164,7 @@ struct snd_timer_instance *snd_timer_instance_new(const char *owner)
}
INIT_LIST_HEAD(&timeri->open_list);
INIT_LIST_HEAD(&timeri->active_list);
+ INIT_LIST_HEAD(&timeri->master_list);
INIT_LIST_HEAD(&timeri->ack_list);
INIT_LIST_HEAD(&timeri->slave_list_head);
INIT_LIST_HEAD(&timeri->slave_active_head);
@@ -245,6 +249,12 @@ static int check_matching_master_slave(struct snd_timer_instance *master,
return 1;
}
+static bool snd_timer_has_slave_key(const struct snd_timer_instance *timeri)
+{
+ return !(timeri->flags & SNDRV_TIMER_IFLG_SLAVE) &&
+ timeri->slave_class > SNDRV_TIMER_SCLASS_NONE;
+}
+
/*
* look for a master instance matching with the slave id of the given slave.
* when found, relink the open_link of the slave.
@@ -253,19 +263,15 @@ static int check_matching_master_slave(struct snd_timer_instance *master,
*/
static int snd_timer_check_slave(struct snd_timer_instance *slave)
{
- struct snd_timer *timer;
struct snd_timer_instance *master;
int err = 0;
- /* FIXME: it's really dumb to look up all entries.. */
- list_for_each_entry(timer, &snd_timer_list, device_list) {
- list_for_each_entry(master, &timer->open_list_head, open_list) {
- err = check_matching_master_slave(master, slave);
- if (err != 0) /* match found or error */
- goto out;
- }
+ list_for_each_entry(master, &snd_timer_master_list, master_list) {
+ err = check_matching_master_slave(master, slave);
+ if (err != 0) /* match found or error */
+ goto out;
}
- out:
+out:
return err < 0 ? err : 0;
}
@@ -377,6 +383,8 @@ int snd_timer_open(struct snd_timer_instance *timeri,
timeri->slave_id = slave_id;
list_add_tail(&timeri->open_list, &timer->open_list_head);
+ if (snd_timer_has_slave_key(timeri))
+ list_add_tail(&timeri->master_list, &snd_timer_master_list);
timer->num_instances++;
err = snd_timer_check_master(timeri);
list_added:
@@ -431,6 +439,9 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri,
num_slaves--;
}
+ if (!list_empty(&timeri->master_list))
+ list_del_init(&timeri->master_list);
+
/* force to stop the timer */
snd_timer_stop(timeri);
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
index 2b595ee0bc35..05550f36fac5 100644
--- a/sound/firewire/fireworks/fireworks_command.c
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -151,10 +151,13 @@ efw_transaction(struct snd_efw *efw, unsigned int category,
(be32_to_cpu(header->category) != category) ||
(be32_to_cpu(header->command) != command) ||
(be32_to_cpu(header->status) != EFR_STATUS_OK)) {
+ u32 st = be32_to_cpu(header->status);
+
dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n",
be32_to_cpu(header->category),
be32_to_cpu(header->command),
- efr_status_names[be32_to_cpu(header->status)]);
+ st < ARRAY_SIZE(efr_status_names) ?
+ efr_status_names[st] : "unknown");
err = -EIO;
goto end;
}
diff --git a/sound/hda/codecs/analog.c b/sound/hda/codecs/analog.c
index 11b1d30b23fd..1ba8ae54e25e 100644
--- a/sound/hda/codecs/analog.c
+++ b/sound/hda/codecs/analog.c
@@ -38,6 +38,8 @@ struct ad198x_spec {
unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */
int num_smux_conns;
+
+ unsigned int gpio_data;
};
@@ -934,9 +936,9 @@ static void ad1884_vmaster_hp_gpio_hook(void *private_data, int enabled)
if (spec->eapd_nid)
ad_vmaster_eapd_hook(private_data, enabled);
- snd_hda_codec_write_cache(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DATA,
- enabled ? 0x00 : 0x02);
+ spec->gpio_data = enabled ? 0x00 : 0x02;
+ snd_hda_codec_write(codec, 0x01, 0,
+ AC_VERB_SET_GPIO_DATA, spec->gpio_data);
}
static void ad1884_fixup_hp_eapd(struct hda_codec *codec,
@@ -948,12 +950,7 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec,
case HDA_FIXUP_ACT_PRE_PROBE:
spec->gen.vmaster_mute.hook = ad1884_vmaster_hp_gpio_hook;
spec->gen.own_eapd_ctl = 1;
- snd_hda_codec_write_cache(codec, 0x01, 0,
- AC_VERB_SET_GPIO_MASK, 0x02);
- snd_hda_codec_write_cache(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DIRECTION, 0x02);
- snd_hda_codec_write_cache(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DATA, 0x02);
+ spec->gpio_data = 0x02;
break;
case HDA_FIXUP_ACT_PROBE:
if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT)
@@ -961,6 +958,9 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec,
else
spec->eapd_nid = spec->gen.autocfg.speaker_pins[0];
break;
+ case HDA_FIXUP_ACT_INIT:
+ snd_hda_codec_set_gpio(codec, 0x02, 0x02, spec->gpio_data, 0);
+ break;
}
}
diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c
index a0677d7da8e2..ad533b04ab29 100644
--- a/sound/hda/codecs/ca0132.c
+++ b/sound/hda/codecs/ca0132.c
@@ -3755,22 +3755,12 @@ static void ca0132_gpio_setup(struct hda_codec *codec)
switch (ca0132_quirk(spec)) {
case QUIRK_SBZ:
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DIRECTION, 0x07);
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_MASK, 0x07);
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DATA, 0x04);
+ snd_hda_codec_set_gpio(codec, 0x07, 0x07, 0x04, 0);
snd_hda_codec_write(codec, 0x01, 0,
AC_VERB_SET_GPIO_DATA, 0x06);
break;
case QUIRK_R3DI:
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DIRECTION, 0x1E);
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_MASK, 0x1F);
- snd_hda_codec_write(codec, 0x01, 0,
- AC_VERB_SET_GPIO_DATA, 0x0C);
+ snd_hda_codec_set_gpio(codec, 0x1F, 0x1E, 0x0C, 0);
break;
default:
break;
diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c
index 52eda0a338ea..42559edbba05 100644
--- a/sound/hda/codecs/cirrus/cs420x.c
+++ b/sound/hda/codecs/cirrus/cs420x.c
@@ -264,14 +264,9 @@ static int cs_init(struct hda_codec *codec)
snd_hda_gen_init(codec);
- if (spec->gpio_mask) {
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
- spec->gpio_mask);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
- spec->gpio_dir);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_data);
- }
+ if (spec->gpio_mask)
+ snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir,
+ spec->gpio_data, 0);
if (spec->vendor_nid == CS420X_VENDOR_NID) {
init_input_coef(codec);
diff --git a/sound/hda/codecs/cirrus/cs421x.c b/sound/hda/codecs/cirrus/cs421x.c
index c8349a2c5a36..645b06599e5f 100644
--- a/sound/hda/codecs/cirrus/cs421x.c
+++ b/sound/hda/codecs/cirrus/cs421x.c
@@ -442,14 +442,9 @@ static int cs421x_init(struct hda_codec *codec)
snd_hda_gen_init(codec);
- if (spec->gpio_mask) {
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
- spec->gpio_mask);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
- spec->gpio_dir);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_data);
- }
+ if (spec->gpio_mask)
+ snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir,
+ spec->gpio_data, 0);
cs4210_spdif_automute(codec, NULL);
diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c
index fad705092777..c43ff3ef75b6 100644
--- a/sound/hda/codecs/cirrus/cs8409.c
+++ b/sound/hda/codecs/cirrus/cs8409.c
@@ -268,7 +268,7 @@ static int cs8409_i2c_bulk_read(struct sub_codec *scodec, struct cs8409_i2c_para
return 0;
error:
- codec_err(codec, "I2C Bulk Write Failed 0x%02x\n", scodec->addr);
+ codec_err(codec, "I2C Bulk Read Failed 0x%02x\n", scodec->addr);
return -EIO;
}
@@ -1042,14 +1042,9 @@ static void cs8409_cs42l42_hw_init(struct hda_codec *codec)
struct cs8409_spec *spec = codec->spec;
struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0];
- if (spec->gpio_mask) {
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK,
- spec->gpio_mask);
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION,
- spec->gpio_dir);
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_data);
- }
+ if (spec->gpio_mask)
+ snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir,
+ spec->gpio_data, 0);
for (; seq->nid; seq++)
cs8409_vendor_coef_set(codec, seq->cir, seq->coeff);
@@ -1442,14 +1437,9 @@ static void dolphin_hw_init(struct hda_codec *codec)
struct sub_codec *cs42l42;
int i;
- if (spec->gpio_mask) {
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK,
- spec->gpio_mask);
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION,
- spec->gpio_dir);
- snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_data);
- }
+ if (spec->gpio_mask)
+ snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir,
+ spec->gpio_data, 0);
for (; seq->nid; seq++)
cs8409_vendor_coef_set(codec, seq->cir, seq->coeff);
diff --git a/sound/hda/codecs/cmedia.c b/sound/hda/codecs/cmedia.c
index e6e12c01339f..88dd80d987d4 100644
--- a/sound/hda/codecs/cmedia.c
+++ b/sound/hda/codecs/cmedia.c
@@ -46,13 +46,6 @@ static int cmedia_probe(struct hda_codec *codec, const struct hda_device_id *id)
if (err < 0)
goto error;
- err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0);
- if (err < 0)
- goto error;
- err = snd_hda_gen_parse_auto_config(codec, cfg);
- if (err < 0)
- goto error;
-
if (is_cmi8888) {
if (get_defcfg_device(snd_hda_codec_get_pincfg(codec, 0x10)) ==
AC_JACK_HP_OUT) {
diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c
index aa726eb323eb..3a9717df39b4 100644
--- a/sound/hda/codecs/conexant.c
+++ b/sound/hda/codecs/conexant.c
@@ -154,14 +154,8 @@ static void cxt_init_gpio_led(struct hda_codec *codec)
struct conexant_spec *spec = codec->spec;
unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask;
- if (mask) {
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
- mask);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
- mask);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_led);
- }
+ if (mask)
+ snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0);
}
static void cx_fixup_headset_recog(struct hda_codec *codec)
@@ -775,9 +769,7 @@ static void cxt_setup_gpio_unmute(struct hda_codec *codec,
{
if (gpio_mute_mask) {
// set gpio data to 0.
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, gpio_mute_mask);
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, gpio_mute_mask);
+ snd_hda_codec_set_gpio(codec, gpio_mute_mask, gpio_mute_mask, 0, 0);
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_STICKY_MASK, 0);
}
}
diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c
index 092428ada29d..660a9f2c0ded 100644
--- a/sound/hda/codecs/generic.c
+++ b/sound/hda/codecs/generic.c
@@ -863,7 +863,7 @@ static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid)
{
if (nid) {
msleep(10);
- snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
+ snd_hda_codec_write_sync(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0);
}
}
diff --git a/sound/hda/codecs/hdmi/intelhdmi.c b/sound/hda/codecs/hdmi/intelhdmi.c
index 9460c8db39a9..6a7882544ab7 100644
--- a/sound/hda/codecs/hdmi/intelhdmi.c
+++ b/sound/hda/codecs/hdmi/intelhdmi.c
@@ -85,7 +85,7 @@ static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg,
}
}
- snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state);
+ snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state);
snd_hda_codec_set_power_to_all(codec, fg, power_state);
}
diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c
index 45f9d6487388..0a5eadec3ef6 100644
--- a/sound/hda/codecs/realtek/alc269.c
+++ b/sound/hda/codecs/realtek/alc269.c
@@ -1004,13 +1004,6 @@ static int alc269_resume(struct hda_codec *codec)
snd_hda_regmap_sync(codec);
hda_call_check_power_status(codec, 0x01);
- /* on some machine, the BIOS will clear the codec gpio data when enter
- * suspend, and won't restore the data after resume, so we restore it
- * in the driver.
- */
- if (spec->gpio_data)
- alc_write_gpio_data(codec);
-
if (spec->has_alc5505_dsp)
alc5505_dsp_resume(codec);
@@ -2296,9 +2289,9 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec,
struct alc_spec *spec = codec->spec;
spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
alc255_set_default_jack_type(codec);
- }
- else
+ } else {
alc_fixup_headset_mode(codec, fix, action);
+ }
}
static void alc288_update_headset_jack_cb(struct hda_codec *codec,
@@ -6639,10 +6632,10 @@ static const struct hda_fixup alc269_fixups[] = {
.type = HDA_FIXUP_FUNC,
.v.func = alc288_fixup_surface_swap_dacs,
},
- [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = {
- .type = HDA_FIXUP_FUNC,
- .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey,
- },
+ [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey,
+ },
[ALC245_FIXUP_BASS_HP_DAC] = {
.type = HDA_FIXUP_FUNC,
/* Borrow the DAC routing selected for those Thinkpads */
@@ -7198,6 +7191,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED),
SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x103c, 0x8e75, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e8a, "HP NexusX", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED),
SND_PCI_QUIRK(0x103c, 0x8e9c, "HP 16 Clipper OmniBook X X360", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8e9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2),
@@ -7219,8 +7213,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = {
SND_PCI_QUIRK(0x103c, 0x8ee4, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
SND_PCI_QUIRK(0x103c, 0x8ee5, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
SND_PCI_QUIRK(0x103c, 0x8ee7, "HP Abe A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO),
+ SND_PCI_QUIRK(0x103c, 0x8f07, "HP Agusta G7KX", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8f0c, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED),
SND_PCI_QUIRK(0x103c, 0x8f0e, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED),
+ SND_PCI_QUIRK(0x103c, 0x8f2d, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2),
+ SND_PCI_QUIRK(0x103c, 0x8f2e, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2),
SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED),
SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED),
SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED),
@@ -7365,6 +7362,10 @@ 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, 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),
+ SND_PCI_QUIRK(0x1043, 0x3711, "ASUS P5606CCA", ALC245_FIXUP_CS35L41_SPI_2),
SND_PCI_QUIRK(0x1043, 0x3a20, "ASUS G614JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS),
SND_PCI_QUIRK(0x1043, 0x3a30, "ASUS G814JVR/JIR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS),
SND_PCI_QUIRK(0x1043, 0x3a40, "ASUS G814JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS),
diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c
index 5073165d1f3c..2cf3664a35ff 100644
--- a/sound/hda/codecs/realtek/alc662.c
+++ b/sound/hda/codecs/realtek/alc662.c
@@ -255,6 +255,17 @@ static void alc_fixup_headset_mode_alc668(struct hda_codec *codec,
alc_fixup_headset_mode(codec, fix, action);
}
+static void alc662_fixup_csl_amp(struct hda_codec *codec,
+ const struct hda_fixup *fix, int action)
+{
+ if (action == HDA_FIXUP_ACT_INIT) {
+ /* need to toggle GPIO to enable the amp */
+ snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x03, 0);
+ msleep(100);
+ snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x00, 0);
+ }
+}
+
enum {
ALC662_FIXUP_ASPIRE,
ALC662_FIXUP_LED_GPIO1,
@@ -313,6 +324,7 @@ enum {
ALC897_FIXUP_HEADSET_MIC_PIN2,
ALC897_FIXUP_UNIS_H3C_X500S,
ALC897_FIXUP_HEADSET_MIC_PIN3,
+ ALC662_FIXUP_CSL_GPIO,
};
static const struct hda_fixup alc662_fixups[] = {
@@ -766,11 +778,16 @@ static const struct hda_fixup alc662_fixups[] = {
{ }
},
},
+ [ALC662_FIXUP_CSL_GPIO] = {
+ .type = HDA_FIXUP_FUNC,
+ .v.func = alc662_fixup_csl_amp,
+ },
};
static const struct hda_quirk alc662_fixup_tbl[] = {
SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2),
SND_PCI_QUIRK(0x1019, 0x9859, "JP-IK LEAP W502", ALC897_FIXUP_HEADSET_MIC_PIN3),
+ SND_PCI_QUIRK(0x1022, 0xc950, "CSL Unity BF24B", ALC662_FIXUP_CSL_GPIO),
SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC),
SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC),
SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE),
diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c
index aad265c0a5b6..db365a746b1a 100644
--- a/sound/hda/codecs/realtek/realtek.c
+++ b/sound/hda/codecs/realtek/realtek.c
@@ -99,15 +99,6 @@ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask)
}
EXPORT_SYMBOL_NS_GPL(alc_setup_gpio, "SND_HDA_CODEC_REALTEK");
-void alc_write_gpio_data(struct hda_codec *codec)
-{
- struct alc_spec *spec = codec->spec;
-
- snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_data);
-}
-EXPORT_SYMBOL_NS_GPL(alc_write_gpio_data, "SND_HDA_CODEC_REALTEK");
-
void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask,
bool on)
{
@@ -119,26 +110,22 @@ void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask,
else
spec->gpio_data &= ~mask;
if (oldval != spec->gpio_data)
- alc_write_gpio_data(codec);
+ snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
+ spec->gpio_data);
}
EXPORT_SYMBOL_NS_GPL(alc_update_gpio_data, "SND_HDA_CODEC_REALTEK");
-void alc_write_gpio(struct hda_codec *codec)
+static void alc_write_gpio(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
if (!spec->gpio_mask)
return;
- snd_hda_codec_write(codec, codec->core.afg, 0,
- AC_VERB_SET_GPIO_MASK, spec->gpio_mask);
- snd_hda_codec_write(codec, codec->core.afg, 0,
- AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir);
- if (spec->gpio_write_delay)
- msleep(1);
- alc_write_gpio_data(codec);
+ snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir,
+ spec->gpio_data,
+ spec->gpio_write_delay ? 1 : 0);
}
-EXPORT_SYMBOL_NS_GPL(alc_write_gpio, "SND_HDA_CODEC_REALTEK");
void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask)
{
@@ -411,9 +398,8 @@ void alc_headset_mic_no_shutup(struct hda_codec *codec)
return;
snd_array_for_each(&codec->init_pins, i, pin) {
- /* use read here for syncing after issuing each verb */
if (pin->nid != mic_pin)
- snd_hda_codec_read(codec, pin->nid, 0,
+ snd_hda_codec_write_sync(codec, pin->nid, 0,
AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
}
@@ -2164,8 +2150,7 @@ void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec,
if (action == HDA_FIXUP_ACT_PRE_PROBE) {
struct alc_spec *spec = codec->spec;
spec->parse_flags |= HDA_PINCFG_HEADSET_MIC;
- }
- else
+ } else
alc_fixup_headset_mode(codec, fix, action);
}
EXPORT_SYMBOL_NS_GPL(alc_fixup_headset_mode_no_hp_mic, "SND_HDA_CODEC_REALTEK");
diff --git a/sound/hda/codecs/realtek/realtek.h b/sound/hda/codecs/realtek/realtek.h
index b2a919904c4c..de95642bb648 100644
--- a/sound/hda/codecs/realtek/realtek.h
+++ b/sound/hda/codecs/realtek/realtek.h
@@ -168,10 +168,8 @@ void alc_process_coef_fw(struct hda_codec *codec, const struct coef_fw *fw);
* GPIO helpers
*/
void alc_setup_gpio(struct hda_codec *codec, unsigned int mask);
-void alc_write_gpio_data(struct hda_codec *codec);
void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask,
bool on);
-void alc_write_gpio(struct hda_codec *codec);
/* common GPIO fixups */
void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask);
diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c
index 6239a25bb8f3..3ee8bc0ea3ab 100644
--- a/sound/hda/codecs/senarytech.c
+++ b/sound/hda/codecs/senarytech.c
@@ -36,6 +36,32 @@ struct senary_spec {
unsigned int gpio_mic_led_mask;
};
+enum {
+ SENARY_FIXUP_PINCFG_DEFAULT,
+};
+
+static const struct hda_pintbl senary_pincfg_default[] = {
+ { 0x16, 0x02211020 }, /* Headphone */
+ { 0x17, 0x40f001f0 }, /* Not used */
+ { 0x18, 0x05a1904d }, /* Mic */
+ { 0x19, 0x02a1104e }, /* Headset Mic */
+ { 0x1a, 0x01819030 }, /* Line-in */
+ { 0x1d, 0x01014010 }, /* Line-out */
+ {}
+};
+
+static const struct hda_fixup senary_fixups[] = {
+ [SENARY_FIXUP_PINCFG_DEFAULT] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = senary_pincfg_default,
+ },
+};
+
+/* Quirk table for specific machines can be added here */
+static const struct hda_quirk sn6186_fixups[] = {
+ {}
+};
+
#ifdef CONFIG_SND_HDA_INPUT_BEEP
/* additional beep mixers; private_value will be overwritten */
static const struct snd_kcontrol_new senary_beep_mixer[] = {
@@ -50,7 +76,6 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid,
unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir);
int i;
- spec->gen.beep_nid = nid;
for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) {
knew = snd_hda_gen_add_kctl(&spec->gen, NULL,
&senary_beep_mixer[i]);
@@ -58,6 +83,8 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid,
return -ENOMEM;
knew->private_value = beep_amp;
}
+
+ spec->gen.beep_nid = nid;
return 0;
}
@@ -93,16 +120,28 @@ static void senary_auto_parse_eapd(struct hda_codec *codec)
}
}
+/* Hardware specific initialization verbs */
+static void senary_init_verb(struct hda_codec *codec)
+{
+ /* Vendor specific init sequence */
+ snd_hda_codec_write(codec, 0x1b, 0x0, 0x05a, 0xaa);
+ snd_hda_codec_write(codec, 0x1b, 0x0, 0x059, 0x48);
+ snd_hda_codec_write(codec, 0x1b, 0x0, 0x01b, 0x00);
+ snd_hda_codec_write(codec, 0x1b, 0x0, 0x01c, 0x00);
+
+ /* Override pin caps for headset mic */
+ snd_hda_override_pin_caps(codec, 0x19, 0x2124);
+}
+
static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins,
const hda_nid_t *pins, bool on)
{
int i;
for (i = 0; i < num_pins; i++) {
- if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
- snd_hda_codec_write(codec, pins[i], 0,
- AC_VERB_SET_EAPD_BTLENABLE,
- on ? 0x02 : 0);
+ snd_hda_codec_write(codec, pins[i], 0,
+ AC_VERB_SET_EAPD_BTLENABLE,
+ on ? 0x02 : 0);
}
}
@@ -120,14 +159,8 @@ static void senary_init_gpio_led(struct hda_codec *codec)
struct senary_spec *spec = codec->spec;
unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask;
- if (mask) {
- snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_MASK,
- mask);
- snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DIRECTION,
- mask);
- snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA,
- spec->gpio_led);
- }
+ if (mask)
+ snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0);
}
static int senary_init(struct hda_codec *codec)
@@ -136,6 +169,7 @@ static int senary_init(struct hda_codec *codec)
snd_hda_gen_init(codec);
senary_init_gpio_led(codec);
+ senary_init_verb(codec);
if (!spec->dynamic_eapd)
senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true);
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT);
@@ -181,11 +215,30 @@ static int senary_probe(struct hda_codec *codec, const struct hda_device_id *id)
senary_auto_parse_eapd(codec);
spec->gen.own_eapd_ctl = 1;
- if (!spec->gen.vmaster_mute.hook)
- spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook;
+ /* Setup fixups based on codec vendor ID */
+ switch (codec->core.vendor_id) {
+ case 0x1fa86186:
+ codec->pin_amp_workaround = 1;
+ spec->gen.mixer_nid = 0x15;
+ snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups);
+
+ /* If no specific quirk found, apply the default pin configuration */
+ if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET)
+ codec->fixup_id = SENARY_FIXUP_PINCFG_DEFAULT;
+ break;
+ default:
+ snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups);
+ break;
+ }
snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+ /* Run hardware init verbs once during probe */
+ senary_init_verb(codec);
+
+ if (!spec->gen.vmaster_mute.hook)
+ spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook;
+
err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL,
spec->parse_flags);
if (err < 0)
diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
index 16d5ea77192f..732ae534db36 100644
--- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
+++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c
@@ -55,6 +55,11 @@ static const struct cs35l41_config cs35l41_config_table[] = {
{ "103C8A30", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8A31", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8A6E", 4, EXTERNAL, { CS35L41_LEFT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_RIGHT }, 0, -1, -1, 0, 0, 0 },
+/*
+ * Device 103C8B63 has _DSD with valid reset-gpios and cs-gpios, however the
+ * boost type is incorrectly set to Internal. Override to External Boost.
+ */
+ { "103C8B63", 4, EXTERNAL, { CS35L41_RIGHT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_LEFT }, -1, -1, -1, 0, 0, 0 },
{ "103C8BB3", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8BB4", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
{ "103C8BDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 },
@@ -475,6 +480,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = {
{ "CSC3551", "103C8A30", generic_dsd_config },
{ "CSC3551", "103C8A31", generic_dsd_config },
{ "CSC3551", "103C8A6E", generic_dsd_config },
+ { "CSC3551", "103C8B63", generic_dsd_config },
{ "CSC3551", "103C8BB3", generic_dsd_config },
{ "CSC3551", "103C8BB4", generic_dsd_config },
{ "CSC3551", "103C8BDD", generic_dsd_config },
diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c
index acbbc7c3508b..ee3bd21adc36 100644
--- a/sound/hda/codecs/sigmatel.c
+++ b/sound/hda/codecs/sigmatel.c
@@ -309,15 +309,7 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask,
/* Configure GPIOx as CMOS */
snd_hda_codec_write(codec, fg, 0, 0x7e7, 0);
- snd_hda_codec_write(codec, fg, 0,
- AC_VERB_SET_GPIO_MASK, gpiomask);
- snd_hda_codec_read(codec, fg, 0,
- AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */
-
- msleep(1);
-
- snd_hda_codec_read(codec, fg, 0,
- AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */
+ snd_hda_codec_set_gpio(codec, gpiomask, gpiodir, gpiostate, 1);
}
/* hook for controlling mic-mute LED GPIO */
diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c
index 09b1329bb8f3..c2af2511a831 100644
--- a/sound/hda/common/codec.c
+++ b/sound/hda/common/codec.c
@@ -606,9 +606,8 @@ void snd_hda_shutup_pins(struct hda_codec *codec)
if (codec->bus->shutdown)
return;
snd_array_for_each(&codec->init_pins, i, pin) {
- /* use read here for syncing after issuing each verb */
- snd_hda_codec_read(codec, pin->nid, 0,
- AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+ snd_hda_codec_write_sync(codec, pin->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
}
codec->pins_shutup = 1;
}
@@ -2529,7 +2528,10 @@ EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign);
static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_multi_out *mout = (void *)kcontrol->private_value;
+
+ guard(mutex)(&codec->spdif_mutex);
ucontrol->value.integer.value[0] = mout->share_spdif;
return 0;
}
@@ -2537,9 +2539,15 @@ static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
- struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
- mout->share_spdif = !!ucontrol->value.integer.value[0];
- return 0;
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_multi_out *mout = (void *)kcontrol->private_value;
+ bool val = !!ucontrol->value.integer.value[0];
+ int change;
+
+ guard(mutex)(&codec->spdif_mutex);
+ change = mout->share_spdif != val;
+ mout->share_spdif = val;
+ return change;
}
static const struct snd_kcontrol_new spdif_share_sw = {
@@ -2550,6 +2558,14 @@ static const struct snd_kcontrol_new spdif_share_sw = {
.put = spdif_share_sw_put,
};
+static void notify_spdif_share_sw(struct hda_codec *codec,
+ struct hda_multi_out *mout)
+{
+ if (mout->share_spdif_kctl)
+ snd_ctl_notify_one(codec->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ mout->share_spdif_kctl, 0);
+}
+
/**
* snd_hda_create_spdif_share_sw - create Default PCM switch
* @codec: the HDA codec
@@ -2559,15 +2575,24 @@ int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
struct hda_multi_out *mout)
{
struct snd_kcontrol *kctl;
+ int err;
if (!mout->dig_out_nid)
return 0;
- kctl = snd_ctl_new1(&spdif_share_sw, mout);
+ kctl = snd_ctl_new1(&spdif_share_sw, codec);
if (!kctl)
return -ENOMEM;
- /* ATTENTION: here mout is passed as private_data, instead of codec */
- return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+ /* snd_ctl_new1() stores @codec in private_data; stash @mout in
+ * private_value for the share-switch callbacks and cache the
+ * assigned control for forced-disable notifications.
+ */
+ kctl->private_value = (unsigned long)mout;
+ err = snd_hda_ctl_add(codec, mout->dig_out_nid, kctl);
+ if (err < 0)
+ return err;
+ mout->share_spdif_kctl = kctl;
+ return 0;
}
EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw);
@@ -2768,9 +2793,9 @@ static unsigned int hda_set_power_state(struct hda_codec *codec,
if (codec->power_filter)
state = codec->power_filter(codec, fg, state);
if (state == power_state || power_state != AC_PWRST_D3)
- snd_hda_codec_read(codec, fg, flags,
- AC_VERB_SET_POWER_STATE,
- state);
+ snd_hda_codec_write_sync(codec, fg, flags,
+ AC_VERB_SET_POWER_STATE,
+ state);
snd_hda_codec_set_power_to_all(codec, fg, power_state);
}
state = snd_hda_sync_power_state(codec, fg, power_state);
@@ -3701,6 +3726,8 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec,
struct hda_pcm_stream *hinfo)
{
struct snd_pcm_runtime *runtime = substream->runtime;
+ bool notify_share_sw = false;
+
runtime->hw.channels_max = mout->max_channels;
if (mout->dig_out_nid) {
if (!mout->analog_rates) {
@@ -3729,10 +3756,12 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec,
hinfo->maxbps = mout->spdif_maxbps;
} else {
mout->share_spdif = 0;
- /* FIXME: need notify? */
+ notify_share_sw = true;
}
}
}
+ if (notify_share_sw)
+ notify_spdif_share_sw(codec, mout);
return snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS, 2);
}
@@ -4024,6 +4053,35 @@ void snd_hda_bus_reset_codecs(struct hda_bus *bus)
}
/**
+ * snd_hda_codec_set_gpio - Set up GPIO bits for AFG
+ * @codec: the HDA codec
+ * @mask: GPIO bitmask
+ * @dir: GPIO direction bits
+ * @data: GPIO data bits
+ * @delay: the delay in msec before writing GPIO data bits
+ */
+void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask,
+ unsigned int dir, unsigned int data,
+ unsigned int delay)
+{
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_MASK, mask);
+ if (delay) {
+ snd_hda_codec_write_sync(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DIRECTION, dir);
+ msleep(delay);
+ snd_hda_codec_write_sync(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, data);
+ } else {
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DIRECTION, dir);
+ snd_hda_codec_write(codec, codec->core.afg, 0,
+ AC_VERB_SET_GPIO_DATA, data);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_hda_codec_set_gpio);
+
+/**
* snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer
* @pcm: PCM caps bits
* @buf: the string buffer to write
diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h
index ab423f1cef54..98b2c4acebc2 100644
--- a/sound/hda/common/hda_local.h
+++ b/sound/hda/common/hda_local.h
@@ -221,6 +221,7 @@ struct hda_multi_out {
unsigned int spdif_rates;
unsigned int spdif_maxbps;
u64 spdif_formats;
+ struct snd_kcontrol *share_spdif_kctl; /* cached shared SPDIF switch */
};
int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c
index 3bc33c5617b2..c83796b13d3d 100644
--- a/sound/hda/common/proc.c
+++ b/sound/hda/common/proc.c
@@ -640,41 +640,78 @@ static void print_gpio(struct snd_info_buffer *buffer,
{
unsigned int gpio =
param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP);
- unsigned int enable, direction, wake, unsol, sticky, data;
- int i, max;
+ int i, gpio_max, gpo_max, gpi_max;
+
+ gpio_max = gpio & AC_GPIO_IO_COUNT;
+ gpo_max = (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT;
+ gpi_max = (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT;
+
snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, "
"unsolicited=%d, wake=%d\n",
- gpio & AC_GPIO_IO_COUNT,
- (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT,
- (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT,
+ gpio_max, gpo_max, gpi_max,
(gpio & AC_GPIO_UNSOLICITED) ? 1 : 0,
(gpio & AC_GPIO_WAKE) ? 1 : 0);
- max = gpio & AC_GPIO_IO_COUNT;
- if (!max || max > 8)
- return;
- enable = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_MASK, 0);
- direction = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_DIRECTION, 0);
- wake = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_WAKE_MASK, 0);
- unsol = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0);
- sticky = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_STICKY_MASK, 0);
- data = snd_hda_codec_read(codec, nid, 0,
- AC_VERB_GET_GPIO_DATA, 0);
- for (i = 0; i < max; ++i)
- snd_iprintf(buffer,
- " IO[%d]: enable=%d, dir=%d, wake=%d, "
- "sticky=%d, data=%d, unsol=%d\n", i,
- (enable & (1<<i)) ? 1 : 0,
- (direction & (1<<i)) ? 1 : 0,
- (wake & (1<<i)) ? 1 : 0,
- (sticky & (1<<i)) ? 1 : 0,
- (data & (1<<i)) ? 1 : 0,
- (unsol & (1<<i)) ? 1 : 0);
- /* FIXME: add GPO and GPI pin information */
+
+ if (gpio_max && gpio_max <= 8) {
+ unsigned int enable, direction, wake, unsol, sticky, data;
+
+ enable = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_MASK, 0);
+ direction = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DIRECTION, 0);
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK,
+ 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPIO_DATA, 0);
+ for (i = 0; i < gpio_max; ++i) {
+ snd_iprintf(buffer,
+ " IO[%d]: enable=%d, dir=%d, wake=%d, ",
+ i, (enable & (1 << i)) ? 1 : 0,
+ (direction & (1 << i)) ? 1 : 0,
+ (wake & (1 << i)) ? 1 : 0);
+ snd_iprintf(buffer,
+ "sticky=%d, data=%d, unsol=%d\n",
+ (sticky & (1 << i)) ? 1 : 0,
+ (data & (1 << i)) ? 1 : 0,
+ (unsol & (1 << i)) ? 1 : 0);
+ }
+ }
+
+ if (gpo_max && gpo_max <= 8) {
+ unsigned int gpo_data;
+
+ gpo_data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPO_DATA, 0);
+ for (i = 0; i < gpo_max; ++i)
+ snd_iprintf(buffer, " GPO[%d]: data=%d\n", i,
+ (gpo_data & (1 << i)) ? 1 : 0);
+ }
+
+ if (gpi_max && gpi_max <= 8) {
+ unsigned int wake, unsol, sticky, data;
+
+ wake = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_WAKE_MASK, 0);
+ unsol = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK,
+ 0);
+ sticky = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_STICKY_MASK, 0);
+ data = snd_hda_codec_read(codec, nid, 0,
+ AC_VERB_GET_GPI_DATA, 0);
+ for (i = 0; i < gpi_max; ++i)
+ snd_iprintf(buffer, " GPI[%d]: wake=%d, sticky=%d, data=%d, unsol=%d\n",
+ i, (wake & (1 << i)) ? 1 : 0,
+ (sticky & (1 << i)) ? 1 : 0,
+ (data & (1 << i)) ? 1 : 0,
+ (unsol & (1 << i)) ? 1 : 0);
+ }
+
print_nid_array(buffer, codec, nid, &codec->mixers);
print_nid_array(buffer, codec, nid, &codec->nids);
}
@@ -940,4 +977,3 @@ int snd_hda_codec_proc_new(struct hda_codec *codec)
snprintf(name, sizeof(name), "codec#%d", codec->core.addr);
return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info);
}
-
diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c
index a4104376523a..257c498c3260 100644
--- a/sound/hda/controllers/intel.c
+++ b/sound/hda/controllers/intel.c
@@ -2436,20 +2436,7 @@ static void azx_remove(struct pci_dev *pci)
/* cancel the pending probing work */
chip = card->private_data;
hda = container_of(chip, struct hda_intel, chip);
- /* FIXME: below is an ugly workaround.
- * Both device_release_driver() and driver_probe_device()
- * take *both* the device's and its parent's lock before
- * calling the remove() and probe() callbacks. The codec
- * probe takes the locks of both the codec itself and its
- * parent, i.e. the PCI controller dev. Meanwhile, when
- * the PCI controller is unbound, it takes its lock, too
- * ==> ouch, a deadlock!
- * As a workaround, we unlock temporarily here the controller
- * device during cancel_work_sync() call.
- */
- device_unlock(&pci->dev);
cancel_delayed_work_sync(&hda->probe_work);
- device_lock(&pci->dev);
clear_bit(chip->dev_index, probed_devs);
pci_set_drvdata(pci, NULL);
diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c
index b24c80410d45..9dd36b82a6ac 100644
--- a/sound/i2c/other/ak4xxx-adda.c
+++ b/sound/i2c/other/ak4xxx-adda.c
@@ -53,6 +53,31 @@ static void ak4524_reset(struct snd_akm4xxx *ak, int state)
}
}
+/* reset procedure for AK4529 */
+static void ak4529_reset(struct snd_akm4xxx *ak, int state)
+{
+ static const unsigned char regs[] = {
+ 0x0a, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x0b, 0x0c, 0x08,
+ };
+ unsigned int i;
+ unsigned char reg;
+
+ if (state) {
+ snd_akm4xxx_write(ak, 0, 0x09,
+ snd_akm4xxx_get(ak, 0, 0x09) & ~0x01);
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(regs); i++) {
+ reg = regs[i];
+ snd_akm4xxx_write(ak, 0, reg,
+ snd_akm4xxx_get(ak, 0, reg));
+ }
+ snd_akm4xxx_write(ak, 0, 0x09,
+ snd_akm4xxx_get(ak, 0, 0x09) | 0x01);
+}
+
/* reset procedure for AK4355 and AK4358 */
static void ak435X_reset(struct snd_akm4xxx *ak, int state)
{
@@ -99,7 +124,7 @@ void snd_akm4xxx_reset(struct snd_akm4xxx *ak, int state)
ak4524_reset(ak, state);
break;
case SND_AK4529:
- /* FIXME: needed for ak4529? */
+ ak4529_reset(ak, state);
break;
case SND_AK4355:
ak435X_reset(ak, state);
@@ -256,6 +281,9 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak)
0x07, 0x00, /* 7: ROUT muted */
0xff, 0xff
};
+ static const unsigned char ak5365_defaults[] = {
+ 0x01, 0x00, 0x00, 0x2b, 0x7f, 0x7f, 0x28, 0x89,
+ };
int chip;
const unsigned char *ptr, *inits;
@@ -302,10 +330,12 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak)
ak->total_regs = 0x05;
break;
case SND_AK5365:
- /* FIXME: any init sequence? */
ak->num_chips = 1;
ak->name = "ak5365";
ak->total_regs = 0x08;
+ memcpy(ak->images, ak5365_defaults, sizeof(ak5365_defaults));
+ snd_akm4xxx_set_vol(ak, 0, 0x04, 127);
+ snd_akm4xxx_set_vol(ak, 0, 0x05, 127);
return;
case SND_AK4620:
inits = inits_ak4620;
diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c
index e8c50a036bea..39c5e87c6ab0 100644
--- a/sound/i2c/tea6330t.c
+++ b/sound/i2c/tea6330t.c
@@ -51,6 +51,7 @@ int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer)
snd_i2c_unlock(bus);
return res;
}
+EXPORT_SYMBOL(snd_tea6330t_detect);
#if 0
static void snd_tea6330t_set(struct tea6330t *tea,
@@ -355,6 +356,42 @@ int snd_tea6330t_update_mixer(struct snd_card *card,
snd_i2c_device_free(device);
return err;
}
-
-EXPORT_SYMBOL(snd_tea6330t_detect);
EXPORT_SYMBOL(snd_tea6330t_update_mixer);
+
+int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus)
+{
+ struct snd_i2c_device *device;
+ struct tea6330t *tea;
+ unsigned char bytes[7];
+ unsigned int idx;
+ int err;
+
+ if (!bus)
+ return -EINVAL;
+
+ snd_i2c_lock(bus);
+ list_for_each_entry(device, &bus->devices, list) {
+ if (device->addr != TEA6330T_ADDR)
+ continue;
+
+ tea = device->private_data;
+ if (!tea) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ bytes[0] = TEA6330T_SADDR_VOLUME_LEFT;
+ for (idx = 0; idx < 6; idx++)
+ bytes[idx + 1] = tea->regs[idx];
+ err = snd_i2c_sendbytes(device, bytes, 7);
+ err = err < 0 ? err : 0;
+ goto unlock;
+ }
+
+ err = -ENODEV;
+
+unlock:
+ snd_i2c_unlock(bus);
+ return err;
+}
+EXPORT_SYMBOL(snd_tea6330t_restore_mixer);
diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c
index 6a95dfb7600a..7255b34f9148 100644
--- a/sound/isa/es1688/es1688.c
+++ b/sound/isa/es1688/es1688.c
@@ -184,12 +184,44 @@ static int snd_es1688_isa_probe(struct device *dev, unsigned int n)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_es1688_card_suspend(struct snd_card *card)
+{
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+
+static int snd_es1688_card_resume(struct snd_card *card)
+{
+ struct snd_es1688 *chip = card->private_data;
+ int err;
+
+ err = snd_es1688_reset(chip);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_es1688_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_es1688_card_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_es1688_isa_resume(struct device *dev, unsigned int n)
+{
+ return snd_es1688_card_resume(dev_get_drvdata(dev));
+}
+#endif
+
static struct isa_driver snd_es1688_driver = {
.match = snd_es1688_match,
.probe = snd_es1688_isa_probe,
-#if 0 /* FIXME */
- .suspend = snd_es1688_suspend,
- .resume = snd_es1688_resume,
+#ifdef CONFIG_PM
+ .suspend = snd_es1688_isa_suspend,
+ .resume = snd_es1688_isa_resume,
#endif
.driver = {
.name = DEV_NAME
@@ -266,20 +298,12 @@ static void snd_es968_pnp_remove(struct pnp_card_link *pcard)
static int snd_es968_pnp_suspend(struct pnp_card_link *pcard,
pm_message_t state)
{
- struct snd_card *card = pnp_get_card_drvdata(pcard);
-
- snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
- return 0;
+ return snd_es1688_card_suspend(pnp_get_card_drvdata(pcard));
}
static int snd_es968_pnp_resume(struct pnp_card_link *pcard)
{
- struct snd_card *card = pnp_get_card_drvdata(pcard);
- struct snd_es1688 *chip = card->private_data;
-
- snd_es1688_reset(chip);
- snd_power_change_state(card, SNDRV_CTL_POWER_D0);
- return 0;
+ return snd_es1688_card_resume(pnp_get_card_drvdata(pcard));
}
#endif
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
index ffc69e26227e..30bd76eee96e 100644
--- a/sound/isa/gus/gus_dma.c
+++ b/sound/isa/gus/gus_dma.c
@@ -173,6 +173,39 @@ int snd_gf1_dma_done(struct snd_gus_card * gus)
return 0;
}
+void snd_gf1_dma_suspend(struct snd_gus_card *gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ guard(mutex)(&gus->dma_mutex);
+ if (!gus->gf1.dma_shared)
+ return;
+
+ snd_dma_disable(gus->gf1.dma1);
+ snd_gf1_dma_ack(gus);
+ if (gus->gf1.dma_ack)
+ gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+ gus->gf1.dma_ack = NULL;
+ gus->gf1.dma_private_data = NULL;
+
+ while ((block = gus->gf1.dma_data_pcm)) {
+ gus->gf1.dma_data_pcm = block->next;
+ if (block->ack)
+ block->ack(gus, block->private_data);
+ kfree(block);
+ }
+ while ((block = gus->gf1.dma_data_synth)) {
+ gus->gf1.dma_data_synth = block->next;
+ if (block->ack)
+ block->ack(gus, block->private_data);
+ kfree(block);
+ }
+
+ gus->gf1.dma_data_pcm_last = NULL;
+ gus->gf1.dma_data_synth_last = NULL;
+ gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+}
+
int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
struct snd_gf1_dma_block * __block,
int atomic,
diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c
index b2b189c83569..6adf8b698e2b 100644
--- a/sound/isa/gus/gus_main.c
+++ b/sound/isa/gus/gus_main.c
@@ -404,6 +404,42 @@ int snd_gus_initialize(struct snd_gus_card *gus)
return 0;
}
+int snd_gus_suspend(struct snd_gus_card *gus)
+{
+ int err;
+
+ if (gus->pcm) {
+ err = snd_pcm_suspend_all(gus->pcm);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_gf1_suspend(gus);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(gus->card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+EXPORT_SYMBOL(snd_gus_suspend);
+
+int snd_gus_resume(struct snd_gus_card *gus)
+{
+ int err;
+
+ err = snd_gus_init_dma_irq(gus, 1);
+ if (err < 0)
+ return err;
+
+ err = snd_gf1_resume(gus);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(gus->card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+EXPORT_SYMBOL(snd_gus_resume);
+
/* gus_io.c */
EXPORT_SYMBOL(snd_gf1_delay);
EXPORT_SYMBOL(snd_gf1_write8);
diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c
index caf371897b78..a0757e1ede46 100644
--- a/sound/isa/gus/gus_pcm.c
+++ b/sound/isa/gus/gus_pcm.c
@@ -471,7 +471,8 @@ static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream,
if (cmd == SNDRV_PCM_TRIGGER_START) {
snd_gf1_pcm_trigger_up(substream);
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+ cmd == SNDRV_PCM_TRIGGER_SUSPEND) {
scoped_guard(spinlock, &pcmp->lock) {
pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE;
}
@@ -558,7 +559,8 @@ static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream,
if (cmd == SNDRV_PCM_TRIGGER_START) {
val = gus->gf1.pcm_rcntrl_reg;
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+ cmd == SNDRV_PCM_TRIGGER_SUSPEND) {
val = 0;
} else {
return -EINVAL;
@@ -856,4 +858,3 @@ int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index)
return 0;
}
-
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
index a7a3e764bb77..998fa245708c 100644
--- a/sound/isa/gus/gus_reset.c
+++ b/sound/isa/gus/gus_reset.c
@@ -6,6 +6,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/time.h>
+#include <asm/dma.h>
#include <sound/core.h>
#include <sound/gus.h>
@@ -263,11 +264,18 @@ void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice)
private_free(voice);
}
-/*
- * call this function only by start of driver
- */
+static void snd_gf1_init_software_state(struct snd_gus_card *gus)
+{
+ unsigned int i;
-int snd_gf1_start(struct snd_gus_card * gus)
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
+ for (i = 0; i < 32; i++) {
+ gus->gf1.voices[i].number = i;
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+ }
+}
+
+static void snd_gf1_hw_start(struct snd_gus_card *gus, bool initial)
{
unsigned int i;
@@ -277,14 +285,14 @@ int snd_gf1_start(struct snd_gus_card * gus)
udelay(160);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
- snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
- for (i = 0; i < 32; i++) {
- gus->gf1.voices[i].number = i;
- snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+ if (initial) {
+ snd_gf1_init_software_state(gus);
+ snd_gf1_uart_cmd(gus, 0x03);
+ } else {
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ outb(0x03, GUSP(gus, MIDICTRL));
}
- snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */
-
if (gus->gf1.enh_mode) { /* enhanced mode !!!! */
snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
@@ -293,6 +301,8 @@ int snd_gf1_start(struct snd_gus_card * gus)
snd_gf1_select_active_voices(gus);
snd_gf1_delay(gus);
gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8;
+ gus->gf1.hw_lfo = 0;
+ gus->gf1.sw_lfo = 0;
/* initialize LFOs & clear LFOs memory */
if (gus->gf1.enh_mode && gus->gf1.memory) {
gus->gf1.hw_lfo = 1;
@@ -321,7 +331,15 @@ int snd_gf1_start(struct snd_gus_card * gus)
outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
}
+}
+int snd_gf1_start(struct snd_gus_card *gus)
+{
+ /*
+ * Probe-time startup initializes both GF1 hardware and the
+ * software state that suspend/resume keeps across PM cycles.
+ */
+ snd_gf1_hw_start(gus, true);
snd_gf1_timers_init(gus);
snd_gf1_look_regs(gus);
snd_gf1_mem_init(gus);
@@ -357,3 +375,27 @@ int snd_gf1_stop(struct snd_gus_card * gus)
return 0;
}
+
+int snd_gf1_suspend(struct snd_gus_card *gus)
+{
+ snd_gf1_dma_suspend(gus);
+ snd_gf1_uart_suspend(gus);
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);
+ snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);
+ snd_gf1_stop_voices(gus, 0, 31);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);
+ snd_dma_disable(gus->gf1.dma2);
+
+ return 0;
+}
+
+int snd_gf1_resume(struct snd_gus_card *gus)
+{
+ snd_gf1_hw_start(gus, false);
+ snd_gf1_timers_resume(gus);
+ snd_gf1_uart_resume(gus);
+
+ return 0;
+}
diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c
index e3a8847e02cf..14dcde138bc7 100644
--- a/sound/isa/gus/gus_timer.c
+++ b/sound/isa/gus/gus_timer.c
@@ -178,3 +178,17 @@ void snd_gf1_timers_done(struct snd_gus_card * gus)
gus->gf1.timer2 = NULL;
}
}
+
+void snd_gf1_timers_resume(struct snd_gus_card *gus)
+{
+ if (gus->gf1.timer1) {
+ gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
+ if (gus->gf1.timer_enabled & 4)
+ snd_gf1_timer1_start(gus->gf1.timer1);
+ }
+ if (gus->gf1.timer2) {
+ gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
+ if (gus->gf1.timer_enabled & 8)
+ snd_gf1_timer2_start(gus->gf1.timer2);
+ }
+}
diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c
index 770d8f3e4cff..25057a5a81b0 100644
--- a/sound/isa/gus/gus_uart.c
+++ b/sound/isa/gus/gus_uart.c
@@ -232,3 +232,50 @@ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device)
gus->midi_uart = rmidi;
return err;
}
+
+void snd_gf1_uart_suspend(struct snd_gus_card *gus)
+{
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ outb(0x03, GUSP(gus, MIDICTRL));
+}
+
+void snd_gf1_uart_resume(struct snd_gus_card *gus)
+{
+ unsigned short uart_cmd;
+ bool active;
+ int i;
+
+ scoped_guard(spinlock_irqsave, &gus->uart_cmd_lock) {
+ active = gus->midi_substream_input || gus->midi_substream_output;
+ }
+ if (!active)
+ return;
+
+ /* snd_gf1_hw_start() already left MIDICTRL in reset. */
+ usleep_range(160, 200);
+
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ if (!gus->midi_substream_input && !gus->midi_substream_output)
+ return;
+
+ if (gus->midi_substream_output)
+ gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
+ if (gus->midi_substream_input)
+ gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
+
+ if (!gus->uart_enable)
+ return;
+
+ uart_cmd = gus->gf1.uart_cmd;
+ snd_gf1_uart_cmd(gus, 0x00);
+
+ if (gus->midi_substream_input) {
+ for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
+ snd_gf1_uart_get(gus);
+ if (i >= 1000)
+ dev_err(gus->card->dev,
+ "gus midi uart resume - cleanup error\n");
+ }
+
+ snd_gf1_uart_cmd(gus, uart_cmd);
+}
diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c
index 101202acefb3..363c819ced89 100644
--- a/sound/isa/gus/gusclassic.c
+++ b/sound/isa/gus/gusclassic.c
@@ -145,6 +145,7 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n)
error = snd_gusclassic_create(card, dev, n, &gus);
if (error < 0)
return error;
+ card->private_data = gus;
error = snd_gusclassic_detect(gus);
if (error < 0)
@@ -193,11 +194,29 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusclassic_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+
+ return snd_gus_suspend(card->private_data);
+}
+
+static int snd_gusclassic_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+
+ return snd_gus_resume(card->private_data);
+}
+#endif
+
static struct isa_driver snd_gusclassic_driver = {
.match = snd_gusclassic_match,
.probe = snd_gusclassic_probe,
-#if 0 /* FIXME */
+#ifdef CONFIG_PM
.suspend = snd_gusclassic_suspend,
+ .resume = snd_gusclassic_resume,
#endif
.driver = {
.name = DEV_NAME
diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c
index ed921b89b00a..0984731740c4 100644
--- a/sound/isa/gus/gusextreme.c
+++ b/sound/isa/gus/gusextreme.c
@@ -44,6 +44,11 @@ static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+struct snd_gusextreme {
+ struct snd_es1688 es1688;
+ struct snd_gus_card *gus;
+};
+
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
@@ -142,17 +147,15 @@ static int snd_gusextreme_gus_card_create(struct snd_card *card,
0, channels[n], pcm_channels[n], 0, rgus);
}
-static int snd_gusextreme_detect(struct snd_gus_card *gus,
- struct snd_es1688 *es1688)
+static void snd_gusextreme_enable_gf1(struct snd_gus_card *gus,
+ struct snd_es1688 *es1688)
{
- unsigned char d;
-
/*
* This is main stuff - enable access to GF1 chip...
* I'm not sure, if this will work for card which have
* ES1688 chip in another place than 0x220.
- *
- * I used reverse-engineering in DOSEMU. [--jk]
+ *
+ * I used reverse-engineering in DOSEMU. [--jk]
*
* ULTRINIT.EXE:
* 0x230 = 0,2,3
@@ -172,7 +175,14 @@ static int snd_gusextreme_detect(struct snd_gus_card *gus,
outb(0, 0x201);
outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1));
}
+}
+
+static int snd_gusextreme_detect(struct snd_gus_card *gus,
+ struct snd_es1688 *es1688)
+{
+ unsigned char d;
+ snd_gusextreme_enable_gf1(gus, es1688);
udelay(100);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
@@ -223,16 +233,18 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
{
struct snd_card *card;
struct snd_gus_card *gus;
+ struct snd_gusextreme *gusextreme;
struct snd_es1688 *es1688;
struct snd_opl3 *opl3;
int error;
error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE,
- sizeof(struct snd_es1688), &card);
+ sizeof(*gusextreme), &card);
if (error < 0)
return error;
- es1688 = card->private_data;
+ gusextreme = card->private_data;
+ es1688 = &gusextreme->es1688;
if (mpu_port[n] == SNDRV_AUTO_PORT)
mpu_port[n] = 0;
@@ -250,6 +262,7 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
error = snd_gusextreme_gus_card_create(card, dev, n, &gus);
if (error < 0)
return error;
+ gusextreme->gus = gus;
error = snd_gusextreme_detect(gus, es1688);
if (error < 0)
@@ -321,10 +334,36 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusextreme_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusextreme *gusextreme = card->private_data;
+
+ return snd_gus_suspend(gusextreme->gus);
+}
+
+static int snd_gusextreme_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusextreme *gusextreme = card->private_data;
+ int err;
+
+ err = snd_es1688_reset(&gusextreme->es1688);
+ if (err < 0)
+ return err;
+
+ snd_gusextreme_enable_gf1(gusextreme->gus, &gusextreme->es1688);
+ usleep_range(100, 200);
+ return snd_gus_resume(gusextreme->gus);
+}
+#endif
+
static struct isa_driver snd_gusextreme_driver = {
.match = snd_gusextreme_match,
.probe = snd_gusextreme_probe,
-#if 0 /* FIXME */
+#ifdef CONFIG_PM
.suspend = snd_gusextreme_suspend,
.resume = snd_gusextreme_resume,
#endif
diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c
index b572411c4422..f1fd7ff2121d 100644
--- a/sound/isa/gus/gusmax.c
+++ b/sound/isa/gus/gusmax.c
@@ -328,12 +328,38 @@ static int snd_gusmax_probe(struct device *pdev, unsigned int dev)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusmax_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusmax *maxcard = card->private_data;
+
+ maxcard->wss->suspend(maxcard->wss);
+ return snd_gus_suspend(maxcard->gus);
+}
+
+static int snd_gusmax_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusmax *maxcard = card->private_data;
+
+ /* Restore the board routing latch before resuming the codec and GF1. */
+ outb(maxcard->gus->max_cntrl_val, GUSP(maxcard->gus, MAXCNTRLPORT));
+ maxcard->wss->resume(maxcard->wss);
+ return snd_gus_resume(maxcard->gus);
+}
+#endif
+
#define DEV_NAME "gusmax"
static struct isa_driver snd_gusmax_driver = {
.match = snd_gusmax_match,
.probe = snd_gusmax_probe,
- /* FIXME: suspend/resume */
+#ifdef CONFIG_PM
+ .suspend = snd_gusmax_suspend,
+ .resume = snd_gusmax_resume,
+#endif
.driver = {
.name = DEV_NAME
},
diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c
index 18adcd35e117..6c3a2977dcb3 100644
--- a/sound/isa/gus/interwave.c
+++ b/sound/isa/gus/interwave.c
@@ -96,6 +96,7 @@ struct snd_interwave {
struct snd_gus_card *gus;
struct snd_wss *wss;
#ifdef SNDRV_STB
+ struct snd_i2c_bus *i2c_bus;
struct resource *i2c_res;
#endif
unsigned short gus_status_reg;
@@ -363,18 +364,30 @@ struct rom_hdr {
/* 511 */ unsigned char csum;
};
-static void snd_interwave_detect_memory(struct snd_gus_card *gus)
+static const unsigned int snd_interwave_memory_configs[] = {
+ 0x00000001, 0x00000101, 0x01010101, 0x00000401,
+ 0x04040401, 0x00040101, 0x04040101, 0x00000004,
+ 0x00000404, 0x04040404, 0x00000010, 0x00001010,
+ 0x10101010
+};
+
+static int snd_interwave_find_memory_config(unsigned int lmct)
{
- static const unsigned int lmc[13] =
- {
- 0x00000001, 0x00000101, 0x01010101, 0x00000401,
- 0x04040401, 0x00040101, 0x04040101, 0x00000004,
- 0x00000404, 0x04040404, 0x00000010, 0x00001010,
- 0x10101010
- };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_interwave_memory_configs); i++) {
+ if (lmct == snd_interwave_memory_configs[i])
+ return i;
+ }
+
+ return -EINVAL;
+}
+static void snd_interwave_detect_memory(struct snd_gus_card *gus)
+{
int bank_pos, pages;
unsigned int i, lmct;
+ int lmc_cfg;
int psizes[4];
unsigned char iwave[8];
unsigned char csum;
@@ -399,17 +412,20 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus)
#if 0
dev_dbg(gus->card->dev, "lmct = 0x%08x\n", lmct);
#endif
- for (i = 0; i < ARRAY_SIZE(lmc); i++)
- if (lmct == lmc[i]) {
+ lmc_cfg = snd_interwave_find_memory_config(lmct);
+ if (lmc_cfg >= 0) {
#if 0
- dev_dbg(gus->card->dev, "found !!! %i\n", i);
+ dev_dbg(gus->card->dev, "found !!! %i\n", lmc_cfg);
#endif
- snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i);
- snd_interwave_bank_sizes(gus, psizes);
- break;
- }
- if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode)
- snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2);
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG,
+ (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) |
+ lmc_cfg);
+ snd_interwave_bank_sizes(gus, psizes);
+ } else if (!gus->gf1.enh_mode) {
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG,
+ (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) |
+ 2);
+ }
for (i = 0; i < 4; i++) {
gus->gf1.mem_alloc.banks_8[i].address =
gus->gf1.mem_alloc.banks_16[i].address = i << 22;
@@ -454,24 +470,28 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus)
snd_interwave_reset(gus);
}
+static void __snd_interwave_restore_regs(struct snd_gus_card *gus)
+{
+ snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00);
+}
+
static void snd_interwave_init(int dev, struct snd_gus_card *gus)
{
- /* ok.. some InterWave specific initialization */
+ /* Probe-time setup also clears the timer control register. */
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00);
- snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f);
- snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49);
- snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11);
- snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00);
- snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30);
- snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00);
+ __snd_interwave_restore_regs(gus);
}
gus->equal_irq = 1;
gus->codec_flag = 1;
gus->interwave = 1;
gus->max_flag = 1;
gus->joystick_dac = joystick_dac[dev];
-
}
static const struct snd_kcontrol_new snd_interwave_controls[] = {
@@ -724,6 +744,7 @@ static int snd_interwave_probe(struct snd_card *card, int dev,
err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1);
if (err < 0)
return err;
+ iwcard->i2c_bus = i2c_bus;
}
#endif
@@ -828,10 +849,97 @@ static int snd_interwave_isa_probe(struct device *pdev,
return 0;
}
+#ifdef CONFIG_PM
+static void snd_interwave_restore_regs(struct snd_gus_card *gus)
+{
+ scoped_guard(spinlock_irqsave, &gus->reg_lock)
+ __snd_interwave_restore_regs(gus);
+}
+
+static void snd_interwave_restore_memory(struct snd_gus_card *gus)
+{
+ unsigned short mem_cfg;
+ unsigned int lmct = 0;
+ int i, lmc_cfg;
+
+ if (!gus->gf1.memory)
+ return;
+
+ for (i = 0; i < 4; i++)
+ lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8);
+
+ lmc_cfg = snd_interwave_find_memory_config(lmct);
+ if (lmc_cfg < 0) {
+ if (!gus->gf1.enh_mode) {
+ lmc_cfg = 2;
+ } else {
+ dev_warn(gus->card->dev,
+ "cannot restore InterWave memory layout 0x%08x\n",
+ lmct);
+ return;
+ }
+ }
+
+ scoped_guard(spinlock_irqsave, &gus->reg_lock) {
+ mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG);
+ mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg;
+ mem_cfg = (mem_cfg & 0xff1f) | (4 << 5);
+ snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg);
+ }
+}
+
+static int snd_interwave_card_suspend(struct snd_card *card)
+{
+ struct snd_interwave *iwcard = card->private_data;
+
+ iwcard->wss->suspend(iwcard->wss);
+ return snd_gus_suspend(iwcard->gus);
+}
+
+static int snd_interwave_card_resume(struct snd_card *card)
+{
+ struct snd_interwave *iwcard = card->private_data;
+ int err;
+
+ err = snd_gus_resume(iwcard->gus);
+ if (err < 0)
+ return err;
+
+ snd_interwave_restore_regs(iwcard->gus);
+ snd_interwave_restore_memory(iwcard->gus);
+ iwcard->wss->resume(iwcard->wss);
+#ifdef SNDRV_STB
+ if (iwcard->i2c_bus) {
+ err = snd_tea6330t_restore_mixer(iwcard->i2c_bus);
+ if (err < 0)
+ dev_warn(card->dev,
+ "failed to restore TEA6330T mixer state: %d\n",
+ err);
+ }
+#endif
+
+ return 0;
+}
+
+static int snd_interwave_isa_suspend(struct device *pdev, unsigned int dev,
+ pm_message_t state)
+{
+ return snd_interwave_card_suspend(dev_get_drvdata(pdev));
+}
+
+static int snd_interwave_isa_resume(struct device *pdev, unsigned int dev)
+{
+ return snd_interwave_card_resume(dev_get_drvdata(pdev));
+}
+#endif
+
static struct isa_driver snd_interwave_driver = {
.match = snd_interwave_isa_match,
.probe = snd_interwave_isa_probe,
- /* FIXME: suspend,resume */
+#ifdef CONFIG_PM
+ .suspend = snd_interwave_isa_suspend,
+ .resume = snd_interwave_isa_resume,
+#endif
.driver = {
.name = INTERWAVE_DRIVER
},
@@ -871,12 +979,28 @@ static int snd_interwave_pnp_detect(struct pnp_card_link *pcard,
return 0;
}
+#ifdef CONFIG_PM
+static int snd_interwave_pnpc_suspend(struct pnp_card_link *pcard,
+ pm_message_t state)
+{
+ return snd_interwave_card_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_interwave_pnpc_resume(struct pnp_card_link *pcard)
+{
+ return snd_interwave_card_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
static struct pnp_card_driver interwave_pnpc_driver = {
.flags = PNP_DRIVER_RES_DISABLE,
.name = INTERWAVE_PNP_DRIVER,
.id_table = snd_interwave_pnpids,
.probe = snd_interwave_pnp_detect,
- /* FIXME: suspend,resume */
+#ifdef CONFIG_PM
+ .suspend = snd_interwave_pnpc_suspend,
+ .resume = snd_interwave_pnpc_resume,
+#endif
};
#endif /* CONFIG_PNP */
diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c
index 5e350234d572..77367e102fda 100644
--- a/sound/isa/msnd/msnd.c
+++ b/sound/isa/msnd/msnd.c
@@ -127,11 +127,8 @@ int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len)
}
EXPORT_SYMBOL(snd_msnd_upload_host);
-int snd_msnd_enable_irq(struct snd_msnd *dev)
+static int __snd_msnd_enable_irq(struct snd_msnd *dev)
{
- if (dev->irq_ref++)
- return 0;
-
dev_dbg(dev->card->dev, LOGNAME ": Enabling IRQ\n");
guard(spinlock_irqsave)(&dev->lock);
@@ -152,17 +149,9 @@ int snd_msnd_enable_irq(struct snd_msnd *dev)
return -EIO;
}
-EXPORT_SYMBOL(snd_msnd_enable_irq);
-int snd_msnd_disable_irq(struct snd_msnd *dev)
+static int __snd_msnd_disable_irq(struct snd_msnd *dev)
{
- if (--dev->irq_ref > 0)
- return 0;
-
- if (dev->irq_ref < 0)
- dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n",
- dev->irq_ref);
-
dev_dbg(dev->card->dev, LOGNAME ": Disabling IRQ\n");
guard(spinlock_irqsave)(&dev->lock);
@@ -178,8 +167,39 @@ int snd_msnd_disable_irq(struct snd_msnd *dev)
return -EIO;
}
+
+int snd_msnd_enable_irq(struct snd_msnd *dev)
+{
+ if (dev->irq_ref++)
+ return 0;
+
+ return __snd_msnd_enable_irq(dev);
+}
+EXPORT_SYMBOL(snd_msnd_enable_irq);
+
+int snd_msnd_disable_irq(struct snd_msnd *dev)
+{
+ if (--dev->irq_ref > 0)
+ return 0;
+
+ if (dev->irq_ref < 0)
+ dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n",
+ dev->irq_ref);
+
+ return __snd_msnd_disable_irq(dev);
+}
EXPORT_SYMBOL(snd_msnd_disable_irq);
+int snd_msnd_force_irq(struct snd_msnd *dev, bool enable)
+{
+ if (!dev->irq_ref)
+ return 0;
+
+ return enable ? __snd_msnd_enable_irq(dev) :
+ __snd_msnd_disable_irq(dev);
+}
+EXPORT_SYMBOL(snd_msnd_force_irq);
+
static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size)
{
long tmp = (size * HZ * chip->play_sample_size) / 8;
@@ -507,25 +527,27 @@ static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream,
int cmd)
{
struct snd_msnd *chip = snd_pcm_substream_chip(substream);
- int result = 0;
- if (cmd == SNDRV_PCM_TRIGGER_START) {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
dev_dbg(chip->card->dev, "%s(START)\n", __func__);
chip->banksPlayed = 0;
set_bit(F_WRITING, &chip->flags);
snd_msnd_DAPQ(chip, 1);
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
dev_dbg(chip->card->dev, "%s(STOP)\n", __func__);
- /* interrupt diagnostic, comment this out later */
clear_bit(F_WRITING, &chip->flags);
snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
- } else {
+ break;
+ default:
dev_dbg(chip->card->dev, "%s(?????)\n", __func__);
- result = -EINVAL;
+ return -EINVAL;
}
dev_dbg(chip->card->dev, "%s() ENDE\n", __func__);
- return result;
+ return 0;
}
static snd_pcm_uframes_t
@@ -589,17 +611,22 @@ static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream,
{
struct snd_msnd *chip = snd_pcm_substream_chip(substream);
- if (cmd == SNDRV_PCM_TRIGGER_START) {
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
chip->last_recbank = -1;
set_bit(F_READING, &chip->flags);
if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0)
return 0;
clear_bit(F_READING, &chip->flags);
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
clear_bit(F_READING, &chip->flags);
snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
return 0;
+ default:
+ break;
}
return -EINVAL;
}
@@ -668,4 +695,3 @@ EXPORT_SYMBOL(snd_msnd_pcm);
MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers");
MODULE_LICENSE("GPL");
-
diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h
index 3d7810ed9186..56a700e6a5cb 100644
--- a/sound/isa/msnd/msnd.h
+++ b/sound/isa/msnd/msnd.h
@@ -253,6 +253,8 @@ struct snd_msnd {
spinlock_t mixer_lock;
int nresets;
unsigned recsrc;
+ u8 pm_recsrc;
+ bool pm_mpu_input;
#define LEVEL_ENTRIES 32
int left_levels[LEVEL_ENTRIES];
int right_levels[LEVEL_ENTRIES];
@@ -280,6 +282,7 @@ int snd_msnd_upload_host(struct snd_msnd *chip,
const u8 *bin, int len);
int snd_msnd_enable_irq(struct snd_msnd *chip);
int snd_msnd_disable_irq(struct snd_msnd *chip);
+int snd_msnd_force_irq(struct snd_msnd *chip, bool enable);
void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file);
int snd_msnd_DAPQ(struct snd_msnd *chip, int start);
int snd_msnd_DARQ(struct snd_msnd *chip, int start);
diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c
index c4eec391cd29..5b729bb02ef6 100644
--- a/sound/isa/msnd/msnd_pinnacle.c
+++ b/sound/isa/msnd/msnd_pinnacle.c
@@ -513,6 +513,19 @@ static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu)
snd_msnd_disable_irq(mpu->private_data);
}
+#ifdef CONFIG_PM
+static u8 snd_msnd_pm_recsrc(struct snd_msnd *chip)
+{
+ /* Convert recsrc to the Capture Source selector: 0=Analog, 1=MASS, 2=SPDIF. */
+ if (chip->recsrc & BIT(4))
+ return 1;
+ if ((chip->recsrc & BIT(17)) &&
+ test_bit(F_HAVEDIGITAL, &chip->flags))
+ return 2;
+ return 0;
+}
+#endif
+
static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;
static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ;
@@ -1001,10 +1014,73 @@ static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_msnd_card_suspend(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ struct snd_mpu401 *mpu;
+ int err;
+
+ mpu = chip->rmidi ? chip->rmidi->private_data : NULL;
+ chip->pm_recsrc = snd_msnd_pm_recsrc(chip);
+ chip->pm_mpu_input = mpu && test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
+ if (chip->pm_mpu_input)
+ snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_STOP);
+
+ err = snd_msnd_force_irq(chip, false);
+ if (err < 0) {
+ if (chip->pm_mpu_input)
+ snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START);
+ return err;
+ }
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+
+static int snd_msnd_card_resume(struct snd_card *card)
+{
+ struct snd_msnd *chip = card->private_data;
+ int err;
+
+ err = snd_msnd_initialize(card);
+ if (err < 0)
+ return err;
+
+ snd_msnd_calibrate_adc(chip, chip->play_sample_rate);
+ snd_msndmix_force_recsrc(chip, chip->pm_recsrc);
+
+ err = snd_msnd_force_irq(chip, true);
+ if (err < 0)
+ return err;
+
+ if (chip->pm_mpu_input)
+ snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START);
+
+ chip->nresets = 0;
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_msnd_isa_suspend(struct device *dev, unsigned int idx,
+ pm_message_t state)
+{
+ return snd_msnd_card_suspend(dev_get_drvdata(dev));
+}
+
+static int snd_msnd_isa_resume(struct device *dev, unsigned int idx)
+{
+ return snd_msnd_card_resume(dev_get_drvdata(dev));
+}
+#endif
+
static struct isa_driver snd_msnd_driver = {
.match = snd_msnd_isa_match,
.probe = snd_msnd_isa_probe,
- /* FIXME: suspend, resume */
+#ifdef CONFIG_PM
+ .suspend = snd_msnd_isa_suspend,
+ .resume = snd_msnd_isa_resume,
+#endif
.driver = {
.name = DEV_NAME
},
@@ -1111,6 +1187,18 @@ static int snd_msnd_pnp_detect(struct pnp_card_link *pcard,
return 0;
}
+#ifdef CONFIG_PM
+static int snd_msnd_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_msnd_card_suspend(pnp_get_card_drvdata(pcard));
+}
+
+static int snd_msnd_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_msnd_card_resume(pnp_get_card_drvdata(pcard));
+}
+#endif
+
static int isa_registered;
static int pnp_registered;
@@ -1127,6 +1215,10 @@ static struct pnp_card_driver msnd_pnpc_driver = {
.name = "msnd_pinnacle",
.id_table = msnd_pnpids,
.probe = snd_msnd_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = snd_msnd_pnp_suspend,
+ .resume = snd_msnd_pnp_resume,
+#endif
};
#endif /* CONFIG_PNP */
@@ -1161,4 +1253,3 @@ static void __exit snd_msnd_exit(void)
module_init(snd_msnd_init);
module_exit(snd_msnd_exit);
-
diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c
index ec354483b9f8..8ca987221753 100644
--- a/sound/isa/msnd/msnd_pinnacle_mixer.c
+++ b/sound/isa/msnd/msnd_pinnacle_mixer.c
@@ -310,6 +310,10 @@ EXPORT_SYMBOL(snd_msndmix_new);
void snd_msndmix_setup(struct snd_msnd *dev)
{
+ writew(dev->left_levels[MSND_MIXER_VOLUME],
+ dev->SMA + SMA_wCurrMastVolLeft);
+ writew(dev->right_levels[MSND_MIXER_VOLUME],
+ dev->SMA + SMA_wCurrMastVolRight);
update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS);
update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS);
update_volm(MSND_MIXER_PCM, wCurrPlayVol);
diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c
index 6d618cc2ba45..cd3a63c7c2a7 100644
--- a/sound/isa/sc6000.c
+++ b/sound/isa/sc6000.c
@@ -100,6 +100,16 @@ MODULE_PARM_DESC(joystick, "Enable gameport.");
#define PFX "sc6000: "
#define DRV_NAME "SC-6000"
+struct snd_sc6000 {
+ char __iomem *vport;
+ char __iomem *vmss_port;
+ struct snd_wss *chip;
+ u8 mss_config;
+ u8 config;
+ u8 hw_cfg[2];
+ bool old_dsp;
+};
+
/* hardware dependent functions */
/*
@@ -267,7 +277,7 @@ static int sc6000_dsp_reset(char __iomem *vport)
/* detection and initialization */
static int sc6000_hw_cfg_write(struct device *devptr,
- char __iomem *vport, const int *cfg)
+ char __iomem *vport, const u8 *cfg)
{
if (sc6000_write(devptr, vport, COMMAND_6C) < 0) {
dev_warn(devptr, "CMD 0x%x: failed!\n", COMMAND_6C);
@@ -353,8 +363,7 @@ static int sc6000_init_mss(struct device *devptr,
return 0;
}
-static void sc6000_hw_cfg_encode(struct device *devptr,
- char __iomem *vport, int *cfg,
+static void sc6000_hw_cfg_encode(struct device *devptr, u8 *cfg,
long xport, long xmpu,
long xmss_port, int joystick)
{
@@ -376,27 +385,83 @@ static void sc6000_hw_cfg_encode(struct device *devptr,
dev_dbg(devptr, "hw cfg %x, %x\n", cfg[0], cfg[1]);
}
-static int sc6000_init_board(struct device *devptr,
- char __iomem *vport,
- char __iomem *vmss_port, int dev)
+static void sc6000_prepare_board(struct device *devptr,
+ struct snd_sc6000 *sc6000,
+ unsigned int dev, int xirq, int xdma)
+{
+ sc6000->mss_config = sc6000_irq_to_softcfg(xirq) |
+ sc6000_dma_to_softcfg(xdma);
+ sc6000->config = sc6000->mss_config |
+ sc6000_mpu_irq_to_softcfg(mpu_irq[dev]);
+ sc6000_hw_cfg_encode(devptr, sc6000->hw_cfg, port[dev], mpu_port[dev],
+ mss_port[dev], joystick[dev]);
+}
+
+static void sc6000_detect_old_dsp(struct device *devptr,
+ struct snd_sc6000 *sc6000)
+{
+ sc6000_write(devptr, sc6000->vport, COMMAND_5C);
+ sc6000->old_dsp = sc6000_read(sc6000->vport) < 0;
+}
+
+static int sc6000_program_board(struct device *devptr,
+ struct snd_sc6000 *sc6000)
+{
+ int err;
+
+ if (!sc6000->old_dsp) {
+ if (sc6000_hw_cfg_write(devptr, sc6000->vport,
+ sc6000->hw_cfg) < 0) {
+ dev_err(devptr, "sc6000_hw_cfg_write: failed!\n");
+ return -EIO;
+ }
+ }
+
+ err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config);
+ if (err < 0) {
+ dev_err(devptr, "sc6000_setup_board: failed!\n");
+ return -ENODEV;
+ }
+
+ sc6000_dsp_reset(sc6000->vport);
+
+ if (!sc6000->old_dsp) {
+ sc6000_write(devptr, sc6000->vport, COMMAND_60);
+ sc6000_write(devptr, sc6000->vport, 0x02);
+ sc6000_dsp_reset(sc6000->vport);
+ }
+
+ err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config);
+ if (err < 0) {
+ dev_err(devptr, "sc6000_setup_board: failed!\n");
+ return -ENODEV;
+ }
+
+ err = sc6000_init_mss(devptr, sc6000->vport, sc6000->config,
+ sc6000->vmss_port, sc6000->mss_config);
+ if (err < 0) {
+ dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int sc6000_init_board(struct device *devptr, struct snd_sc6000 *sc6000)
{
char answer[15];
char version[2];
- int mss_config = sc6000_irq_to_softcfg(irq[dev]) |
- sc6000_dma_to_softcfg(dma[dev]);
- int config = mss_config |
- sc6000_mpu_irq_to_softcfg(mpu_irq[dev]);
int err;
- int old = 0;
- err = sc6000_dsp_reset(vport);
+ err = sc6000_dsp_reset(sc6000->vport);
if (err < 0) {
dev_err(devptr, "sc6000_dsp_reset: failed!\n");
return err;
}
memset(answer, 0, sizeof(answer));
- err = sc6000_dsp_get_answer(devptr, vport, GET_DSP_COPYRIGHT, answer, 15);
+ err = sc6000_dsp_get_answer(devptr, sc6000->vport, GET_DSP_COPYRIGHT,
+ answer, 15);
if (err <= 0) {
dev_err(devptr, "sc6000_dsp_copyright: failed!\n");
return -ENODEV;
@@ -408,54 +473,17 @@ static int sc6000_init_board(struct device *devptr,
if (strncmp("SC-6000", answer, 7))
dev_warn(devptr, "Warning: non SC-6000 audio card!\n");
- if (sc6000_dsp_get_answer(devptr, vport, GET_DSP_VERSION, version, 2) < 2) {
+ if (sc6000_dsp_get_answer(devptr, sc6000->vport,
+ GET_DSP_VERSION, version, 2) < 2) {
dev_err(devptr, "sc6000_dsp_version: failed!\n");
return -ENODEV;
}
dev_info(devptr, "Detected model: %s, DSP version %d.%d\n",
answer, version[0], version[1]);
- /* set configuration */
- sc6000_write(devptr, vport, COMMAND_5C);
- if (sc6000_read(vport) < 0)
- old = 1;
-
- if (!old) {
- int cfg[2];
- sc6000_hw_cfg_encode(devptr,
- vport, &cfg[0], port[dev], mpu_port[dev],
- mss_port[dev], joystick[dev]);
- if (sc6000_hw_cfg_write(devptr, vport, cfg) < 0) {
- dev_err(devptr, "sc6000_hw_cfg_write: failed!\n");
- return -EIO;
- }
- }
- err = sc6000_setup_board(devptr, vport, config);
- if (err < 0) {
- dev_err(devptr, "sc6000_setup_board: failed!\n");
- return -ENODEV;
- }
-
- sc6000_dsp_reset(vport);
-
- if (!old) {
- sc6000_write(devptr, vport, COMMAND_60);
- sc6000_write(devptr, vport, 0x02);
- sc6000_dsp_reset(vport);
- }
+ sc6000_detect_old_dsp(devptr, sc6000);
- err = sc6000_setup_board(devptr, vport, config);
- if (err < 0) {
- dev_err(devptr, "sc6000_setup_board: failed!\n");
- return -ENODEV;
- }
- err = sc6000_init_mss(devptr, vport, config, vmss_port, mss_config);
- if (err < 0) {
- dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n");
- return -ENODEV;
- }
-
- return 0;
+ return sc6000_program_board(devptr, sc6000);
}
static int snd_sc6000_mixer(struct snd_wss *chip)
@@ -538,10 +566,10 @@ static int snd_sc6000_match(struct device *devptr, unsigned int dev)
static void snd_sc6000_free(struct snd_card *card)
{
- char __iomem *vport = (char __force __iomem *)card->private_data;
+ struct snd_sc6000 *sc6000 = card->private_data;
- if (vport)
- sc6000_setup_board(card->dev, vport, 0);
+ if (sc6000->vport)
+ sc6000_setup_board(card->dev, sc6000->vport, 0);
}
static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
@@ -552,15 +580,17 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
int xirq = irq[dev];
int xdma = dma[dev];
struct snd_card *card;
+ struct snd_sc6000 *sc6000;
struct snd_wss *chip;
struct snd_opl3 *opl3;
char __iomem *vport;
char __iomem *vmss_port;
err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE,
- 0, &card);
+ sizeof(*sc6000), &card);
if (err < 0)
return err;
+ sc6000 = card->private_data;
if (xirq == SNDRV_AUTO_IRQ) {
xirq = snd_legacy_find_free_irq(possible_irqs);
@@ -587,7 +617,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
dev_err(devptr, "I/O port cannot be iomapped.\n");
return -EBUSY;
}
- card->private_data = (void __force *)vport;
+ sc6000->vport = vport;
/* to make it marked as used */
if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) {
@@ -600,12 +630,15 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
dev_err(devptr, "MSS port I/O cannot be iomapped.\n");
return -EBUSY;
}
+ sc6000->vmss_port = vmss_port;
dev_dbg(devptr, "Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n",
port[dev], xirq, xdma,
mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]);
- err = sc6000_init_board(devptr, vport, vmss_port, dev);
+ sc6000_prepare_board(devptr, sc6000, dev, xirq, xdma);
+
+ err = sc6000_init_board(devptr, sc6000);
if (err < 0)
return err;
card->private_free = snd_sc6000_free;
@@ -614,6 +647,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev)
WSS_HW_DETECT, 0, &chip);
if (err < 0)
return err;
+ sc6000->chip = chip;
err = snd_wss_pcm(chip, 0);
if (err < 0) {
@@ -670,10 +704,47 @@ 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)
+{
+ struct snd_card *card = dev_get_drvdata(devptr);
+ struct snd_sc6000 *sc6000 = card->private_data;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ sc6000->chip->suspend(sc6000->chip);
+ return 0;
+}
+
+static int snd_sc6000_resume(struct device *devptr, unsigned int dev)
+{
+ struct snd_card *card = dev_get_drvdata(devptr);
+ struct snd_sc6000 *sc6000 = card->private_data;
+ int err;
+
+ err = sc6000_dsp_reset(sc6000->vport);
+ if (err < 0) {
+ dev_err(devptr, "sc6000_dsp_reset: failed!\n");
+ return err;
+ }
+
+ err = sc6000_program_board(devptr, sc6000);
+ if (err < 0)
+ return err;
+
+ sc6000->chip->resume(sc6000->chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+#endif
+
static struct isa_driver snd_sc6000_driver = {
.match = snd_sc6000_match,
.probe = snd_sc6000_probe,
- /* FIXME: suspend/resume */
+#ifdef CONFIG_PM
+ .suspend = snd_sc6000_suspend,
+ .resume = snd_sc6000_resume,
+#endif
.driver = {
.name = DRV_NAME,
},
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
index a31ca75774a6..553ceb92d298 100644
--- a/sound/isa/sscape.c
+++ b/sound/isa/sscape.c
@@ -131,6 +131,11 @@ enum card_type {
struct soundscape {
spinlock_t lock;
unsigned io_base;
+ unsigned long wss_base;
+ int irq;
+ int mpu_irq;
+ int dma1;
+ int dma2;
int ic_type;
enum card_type type;
struct resource *io_res;
@@ -138,6 +143,8 @@ struct soundscape {
struct snd_wss *chip;
unsigned char midi_vol;
+ bool joystick;
+ bool midi_enabled;
struct device *dev;
};
@@ -150,6 +157,21 @@ static inline struct soundscape *get_card_soundscape(struct snd_card *c)
}
/*
+ * Store the resolved board settings in the per-card state so that
+ * the same configuration can be replayed later if necessary.
+ */
+static void sscape_store_settings(struct soundscape *sscape, int dev)
+{
+ sscape->io_base = port[dev];
+ sscape->wss_base = wss_port[dev];
+ sscape->irq = irq[dev];
+ sscape->mpu_irq = mpu_irq[dev];
+ sscape->dma1 = dma[dev];
+ sscape->dma2 = dma2[dev];
+ sscape->joystick = joystick[dev];
+}
+
+/*
* Allocates some kernel memory that we can use for DMA.
* I think this means that the memory has to map to
* contiguous pages of physical memory.
@@ -263,34 +285,36 @@ static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout)
/*
* Write to the SoundScape's host-mode control registers, but
- * leave any locking issues to the caller ...
+ * leave any locking issues to the caller. Returns true if
+ * the write succeeded.
*/
-static inline int host_write_unsafe(unsigned io_base, unsigned char data)
+static inline bool host_write_unsafe(unsigned int io_base, unsigned char data)
{
if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) {
outb(data, HOST_DATA_IO(io_base));
- return 1;
+ return true;
}
- return 0;
+ return false;
}
/*
* Write to the SoundScape's host-mode control registers, performing
* a limited amount of busy-waiting if the register isn't ready.
- * Also leaves all locking-issues to the caller ...
+ * Also leaves all locking-issues to the caller. Returns true if
+ * the write succeeded before timing out.
*/
-static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data,
- unsigned timeout)
+static bool host_write_ctrl_unsafe(unsigned int io_base, unsigned char data,
+ unsigned int timeout)
{
- int err;
+ bool written;
- while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) {
+ while (!(written = host_write_unsafe(io_base, data)) && timeout != 0) {
udelay(100);
--timeout;
} /* while */
- return err;
+ return written;
}
@@ -561,6 +585,30 @@ static int sscape_upload_microcode(struct snd_card *card, int version)
}
/*
+ * Restore the SoundScape's MIDI control state after the firmware
+ * upload has made the host interface available again.
+ */
+static int sscape_restore_midi_state(struct soundscape *sscape)
+{
+ bool success;
+
+ guard(spinlock_irqsave)(&sscape->lock);
+ set_host_mode_unsafe(sscape->io_base);
+
+ success = host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, CMD_SET_EXTMIDI, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, 0, 100) &&
+ host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
+
+ set_midi_mode_unsafe(sscape->io_base);
+
+ return success ? 0 : -EIO;
+}
+
+/*
* Mixer control for the SoundScape's MIDI device.
*/
static int sscape_midi_info(struct snd_kcontrol *ctl,
@@ -661,6 +709,59 @@ static unsigned get_irq_config(int sscape_type, int irq)
}
/*
+ * Program the SoundScape's board-specific routing and enable the
+ * codec path using the resolved IRQ, DMA and joystick settings.
+ */
+static int sscape_configure_board(struct soundscape *sscape)
+{
+ unsigned int dma_cfg;
+ unsigned int irq_cfg;
+ unsigned int mpu_irq_cfg;
+ int val;
+
+ irq_cfg = get_irq_config(sscape->type, sscape->irq);
+ if (irq_cfg == INVALID_IRQ)
+ return -ENXIO;
+
+ mpu_irq_cfg = get_irq_config(sscape->type, sscape->mpu_irq);
+ if (mpu_irq_cfg == INVALID_IRQ)
+ return -ENXIO;
+
+ scoped_guard(spinlock_irqsave, &sscape->lock) {
+ if (sscape->ic_type == IC_OPUS)
+ activate_ad1845_unsafe(sscape->io_base);
+
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
+
+ /*
+ * Enable and configure the DMA channels ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
+ dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
+ sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+ sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+ mpu_irq_cfg |= mpu_irq_cfg << 2;
+ val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xf7;
+ if (sscape->joystick)
+ val |= 0x08;
+ sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0xd0);
+ sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG,
+ 0xf0 | mpu_irq_cfg);
+ sscape_write_unsafe(sscape->io_base, GA_CDCFG_REG,
+ 0x09 | DMA_8BIT |
+ (sscape->dma1 << 4) | (irq_cfg << 1));
+ /*
+ * Enable the master IRQ ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
+ }
+
+ return 0;
+}
+
+/*
* Perform certain arcane port-checks to see whether there
* is a SoundScape board lurking behind the given ports.
*/
@@ -890,37 +991,33 @@ _error:
/*
* Create an ALSA soundcard entry for the SoundScape, using
- * the given list of port, IRQ and DMA resources.
+ * the resolved port, IRQ and DMA resources.
*/
-static int create_sscape(int dev, struct snd_card *card)
+static int create_sscape(struct snd_card *card)
{
struct soundscape *sscape = get_card_soundscape(card);
- unsigned dma_cfg;
- unsigned irq_cfg;
- unsigned mpu_irq_cfg;
struct resource *io_res;
struct resource *wss_res;
int err;
- int val;
const char *name;
/*
* Grab IO ports that we will need to probe so that we
* can detect and control this hardware ...
*/
- io_res = devm_request_region(card->dev, port[dev], 8, "SoundScape");
+ io_res = devm_request_region(card->dev, sscape->io_base, 8, "SoundScape");
if (!io_res) {
dev_err(card->dev,
- "sscape: can't grab port 0x%lx\n", port[dev]);
+ "sscape: can't grab port 0x%x\n", sscape->io_base);
return -EBUSY;
}
wss_res = NULL;
if (sscape->type == SSCAPE_VIVO) {
- wss_res = devm_request_region(card->dev, wss_port[dev], 4,
+ wss_res = devm_request_region(card->dev, sscape->wss_base, 4,
"SoundScape");
if (!wss_res) {
dev_err(card->dev, "sscape: can't grab port 0x%lx\n",
- wss_port[dev]);
+ sscape->wss_base);
return -EBUSY;
}
}
@@ -928,18 +1025,17 @@ static int create_sscape(int dev, struct snd_card *card)
/*
* Grab one DMA channel ...
*/
- err = snd_devm_request_dma(card->dev, dma[dev], "SoundScape");
+ err = snd_devm_request_dma(card->dev, sscape->dma1, "SoundScape");
if (err < 0) {
- dev_err(card->dev, "sscape: can't grab DMA %d\n", dma[dev]);
+ dev_err(card->dev, "sscape: can't grab DMA %d\n", sscape->dma1);
return err;
}
spin_lock_init(&sscape->lock);
sscape->io_res = io_res;
sscape->wss_res = wss_res;
- sscape->io_base = port[dev];
- if (!detect_sscape(sscape, wss_port[dev])) {
+ if (!detect_sscape(sscape, sscape->wss_base)) {
dev_err(card->dev, "sscape: hardware not detected at 0x%x\n",
sscape->io_base);
return -ENODEV;
@@ -964,66 +1060,28 @@ static int create_sscape(int dev, struct snd_card *card)
}
dev_info(card->dev, "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
- name, sscape->io_base, irq[dev], dma[dev]);
-
- /*
- * Check that the user didn't pass us garbage data ...
- */
- irq_cfg = get_irq_config(sscape->type, irq[dev]);
- if (irq_cfg == INVALID_IRQ) {
- dev_err(card->dev, "sscape: Invalid IRQ %d\n", irq[dev]);
- return -ENXIO;
- }
-
- mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
- if (mpu_irq_cfg == INVALID_IRQ) {
- dev_err(card->dev, "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
- return -ENXIO;
- }
+ name, sscape->io_base, sscape->irq, sscape->dma1);
/*
* Tell the on-board devices where their resources are (I think -
* I can't be sure without a datasheet ... So many magic values!)
*/
- scoped_guard(spinlock_irqsave, &sscape->lock) {
-
- sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
- sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
-
- /*
- * Enable and configure the DMA channels ...
- */
- sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
- dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
- sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
- sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
-
- mpu_irq_cfg |= mpu_irq_cfg << 2;
- val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
- if (joystick[dev])
- val |= 8;
- sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
- sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
- sscape_write_unsafe(sscape->io_base,
- GA_CDCFG_REG, 0x09 | DMA_8BIT
- | (dma[dev] << 4) | (irq_cfg << 1));
- /*
- * Enable the master IRQ ...
- */
- sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
-
+ err = sscape_configure_board(sscape);
+ if (err < 0) {
+ dev_err(card->dev, "sscape: Invalid IRQ configuration\n");
+ return err;
}
/*
* We have now enabled the codec chip, and so we should
* detect the AD1845 device ...
*/
- err = create_ad1845(card, wss_port[dev], irq[dev],
- dma[dev], dma2[dev]);
+ err = create_ad1845(card, sscape->wss_base, sscape->irq,
+ sscape->dma1, sscape->dma2);
if (err < 0) {
dev_err(card->dev,
"sscape: No AD1845 device at 0x%lx, IRQ %d\n",
- wss_port[dev], irq[dev]);
+ sscape->wss_base, sscape->irq);
return err;
}
strscpy(card->driver, "SoundScape");
@@ -1040,41 +1098,99 @@ static int create_sscape(int dev, struct snd_card *card)
err = sscape_upload_microcode(card, err);
if (err == 0) {
- err = create_mpu401(card, MIDI_DEVNUM, port[dev],
- mpu_irq[dev]);
+ err = create_mpu401(card, MIDI_DEVNUM, sscape->io_base,
+ sscape->mpu_irq);
if (err < 0) {
dev_err(card->dev,
"sscape: Failed to create MPU-401 device at 0x%lx\n",
- port[dev]);
+ (unsigned long)sscape->io_base);
return err;
}
- /*
- * Initialize mixer
- */
- guard(spinlock_irqsave)(&sscape->lock);
sscape->midi_vol = 0;
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_SET_MIDI_VOL, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- sscape->midi_vol, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_XXX_MIDI_VOL, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- sscape->midi_vol, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_SET_EXTMIDI, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- 0, 100);
- host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
-
- set_midi_mode_unsafe(sscape->io_base);
+ sscape->midi_enabled = true;
+ err = sscape_restore_midi_state(sscape);
+ if (err < 0)
+ dev_warn(card->dev,
+ "sscape: MIDI init incomplete: %d\n",
+ err);
}
}
return 0;
}
+#ifdef CONFIG_PM
+/*
+ * Reload the MIDI firmware and restore the saved MIDI state for
+ * boards whose MPU-401 side was enabled during probe.
+ */
+static int sscape_resume_midi(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ int err, version;
+
+ if (!sscape->midi_enabled)
+ return 0;
+
+ version = sscape_upload_bootblock(card);
+ if (version < 0)
+ return version;
+
+ err = sscape_upload_microcode(card, version);
+ if (err < 0)
+ return err;
+
+ outb(0, sscape->io_base);
+
+ return sscape_restore_midi_state(sscape);
+}
+
+/*
+ * Save the WSS codec state before the SoundScape is suspended.
+ */
+static int snd_sscape_suspend_card(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ sscape->chip->suspend(sscape->chip);
+ return 0;
+}
+
+/*
+ * Restore the board-specific state before resuming the WSS codec.
+ */
+static int snd_sscape_resume_card(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ int err;
+
+ err = sscape_configure_board(sscape);
+ if (err < 0)
+ return err;
+
+ err = sscape_resume_midi(card);
+ if (err < 0)
+ dev_warn(card->dev, "sscape: MIDI restore failed: %d\n", err);
+
+ sscape->chip->resume(sscape->chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_sscape_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_sscape_suspend_card(dev_get_drvdata(dev));
+}
+
+static int snd_sscape_resume(struct device *dev, unsigned int n)
+{
+ return snd_sscape_resume_card(dev_get_drvdata(dev));
+}
+#endif
+
static int snd_sscape_match(struct device *pdev, unsigned int i)
{
@@ -1111,8 +1227,9 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev)
sscape->type = SSCAPE;
dma[dev] &= 0x03;
+ sscape_store_settings(sscape, dev);
- ret = create_sscape(dev, card);
+ ret = create_sscape(card);
if (ret < 0)
return ret;
@@ -1130,7 +1247,10 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev)
static struct isa_driver snd_sscape_driver = {
.match = snd_sscape_match,
.probe = snd_sscape_probe,
- /* FIXME: suspend/resume */
+#ifdef CONFIG_PM
+ .suspend = snd_sscape_suspend,
+ .resume = snd_sscape_resume,
+#endif
.driver = {
.name = DEV_NAME
},
@@ -1211,8 +1331,9 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard,
wss_port[idx] = pnp_port_start(dev, 1);
dma2[idx] = pnp_dma(dev, 1);
}
+ sscape_store_settings(sscape, idx);
- ret = create_sscape(idx, card);
+ ret = create_sscape(card);
if (ret < 0)
return ret;
@@ -1227,11 +1348,27 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard,
return 0;
}
+#ifdef CONFIG_PM
+static int sscape_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_sscape_suspend_card(pnp_get_card_drvdata(pcard));
+}
+
+static int sscape_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_sscape_resume_card(pnp_get_card_drvdata(pcard));
+}
+#endif
+
static struct pnp_card_driver sscape_pnpc_driver = {
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
.name = "sscape",
.id_table = sscape_pnpids,
.probe = sscape_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = sscape_pnp_suspend,
+ .resume = sscape_pnp_resume,
+#endif
};
#endif /* CONFIG_PNP */
diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c
index 3a64d0562803..b1c7ed7f1604 100644
--- a/sound/pci/asihpi/asihpi.c
+++ b/sound/pci/asihpi/asihpi.c
@@ -1362,6 +1362,7 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,
struct hpi_control *hpi_ctl,
char *name)
{
+ int len;
char *dir;
memset(snd_control, 0, sizeof(*snd_control));
snd_control->name = hpi_ctl->name;
@@ -1384,23 +1385,30 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control,
dir = "Playback "; /* PCM Playback source, or output node */
if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type)
- sprintf(hpi_ctl->name, "%s %d %s %d %s%s",
- asihpi_src_names[hpi_ctl->src_node_type],
- hpi_ctl->src_node_index,
- asihpi_dst_names[hpi_ctl->dst_node_type],
- hpi_ctl->dst_node_index,
- dir, name);
+ len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name),
+ "%s %d %s %d %s%s",
+ asihpi_src_names[hpi_ctl->src_node_type],
+ hpi_ctl->src_node_index,
+ asihpi_dst_names[hpi_ctl->dst_node_type],
+ hpi_ctl->dst_node_index,
+ dir, name);
else if (hpi_ctl->dst_node_type) {
- sprintf(hpi_ctl->name, "%s %d %s%s",
- asihpi_dst_names[hpi_ctl->dst_node_type],
- hpi_ctl->dst_node_index,
- dir, name);
+ len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name),
+ "%s %d %s%s",
+ asihpi_dst_names[hpi_ctl->dst_node_type],
+ hpi_ctl->dst_node_index,
+ dir, name);
} else {
- sprintf(hpi_ctl->name, "%s %d %s%s",
- asihpi_src_names[hpi_ctl->src_node_type],
- hpi_ctl->src_node_index,
- dir, name);
+ len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name),
+ "%s %d %s%s",
+ asihpi_src_names[hpi_ctl->src_node_type],
+ hpi_ctl->src_node_index,
+ dir, name);
}
+
+ if (len >= sizeof(hpi_ctl->name))
+ pr_err("asihpi: truncated control name: %s\n",
+ hpi_ctl->name);
}
/*------------------------------------------------------------
diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c
index da2667cb2489..02fe09330939 100644
--- a/sound/pci/ctxfi/ctatc.c
+++ b/sound/pci/ctxfi/ctatc.c
@@ -794,7 +794,8 @@ static int spdif_passthru_playback_get_resources(struct ct_atc *atc,
struct src *src;
int err;
int n_amixer = apcm->substream->runtime->channels, i;
- unsigned int pitch, rsr = atc->pll_rate;
+ unsigned int pitch;
+ unsigned int rsr = atc->pll_rate ? atc->pll_rate : atc->rsr;
/* first release old resources */
atc_pcm_release_resources(atc, apcm);
@@ -983,6 +984,11 @@ static int atc_select_mic_in(struct ct_atc *atc)
return 0;
}
+static inline enum DAIOTYP atc_spdif_in_type(struct ct_atc *atc)
+{
+ return (atc->model == CTSB073X) ? SPDIFI_BAY : SPDIFIO;
+}
+
static struct capabilities atc_capabilities(struct ct_atc *atc)
{
struct hw *hw = atc->hw;
@@ -1121,7 +1127,7 @@ static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state)
static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state)
{
- return atc_daio_unmute(atc, state, SPDIFIO);
+ return atc_daio_unmute(atc, state, atc_spdif_in_type(atc));
}
static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status)
@@ -1404,9 +1410,11 @@ static int atc_get_resources(struct ct_atc *atc)
struct sum_desc sum_dsc = {0};
struct sum_mgr *sum_mgr;
struct capabilities cap;
+ int atc_srcs_limit;
int err, i;
cap = atc->capabilities(atc);
+ atc_srcs_limit = cap.dedicated_mic ? NUM_ATC_SRCS : 4;
atc->daios = kcalloc(NUM_DAIOTYP, sizeof(void *), GFP_KERNEL);
if (!atc->daios)
@@ -1427,14 +1435,12 @@ static int atc_get_resources(struct ct_atc *atc)
daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO];
da_desc.msr = atc->msr;
for (i = 0; i < NUM_DAIOTYP; i++) {
- if (((i == MIC) && !cap.dedicated_mic) ||
- ((i == RCA) && !cap.dedicated_rca) ||
- i == SPDIFI1)
+ if (((i == SPDIFIO) && (atc->model == CTSB073X)) ||
+ ((i == SPDIFI_BAY) && (atc->model != CTSB073X)) ||
+ ((i == MIC) && !cap.dedicated_mic) ||
+ ((i == RCA) && !cap.dedicated_rca))
continue;
- if (atc->model == CTSB073X && i == SPDIFIO)
- da_desc.type = SPDIFI1;
- else
- da_desc.type = i;
+ da_desc.type = i;
da_desc.output = (i < LINEIM) || (i == RCA);
err = daio_mgr->get_daio(daio_mgr, &da_desc,
(struct daio **)&atc->daios[i]);
@@ -1450,9 +1456,7 @@ static int atc_get_resources(struct ct_atc *atc)
src_dsc.multi = 1;
src_dsc.msr = atc->msr;
src_dsc.mode = ARCRW;
- for (i = 0; i < NUM_ATC_SRCS; i++) {
- if (((i > 3) && !cap.dedicated_mic))
- continue;
+ for (i = 0; i < atc_srcs_limit; i++) {
err = src_mgr->get_src(src_mgr, &src_dsc,
(struct src **)&atc->srcs[i]);
if (err)
@@ -1461,9 +1465,7 @@ static int atc_get_resources(struct ct_atc *atc)
srcimp_mgr = atc->rsc_mgrs[SRCIMP];
srcimp_dsc.msr = 8;
- for (i = 0; i < NUM_ATC_SRCS; i++) {
- if (((i > 3) && !cap.dedicated_mic))
- continue;
+ for (i = 0; i < atc_srcs_limit; i++) {
err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc,
(struct srcimp **)&atc->srcimps[i]);
if (err)
@@ -1569,7 +1571,7 @@ static void atc_connect_resources(struct ct_atc *atc)
mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc);
}
- dai = container_of(atc->daios[SPDIFIO], struct dai, daio);
+ dai = container_of(atc->daios[atc_spdif_in_type(atc)], struct dai, daio);
atc_connect_dai(atc->rsc_mgrs[SRC], dai,
(struct src **)&atc->srcs[0],
(struct srcimp **)&atc->srcimps[0]);
diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c
index 4dbb1dd7af32..69aacd06716c 100644
--- a/sound/pci/ctxfi/ctdaio.c
+++ b/sound/pci/ctxfi/ctdaio.c
@@ -35,7 +35,7 @@ static const struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = {
[LINEIM] = {.left = 0x1b5, .right = 0x1bd},
[SPDIFOO] = {.left = 0x20, .right = 0x21},
[SPDIFIO] = {.left = 0x15, .right = 0x1d},
- [SPDIFI1] = {.left = 0x95, .right = 0x9d},
+ [SPDIFI_BAY] = {.left = 0x95, .right = 0x9d},
};
static const struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = {
@@ -106,7 +106,7 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw)
switch (type) {
case SPDIFOO: return 0;
case SPDIFIO: return 0;
- case SPDIFI1: return 1;
+ case SPDIFI_BAY: return 1;
case LINEO1: return 4;
case LINEO2: return 7;
case LINEO3: return 5;
@@ -120,7 +120,6 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw)
switch (type) {
case SPDIFOO: return 0;
case SPDIFIO: return 0;
- case SPDIFI1: return 1;
case LINEO1: return 4;
case LINEO2: return 7;
case LINEO3: return 5;
diff --git a/sound/pci/ctxfi/ctdaio.h b/sound/pci/ctxfi/ctdaio.h
index ff77d55539a5..c9f6207fe92f 100644
--- a/sound/pci/ctxfi/ctdaio.h
+++ b/sound/pci/ctxfi/ctdaio.h
@@ -32,7 +32,7 @@ enum DAIOTYP {
SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */
MIC, /* Dedicated mic on Titanium HD */
RCA, /* Dedicated RCA on SE-300PCIE */
- SPDIFI1, /* S/PDIF In on internal Drive Bay */
+ SPDIFI_BAY, /* S/PDIF In on internal drive bay */
NUM_DAIOTYP
};
diff --git a/sound/pci/ctxfi/ctvmem.h b/sound/pci/ctxfi/ctvmem.h
index da54cbcdb0be..43a0065b40c3 100644
--- a/sound/pci/ctxfi/ctvmem.h
+++ b/sound/pci/ctxfi/ctvmem.h
@@ -15,7 +15,7 @@
#ifndef CTVMEM_H
#define CTVMEM_H
-#define CT_PTP_NUM 4 /* num of device page table pages */
+#define CT_PTP_NUM 1 /* num of device page table pages */
#include <linux/mutex.h>
#include <linux/list.h>
diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c
index 5ff78814e687..874f6cd503ca 100644
--- a/sound/usb/6fire/chip.c
+++ b/sound/usb/6fire/chip.c
@@ -53,11 +53,6 @@ static void usb6fire_chip_abort(struct sfire_chip *chip)
usb6fire_comm_abort(chip);
if (chip->control)
usb6fire_control_abort(chip);
- if (chip->card) {
- snd_card_disconnect(chip->card);
- snd_card_free_when_closed(chip->card);
- chip->card = NULL;
- }
}
}
@@ -168,6 +163,7 @@ destroy_chip:
static void usb6fire_chip_disconnect(struct usb_interface *intf)
{
struct sfire_chip *chip;
+ struct snd_card *card;
chip = usb_get_intfdata(intf);
if (chip) { /* if !chip, fw upload has been performed */
@@ -178,8 +174,19 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf)
chips[chip->regidx] = NULL;
}
+ /*
+ * Save card pointer before teardown.
+ * snd_card_free_when_closed() may free card (and
+ * the embedded chip) immediately, so it must be
+ * called last and chip must not be accessed after.
+ */
+ card = chip->card;
chip->shutdown = true;
+ if (card)
+ snd_card_disconnect(card);
usb6fire_chip_abort(chip);
+ if (card)
+ snd_card_free_when_closed(card);
}
}
}
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 270dad84d825..f42d72cd0378 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -631,9 +631,9 @@ static void usb_audio_make_shortname(struct usb_device *dev,
}
/* retrieve the device string as shortname */
- if (!dev->descriptor.iProduct ||
- usb_string(dev, dev->descriptor.iProduct,
- card->shortname, sizeof(card->shortname)) <= 0) {
+ if (dev->product && *dev->product) {
+ strscpy(card->shortname, dev->product);
+ } else {
/* no name available from anywhere, so use ID */
scnprintf(card->shortname, sizeof(card->shortname),
"USB Device %#04x:%#04x",
@@ -668,15 +668,11 @@ static void usb_audio_make_longname(struct usb_device *dev,
else if (quirk && quirk->vendor_name)
s = quirk->vendor_name;
*card->longname = 0;
- if (s && *s) {
- strscpy(card->longname, s, sizeof(card->longname));
- } else {
- /* retrieve the vendor and device strings as longname */
- if (dev->descriptor.iManufacturer)
- usb_string(dev, dev->descriptor.iManufacturer,
- card->longname, sizeof(card->longname));
- /* we don't really care if there isn't any vendor string */
- }
+ if (s && *s)
+ strscpy(card->longname, s);
+ else if (dev->manufacturer && *dev->manufacturer)
+ strscpy(card->longname, dev->manufacturer);
+
if (*card->longname) {
strim(card->longname);
if (*card->longname)
@@ -870,19 +866,25 @@ static void find_last_interface(struct snd_usb_audio *chip)
/* look for the corresponding quirk */
static const struct snd_usb_audio_quirk *
-get_alias_quirk(struct usb_device *dev, unsigned int id)
+get_alias_quirk(struct usb_interface *intf, unsigned int id)
{
const struct usb_device_id *p;
+ struct usb_device_id match_id;
for (p = usb_audio_ids; p->match_flags; p++) {
- /* FIXME: this checks only vendor:product pair in the list */
- if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) ==
- USB_DEVICE_ID_MATCH_DEVICE &&
- p->idVendor == USB_ID_VENDOR(id) &&
- p->idProduct == USB_ID_PRODUCT(id))
- return (const struct snd_usb_audio_quirk *)p->driver_info;
- }
+ if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) !=
+ USB_DEVICE_ID_MATCH_DEVICE)
+ continue;
+ if (p->idVendor != USB_ID_VENDOR(id) ||
+ p->idProduct != USB_ID_PRODUCT(id))
+ continue;
+ match_id = *p;
+ match_id.match_flags &= ~USB_DEVICE_ID_MATCH_DEVICE;
+ if (!match_id.match_flags || usb_match_one_id(intf, &match_id))
+ return (const struct snd_usb_audio_quirk *)
+ p->driver_info;
+ }
return NULL;
}
@@ -931,7 +933,7 @@ static int usb_audio_probe(struct usb_interface *intf,
id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
le16_to_cpu(dev->descriptor.idProduct));
if (get_alias_id(dev, &id))
- quirk = get_alias_quirk(dev, id);
+ quirk = get_alias_quirk(intf, id);
if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
return -ENXIO;
if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE)
diff --git a/sound/usb/format.c b/sound/usb/format.c
index 1207c507882a..030b4307927a 100644
--- a/sound/usb/format.c
+++ b/sound/usb/format.c
@@ -455,6 +455,10 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
if (chip->usb_id == USB_ID(0x194f, 0x010d) &&
!s1810c_valid_sample_rate(fp, rate))
goto skip_rate;
+ /* Filter out invalid rates on Presonus Studio 1824 */
+ if (chip->usb_id == USB_ID(0x194f, 0x0107) &&
+ !s1810c_valid_sample_rate(fp, rate))
+ goto skip_rate;
/* Filter out invalid rates on Focusrite devices */
if (USB_ID_VENDOR(chip->usb_id) == 0x1235 &&
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index a8bddc90c0ed..0a5b8941ebda 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -699,15 +699,18 @@ static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port,
static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep,
struct urb *urb)
{
- int p;
+ int port0 = ep->current_port;
+ int i;
+
+ for (i = 0; i < 0x10; ++i) {
+ int portnum = (port0 + i) & 15;
+ struct usbmidi_out_port *port = &ep->ports[portnum];
- /* FIXME: lower-numbered ports can starve higher-numbered ports */
- for (p = 0; p < 0x10; ++p) {
- struct usbmidi_out_port *port = &ep->ports[p];
if (!port->active)
continue;
while (urb->transfer_buffer_length + 3 < ep->max_transfer) {
uint8_t b;
+
if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) {
port->active = 0;
break;
@@ -715,6 +718,7 @@ static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep,
snd_usbmidi_transmit_byte(port, b, urb);
}
}
+ ep->current_port = (port0 + 1) & 15;
}
static const struct usb_protocol_ops snd_usbmidi_standard_ops = {
diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c
index ef602e81576d..3546ba926cb3 100644
--- a/sound/usb/midi2.c
+++ b/sound/usb/midi2.c
@@ -1057,10 +1057,8 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi)
strscpy(ump->core.name, ump->info.name,
sizeof(ump->core.name));
/* use serial number string as unique UMP product id */
- if (!*ump->info.product_id && dev->descriptor.iSerialNumber)
- usb_string(dev, dev->descriptor.iSerialNumber,
- ump->info.product_id,
- sizeof(ump->info.product_id));
+ if (!*ump->info.product_id && dev->serial && *dev->serial)
+ strscpy(ump->info.product_id, dev->serial);
}
}
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index ac8c71ba9483..d4ef45bf53d7 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1204,6 +1204,13 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
cval->min = -11264; /* Mute under it */
}
break;
+ case USB_ID(0x31b2, 0x0111): /* MOONDROP JU Jiu */
+ if (!strcmp(kctl->id.name, "PCM Playback Volume")) {
+ usb_audio_info(chip,
+ "set volume quirk for MOONDROP JU Jiu\n");
+ cval->min = -10880; /* Mute under it */
+ }
+ break;
}
}
@@ -1226,12 +1233,79 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
}
/*
+ * Additional checks for sticky mixers
+ *
+ * Some devices' volume control mixers are sticky, which accept SET_CUR but
+ * do absolutely nothing.
+ *
+ * Prevent sticky mixers from being registered, otherwise they confuses
+ * userspace and results in ineffective volume control.
+ */
+static int check_sticky_volume_control(struct usb_mixer_elem_info *cval,
+ int channel, int saved)
+{
+ int sticky_test_values[] = { cval->min, cval->max };
+ int test, check, i;
+
+ for (i = 0; i < ARRAY_SIZE(sticky_test_values); i++) {
+ test = sticky_test_values[i];
+ if (test == saved)
+ continue;
+
+ /* Assume non-sticky on failure. */
+ if (snd_usb_set_cur_mix_value(cval, channel, 0, test) ||
+ get_cur_mix_raw(cval, channel, &check) ||
+ check != saved) /* SET_CUR effective, non-sticky. */
+ return 0;
+ }
+
+ usb_audio_err(cval->head.mixer->chip,
+ "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n",
+ cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+ cval->min, cval->max, cval->res, saved);
+
+ return -ENODEV;
+}
+
+/*
+ * Additional checks for the proper resolution
+ *
+ * Some devices report smaller resolutions than actually reacting.
+ * They don't return errors but simply clip to the lower aligned value.
+ */
+static void check_volume_control_res(struct usb_mixer_elem_info *cval,
+ int channel, int saved)
+{
+ int last_valid_res = cval->res;
+ int test, check;
+
+ for (;;) {
+ test = saved;
+ if (test < cval->max)
+ test += cval->res;
+ else
+ test -= cval->res;
+
+ if (test < cval->min || test > cval->max ||
+ snd_usb_set_cur_mix_value(cval, channel, 0, test) ||
+ get_cur_mix_raw(cval, channel, &check)) {
+ cval->res = last_valid_res;
+ break;
+ }
+ if (test == check)
+ break;
+
+ cval->res *= 2;
+ }
+}
+
+/*
* retrieve the minimum and maximum values for the specified control
*/
static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
int default_min, struct snd_kcontrol *kctl)
{
- int i, idx;
+ int i, idx, ret;
/* for failsafe */
cval->min = default_min;
@@ -1257,7 +1331,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
"%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 -EINVAL;
+ return -EAGAIN;
}
if (get_ctl_value(cval, UAC_GET_RES,
(cval->control << 8) | minchn,
@@ -1280,37 +1354,25 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
if (cval->res == 0)
cval->res = 1;
- /* Additional checks for the proper resolution
- *
- * Some devices report smaller resolutions than actually
- * reacting. They don't return errors but simply clip
- * to the lower aligned value.
- */
- if (cval->min + cval->res < cval->max) {
- int last_valid_res = cval->res;
- int saved, test, check;
+ if (cval->min < cval->max) {
+ int saved;
+
if (get_cur_mix_raw(cval, minchn, &saved) < 0)
- goto no_res_check;
- for (;;) {
- test = saved;
- if (test < cval->max)
- test += cval->res;
- else
- test -= cval->res;
- if (test < cval->min || test > cval->max ||
- snd_usb_set_cur_mix_value(cval, minchn, 0, test) ||
- get_cur_mix_raw(cval, minchn, &check)) {
- cval->res = last_valid_res;
- break;
- }
- if (test == check)
- break;
- cval->res *= 2;
+ goto no_checks;
+
+ ret = check_sticky_volume_control(cval, minchn, saved);
+ if (ret < 0) {
+ snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
+ return ret;
}
+
+ if (cval->min + cval->res < cval->max)
+ check_volume_control_res(cval, minchn, saved);
+
snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
}
-no_res_check:
+no_checks:
cval->initialized = 1;
}
@@ -1381,6 +1443,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
+ int ret;
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN)
@@ -1391,8 +1454,9 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
if (cval->val_type != USB_MIXER_BOOLEAN &&
cval->val_type != USB_MIXER_INV_BOOLEAN) {
if (!cval->initialized) {
- get_min_max_with_quirks(cval, 0, kcontrol);
- if (cval->initialized && cval->dBmin >= cval->dBmax) {
+ ret = get_min_max_with_quirks(cval, 0, kcontrol);
+ if ((ret >= 0 || ret == -EAGAIN) &&
+ cval->initialized && cval->dBmin >= cval->dBmax) {
kcontrol->vd[0].access &=
~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
@@ -1660,9 +1724,72 @@ static const struct usb_feature_control_info *get_feature_control_info(int contr
return NULL;
}
+static bool check_insane_volume_range(struct usb_mixer_interface *mixer,
+ struct snd_kcontrol *kctl,
+ struct usb_mixer_elem_info *cval)
+{
+ int range, steps, threshold;
+
+ /*
+ * If a device quirk has overrode our TLV callback, no warning should
+ * be generated since our checks are only meaningful for dB volume.
+ */
+ if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) ||
+ kctl->tlv.c != snd_usb_mixer_vol_tlv)
+ return false;
+
+ /*
+ * Meaningless volume control capability (<1dB). This should cover
+ * devices mapping their volume to val = 0/100/1, which are very likely
+ * to be quirky.
+ */
+ range = cval->max - cval->min;
+ if (range < 256) {
+ usb_audio_warn(mixer->chip,
+ "Warning! Unlikely small volume range (=%u), linear volume or custom curve?",
+ range);
+ return true;
+ }
+
+ steps = range / cval->res;
+
+ /*
+ * There are definitely devices with ~20,000 ranges (e.g., HyperX Cloud
+ * III with val = -18944/0/1), so we use some heuristics here:
+ *
+ * min < 0 < max: Attenuator + amplifier? Likely to be sane
+ *
+ * min < 0 = max: DSP? Voltage attenuator with FW conversion to dB?
+ * Likely to be sane
+ *
+ * min < max < 0: Measured values? Neutral
+ *
+ * min = 0 < max: Oversimplified FW conversion? Linear volume? Likely to
+ * be quirky (e.g., MV-SILICON)
+ *
+ * 0 < min < max: Amplifier with fixed gains? Likely to be quirky
+ * (e.g., Logitech webcam)
+ */
+ if (cval->min < 0 && 0 <= cval->max)
+ threshold = 24576; /* 65535 * (3 / 8) */
+ else if (cval->min < cval->max && cval->max < 0)
+ threshold = 1024;
+ else
+ threshold = 384;
+
+ if (steps > threshold) {
+ usb_audio_warn(mixer->chip,
+ "Warning! Unlikely big volume step count (=%u), linear volume or wrong cval->res?",
+ steps);
+ return true;
+ }
+
+ return false;
+}
+
static void __build_feature_ctl(struct usb_mixer_interface *mixer,
const struct usbmix_name_map *imap,
- unsigned int ctl_mask, int control,
+ u64 ctl_mask, int control,
struct usb_audio_term *iterm,
struct usb_audio_term *oterm,
int unitid, int nameid, int readonly_mask)
@@ -1673,7 +1800,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
struct snd_kcontrol *kctl;
struct usb_mixer_elem_info *cval;
const struct usbmix_name_map *map;
- unsigned int range;
+ int ret;
if (control == UAC_FU_GRAPHIC_EQUALIZER) {
/* FIXME: not supported yet */
@@ -1707,7 +1834,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
cval->master_readonly = readonly_mask;
} else {
int i, c = 0;
- for (i = 0; i < 16; i++)
+ for (i = 0; i < MAX_CHANNELS; i++)
if (ctl_mask & BIT(i))
c++;
cval->channels = c;
@@ -1787,10 +1914,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
}
/* get min/max values */
- get_min_max_with_quirks(cval, 0, kctl);
+ ret = get_min_max_with_quirks(cval, 0, kctl);
/* skip a bogus volume range */
- if (cval->max <= cval->min) {
+ if ((ret < 0 && ret != -EAGAIN) || cval->max <= cval->min) {
usb_audio_dbg(mixer->chip,
"[%d] FU [%s] skipped due to invalid volume\n",
cval->head.id, kctl->id.name);
@@ -1811,29 +1938,21 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl);
- range = (cval->max - cval->min) / cval->res;
- /*
- * There are definitely devices with a range of ~20,000, so let's be
- * conservative and allow for a bit more.
- */
- if (range > 65535) {
- usb_audio_warn(mixer->chip,
- "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.",
- range);
- usb_audio_warn(mixer->chip,
- "[%d] FU [%s] ch = %d, val = %d/%d/%d",
+ if (check_insane_volume_range(mixer, kctl, cval)) {
+ usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
cval->head.id, kctl->id.name, cval->channels,
cval->min, cval->max, cval->res);
+ } else {
+ usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
+ cval->head.id, kctl->id.name, cval->channels,
+ cval->min, cval->max, cval->res);
}
- usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n",
- cval->head.id, kctl->id.name, cval->channels,
- cval->min, cval->max, cval->res);
snd_usb_mixer_add_control(&cval->head, kctl);
}
static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
- unsigned int ctl_mask, int control,
+ u64 ctl_mask, int control,
struct usb_audio_term *iterm, int unitid,
int readonly_mask)
{
@@ -1845,7 +1964,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
}
static void build_feature_ctl_badd(struct usb_mixer_interface *mixer,
- unsigned int ctl_mask, int control, int unitid,
+ u64 ctl_mask, int control, int unitid,
const struct usbmix_name_map *badd_map)
{
__build_feature_ctl(mixer, badd_map, ctl_mask, control,
@@ -2021,7 +2140,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
bmaControls = ftr->bmaControls;
}
- if (channels > 32) {
+ if (channels > MAX_CHANNELS) {
usb_audio_info(state->chip,
"usbmixer: too many channels (%d) in unit %d\n",
channels, unitid);
@@ -2059,7 +2178,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
if (state->mixer->protocol == UAC_VERSION_1) {
/* check all control types */
for (i = 0; i < 10; i++) {
- unsigned int ch_bits = 0;
+ u64 ch_bits = 0;
int control = audio_feature_info[i].control;
for (j = 0; j < channels; j++) {
@@ -2085,7 +2204,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid,
}
} else { /* UAC_VERSION_2/3 */
for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) {
- unsigned int ch_bits = 0;
+ u64 ch_bits = 0;
unsigned int ch_read_only = 0;
int control = audio_feature_info[i].control;
@@ -2172,6 +2291,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
unsigned int i, len;
struct snd_kcontrol *kctl;
const struct usbmix_name_map *map;
+ int ret;
map = find_map(state->map, unitid, 0);
if (check_ignored_ctl(map))
@@ -2194,7 +2314,11 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
}
/* get min/max values */
- get_min_max(cval, 0);
+ ret = get_min_max(cval, 0);
+ if (ret < 0 && ret != -EAGAIN) {
+ usb_mixer_elem_info_free(cval);
+ return;
+ }
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (!kctl) {
@@ -2566,7 +2690,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
break;
}
- get_min_max(cval, valinfo->min_value);
+ err = get_min_max(cval, valinfo->min_value);
break;
}
case USB_XU_CLOCK_RATE:
@@ -2578,11 +2702,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
cval->max = 5;
cval->res = 1;
cval->initialized = 1;
+ err = 0;
break;
default:
- get_min_max(cval, valinfo->min_value);
+ err = get_min_max(cval, valinfo->min_value);
break;
}
+ if (err < 0 && err != -EAGAIN) {
+ usb_mixer_elem_info_free(cval);
+ return err;
+ }
err = get_cur_ctl_value(cval, cval->control << 8, &val);
if (err < 0) {
@@ -3398,7 +3527,7 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer,
[USB_MIXER_U32] = "U32",
[USB_MIXER_BESPOKEN] = "BESPOKEN",
};
- snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, "
+ snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%llx, "
"channels=%i, type=\"%s\"\n", cval->head.id,
cval->control, cval->cmask, cval->channels,
val_types[cval->val_type]);
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h
index 167fbfcf01ac..afbb3dd9f177 100644
--- a/sound/usb/mixer.h
+++ b/sound/usb/mixer.h
@@ -44,7 +44,7 @@ struct usb_mixer_interface {
void (*private_suspend)(struct usb_mixer_interface *mixer);
};
-#define MAX_CHANNELS 16 /* max logical channels */
+#define MAX_CHANNELS 64 /* max logical channels */
enum {
USB_MIXER_BOOLEAN,
@@ -81,7 +81,7 @@ struct usb_mixer_elem_list {
struct usb_mixer_elem_info {
struct usb_mixer_elem_list head;
unsigned int control; /* CS or ICN (high byte) */
- unsigned int cmask; /* channel mask bitmap: 0 = master */
+ u64 cmask; /* channel mask bitmap: 0 = master */
unsigned int idx_off; /* Control index offset */
unsigned int ch_readonly;
unsigned int master_readonly;
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 11e205da7964..a01510a855c2 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -4477,6 +4477,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */
err = snd_sc1810_init_mixer(mixer);
break;
+ case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */
+ err = snd_sc1810_init_mixer(mixer);
+ break;
case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */
err = snd_bbfpro_controls_create(mixer);
break;
@@ -4588,6 +4591,24 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer,
}
}
+static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer,
+ struct usb_mixer_elem_info *cval,
+ struct snd_kcontrol *kctl)
+{
+ if (cval->min == 0 && cval->max == 4096 && cval->res == 1) {
+ /* The final effects will be printed later. */
+ usb_audio_info(mixer->chip, "applying MV-SILICON quirks (0/4096/1 variant)\n");
+
+ /* Respect MIN_MUTE set by module parameters. */
+ if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE))
+ mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL;
+ if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE))
+ mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL;
+ } else {
+ usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant");
+ }
+}
+
/*
* Some Plantronics headsets have control names that don't meet ALSA naming
* standards. This function fixes nonstandard source names. By the time
@@ -4634,6 +4655,25 @@ triggered:
usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name);
}
+static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer,
+ struct usb_mixer_elem_info *cval,
+ struct snd_kcontrol *kctl)
+{
+ static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0);
+
+ if (cval->min_mute) {
+ /*
+ * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+ * resulting in min_mute being a no-op.
+ */
+ usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n");
+ }
+
+ kctl->tlv.p = scale;
+ kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+ kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+}
+
void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
struct usb_mixer_elem_info *cval, int unitid,
struct snd_kcontrol *kctl)
@@ -4645,6 +4685,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
break;
}
+ if (cval->control == UAC_FU_VOLUME &&
+ !strncmp(mixer->chip->card->longname, "MV-SILICON", 10))
+ snd_usb_mv_silicon_quirks(mixer, cval, kctl);
+
/* lowest playback value is muted on some devices */
if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE)
if (strstr(kctl->id.name, "Playback")) {
@@ -4660,6 +4704,21 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer,
"applying capture min mute quirk\n");
cval->min_mute = 1;
}
+
+ if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL)
+ if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) {
+ usb_audio_info(mixer->chip,
+ "applying playback linear volume quirk\n");
+ snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl);
+ }
+
+ if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL)
+ if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) {
+ usb_audio_info(mixer->chip,
+ "applying capture linear volume quirk\n");
+ snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl);
+ }
+
/* ALSA-ify some Plantronics headset control names */
if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f &&
(cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME))
diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c
index 7eac7d1bce64..2e5a8d37ec57 100644
--- a/sound/usb/mixer_s1810c.c
+++ b/sound/usb/mixer_s1810c.c
@@ -362,6 +362,7 @@ static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip)
snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, MIXER_LEVEL_0DB);
break;
+ case USB_ID(0x194f, 0x0107): /* 1824 */
case USB_ID(0x194f, 0x010d): /* 1824c */
/* Set all output faders to unity gain */
a = SC1810C_SEL_OUTPUT;
@@ -685,6 +686,7 @@ int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer)
return ret;
break;
+ case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */
case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */
ret = snd_s1810c_switch_init(mixer, &snd_s1824c_mono_sw);
if (ret < 0)
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index fd1fb668929a..8eaa96222759 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -2262,7 +2262,7 @@ static const struct scarlett2_device_entry scarlett2_devices[] = {
{ USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" },
/* End of list */
- { 0, NULL },
+ { 0, NULL, NULL },
};
/* get the starting port index number for a given port type/direction */
diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c
index 2ac813d57f4f..5f993b88448c 100644
--- a/sound/usb/qcom/qc_audio_offload.c
+++ b/sound/usb/qcom/qc_audio_offload.c
@@ -948,7 +948,7 @@ static int enable_audio_stream(struct snd_usb_substream *subs,
_snd_pcm_hw_params_any(&params);
m = hw_param_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT);
- snd_mask_leave(m, pcm_format);
+ snd_mask_leave(m, (__force unsigned int)pcm_format);
i = hw_param_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS);
snd_interval_setinteger(i);
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index eafc0d73cca1..803e03d4d77b 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -2652,6 +2652,54 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
}
},
+{
+ /*
+ * The AudioBox USB advertises S24_3LE as the only supported format
+ * for both playback and capture. It does not support S16_LE despite
+ * being a USB full-speed device.
+ */
+ USB_DEVICE(0x194f, 0x0301),
+ QUIRK_DRIVER_INFO {
+ .vendor_name = "PreSonus",
+ .product_name = "AudioBox USB",
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_IGNORE(0) },
+ {
+ QUIRK_DATA_AUDIOFORMAT(2) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 2,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ }
+ },
+ {
+ QUIRK_DATA_AUDIOFORMAT(3) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 2,
+ .iface = 3,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = 0,
+ .endpoint = 0x82,
+ .ep_attr = USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000,
+ .rate_min = 44100,
+ .rate_max = 48000,
+ }
+ },
+ QUIRK_COMPOSITE_END
+ }
+ }
+},
#endif /* disabled */
{
@@ -3900,5 +3948,70 @@ YAMAHA_DEVICE(0x7010, "UB99"),
QUIRK_RME_DIGIFACE(0x3f8c),
QUIRK_RME_DIGIFACE(0x3fa0),
+#define QUIRK_AF16RIG(channel_count_, alt_setting_, \
+ low_rate_, high_rate_, pack_size_, \
+ clock_, interface_, endpoint_) \
+ { \
+ QUIRK_DATA_AUDIOFORMAT(interface_) { \
+ .formats = SNDRV_PCM_FMTBIT_S32_LE, \
+ .channels = channel_count_, \
+ .fmt_type = UAC_FORMAT_TYPE_I_PCM, \
+ .fmt_bits = 24, \
+ .fmt_sz = 4, \
+ .iface = interface_, \
+ .altsetting = alt_setting_, \
+ .altset_idx = alt_setting_, \
+ .endpoint = endpoint_, \
+ .ep_attr = USB_ENDPOINT_XFER_ISOC | \
+ USB_ENDPOINT_SYNC_ASYNC, \
+ .datainterval = 1, \
+ .protocol = UAC_VERSION_2, \
+ .maxpacksize = pack_size_, \
+ .rates = SNDRV_PCM_RATE_##low_rate_ | \
+ SNDRV_PCM_RATE_##high_rate_, \
+ .rate_min = low_rate_, \
+ .rate_max = high_rate_, \
+ .nr_rates = 2, \
+ .rate_table = (unsigned int[]) { \
+ low_rate_, high_rate_ }, \
+ .clock = clock_, \
+ } \
+ }
+
+#define QUIRK_AF16RIG_CLOCK(clock) \
+ QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 1, 0x01), \
+ QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 2, 0x81), \
+ QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 1, 0x01), \
+ QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 2, 0x81), \
+ QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 1, 0x01), \
+ QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 2, 0x81)
+
+/* Arturia AudioFuse 16Rig Audio */
+/* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */
+{
+ USB_DEVICE(0x1c75, 0xaf20),
+ QUIRK_DRIVER_INFO {
+ .vendor_name = "Arturia",
+ .product_name = "AF16Rig",
+ QUIRK_DATA_COMPOSITE {
+ { QUIRK_DATA_STANDARD_MIXER(0) },
+ QUIRK_AF16RIG_CLOCK(41), /* Internal clock */
+#if 0
+/* These are disabled because I don't have the required hardware to test
+ * them. I suspect that the ADAT clock might not follow 176400 or 192000
+ * because the AF16Rig won't accept ADAT audio data at those rates.
+ */
+ QUIRK_AF16RIG_CLOCK(43), /* ADAT clock */
+ QUIRK_AF16RIG_CLOCK(44), /* BNC word clock */
+#endif
+ { QUIRK_DATA_IGNORE(3) }, /* Firmware update */
+ QUIRK_COMPOSITE_END
+ }
+ }
+},
+
+#undef QUIRK_AF16RIG_CLOCK
+#undef QUIRK_AF16RIG
+
#undef USB_DEVICE_VENDOR_SPEC
#undef USB_AUDIO_DEVICE
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 4cfa24c06fcd..519d9d1a2a41 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2,8 +2,11 @@
/*
*/
+#include <linux/cleanup.h>
+#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
+#include <linux/string.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/usb/midi.h>
@@ -2135,16 +2138,69 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
/*
* driver behavior quirk flags
*/
+struct usb_string_match {
+ const char *manufacturer;
+ const char *product;
+};
+
struct usb_audio_quirk_flags_table {
u32 id;
u32 flags;
+ const struct usb_string_match *usb_string_match;
};
#define DEVICE_FLG(vid, pid, _flags) \
{ .id = USB_ID(vid, pid), .flags = (_flags) }
#define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags)
+/*
+ * Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts.
+ *
+ * Usage:
+ * // match vid, pid, "manufacturer", and "product"
+ * DEVICE_STRING_FLG(vid, pid, "manufacturer", "product", flags)
+ *
+ * // match vid, pid, "manufacturer", and any product string
+ * DEVICE_STRING_FLG(vid, pid, "manufacturer", NULL, flags)
+ *
+ * // match vid, pid, "manufacturer", and device must have no product string
+ * DEVICE_STRING_FLG(vid, pid, "manufacturer", "", flags)
+ *
+ * // match vid, pid, any manufacturer string, and "product"
+ * DEVICE_STRING_FLG(vid, pid, NULL, "product", flags)
+ *
+ * // match vid, pid, no manufacturer string, and "product"
+ * DEVICE_STRING_FLG(vid, pid, "", "product", flags)
+ *
+ * // match vid, pid, no manufacturer string, and no product string
+ * DEVICE_STRING_FLG(vid, pid, "", "", flags)
+ */
+#define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \
+{ \
+ .id = USB_ID(vid, pid), \
+ .usb_string_match = &(const struct usb_string_match) { \
+ .manufacturer = _manufacturer, \
+ .product = _product, \
+ }, \
+ .flags = (_flags), \
+}
+
+/*
+ * Use as a last resort if using VENDOR_FLG() is prone to VID conflicts.
+ *
+ * Usage:
+ * // match vid, and "manufacturer"
+ * VENDOR_STRING_FLG(vid, "manufacturer", flags)
+ *
+ * // match vid, and device must have no manufacturer string
+ * VENDOR_STRING_FLG(vid, "", flags)
+ */
+#define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \
+ DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags)
+
static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
+ /* Device and string descriptor matches */
+
/* Device matches */
DEVICE_FLG(0x001f, 0x0b21, /* AB13X USB Audio */
QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY),
@@ -2281,6 +2337,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x0d8c, 0x0014, /* C-Media */
QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
+ DEVICE_FLG(0x0e0b, 0xfa01, /* Feaulle Rainbow */
+ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE),
DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */
QUIRK_FLAG_FIXED_RATE),
DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */
@@ -2291,8 +2349,9 @@ 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(0x12d1, 0x3a07, /* Huawei Technologies Co., Ltd. */
- QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE),
+ 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),
DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */
QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16),
DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */
@@ -2421,6 +2480,13 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
QUIRK_FLAG_ALIGN_TRANSFER),
DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */
QUIRK_FLAG_ALIGN_TRANSFER),
+ DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */
+ QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL),
+
+ /* Vendor and string descriptor matches */
+ VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */
+ "MV-SILICON",
+ 0), /* Stop matching */
/* Vendor matches */
VENDOR_FLG(0x045e, /* MS Lifecam */
@@ -2522,6 +2588,8 @@ static const char *const snd_usb_audio_quirk_flag_names[] = {
QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE),
QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE),
QUIRK_STRING_ENTRY(SKIP_IFACE_SETUP),
+ QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL),
+ QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL),
NULL
};
@@ -2578,6 +2646,16 @@ void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip)
if (chip->usb_id == p->id ||
(!USB_ID_PRODUCT(p->id) &&
USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) {
+ /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */
+ if (p->usb_string_match && p->usb_string_match->manufacturer &&
+ strcmp(p->usb_string_match->manufacturer,
+ chip->dev->manufacturer ? chip->dev->manufacturer : ""))
+ continue;
+ if (p->usb_string_match && p->usb_string_match->product &&
+ strcmp(p->usb_string_match->product,
+ chip->dev->product ? chip->dev->product : ""))
+ continue;
+
snd_usb_apply_flag_dbg("builtin table", chip, p->flags);
chip->quirk_flags |= p->flags;
return;
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index d38c39e28f38..2532bf97e05e 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -366,6 +366,8 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor
/*
* TODO: this conversion is not complete, update it
* after adding UAC3 values to asound.h
+ * NOTE: not all UAC3 channel relationship have a
+ * direct ALSA chmap equivalent.
*/
switch (is->bChRelationship) {
case UAC3_CH_MONO:
@@ -390,6 +392,12 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor
case UAC3_CH_FRONT_RIGHT_OF_CENTER:
map = SNDRV_CHMAP_FRC;
break;
+ case UAC3_CH_FRONT_WIDE_LEFT:
+ map = SNDRV_CHMAP_FLW;
+ break;
+ case UAC3_CH_FRONT_WIDE_RIGHT:
+ map = SNDRV_CHMAP_FRW;
+ break;
case UAC3_CH_SIDE_LEFT:
map = SNDRV_CHMAP_SL;
break;
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index 085530cf62d9..58fd07f8c3c9 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -228,6 +228,14 @@ extern bool snd_usb_skip_validation;
* Skip the probe-time interface setup (usb_set_interface,
* init_pitch, init_sample_rate); redundant with
* snd_usb_endpoint_prepare() at stream-open time
+ * QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL
+ * Set linear volume mapping for devices where the playback volume control
+ * value is mapped to voltage (instead of dB) level linearly. In short:
+ * x(raw) = (raw - raw_min) / (raw_max - raw_min); V(x) = k * x;
+ * dB(x) = 20 * log10(x). Overrides QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE
+ * QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL
+ * Similar to QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL, but for capture streams.
+ * Overrides QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE
*/
enum {
@@ -258,6 +266,8 @@ enum {
QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE = 24,
QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE = 25,
QUIRK_TYPE_SKIP_IFACE_SETUP = 26,
+ QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27,
+ QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28,
/* Please also edit snd_usb_audio_quirk_flag_names */
};
@@ -290,5 +300,7 @@ enum {
#define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE)
#define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE)
#define QUIRK_FLAG_SKIP_IFACE_SETUP QUIRK_FLAG(SKIP_IFACE_SETUP)
+#define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL)
+#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL)
#endif /* __USBAUDIO_H */
diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c
index 0cf4fa74e210..94553b61013c 100644
--- a/sound/usb/usx2y/us144mkii.c
+++ b/sound/usb/usx2y/us144mkii.c
@@ -420,7 +420,11 @@ static int tascam_probe(struct usb_interface *intf,
/* The device has two interfaces; we drive both from this driver. */
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
- tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0));
+ struct usb_interface *intf_zero = usb_ifnum_to_if(dev, 0);
+
+ if (!intf_zero)
+ return -ENODEV;
+ tascam = usb_get_intfdata(intf_zero);
if (tascam) {
usb_set_intfdata(intf, tascam);
tascam->iface1 = intf;
diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c
index d221972cd8fb..1a9ff010cb11 100644
--- a/tools/testing/selftests/alsa/utimer-test.c
+++ b/tools/testing/selftests/alsa/utimer-test.c
@@ -15,6 +15,7 @@
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
+#include <errno.h>
#define FRAME_RATE 8000
#define PERIOD_SIZE 4410
@@ -52,7 +53,14 @@ FIXTURE_SETUP(timer_f) {
timer_dev_fd = open("/dev/snd/timer", O_RDONLY);
ASSERT_GE(timer_dev_fd, 0);
- ASSERT_EQ(ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info), 0);
+ if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) {
+ int err = errno;
+
+ close(timer_dev_fd);
+ if (err == ENOTTY || err == ENXIO)
+ SKIP(return, "CONFIG_SND_UTIMER not enabled");
+ ASSERT_EQ(err, 0);
+ }
ASSERT_GE(self->utimer_info->fd, 0);
close(timer_dev_fd);