From cc3c2376412098ca5f8c1a50e61e69620e9f09d1 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sun, 2 Jun 2024 00:26:04 +0100 Subject: [PATCH 01/29] ALSA: xen-front: remove unused struct 'alsa_sndif_hw_param' 'alsa_sndif_hw_param' has been unused since the original commit 1cee559351a7 ("ALSA: xen-front: Implement ALSA virtual sound driver"). Remove it. Signed-off-by: Dr. David Alan Gilbert Link: https://lore.kernel.org/r/20240601232604.198662-1-linux@treblig.org Signed-off-by: Takashi Iwai --- sound/xen/xen_snd_front_alsa.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sound/xen/xen_snd_front_alsa.c b/sound/xen/xen_snd_front_alsa.c index 31b5dc0f34d2..b229eb6f7057 100644 --- a/sound/xen/xen_snd_front_alsa.c +++ b/sound/xen/xen_snd_front_alsa.c @@ -69,11 +69,6 @@ struct alsa_sndif_sample_format { snd_pcm_format_t alsa; }; -struct alsa_sndif_hw_param { - u8 sndif; - snd_pcm_hw_param_t alsa; -}; - static const struct alsa_sndif_sample_format ALSA_SNDIF_FORMATS[] = { { .sndif = XENSND_PCM_FORMAT_U8, From 163f10b2935362f0e8ef8d7fadd0b5aa33e9130f Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Wed, 12 Jun 2024 08:47:07 +0200 Subject: [PATCH 02/29] PCI: Add INTEL_HDA_PTL to pci_ids.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More PCI ids for Intel audio. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Bard Liao Reviewed-by: Péter Ujfalusi Acked-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20240612064709.51141-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Takashi Iwai --- include/linux/pci_ids.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 942a587bb97e..0168c6a60148 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -3112,6 +3112,7 @@ #define PCI_DEVICE_ID_INTEL_HDA_LNL_P 0xa828 #define PCI_DEVICE_ID_INTEL_S21152BB 0xb152 #define PCI_DEVICE_ID_INTEL_HDA_BMG 0xe2f7 +#define PCI_DEVICE_ID_INTEL_HDA_PTL 0xe428 #define PCI_DEVICE_ID_INTEL_HDA_CML_R 0xf0c8 #define PCI_DEVICE_ID_INTEL_HDA_RKL_S 0xf1c8 From 9c7fb8e832b741f6f0c9fd4bb2cce05e38d48335 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Wed, 12 Jun 2024 08:47:08 +0200 Subject: [PATCH 03/29] ALSA: hda: hda-intel: add PantherLake support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One more PCI ID. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Bard Liao Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20240612064709.51141-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 3500108f6ba3..b33602e64d17 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -2495,6 +2495,8 @@ static const struct pci_device_id azx_ids[] = { { PCI_DEVICE_DATA(INTEL, HDA_ARL_S, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE) }, /* Arrow Lake */ { PCI_DEVICE_DATA(INTEL, HDA_ARL, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_SKYLAKE) }, + /* Panther Lake */ + { PCI_DEVICE_DATA(INTEL, HDA_PTL, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_LNL) }, /* Apollolake (Broxton-P) */ { PCI_DEVICE_DATA(INTEL, HDA_APL, AZX_DRIVER_SKL | AZX_DCAPS_INTEL_BROXTON) }, /* Gemini-Lake */ From 19765dbef1cda16027559bff2dd1370da87a2eb7 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Wed, 12 Jun 2024 08:47:09 +0200 Subject: [PATCH 04/29] ALSA: hda: intel-dsp-config: Add PTL support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same recipes as LNL Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Bard Liao Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20240612064709.51141-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Takashi Iwai --- sound/hda/intel-dsp-config.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sound/hda/intel-dsp-config.c b/sound/hda/intel-dsp-config.c index 537863447358..a7419d2e912e 100644 --- a/sound/hda/intel-dsp-config.c +++ b/sound/hda/intel-dsp-config.c @@ -543,6 +543,15 @@ static const struct config_entry config_table[] = { .device = PCI_DEVICE_ID_INTEL_HDA_LNL_P, }, #endif + + /* Panther Lake */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_PANTHERLAKE) + { + .flags = FLAG_SOF | FLAG_SOF_ONLY_IF_DMIC_OR_SOUNDWIRE, + .device = PCI_DEVICE_ID_INTEL_HDA_PTL, + }, +#endif + }; static const struct config_entry *snd_intel_dsp_find_config From 960ccf6eafacaa4fc6b244fb91e736113c3fb715 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Mon, 17 Jun 2024 16:41:02 +0100 Subject: [PATCH 05/29] ALSA: hda: hda_component: Introduce component parent structure In preparation for moving duplicated members from the hda_component structure introduce a parent structure that wraps the array of components. This also allows us to confine the knowledge of the maximum number of entries to the hda_component files and eliminate passing that redundant information around and making direct accesses to the array. Signed-off-by: Simon Trimmer Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240617154105.108635-2-simont@opensource.cirrus.com --- sound/pci/hda/hda_component.c | 65 +++++++++++++++++++---------------- sound/pci/hda/hda_component.h | 42 ++++++++++++++-------- sound/pci/hda/patch_realtek.c | 17 +++++---- 3 files changed, 71 insertions(+), 53 deletions(-) diff --git a/sound/pci/hda/hda_component.c b/sound/pci/hda/hda_component.c index d02589014a3f..b05a0b87d32a 100644 --- a/sound/pci/hda/hda_component.c +++ b/sound/pci/hda/hda_component.c @@ -15,35 +15,39 @@ #include "hda_local.h" #ifdef CONFIG_ACPI -void hda_component_acpi_device_notify(struct hda_component *comps, int num_comps, +void hda_component_acpi_device_notify(struct hda_component_parent *parent, acpi_handle handle, u32 event, void *data) { + struct hda_component *comp; int i; - for (i = 0; i < num_comps; i++) { - if (comps[i].dev && comps[i].acpi_notify) - comps[i].acpi_notify(acpi_device_handle(comps[i].adev), event, - comps[i].dev); + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { + comp = hda_component_from_index(parent, i); + if (comp->dev && comp->acpi_notify) + comp->acpi_notify(acpi_device_handle(comp->adev), event, comp->dev); } } EXPORT_SYMBOL_NS_GPL(hda_component_acpi_device_notify, SND_HDA_SCODEC_COMPONENT); int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, int num_comps, + struct hda_component_parent *parent, acpi_notify_handler handler, void *data) { bool support_notifications = false; struct acpi_device *adev; + struct hda_component *comp; int ret; int i; - adev = comps[0].adev; + adev = parent->comps[0].adev; if (!acpi_device_handle(adev)) return 0; - for (i = 0; i < num_comps; i++) + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { + comp = hda_component_from_index(parent, i); support_notifications = support_notifications || - comps[i].acpi_notifications_supported; + comp->acpi_notifications_supported; + } if (support_notifications) { ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, @@ -61,13 +65,13 @@ int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc, EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind_acpi_notifications, SND_HDA_SCODEC_COMPONENT); void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, + struct hda_component_parent *parent, acpi_notify_handler handler) { struct acpi_device *adev; int ret; - adev = comps[0].adev; + adev = parent->comps[0].adev; if (!acpi_device_handle(adev)) return; @@ -78,21 +82,25 @@ void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc, EXPORT_SYMBOL_NS_GPL(hda_component_manager_unbind_acpi_notifications, SND_HDA_SCODEC_COMPONENT); #endif /* ifdef CONFIG_ACPI */ -void hda_component_manager_playback_hook(struct hda_component *comps, int num_comps, int action) +void hda_component_manager_playback_hook(struct hda_component_parent *parent, int action) { + struct hda_component *comp; int i; - for (i = 0; i < num_comps; i++) { - if (comps[i].dev && comps[i].pre_playback_hook) - comps[i].pre_playback_hook(comps[i].dev, action); + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { + comp = hda_component_from_index(parent, i); + if (comp->dev && comp->pre_playback_hook) + comp->pre_playback_hook(comp->dev, action); } - for (i = 0; i < num_comps; i++) { - if (comps[i].dev && comps[i].playback_hook) - comps[i].playback_hook(comps[i].dev, action); + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { + comp = hda_component_from_index(parent, i); + if (comp->dev && comp->playback_hook) + comp->playback_hook(comp->dev, action); } - for (i = 0; i < num_comps; i++) { - if (comps[i].dev && comps[i].post_playback_hook) - comps[i].post_playback_hook(comps[i].dev, action); + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { + comp = hda_component_from_index(parent, i); + if (comp->dev && comp->post_playback_hook) + comp->post_playback_hook(comp->dev, action); } } EXPORT_SYMBOL_NS_GPL(hda_component_manager_playback_hook, SND_HDA_SCODEC_COMPONENT); @@ -124,22 +132,21 @@ static int hda_comp_match_dev_name(struct device *dev, void *data) } int hda_component_manager_bind(struct hda_codec *cdc, - struct hda_component *comps, int count) + struct hda_component_parent *parent) { int i; - /* Init shared data */ - for (i = 0; i < count; ++i) { - memset(&comps[i], 0, sizeof(comps[i])); - comps[i].codec = cdc; - } + /* Init shared and component specific data */ + memset(parent, 0, sizeof(*parent)); + for (i = 0; i < ARRAY_SIZE(parent->comps); i++) + parent->comps[i].codec = cdc; - return component_bind_all(hda_codec_dev(cdc), comps); + return component_bind_all(hda_codec_dev(cdc), &parent->comps); } EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind, SND_HDA_SCODEC_COMPONENT); int hda_component_manager_init(struct hda_codec *cdc, - struct hda_component *comps, int count, + struct hda_component_parent *parent, int count, const char *bus, const char *hid, const char *match_str, const struct component_master_ops *ops) diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index c70b3de68ab2..a016f1b942a2 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -28,18 +28,21 @@ struct hda_component { void (*post_playback_hook)(struct device *dev, int action); }; +struct hda_component_parent { + struct hda_component comps[HDA_MAX_COMPONENTS]; +}; + #ifdef CONFIG_ACPI -void hda_component_acpi_device_notify(struct hda_component *comps, int num_comps, +void hda_component_acpi_device_notify(struct hda_component_parent *parent, acpi_handle handle, u32 event, void *data); int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, int num_comps, + struct hda_component_parent *parent, acpi_notify_handler handler, void *data); void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, + struct hda_component_parent *parent, acpi_notify_handler handler); #else -static inline void hda_component_acpi_device_notify(struct hda_component *comps, - int num_comps, +static inline void hda_component_acpi_device_notify(struct hda_component_parent *parent, acpi_handle handle, u32 event, void *data) @@ -47,8 +50,7 @@ static inline void hda_component_acpi_device_notify(struct hda_component *comps, } static inline int hda_component_manager_bind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, - int num_comps, + struct hda_component_parent *parent, acpi_notify_handler handler, void *data) @@ -57,17 +59,16 @@ static inline int hda_component_manager_bind_acpi_notifications(struct hda_codec } static inline void hda_component_manager_unbind_acpi_notifications(struct hda_codec *cdc, - struct hda_component *comps, + struct hda_component_parent *parent, acpi_notify_handler handler) { } #endif /* ifdef CONFIG_ACPI */ -void hda_component_manager_playback_hook(struct hda_component *comps, int num_comps, - int action); +void hda_component_manager_playback_hook(struct hda_component_parent *parent, int action); int hda_component_manager_init(struct hda_codec *cdc, - struct hda_component *comps, int count, + struct hda_component_parent *parent, int count, const char *bus, const char *hid, const char *match_str, const struct component_master_ops *ops); @@ -75,13 +76,24 @@ int hda_component_manager_init(struct hda_codec *cdc, void hda_component_manager_free(struct hda_codec *cdc, const struct component_master_ops *ops); -int hda_component_manager_bind(struct hda_codec *cdc, - struct hda_component *comps, int count); +int hda_component_manager_bind(struct hda_codec *cdc, struct hda_component_parent *parent); + +static inline struct hda_component *hda_component_from_index(struct hda_component_parent *parent, + int index) +{ + if (!parent) + return NULL; + + if (index < 0 || index >= ARRAY_SIZE(parent->comps)) + return NULL; + + return &parent->comps[index]; +} static inline void hda_component_manager_unbind(struct hda_codec *cdc, - struct hda_component *comps) + struct hda_component_parent *parent) { - component_unbind_all(hda_codec_dev(cdc), comps); + component_unbind_all(hda_codec_dev(cdc), &parent->comps); } #endif /* ifndef __HDA_COMPONENT_H__ */ diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index e2dbcf8f5bcf..e8f931baae4f 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -131,7 +131,7 @@ struct alc_spec { u8 alc_mute_keycode_map[1]; /* component binding */ - struct hda_component comps[HDA_MAX_COMPONENTS]; + struct hda_component_parent comps; }; /* @@ -6793,8 +6793,7 @@ static void comp_acpi_device_notify(acpi_handle handle, u32 event, void *data) codec_info(cdc, "ACPI Notification %d\n", event); - hda_component_acpi_device_notify(spec->comps, ARRAY_SIZE(spec->comps), - handle, event, data); + hda_component_acpi_device_notify(&spec->comps, handle, event, data); } static int comp_bind(struct device *dev) @@ -6803,12 +6802,12 @@ static int comp_bind(struct device *dev) struct alc_spec *spec = cdc->spec; int ret; - ret = hda_component_manager_bind(cdc, spec->comps, ARRAY_SIZE(spec->comps)); + ret = hda_component_manager_bind(cdc, &spec->comps); if (ret) return ret; return hda_component_manager_bind_acpi_notifications(cdc, - spec->comps, ARRAY_SIZE(spec->comps), + &spec->comps, comp_acpi_device_notify, cdc); } @@ -6817,8 +6816,8 @@ static void comp_unbind(struct device *dev) struct hda_codec *cdc = dev_to_hda_codec(dev); struct alc_spec *spec = cdc->spec; - hda_component_manager_unbind_acpi_notifications(cdc, spec->comps, comp_acpi_device_notify); - hda_component_manager_unbind(cdc, spec->comps); + hda_component_manager_unbind_acpi_notifications(cdc, &spec->comps, comp_acpi_device_notify); + hda_component_manager_unbind(cdc, &spec->comps); } static const struct component_master_ops comp_master_ops = { @@ -6831,7 +6830,7 @@ static void comp_generic_playback_hook(struct hda_pcm_stream *hinfo, struct hda_ { struct alc_spec *spec = cdc->spec; - hda_component_manager_playback_hook(spec->comps, ARRAY_SIZE(spec->comps), action); + hda_component_manager_playback_hook(&spec->comps, action); } static void comp_generic_fixup(struct hda_codec *cdc, int action, const char *bus, @@ -6842,7 +6841,7 @@ static void comp_generic_fixup(struct hda_codec *cdc, int action, const char *bu switch (action) { case HDA_FIXUP_ACT_PRE_PROBE: - ret = hda_component_manager_init(cdc, spec->comps, count, bus, hid, + ret = hda_component_manager_init(cdc, &spec->comps, count, bus, hid, match_str, &comp_master_ops); if (ret) return; From 1adf91011f60e6d992cb1294e77a872845a390ed Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Mon, 17 Jun 2024 16:41:03 +0100 Subject: [PATCH 06/29] ALSA: hda: hda_component: Change codecs to use component parent structure Change the hda_component binding APIs to pass the hds_component_parent as the parameter so the array of components can be abstracted. Signed-off-by: Simon Trimmer Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240617154105.108635-3-simont@opensource.cirrus.com --- sound/pci/hda/cs35l41_hda.c | 42 +++++++++++++++++++-------------- sound/pci/hda/cs35l56_hda.c | 25 +++++++++++--------- sound/pci/hda/hda_component.c | 2 +- sound/pci/hda/hda_component.h | 2 +- sound/pci/hda/tas2781_hda_i2c.c | 31 ++++++++++++------------ 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index 031703f010be..ceba4f2c85f1 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -1419,27 +1419,28 @@ static void cs35l41_acpi_device_notify(acpi_handle handle, u32 event, struct dev static int cs35l41_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); - struct hda_component *comps = master_data; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; unsigned int sleep_flags; int ret = 0; - if (!comps || cs35l41->index < 0 || cs35l41->index >= HDA_MAX_COMPONENTS) + comp = hda_component_from_index(parent, cs35l41->index); + if (!comp) return -EINVAL; - comps = &comps[cs35l41->index]; - if (comps->dev) + if (comp->dev) return -EBUSY; pm_runtime_get_sync(dev); mutex_lock(&cs35l41->fw_mutex); - comps->dev = dev; + comp->dev = dev; if (!cs35l41->acpi_subsystem_id) cs35l41->acpi_subsystem_id = kasprintf(GFP_KERNEL, "%.8x", - comps->codec->core.subsystem_id); - cs35l41->codec = comps->codec; - strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + comp->codec->core.subsystem_id); + cs35l41->codec = comp->codec; + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT; @@ -1454,13 +1455,13 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas ret = cs35l41_create_controls(cs35l41); - comps->playback_hook = cs35l41_hda_playback_hook; - comps->pre_playback_hook = cs35l41_hda_pre_playback_hook; - comps->post_playback_hook = cs35l41_hda_post_playback_hook; - comps->acpi_notify = cs35l41_acpi_device_notify; - comps->adev = cs35l41->dacpi; + comp->playback_hook = cs35l41_hda_playback_hook; + comp->pre_playback_hook = cs35l41_hda_pre_playback_hook; + comp->post_playback_hook = cs35l41_hda_post_playback_hook; + comp->acpi_notify = cs35l41_acpi_device_notify; + comp->adev = cs35l41->dacpi; - comps->acpi_notifications_supported = cs35l41_dsm_supported(acpi_device_handle(comps->adev), + comp->acpi_notifications_supported = cs35l41_dsm_supported(acpi_device_handle(comp->adev), CS35L41_DSM_GET_MUTE); cs35l41->mute_override = cs35l41_get_acpi_mute_state(cs35l41, @@ -1469,7 +1470,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas mutex_unlock(&cs35l41->fw_mutex); sleep_flags = lock_system_sleep(); - if (!device_link_add(&comps->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) + if (!device_link_add(&comp->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) dev_warn(dev, "Unable to create device link\n"); unlock_system_sleep(sleep_flags); @@ -1489,14 +1490,19 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas static void cs35l41_hda_unbind(struct device *dev, struct device *master, void *master_data) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); - struct hda_component *comps = master_data; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; unsigned int sleep_flags; - if (comps[cs35l41->index].dev == dev) { - memset(&comps[cs35l41->index], 0, sizeof(*comps)); + comp = hda_component_from_index(parent, cs35l41->index); + if (!comp) + return; + + if (comp->dev == dev) { sleep_flags = lock_system_sleep(); device_link_remove(&cs35l41->codec->core.dev, cs35l41->dev); unlock_system_sleep(sleep_flags); + memset(comp, 0, sizeof(*comp)); } } diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c index e134ede6c5aa..df4498c77171 100644 --- a/sound/pci/hda/cs35l56_hda.c +++ b/sound/pci/hda/cs35l56_hda.c @@ -685,20 +685,21 @@ err_pm_put: static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data) { struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); - struct hda_component *comps = master_data; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; int ret; - if (!comps || cs35l56->index < 0 || cs35l56->index >= HDA_MAX_COMPONENTS) + comp = hda_component_from_index(parent, cs35l56->index); + if (!comp) return -EINVAL; - comps = &comps[cs35l56->index]; - if (comps->dev) + if (comp->dev) return -EBUSY; - comps->dev = dev; - cs35l56->codec = comps->codec; - strscpy(comps->name, dev_name(dev), sizeof(comps->name)); - comps->playback_hook = cs35l56_hda_playback_hook; + comp->dev = dev; + cs35l56->codec = comp->codec; + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); + comp->playback_hook = cs35l56_hda_playback_hook; ret = cs35l56_hda_fw_load(cs35l56); if (ret) @@ -720,7 +721,8 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas static void cs35l56_hda_unbind(struct device *dev, struct device *master, void *master_data) { struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); - struct hda_component *comps = master_data; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; cs35l56_hda_remove_controls(cs35l56); @@ -732,8 +734,9 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * if (cs35l56->base.fw_patched) cs_dsp_power_down(&cs35l56->cs_dsp); - if (comps[cs35l56->index].dev == dev) - memset(&comps[cs35l56->index], 0, sizeof(*comps)); + comp = hda_component_from_index(parent, cs35l56->index); + if (comp && (comp->dev == dev)) + memset(comp, 0, sizeof(*comp)); cs35l56->codec = NULL; diff --git a/sound/pci/hda/hda_component.c b/sound/pci/hda/hda_component.c index b05a0b87d32a..8c11c8b37799 100644 --- a/sound/pci/hda/hda_component.c +++ b/sound/pci/hda/hda_component.c @@ -141,7 +141,7 @@ int hda_component_manager_bind(struct hda_codec *cdc, for (i = 0; i < ARRAY_SIZE(parent->comps); i++) parent->comps[i].codec = cdc; - return component_bind_all(hda_codec_dev(cdc), &parent->comps); + return component_bind_all(hda_codec_dev(cdc), parent); } EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind, SND_HDA_SCODEC_COMPONENT); diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index a016f1b942a2..e547e1f1e674 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -93,7 +93,7 @@ static inline struct hda_component *hda_component_from_index(struct hda_componen static inline void hda_component_manager_unbind(struct hda_codec *cdc, struct hda_component_parent *parent) { - component_unbind_all(hda_codec_dev(cdc), &parent->comps); + component_unbind_all(hda_codec_dev(cdc), parent); } #endif /* ifndef __HDA_COMPONENT_H__ */ diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index fdee6592c502..c6c1e8e81972 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -706,20 +706,20 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, void *master_data) { struct tas2781_hda *tas_hda = dev_get_drvdata(dev); - struct hda_component *comps = master_data; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; struct hda_codec *codec; unsigned int subid; int ret; - if (!comps || tas_hda->priv->index < 0 || - tas_hda->priv->index >= HDA_MAX_COMPONENTS) + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (!comp) return -EINVAL; - comps = &comps[tas_hda->priv->index]; - if (comps->dev) + if (comp->dev) return -EBUSY; - codec = comps->codec; + codec = comp->codec; subid = codec->core.subsystem_id >> 16; switch (subid) { @@ -733,13 +733,13 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, pm_runtime_get_sync(dev); - comps->dev = dev; + comp->dev = dev; - strscpy(comps->name, dev_name(dev), sizeof(comps->name)); + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); ret = tascodec_init(tas_hda->priv, codec, THIS_MODULE, tasdev_fw_ready); if (!ret) - comps->playback_hook = tas2781_hda_playback_hook; + comp->playback_hook = tas2781_hda_playback_hook; pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); @@ -751,13 +751,14 @@ static void tas2781_hda_unbind(struct device *dev, struct device *master, void *master_data) { struct tas2781_hda *tas_hda = dev_get_drvdata(dev); - struct hda_component *comps = master_data; - comps = &comps[tas_hda->priv->index]; + struct hda_component_parent *parent = master_data; + struct hda_component *comp; - if (comps->dev == dev) { - comps->dev = NULL; - memset(comps->name, 0, sizeof(comps->name)); - comps->playback_hook = NULL; + comp = hda_component_from_index(parent, tas_hda->priv->index); + if (comp && (comp->dev == dev)) { + comp->dev = NULL; + memset(comp->name, 0, sizeof(comp->name)); + comp->playback_hook = NULL; } tas2781_hda_remove_controls(tas_hda); From 3b2a8582876d0b1ff1d8df9ff3cca03a70268fe2 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Mon, 17 Jun 2024 16:41:04 +0100 Subject: [PATCH 07/29] ALSA: hda: hda_component: Move codec field into the parent There is one codec shared across all of the bound HDA components and a copy is usually stashed in the amp driver so it doesn't need to be in every hda_component. Signed-off-by: Simon Trimmer Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240617154105.108635-4-simont@opensource.cirrus.com --- sound/pci/hda/cs35l41_hda.c | 7 ++++--- sound/pci/hda/cs35l56_hda.c | 2 +- sound/pci/hda/hda_component.c | 5 +---- sound/pci/hda/hda_component.h | 2 +- sound/pci/hda/tas2781_hda_i2c.c | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index ceba4f2c85f1..ee9f83b737de 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -1436,10 +1436,11 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas mutex_lock(&cs35l41->fw_mutex); comp->dev = dev; + cs35l41->codec = parent->codec; if (!cs35l41->acpi_subsystem_id) cs35l41->acpi_subsystem_id = kasprintf(GFP_KERNEL, "%.8x", - comp->codec->core.subsystem_id); - cs35l41->codec = comp->codec; + cs35l41->codec->core.subsystem_id); + strscpy(comp->name, dev_name(dev), sizeof(comp->name)); cs35l41->firmware_type = HDA_CS_DSP_FW_SPK_PROT; @@ -1470,7 +1471,7 @@ static int cs35l41_hda_bind(struct device *dev, struct device *master, void *mas mutex_unlock(&cs35l41->fw_mutex); sleep_flags = lock_system_sleep(); - if (!device_link_add(&comp->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) + if (!device_link_add(&cs35l41->codec->core.dev, cs35l41->dev, DL_FLAG_STATELESS)) dev_warn(dev, "Unable to create device link\n"); unlock_system_sleep(sleep_flags); diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c index df4498c77171..cc4aa90db1d1 100644 --- a/sound/pci/hda/cs35l56_hda.c +++ b/sound/pci/hda/cs35l56_hda.c @@ -697,7 +697,7 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas return -EBUSY; comp->dev = dev; - cs35l56->codec = comp->codec; + cs35l56->codec = parent->codec; strscpy(comp->name, dev_name(dev), sizeof(comp->name)); comp->playback_hook = cs35l56_hda_playback_hook; diff --git a/sound/pci/hda/hda_component.c b/sound/pci/hda/hda_component.c index 8c11c8b37799..1a9950b76866 100644 --- a/sound/pci/hda/hda_component.c +++ b/sound/pci/hda/hda_component.c @@ -134,12 +134,9 @@ static int hda_comp_match_dev_name(struct device *dev, void *data) int hda_component_manager_bind(struct hda_codec *cdc, struct hda_component_parent *parent) { - int i; - /* Init shared and component specific data */ memset(parent, 0, sizeof(*parent)); - for (i = 0; i < ARRAY_SIZE(parent->comps); i++) - parent->comps[i].codec = cdc; + parent->codec = cdc; return component_bind_all(hda_codec_dev(cdc), parent); } diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index e547e1f1e674..dd4dabeae9ee 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -19,7 +19,6 @@ struct hda_component { struct device *dev; char name[HDA_MAX_NAME_SIZE]; - struct hda_codec *codec; struct acpi_device *adev; bool acpi_notifications_supported; void (*acpi_notify)(acpi_handle handle, u32 event, struct device *dev); @@ -29,6 +28,7 @@ struct hda_component { }; struct hda_component_parent { + struct hda_codec *codec; struct hda_component comps[HDA_MAX_COMPONENTS]; }; diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index c6c1e8e81972..d7af4fd10714 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -719,7 +719,7 @@ static int tas2781_hda_bind(struct device *dev, struct device *master, if (comp->dev) return -EBUSY; - codec = comp->codec; + codec = parent->codec; subid = codec->core.subsystem_id >> 16; switch (subid) { From 047b9cbbaa8ee3f1d71ff07d181ea6397be97ffe Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Mon, 17 Jun 2024 16:41:05 +0100 Subject: [PATCH 08/29] ALSA: hda: hda_component: Protect shared data with a mutex The hda_component contains information shared from the amp drivers to the codec that can be altered (for example as the driver unloads). Guard the update and use of these to prevent use of stale data. Signed-off-by: Simon Trimmer Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240617154105.108635-5-simont@opensource.cirrus.com --- sound/pci/hda/hda_component.c | 13 ++++++++++++- sound/pci/hda/hda_component.h | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/hda_component.c b/sound/pci/hda/hda_component.c index 1a9950b76866..7b19cb38b4e0 100644 --- a/sound/pci/hda/hda_component.c +++ b/sound/pci/hda/hda_component.c @@ -21,11 +21,13 @@ void hda_component_acpi_device_notify(struct hda_component_parent *parent, struct hda_component *comp; int i; + mutex_lock(&parent->mutex); for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { comp = hda_component_from_index(parent, i); if (comp->dev && comp->acpi_notify) comp->acpi_notify(acpi_device_handle(comp->adev), event, comp->dev); } + mutex_unlock(&parent->mutex); } EXPORT_SYMBOL_NS_GPL(hda_component_acpi_device_notify, SND_HDA_SCODEC_COMPONENT); @@ -87,6 +89,7 @@ void hda_component_manager_playback_hook(struct hda_component_parent *parent, in struct hda_component *comp; int i; + mutex_lock(&parent->mutex); for (i = 0; i < ARRAY_SIZE(parent->comps); i++) { comp = hda_component_from_index(parent, i); if (comp->dev && comp->pre_playback_hook) @@ -102,6 +105,7 @@ void hda_component_manager_playback_hook(struct hda_component_parent *parent, in if (comp->dev && comp->post_playback_hook) comp->post_playback_hook(comp->dev, action); } + mutex_unlock(&parent->mutex); } EXPORT_SYMBOL_NS_GPL(hda_component_manager_playback_hook, SND_HDA_SCODEC_COMPONENT); @@ -134,11 +138,18 @@ static int hda_comp_match_dev_name(struct device *dev, void *data) int hda_component_manager_bind(struct hda_codec *cdc, struct hda_component_parent *parent) { + int ret; + /* Init shared and component specific data */ memset(parent, 0, sizeof(*parent)); + mutex_init(&parent->mutex); parent->codec = cdc; - return component_bind_all(hda_codec_dev(cdc), parent); + mutex_lock(&parent->mutex); + ret = component_bind_all(hda_codec_dev(cdc), parent); + mutex_unlock(&parent->mutex); + + return ret; } EXPORT_SYMBOL_NS_GPL(hda_component_manager_bind, SND_HDA_SCODEC_COMPONENT); diff --git a/sound/pci/hda/hda_component.h b/sound/pci/hda/hda_component.h index dd4dabeae9ee..9f786608144c 100644 --- a/sound/pci/hda/hda_component.h +++ b/sound/pci/hda/hda_component.h @@ -11,6 +11,7 @@ #include #include +#include #include #define HDA_MAX_COMPONENTS 4 @@ -28,6 +29,7 @@ struct hda_component { }; struct hda_component_parent { + struct mutex mutex; struct hda_codec *codec; struct hda_component comps[HDA_MAX_COMPONENTS]; }; @@ -93,7 +95,9 @@ static inline struct hda_component *hda_component_from_index(struct hda_componen static inline void hda_component_manager_unbind(struct hda_codec *cdc, struct hda_component_parent *parent) { + mutex_lock(&parent->mutex); component_unbind_all(hda_codec_dev(cdc), parent); + mutex_unlock(&parent->mutex); } #endif /* ifndef __HDA_COMPONENT_H__ */ From eb882afcfa83991c0b72913e8a18f941a9986b8f Mon Sep 17 00:00:00 2001 From: bo liu Date: Tue, 18 Jun 2024 09:01:21 +0800 Subject: [PATCH 09/29] ALSA: hda/senarytech: add senarytech codec support Add initial Senarytech codec support for SN6186. Note that this hda patch relies on the configuration default registers to be set correctly (normally by BIOS/firmware) in order for it to set up pin widgets properly. Signed-off-by: bo liu Link: https://lore.kernel.org/20240618010121.67335-1-bo.liu@senarytech.com Signed-off-by: Takashi Iwai --- MAINTAINERS | 6 + sound/hda/hdac_device.c | 1 + sound/pci/hda/Kconfig | 11 ++ sound/pci/hda/Makefile | 2 + sound/pci/hda/patch_senarytech.c | 244 +++++++++++++++++++++++++++++++ 5 files changed, 264 insertions(+) create mode 100644 sound/pci/hda/patch_senarytech.c diff --git a/MAINTAINERS b/MAINTAINERS index a6a011792167..82ea8c8f74c1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24949,6 +24949,12 @@ F: mm/zpool.c F: mm/zswap.c F: tools/testing/selftests/cgroup/test_zswap.c +SENARYTECH AUDIO CODEC DRIVER +M: bo liu +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git +F: sound/pci/hda/patch_senarytech.c + THE REST M: Linus Torvalds L: linux-kernel@vger.kernel.org diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index 068c16e52dff..3fbb9793dcfc 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -665,6 +665,7 @@ static const struct hda_vendor_id hda_vendor_ids[] = { { 0x19e5, "Huawei" }, { 0x1aec, "Wolfson Microelectronics" }, { 0x1af4, "QEMU" }, + { 0x1fa8, "Senarytech" }, { 0x434d, "C-Media" }, { 0x8086, "Intel" }, { 0x8384, "SigmaTel" }, diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index 0da625533afc..1252632f0ffa 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -292,6 +292,17 @@ config SND_HDA_CODEC_CONEXANT comment "Set to Y if you want auto-loading the codec driver" depends on SND_HDA=y && SND_HDA_CODEC_CONEXANT=m +config SND_HDA_CODEC_SENARYTECH + tristate "Build Senarytech HD-audio codec support" + select SND_HDA_GENERIC + select SND_HDA_GENERIC_LEDS + help + Say Y or M here to include Senarytech HD-audio codec support in + snd-hda-intel driver, such as SN6186. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_SENARYTECH=m + config SND_HDA_CODEC_CA0110 tristate "Build Creative CA0110-IBG codec support" select SND_HDA_GENERIC diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index 058ca0a289e4..80210f845df2 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -24,6 +24,7 @@ snd-hda-codec-cs8409-y := patch_cs8409.o patch_cs8409-tables.o snd-hda-codec-ca0110-y := patch_ca0110.o snd-hda-codec-ca0132-y := patch_ca0132.o snd-hda-codec-conexant-y := patch_conexant.o +snd-hda-codec-senarytech-y :=patch_senarytech.o snd-hda-codec-via-y := patch_via.o snd-hda-codec-hdmi-y := patch_hdmi.o hda_eld.o @@ -55,6 +56,7 @@ obj-$(CONFIG_SND_HDA_CODEC_CS8409) += snd-hda-codec-cs8409.o obj-$(CONFIG_SND_HDA_CODEC_CA0110) += snd-hda-codec-ca0110.o obj-$(CONFIG_SND_HDA_CODEC_CA0132) += snd-hda-codec-ca0132.o obj-$(CONFIG_SND_HDA_CODEC_CONEXANT) += snd-hda-codec-conexant.o +obj-$(CONFIG_SND_HDA_CODEC_SENARYTECH) += snd-hda-codec-senarytech.o obj-$(CONFIG_SND_HDA_CODEC_VIA) += snd-hda-codec-via.o obj-$(CONFIG_SND_HDA_CODEC_HDMI) += snd-hda-codec-hdmi.o diff --git a/sound/pci/hda/patch_senarytech.c b/sound/pci/hda/patch_senarytech.c new file mode 100644 index 000000000000..0691996fa971 --- /dev/null +++ b/sound/pci/hda/patch_senarytech.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Senary HDA audio codec + * + * Initially based on sound/pci/hda/patch_conexant.c + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_beep.h" +#include "hda_jack.h" +#include "hda_generic.h" + +struct senary_spec { + struct hda_gen_spec gen; + + /* extra EAPD pins */ + unsigned int num_eapds; + hda_nid_t eapds[4]; + hda_nid_t mute_led_eapd; + + unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */ + + int mute_led_polarity; + unsigned int gpio_led; + unsigned int gpio_mute_led_mask; + unsigned int gpio_mic_led_mask; +}; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +/* additional beep mixers; private_value will be overwritten */ +static const struct snd_kcontrol_new senary_beep_mixer[] = { + HDA_CODEC_VOLUME_MONO("Beep Playback Volume", 0, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_BEEP_MONO("Beep Playback Switch", 0, 1, 0, HDA_OUTPUT), +}; + +static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, + int idx, int dir) +{ + struct snd_kcontrol_new *knew; + 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]); + if (!knew) + return -ENOMEM; + knew->private_value = beep_amp; + } + return 0; +} + +static int senary_auto_parse_beep(struct hda_codec *codec) +{ + struct senary_spec *spec = codec->spec; + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) + if ((get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) && + (get_wcaps(codec, nid) & (AC_WCAP_OUT_AMP | AC_WCAP_AMP_OVRD))) + return set_beep_amp(spec, nid, 0, HDA_OUTPUT); + return 0; +} +#else +#define senary_auto_parse_beep(codec) 0 +#endif + +/* parse EAPDs */ +static void senary_auto_parse_eapd(struct hda_codec *codec) +{ + struct senary_spec *spec = codec->spec; + hda_nid_t nid; + + for_each_hda_codec_node(nid, codec) { + if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN) + continue; + if (!(snd_hda_query_pin_caps(codec, nid) & AC_PINCAP_EAPD)) + continue; + spec->eapds[spec->num_eapds++] = nid; + if (spec->num_eapds >= ARRAY_SIZE(spec->eapds)) + break; + } +} + +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); + } +} + +/* turn on/off EAPD according to Master switch */ +static void senary_auto_vmaster_hook(void *private_data, int enabled) +{ + struct hda_codec *codec = private_data; + struct senary_spec *spec = codec->spec; + + senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, enabled); +} + +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, 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); + } +} + +static int senary_auto_init(struct hda_codec *codec) +{ + snd_hda_gen_init(codec); + senary_init_gpio_led(codec); + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); + + return 0; +} + +static void senary_auto_shutdown(struct hda_codec *codec) +{ + struct senary_spec *spec = codec->spec; + + /* Turn the problematic codec into D3 to avoid spurious noises + * from the internal speaker during (and after) reboot + */ + senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, false); +} + +static void senary_auto_free(struct hda_codec *codec) +{ + senary_auto_shutdown(codec); + snd_hda_gen_free(codec); +} + +static int senary_auto_suspend(struct hda_codec *codec) +{ + senary_auto_shutdown(codec); + return 0; +} + +static const struct hda_codec_ops senary_auto_patch_ops = { + .build_controls = snd_hda_gen_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = senary_auto_init, + .free = senary_auto_free, + .unsol_event = snd_hda_jack_unsol_event, + .suspend = senary_auto_suspend, + .check_power_status = snd_hda_gen_check_power_status, +}; + +static int patch_senary_auto(struct hda_codec *codec) +{ + struct senary_spec *spec; + int err; + + codec_info(codec, "%s: BIOS auto-probing.\n", codec->core.chip_name); + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + snd_hda_gen_spec_init(&spec->gen); + codec->spec = spec; + codec->patch_ops = senary_auto_patch_ops; + + 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; + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, + spec->parse_flags); + if (err < 0) + goto error; + + err = senary_auto_parse_beep(codec); + if (err < 0) + goto error; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + goto error; + + /* Some laptops with Senary chips show stalls in S3 resume, + * which falls into the single-cmd mode. + * Better to make reset, then. + */ + if (!codec->bus->core.sync_write) { + codec_info(codec, + "Enable sync_write for stable communication\n"); + codec->bus->core.sync_write = 1; + codec->bus->allow_bus_reset = 1; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + + return 0; + + error: + senary_auto_free(codec); + return err; +} + +/* + */ + +static const struct hda_device_id snd_hda_id_senary[] = { + HDA_CODEC_ENTRY(0x1fa86186, "SN6186", patch_senary_auto), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_senary); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Senarytech HD-audio codec"); + +static struct hda_codec_driver senary_driver = { + .id = snd_hda_id_senary, +}; + +module_hda_codec_driver(senary_driver); From 10457f5042b4890a667e2f15a2e783490dda44d2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 16 Jun 2024 09:34:42 +0200 Subject: [PATCH 10/29] ALSA: vmaster: Return error for invalid input values So far the vmaster code has been tolerant about the input values and accepts any values by correcting internally. But now our own selftest starts complaining about this behavior, so let's be picky and change the behavior to return -EINVAL for invalid input values instead. Reported-by: Paul Menzel Closes: https://lore.kernel.org/r/1d44be36-9bb9-4d82-8953-5ae2a4f09405@molgen.mpg.de Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-2-tiwai@suse.de --- sound/core/vmaster.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/core/vmaster.c b/sound/core/vmaster.c index 04a57f7be6ea..c657659b236c 100644 --- a/sound/core/vmaster.c +++ b/sound/core/vmaster.c @@ -198,6 +198,12 @@ static int follower_put(struct snd_kcontrol *kcontrol, err = follower_init(follower); if (err < 0) return err; + for (ch = 0; ch < follower->info.count; ch++) { + if (ucontrol->value.integer.value[ch] < follower->info.min_val || + ucontrol->value.integer.value[ch] > follower->info.max_val) + return -EINVAL; + } + for (ch = 0; ch < follower->info.count; ch++) { if (follower->vals[ch] != ucontrol->value.integer.value[ch]) { changed = 1; @@ -365,6 +371,8 @@ static int master_put(struct snd_kcontrol *kcontrol, new_val = ucontrol->value.integer.value[0]; if (new_val == old_val) return 0; + if (new_val < master->info.min_val || new_val > master->info.max_val) + return -EINVAL; err = sync_followers(master, old_val, new_val); if (err < 0) From 5bae83007bdd71919a38d5c6db020ef43e2299c6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 16 Jun 2024 09:34:43 +0200 Subject: [PATCH 11/29] ALSA: hda: Return -EINVAL for invalid volume/switch inputs So far the HD-audio driver has been tolerant about the input values and accepts any values by correcting the amp volume and switch values internally. But now our own selftest starts complaining about this behavior, so let's be picky and change the behavior to return -EINVAL for invalid input values instead. Reported-by: Paul Menzel Closes: https://lore.kernel.org/r/1d44be36-9bb9-4d82-8953-5ae2a4f09405@molgen.mpg.de Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-3-tiwai@suse.de --- sound/pci/hda/hda_codec.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 325e8f0b99a8..3dd1bda0c5c6 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -1496,7 +1496,7 @@ update_amp_value(struct hda_codec *codec, hda_nid_t nid, /* ofs = 0: raw max value */ maxval = get_amp_max_value(codec, nid, dir, 0); if (val > maxval) - val = maxval; + return -EINVAL; return snd_hda_codec_amp_update(codec, nid, ch, dir, idx, HDA_AMP_VOLMASK, val); } @@ -1547,13 +1547,21 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, unsigned int ofs = get_amp_offset(kcontrol); long *valp = ucontrol->value.integer.value; int change = 0; + int err; if (chs & 1) { - change = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp); + err = update_amp_value(codec, nid, 0, dir, idx, ofs, *valp); + if (err < 0) + return err; + change |= err; valp++; } - if (chs & 2) - change |= update_amp_value(codec, nid, 1, dir, idx, ofs, *valp); + if (chs & 2) { + err = update_amp_value(codec, nid, 1, dir, idx, ofs, *valp); + if (err < 0) + return err; + change |= err; + } return change; } EXPORT_SYMBOL_GPL(snd_hda_mixer_amp_volume_put); @@ -2149,15 +2157,20 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, int change = 0; if (chs & 1) { + if (*valp < 0 || *valp > 1) + return -EINVAL; change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, HDA_AMP_MUTE, *valp ? 0 : HDA_AMP_MUTE); valp++; } - if (chs & 2) + if (chs & 2) { + if (*valp < 0 || *valp > 1) + return -EINVAL; change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, HDA_AMP_MUTE, *valp ? 0 : HDA_AMP_MUTE); + } hda_call_check_power_status(codec, nid); return change; } From 50ed081284fe2bfd1f25e8b92f4f6a4990e73c0a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 16 Jun 2024 09:34:44 +0200 Subject: [PATCH 12/29] ALSA: control: Apply sanity check of input values for user elements Although we have already a mechanism for sanity checks of input values for control writes, it's not applied unless the kconfig CONFIG_SND_CTL_INPUT_VALIDATION is set due to the performance reason. Nevertheless, it still makes sense to apply the same check for user elements despite of its cost, as that's the only way to filter out the invalid values; the user controls are handled solely in ALSA core code, and there is no corresponding driver, after all. This patch adds the same input value validation for user control elements at its put callback. The kselftest will be happier with this change, as the incorrect values will be bailed out now with errors. For other normal controls, the check is applied still only when CONFIG_SND_CTL_INPUT_VALIDATION is set. Reported-by: Paul Menzel Closes: https://lore.kernel.org/r/1d44be36-9bb9-4d82-8953-5ae2a4f09405@molgen.mpg.de Reviewed-by: Jaroslav Kysela Reviewed-by: Mark Brown Reviewed-by: Takashi Sakamoto Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-4-tiwai@suse.de --- sound/core/control.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/control.c b/sound/core/control.c index fb0c60044f7b..1dd2337e2930 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1480,12 +1480,16 @@ static int snd_ctl_elem_user_get(struct snd_kcontrol *kcontrol, static int snd_ctl_elem_user_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - int change; + int err, change; struct user_element *ue = kcontrol->private_data; unsigned int size = ue->elem_data_size; char *dst = ue->elem_data + snd_ctl_get_ioff(kcontrol, &ucontrol->id) * size; + err = sanity_check_input_values(ue->card, ucontrol, &ue->info, false); + if (err < 0) + return err; + change = memcmp(&ucontrol->value, dst, size) != 0; if (change) memcpy(dst, &ucontrol->value, size); From 1b1285e4759e4d9df4f8e46d92802ff87ce8709b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 16 Jun 2024 09:34:45 +0200 Subject: [PATCH 13/29] kselftest/alsa: Fix validation of writes to volatile controls When validating writes to controls we check that whatever value we wrote actually appears in the control. For volatile controls we cannot assume that this will be the case, the value may be changed at any time including between our write and read. Handle this by moving the check for volatile controls that we currently do for events to a separate block and just verifying that whatever value we read is valid for the control. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20240614-alsa-selftest-volatile-v1-1-3874f02964b1@kernel.org Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-5-tiwai@suse.de --- tools/testing/selftests/alsa/mixer-test.c | 45 +++++++++++++++-------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/tools/testing/selftests/alsa/mixer-test.c b/tools/testing/selftests/alsa/mixer-test.c index 1c04e5f638a0..dd74f8cc7ece 100644 --- a/tools/testing/selftests/alsa/mixer-test.c +++ b/tools/testing/selftests/alsa/mixer-test.c @@ -625,6 +625,21 @@ static int write_and_verify(struct ctl_data *ctl, return err; } + /* + * We can't verify any specific value for volatile controls + * but we should still check that whatever we read is a valid + * vale for the control. + */ + if (snd_ctl_elem_info_is_volatile(ctl->info)) { + if (!ctl_value_valid(ctl, read_val)) { + ksft_print_msg("Volatile control %s has invalid value\n", + ctl->name); + return -EINVAL; + } + + return 0; + } + /* * Check for an event if the value changed, or confirm that * there was none if it didn't. We rely on the kernel @@ -632,22 +647,20 @@ static int write_and_verify(struct ctl_data *ctl, * write, this is currently true, should that ever change this * will most likely break and need updating. */ - if (!snd_ctl_elem_info_is_volatile(ctl->info)) { - err = wait_for_event(ctl, 0); - if (snd_ctl_elem_value_compare(initial_val, read_val)) { - if (err < 1) { - ksft_print_msg("No event generated for %s\n", - ctl->name); - show_values(ctl, initial_val, read_val); - ctl->event_missing++; - } - } else { - if (err != 0) { - ksft_print_msg("Spurious event generated for %s\n", - ctl->name); - show_values(ctl, initial_val, read_val); - ctl->event_spurious++; - } + err = wait_for_event(ctl, 0); + if (snd_ctl_elem_value_compare(initial_val, read_val)) { + if (err < 1) { + ksft_print_msg("No event generated for %s\n", + ctl->name); + show_values(ctl, initial_val, read_val); + ctl->event_missing++; + } + } else { + if (err != 0) { + ksft_print_msg("Spurious event generated for %s\n", + ctl->name); + show_values(ctl, initial_val, read_val); + ctl->event_spurious++; } } From 210e6a844112bd7e28a04faedac677918e1f3040 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 16 Jun 2024 09:34:46 +0200 Subject: [PATCH 14/29] ALSA: chmap: Mark Channel Map controls as volatile The values returned from Playback Channel Map and Capture Channel Map controls may vary dynamically depending on the corresponding PCM stream. Mark those as volatile to indicate the values are unstable and not suitable for testing. Note that we may change the driver to return -EINVAL, but this would bring other side effects, such as "alsactl restore" would start receiving unexpected errors. So we still keep returning 0 for those invalid inputs. Reported-by: Paul Menzel Closes: https://lore.kernel.org/r/1d44be36-9bb9-4d82-8953-5ae2a4f09405@molgen.mpg.de Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-6-tiwai@suse.de --- sound/core/pcm_lib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 6f73b3c2c205..071c67cbc479 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -2556,6 +2556,7 @@ int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream, struct snd_kcontrol_new knew = { .iface = SNDRV_CTL_ELEM_IFACE_PCM, .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE | SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, .info = pcm_chmap_ctl_info, From 6278056e42d953e207e2afd416be39d09ed2d496 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sun, 16 Jun 2024 09:34:47 +0200 Subject: [PATCH 15/29] ALSA: hda: Add input value sanity checks to HDMI channel map controls Add a simple sanity check to HD-audio HDMI Channel Map controls. Although the value might not be accepted for the actual connection, we can filter out some bogus values beforehand, and that should be enough for making kselftest happier. Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://lore.kernel.org/20240616073454.16512-7-tiwai@suse.de --- sound/hda/hdmi_chmap.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/sound/hda/hdmi_chmap.c b/sound/hda/hdmi_chmap.c index 5d8e1d944b0a..7b276047f85a 100644 --- a/sound/hda/hdmi_chmap.c +++ b/sound/hda/hdmi_chmap.c @@ -753,6 +753,20 @@ static int hdmi_chmap_ctl_get(struct snd_kcontrol *kcontrol, return 0; } +/* a simple sanity check for input values to chmap kcontrol */ +static int chmap_value_check(struct hdac_chmap *hchmap, + const struct snd_ctl_elem_value *ucontrol) +{ + int i; + + for (i = 0; i < hchmap->channels_max; i++) { + if (ucontrol->value.integer.value[i] < 0 || + ucontrol->value.integer.value[i] > SNDRV_CHMAP_LAST) + return -EINVAL; + } + return 0; +} + static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -764,6 +778,10 @@ static int hdmi_chmap_ctl_put(struct snd_kcontrol *kcontrol, unsigned char chmap[8], per_pin_chmap[8]; int i, err, ca, prepared = 0; + err = chmap_value_check(hchmap, ucontrol); + if (err < 0) + return err; + /* No monitor is connected in dyn_pcm_assign. * It's invalid to setup the chmap */ From e946455ce116fd822718d6acd874f3f4cf306520 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 17 Jun 2024 12:05:23 +0200 Subject: [PATCH 16/29] ALSA: control: Allow NULL passed to snd_ctl_remove() There are lots of code checking NULL for kcontrol passed to snd_ctl_remove() in the caller side. Let's make snd_ctl_remove() accepting the NULL kcontrol instead a la free(), so that we can clean up the caller side. Link: https://lore.kernel.org/20240617100529.6667-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/control.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/core/control.c b/sound/core/control.c index 1dd2337e2930..f64a555f404f 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -604,6 +604,7 @@ static inline int snd_ctl_remove_locked(struct snd_card *card, * * Removes the control from the card and then releases the instance. * You don't need to call snd_ctl_free_one(). + * Passing NULL to @kcontrol argument is allowed as noop. * * Return: 0 if successful, or a negative error code on failure. * @@ -611,6 +612,8 @@ static inline int snd_ctl_remove_locked(struct snd_card *card, */ int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol) { + if (!kcontrol) + return 0; guard(rwsem_write)(&card->controls_rwsem); return snd_ctl_remove_locked(card, kcontrol); } From 4d4500b4396a226d159b2633b091c0b65509b24a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 17 Jun 2024 12:05:24 +0200 Subject: [PATCH 17/29] ALSA: sb: Drop NULL check for snd_ctl_remove() Since snd_ctl_remove() accepts the NULL kcontrol argument now, we can drop the check in the caller side. Link: https://lore.kernel.org/20240617100529.6667-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/isa/sb/emu8000.c | 6 ++---- sound/isa/sb/sb16_csp.c | 12 ++++-------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c index a6405772d537..af478c36ce5b 100644 --- a/sound/isa/sb/emu8000.c +++ b/sound/isa/sb/emu8000.c @@ -1039,10 +1039,8 @@ snd_emu8000_create_mixer(struct snd_card *card, struct snd_emu8000 *emu) return 0; __error: - for (i = 0; i < EMU8000_NUM_CONTROLS; i++) { - if (emu->controls[i]) - snd_ctl_remove(card, emu->controls[i]); - } + for (i = 0; i < EMU8000_NUM_CONTROLS; i++) + snd_ctl_remove(card, emu->controls[i]); return err; } diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c index 8d8357019719..fdb992733bde 100644 --- a/sound/isa/sb/sb16_csp.c +++ b/sound/isa/sb/sb16_csp.c @@ -1080,14 +1080,10 @@ static void snd_sb_qsound_destroy(struct snd_sb_csp * p) card = p->chip->card; - if (p->qsound_switch) { - snd_ctl_remove(card, p->qsound_switch); - p->qsound_switch = NULL; - } - if (p->qsound_space) { - snd_ctl_remove(card, p->qsound_space); - p->qsound_space = NULL; - } + snd_ctl_remove(card, p->qsound_switch); + p->qsound_switch = NULL; + snd_ctl_remove(card, p->qsound_space); + p->qsound_space = NULL; /* cancel pending transfer of QSound parameters */ spin_lock_irqsave (&p->q_lock, flags); From 9d67a4006f7219577c5c4502d43892feb8773aa6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 17 Jun 2024 12:05:26 +0200 Subject: [PATCH 18/29] ALSA: hda: Drop NULL check for snd_ctl_remove() Since snd_ctl_remove() accepts the NULL kcontrol argument now, we can drop the check in the caller side. Link: https://lore.kernel.org/20240617100529.6667-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_cs_dsp_ctl.c | 2 +- sound/pci/hda/tas2781_hda_i2c.c | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/sound/pci/hda/hda_cs_dsp_ctl.c b/sound/pci/hda/hda_cs_dsp_ctl.c index e6e876998e71..deb74c247082 100644 --- a/sound/pci/hda/hda_cs_dsp_ctl.c +++ b/sound/pci/hda/hda_cs_dsp_ctl.c @@ -207,7 +207,7 @@ void hda_cs_dsp_control_remove(struct cs_dsp_coeff_ctl *cs_ctl) struct hda_cs_dsp_coeff_ctl *ctl = cs_ctl->priv; /* ctl and kctl may already have been removed by ALSA private_free */ - if (ctl && ctl->kctl) + if (ctl) snd_ctl_remove(ctl->card, ctl->kctl); } EXPORT_SYMBOL_NS_GPL(hda_cs_dsp_control_remove, SND_HDA_CS_DSP_CONTROLS); diff --git a/sound/pci/hda/tas2781_hda_i2c.c b/sound/pci/hda/tas2781_hda_i2c.c index d7af4fd10714..879d4b48e6c7 100644 --- a/sound/pci/hda/tas2781_hda_i2c.c +++ b/sound/pci/hda/tas2781_hda_i2c.c @@ -597,18 +597,13 @@ static void tas2781_hda_remove_controls(struct tas2781_hda *tas_hda) { struct hda_codec *codec = tas_hda->priv->codec; - if (tas_hda->dsp_prog_ctl) - snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl); - - if (tas_hda->dsp_conf_ctl) - snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl); + snd_ctl_remove(codec->card, tas_hda->dsp_prog_ctl); + snd_ctl_remove(codec->card, tas_hda->dsp_conf_ctl); for (int i = ARRAY_SIZE(tas_hda->snd_ctls) - 1; i >= 0; i--) - if (tas_hda->snd_ctls[i]) - snd_ctl_remove(codec->card, tas_hda->snd_ctls[i]); + snd_ctl_remove(codec->card, tas_hda->snd_ctls[i]); - if (tas_hda->prof_ctl) - snd_ctl_remove(codec->card, tas_hda->prof_ctl); + snd_ctl_remove(codec->card, tas_hda->prof_ctl); } static void tasdev_fw_ready(const struct firmware *fmw, void *context) From f900a055f8e279a0f572f54f57b967a99aa7a465 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 17 Jun 2024 12:05:27 +0200 Subject: [PATCH 19/29] ALSA: spi: Drop NULL check for snd_ctl_remove() Since snd_ctl_remove() accepts the NULL kcontrol argument now, we can drop the check in the caller side. Link: https://lore.kernel.org/20240617100529.6667-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/spi/at73c213.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/sound/spi/at73c213.c b/sound/spi/at73c213.c index 5648d744aa79..8f6929ced2c8 100644 --- a/sound/spi/at73c213.c +++ b/sound/spi/at73c213.c @@ -726,12 +726,8 @@ static int snd_at73c213_mixer(struct snd_at73c213 *chip) return 0; cleanup: - for (idx = 1; idx < ARRAY_SIZE(snd_at73c213_controls) + 1; idx++) { - struct snd_kcontrol *kctl; - kctl = snd_ctl_find_numid(card, idx); - if (kctl) - snd_ctl_remove(card, kctl); - } + for (idx = 1; idx < ARRAY_SIZE(snd_at73c213_controls) + 1; idx++) + snd_ctl_remove(card, snd_ctl_find_numid(card, idx)); return errval; } From 634f3b4e7dde3827e3b81306416ec306b323e39b Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Tue, 18 Jun 2024 14:00:11 +0100 Subject: [PATCH 20/29] ALSA: hda: cs35l56: Perform firmware download in the background It is possible that during system boot when there multiple devices attempting simultaneous initialization on a slow control bus the download of firmware and tuning data may take a user perceivable amount of time (a slow I2C bus with 4 amps this work could take over 2 seconds). Adopt a pattern used in the ASoC driver and perform this activity in a background thread so that interactive performance is not impaired. The system_long_wq is a parallel workqueue and driver instances will perform their firmware downloads in parallel to make best use of available bus bandwidth. Signed-off-by: Simon Trimmer Link: https://lore.kernel.org/20240618130011.62860-1-simont@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l56_hda.c | 90 ++++++++++++++++++++++++++++++------- sound/pci/hda/cs35l56_hda.h | 3 ++ 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/sound/pci/hda/cs35l56_hda.c b/sound/pci/hda/cs35l56_hda.c index cc4aa90db1d1..96d3f13c5abf 100644 --- a/sound/pci/hda/cs35l56_hda.c +++ b/sound/pci/hda/cs35l56_hda.c @@ -50,11 +50,19 @@ static const struct reg_sequence cs35l56_hda_dai_config[] = { }; +static void cs35l56_hda_wait_dsp_ready(struct cs35l56_hda *cs35l56) +{ + /* Wait for patching to complete */ + flush_work(&cs35l56->dsp_work); +} + static void cs35l56_hda_play(struct cs35l56_hda *cs35l56) { unsigned int val; int ret; + cs35l56_hda_wait_dsp_ready(cs35l56); + pm_runtime_get_sync(cs35l56->base.dev); ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_AUDIO_PLAY); if (ret == 0) { @@ -180,6 +188,8 @@ static int cs35l56_hda_mixer_get(struct snd_kcontrol *kcontrol, unsigned int reg_val; int i; + cs35l56_hda_wait_dsp_ready(cs35l56); + regmap_read(cs35l56->base.regmap, kcontrol->private_value, ®_val); reg_val &= CS35L56_ASP_TXn_SRC_MASK; @@ -203,6 +213,8 @@ static int cs35l56_hda_mixer_put(struct snd_kcontrol *kcontrol, if (item >= CS35L56_NUM_INPUT_SRC) return -EINVAL; + cs35l56_hda_wait_dsp_ready(cs35l56); + regmap_update_bits_check(cs35l56->base.regmap, kcontrol->private_value, CS35L56_INPUT_MASK, cs35l56_tx_input_values[item], &changed); @@ -227,6 +239,8 @@ static int cs35l56_hda_posture_get(struct snd_kcontrol *kcontrol, unsigned int pos; int ret; + cs35l56_hda_wait_dsp_ready(cs35l56); + ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, &pos); if (ret) return ret; @@ -248,6 +262,8 @@ static int cs35l56_hda_posture_put(struct snd_kcontrol *kcontrol, (pos > CS35L56_MAIN_POSTURE_MAX)) return -EINVAL; + cs35l56_hda_wait_dsp_ready(cs35l56); + ret = regmap_update_bits_check(cs35l56->base.regmap, CS35L56_MAIN_POSTURE_NUMBER, CS35L56_MAIN_POSTURE_MASK, @@ -291,6 +307,8 @@ static int cs35l56_hda_vol_get(struct snd_kcontrol *kcontrol, int vol; int ret; + cs35l56_hda_wait_dsp_ready(cs35l56); + ret = regmap_read(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, &raw_vol); if (ret) @@ -323,6 +341,8 @@ static int cs35l56_hda_vol_put(struct snd_kcontrol *kcontrol, raw_vol = (vol + CS35L56_MAIN_RENDER_USER_VOLUME_MIN) << CS35L56_MAIN_RENDER_USER_VOLUME_SHIFT; + cs35l56_hda_wait_dsp_ready(cs35l56); + ret = regmap_update_bits_check(cs35l56->base.regmap, CS35L56_MAIN_RENDER_USER_VOLUME, CS35L56_MAIN_RENDER_USER_VOLUME_MASK, @@ -539,8 +559,9 @@ static void cs35l56_hda_release_firmware_files(const struct firmware *wmfw_firmw kfree(coeff_filename); } -static void cs35l56_hda_add_dsp_controls(struct cs35l56_hda *cs35l56) +static void cs35l56_hda_create_dsp_controls_work(struct work_struct *work) { + struct cs35l56_hda *cs35l56 = container_of(work, struct cs35l56_hda, control_work); struct hda_cs_dsp_ctl_info info; info.device_name = cs35l56->amp_name; @@ -566,7 +587,7 @@ static void cs35l56_hda_apply_calibration(struct cs35l56_hda *cs35l56) dev_info(cs35l56->base.dev, "Calibration applied\n"); } -static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) +static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) { const struct firmware *coeff_firmware = NULL; const struct firmware *wmfw_firmware = NULL; @@ -574,15 +595,34 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) char *wmfw_filename = NULL; unsigned int preloaded_fw_ver; bool firmware_missing; - int ret = 0; + bool add_dsp_controls_required = false; + int ret; - /* Prepare for a new DSP power-up */ + /* + * control_work must be flushed before proceeding, but we can't do that + * here as it would create a deadlock on controls_rwsem so it must be + * performed before queuing dsp_work. + */ + WARN_ON_ONCE(work_busy(&cs35l56->control_work)); + + /* + * Prepare for a new DSP power-up. If the DSP has had firmware + * downloaded previously then it needs to be powered down so that it + * can be updated and if hadn't been patched before then the controls + * will need to be added once firmware download succeeds. + */ if (cs35l56->base.fw_patched) cs_dsp_power_down(&cs35l56->cs_dsp); + else + add_dsp_controls_required = true; cs35l56->base.fw_patched = false; - pm_runtime_get_sync(cs35l56->base.dev); + ret = pm_runtime_resume_and_get(cs35l56->base.dev); + if (ret < 0) { + dev_err(cs35l56->base.dev, "Failed to resume and get %d\n", ret); + return; + } /* * The firmware can only be upgraded if it is currently running @@ -606,7 +646,6 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) */ if (!coeff_firmware && firmware_missing) { dev_err(cs35l56->base.dev, ".bin file required but not found\n"); - ret = -ENOENT; goto err_fw_release; } @@ -659,6 +698,15 @@ static int cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) CS35L56_FIRMWARE_MISSING); cs35l56->base.fw_patched = true; + /* + * Adding controls is deferred to prevent a lock inversion - ALSA takes + * the controls_rwsem when adding a control, the get() / put() + * functions of a control are called holding controls_rwsem and those + * that depend on running firmware wait for dsp_work() to complete. + */ + if (add_dsp_controls_required) + queue_work(system_long_wq, &cs35l56->control_work); + ret = cs_dsp_run(&cs35l56->cs_dsp); if (ret) dev_dbg(cs35l56->base.dev, "%s: cs_dsp_run ret %d\n", __func__, ret); @@ -678,8 +726,13 @@ err_fw_release: coeff_firmware, coeff_filename); err_pm_put: pm_runtime_put(cs35l56->base.dev); +} - return ret; +static void cs35l56_hda_dsp_work(struct work_struct *work) +{ + struct cs35l56_hda *cs35l56 = container_of(work, struct cs35l56_hda, dsp_work); + + cs35l56_hda_fw_load(cs35l56); } static int cs35l56_hda_bind(struct device *dev, struct device *master, void *master_data) @@ -687,7 +740,6 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); struct hda_component_parent *parent = master_data; struct hda_component *comp; - int ret; comp = hda_component_from_index(parent, cs35l56->index); if (!comp) @@ -701,12 +753,10 @@ static int cs35l56_hda_bind(struct device *dev, struct device *master, void *mas strscpy(comp->name, dev_name(dev), sizeof(comp->name)); comp->playback_hook = cs35l56_hda_playback_hook; - ret = cs35l56_hda_fw_load(cs35l56); - if (ret) - return ret; + flush_work(&cs35l56->control_work); + queue_work(system_long_wq, &cs35l56->dsp_work); cs35l56_hda_create_controls(cs35l56); - cs35l56_hda_add_dsp_controls(cs35l56); #if IS_ENABLED(CONFIG_SND_DEBUG) cs35l56->debugfs_root = debugfs_create_dir(dev_name(cs35l56->base.dev), sound_debugfs_root); @@ -724,6 +774,9 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * struct hda_component_parent *parent = master_data; struct hda_component *comp; + cancel_work_sync(&cs35l56->dsp_work); + cancel_work_sync(&cs35l56->control_work); + cs35l56_hda_remove_controls(cs35l56); #if IS_ENABLED(CONFIG_SND_DEBUG) @@ -752,6 +805,9 @@ static int cs35l56_hda_system_suspend(struct device *dev) { struct cs35l56_hda *cs35l56 = dev_get_drvdata(dev); + cs35l56_hda_wait_dsp_ready(cs35l56); + flush_work(&cs35l56->control_work); + if (cs35l56->playing) cs35l56_hda_pause(cs35l56); @@ -850,11 +906,8 @@ static int cs35l56_hda_system_resume(struct device *dev) ret = cs35l56_is_fw_reload_needed(&cs35l56->base); dev_dbg(cs35l56->base.dev, "fw_reload_needed: %d\n", ret); - if (ret > 0) { - ret = cs35l56_hda_fw_load(cs35l56); - if (ret) - return ret; - } + if (ret > 0) + queue_work(system_long_wq, &cs35l56->dsp_work); if (cs35l56->playing) cs35l56_hda_play(cs35l56); @@ -972,6 +1025,9 @@ int cs35l56_hda_common_probe(struct cs35l56_hda *cs35l56, int hid, int id) mutex_init(&cs35l56->base.irq_lock); dev_set_drvdata(cs35l56->base.dev, cs35l56); + INIT_WORK(&cs35l56->dsp_work, cs35l56_hda_dsp_work); + INIT_WORK(&cs35l56->control_work, cs35l56_hda_create_dsp_controls_work); + ret = cs35l56_hda_read_acpi(cs35l56, hid, id); if (ret) goto err; diff --git a/sound/pci/hda/cs35l56_hda.h b/sound/pci/hda/cs35l56_hda.h index 464e4aa63cd1..c40d159507c2 100644 --- a/sound/pci/hda/cs35l56_hda.h +++ b/sound/pci/hda/cs35l56_hda.h @@ -14,6 +14,7 @@ #include #include #include +#include #include struct dentry; @@ -21,6 +22,8 @@ struct dentry; struct cs35l56_hda { struct cs35l56_base base; struct hda_codec *codec; + struct work_struct dsp_work; + struct work_struct control_work; int index; const char *system_name; From 337b7b0b8b02aa10b52386371433eb6f522dc550 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 25 Jun 2024 13:33:55 +0200 Subject: [PATCH 21/29] ALSA: pcm_dmaengine: Unify two close functions Both snd_dmaengine_pcm_close() and snd_dmaengine_pcm_close_release_chan() are almost identical except for a call of dma_release_channel() in the latter. For code simplification, unify them internally. Just a code refactoring, and no functional changes. Link: https://patch.msgid.link/20240625113356.2123-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_dmaengine.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 7346c993a067..4f18511f1c92 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -349,13 +349,8 @@ int snd_dmaengine_pcm_open_request_chan(struct snd_pcm_substream *substream, } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_open_request_chan); -/** - * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream - * @substream: PCM substream - * - * Return: 0 on success, a negative error code otherwise - */ -int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) +static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream, + bool release_channel) { struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); struct dma_tx_state state; @@ -366,8 +361,20 @@ int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) dmaengine_terminate_async(prtd->dma_chan); dmaengine_synchronize(prtd->dma_chan); + if (release_channel) + dma_release_channel(prtd->dma_chan); kfree(prtd); +} +/** + * snd_dmaengine_pcm_close - Close a dmaengine based PCM substream + * @substream: PCM substream + * + * Return: 0 on success, a negative error code otherwise + */ +int snd_dmaengine_pcm_close(struct snd_pcm_substream *substream) +{ + __snd_dmaengine_pcm_close(substream, false); return 0; } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); @@ -383,18 +390,7 @@ EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close); */ int snd_dmaengine_pcm_close_release_chan(struct snd_pcm_substream *substream) { - struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream); - struct dma_tx_state state; - enum dma_status status; - - status = dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state); - if (status == DMA_PAUSED) - dmaengine_terminate_async(prtd->dma_chan); - - dmaengine_synchronize(prtd->dma_chan); - dma_release_channel(prtd->dma_chan); - kfree(prtd); - + __snd_dmaengine_pcm_close(substream, true); return 0; } EXPORT_SYMBOL_GPL(snd_dmaengine_pcm_close_release_chan); From 5b707581c072bcef086ef4c122a2ea81470a1027 Mon Sep 17 00:00:00 2001 From: Zhu Jun Date: Wed, 26 Jun 2024 01:48:59 -0700 Subject: [PATCH 22/29] selftests/alsa:Fix printf format string in pcm-test.c Inside of test_pcm_time() arguments are printed via printf but '%d' is used to print @flags (of type unsigned int). Use '%u' instead, just like we do everywhere else. Signed-off-by: Zhu Jun Reviewed-by: Mark Brown Link: https://patch.msgid.link/20240626084859.4350-1-zhujun2@cmss.chinamobile.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/pcm-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/pcm-test.c b/tools/testing/selftests/alsa/pcm-test.c index de664dedb541..914efcdce1ec 100644 --- a/tools/testing/selftests/alsa/pcm-test.c +++ b/tools/testing/selftests/alsa/pcm-test.c @@ -383,7 +383,7 @@ __format: goto __close; } if (rrate != rate) { - snprintf(msg, sizeof(msg), "rate mismatch %ld != %d", rate, rrate); + snprintf(msg, sizeof(msg), "rate mismatch %ld != %u", rate, rrate); goto __close; } rperiod_size = period_size; From f05c1ffc274516ef101d2e0f860bcb9b08c6c622 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 25 Jun 2024 19:25:46 +0200 Subject: [PATCH 23/29] ALSA: pcm: reinvent the stream synchronization ID API Until the commit e11f0f90a626 ("ALSA: pcm: remove SNDRV_PCM_IOCTL1_INFO internal command"), there was a possibility to pass information about the synchronized streams to the user space. The mentioned commit removed blindly the appropriate code with an irrelevant comment. The revert may be appropriate, but since this API was lost for several years without any complains, it's time to improve it. The hardware parameters may change the used stream clock source (e.g. USB hardware) so move this synchronization ID to hw_params as read-only field. It seems that pipewire can benefit from this API (disable adaptive resampling for perfectly synchronized PCM streams) now. Note that the contents of ID is not supposed to be used for direct comparison with a specific byte sequence. The "empty" case is when all bytes are zero (driver does not offer this information) and all other cases must be only used for equal comparison among PCM streams (including different sound cards) if they are using identical hardware clock. Cc: Takashi Sakamoto Signed-off-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20240625172836.589380-2-perex@perex.cz --- include/sound/pcm.h | 3 ++- include/uapi/sound/asound.h | 9 +++++---- sound/core/pcm_lib.c | 27 +++++++++++++++++++++++---- sound/core/pcm_native.c | 6 ++++++ sound/pci/emu10k1/p16v.c | 7 +++---- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 3edd7a7346da..dbce137d8806 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -93,6 +93,7 @@ struct snd_pcm_ops { #define SNDRV_PCM_IOCTL1_CHANNEL_INFO 2 /* 3 is absent slot. */ #define SNDRV_PCM_IOCTL1_FIFO_SIZE 4 +#define SNDRV_PCM_IOCTL1_SYNC_ID 5 #define SNDRV_PCM_TRIGGER_STOP 0 #define SNDRV_PCM_TRIGGER_START 1 @@ -401,7 +402,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t silence_start; /* starting pointer to silence area */ snd_pcm_uframes_t silence_filled; /* already filled part of silence area */ - union snd_pcm_sync_id sync; /* hardware synchronization ID */ + unsigned char sync[16]; /* hardware synchronization ID */ /* -- mmap -- */ struct snd_pcm_mmap_status *status; diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 628d46a0da92..8bf7e8a0eb6f 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -142,7 +142,7 @@ struct snd_hwdep_dsp_image { * * *****************************************************************************/ -#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 17) +#define SNDRV_PCM_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 18) typedef unsigned long snd_pcm_uframes_t; typedef signed long snd_pcm_sframes_t; @@ -334,7 +334,7 @@ union snd_pcm_sync_id { unsigned char id[16]; unsigned short id16[8]; unsigned int id32[4]; -}; +} __attribute__((deprecated)); struct snd_pcm_info { unsigned int device; /* RO/WR (control): device number */ @@ -348,7 +348,7 @@ struct snd_pcm_info { int dev_subclass; /* SNDRV_PCM_SUBCLASS_* */ unsigned int subdevices_count; unsigned int subdevices_avail; - union snd_pcm_sync_id sync; /* hardware synchronization ID */ + unsigned char pad1[16]; /* was: hardware synchronization ID */ unsigned char reserved[64]; /* reserved for future... */ }; @@ -420,7 +420,8 @@ struct snd_pcm_hw_params { unsigned int rate_num; /* R: rate numerator */ unsigned int rate_den; /* R: rate denominator */ snd_pcm_uframes_t fifo_size; /* R: chip FIFO size in frames */ - unsigned char reserved[64]; /* reserved for future */ + unsigned char sync[16]; /* R: synchronization ID (perfect sync - one clock source) */ + unsigned char reserved[48]; /* reserved for future */ }; enum { diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 071c67cbc479..f64a03f79b01 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -525,10 +525,8 @@ void snd_pcm_set_sync(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - runtime->sync.id32[0] = substream->pcm->card->number; - runtime->sync.id32[1] = -1; - runtime->sync.id32[2] = -1; - runtime->sync.id32[3] = -1; + *(__u32 *)runtime->sync = cpu_to_le32(substream->pcm->card->number); + memset(runtime->sync + 4, 0xff, sizeof(runtime->sync) - 4); } EXPORT_SYMBOL(snd_pcm_set_sync); @@ -1810,6 +1808,25 @@ static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream, return 0; } +/** + * is sync id (clock id) empty? + */ +static inline bool pcm_sync_empty(const unsigned char *sync) +{ + return sync[0] == 0 && sync[1] == 0 && sync[2] == 0 && sync[3] == 0 && + sync[4] == 0 && sync[5] == 0 && sync[6] == 0 && sync[7] == 0; +} + +static int snd_pcm_lib_ioctl_sync_id(struct snd_pcm_substream *substream, + void *arg) +{ + struct snd_pcm_hw_params *params = arg; + + if (pcm_sync_empty(params->sync)) + memcpy(params->sync, substream->runtime->sync, sizeof(params->sync)); + return 0; +} + /** * snd_pcm_lib_ioctl - a generic PCM ioctl callback * @substream: the pcm substream instance @@ -1831,6 +1848,8 @@ int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, return snd_pcm_lib_ioctl_channel_info(substream, arg); case SNDRV_PCM_IOCTL1_FIFO_SIZE: return snd_pcm_lib_ioctl_fifo_size(substream, arg); + case SNDRV_PCM_IOCTL1_SYNC_ID: + return snd_pcm_lib_ioctl_sync_id(substream, arg); } return -ENXIO; } diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index c152ccf32214..4057f9f10aee 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -533,6 +533,12 @@ static int fixup_unreferenced_params(struct snd_pcm_substream *substream, SNDRV_PCM_INFO_MMAP_VALID); } + err = snd_pcm_ops_ioctl(substream, + SNDRV_PCM_IOCTL1_SYNC_ID, + params); + if (err < 0) + return err; + return 0; } diff --git a/sound/pci/emu10k1/p16v.c b/sound/pci/emu10k1/p16v.c index e7f097cae574..773725dbdfbd 100644 --- a/sound/pci/emu10k1/p16v.c +++ b/sound/pci/emu10k1/p16v.c @@ -174,10 +174,9 @@ static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substrea if (err < 0) return err; - runtime->sync.id32[0] = substream->pcm->card->number; - runtime->sync.id32[1] = 'P'; - runtime->sync.id32[2] = 16; - runtime->sync.id32[3] = 'V'; + *(__u32 *)runtime->sync = cpu_to_le32(substream->pcm->card->number); + memset(runtime->sync + 4, 0, sizeof(runtime->sync) - 4); + strncpy(runtime->sync + 4, "P16V", 4); return 0; } From d712c58c55d9a4b4cc88ec2e1f8cd2e3b82359b5 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 25 Jun 2024 19:25:47 +0200 Subject: [PATCH 24/29] ALSA: pcm: optimize and clarify stream synchronization ID API Optimize the memory usage in struct snd_pcm_runtime - use boolean value for the standard sync ID scheme. Introduce snd_pcm_set_sync_per_card function to build synchronization IDs. Signed-off-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20240625172836.589380-3-perex@perex.cz --- include/sound/pcm.h | 15 ++++++++++-- sound/core/pcm_lib.c | 51 ++++++++++++++++++++++++---------------- sound/pci/emu10k1/p16v.c | 16 +++++++++---- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index dbce137d8806..ac8f3aef9205 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -402,7 +402,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t silence_start; /* starting pointer to silence area */ snd_pcm_uframes_t silence_filled; /* already filled part of silence area */ - unsigned char sync[16]; /* hardware synchronization ID */ + bool std_sync_id; /* hardware synchronization - standard per card ID */ /* -- mmap -- */ struct snd_pcm_mmap_status *status; @@ -1156,7 +1156,18 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *buf, unsigned int void snd_pcm_set_ops(struct snd_pcm * pcm, int direction, const struct snd_pcm_ops *ops); -void snd_pcm_set_sync(struct snd_pcm_substream *substream); +void snd_pcm_set_sync_per_card(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, + const unsigned char *id, unsigned int len); +/** + * snd_pcm_set_sync - set the PCM sync id + * @substream: the pcm substream + * + * Use the default PCM sync identifier for the specific card. + */ +static inline void snd_pcm_set_sync(struct snd_pcm_substream *substream) +{ + substream->runtime->std_sync_id = true; +} int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg); void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substream); diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index f64a03f79b01..e48a84f8e977 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -516,19 +516,37 @@ void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, EXPORT_SYMBOL(snd_pcm_set_ops); /** - * snd_pcm_set_sync - set the PCM sync id + * snd_pcm_set_sync_per_card - set the PCM sync id with card number * @substream: the pcm substream + * @params: modified hardware parameters + * @id: identifier (max 12 bytes) + * @len: identifier length (max 12 bytes) * - * Sets the PCM sync identifier for the card. + * Sets the PCM sync identifier for the card with zero padding. + * + * User space or any user should use this 16-byte identifier for a comparison only + * to check if two IDs are similar or different. Special case is the identifier + * containing only zeros. Interpretation for this combination is - empty (not set). + * The contents of the identifier should not be interpreted in any other way. + * + * The synchronization ID must be unique per clock source (usually one sound card, + * but multiple soundcard may use one PCM word clock source which means that they + * are fully synchronized). + * + * This routine composes this ID using card number in first four bytes and + * 12-byte additional ID. When other ID composition is used (e.g. for multiple + * sound cards), make sure that the composition does not clash with this + * composition scheme. */ -void snd_pcm_set_sync(struct snd_pcm_substream *substream) +void snd_pcm_set_sync_per_card(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + const unsigned char *id, unsigned int len) { - struct snd_pcm_runtime *runtime = substream->runtime; - - *(__u32 *)runtime->sync = cpu_to_le32(substream->pcm->card->number); - memset(runtime->sync + 4, 0xff, sizeof(runtime->sync) - 4); + *(__u32 *)params->sync = cpu_to_le32(substream->pcm->card->number); + len = max(12, len); + strncpy(params->sync + 4, id, len); + memset(params->sync + 4 + len, 0, 12 - len); } -EXPORT_SYMBOL(snd_pcm_set_sync); /* * Standard ioctl routine @@ -1808,22 +1826,15 @@ static int snd_pcm_lib_ioctl_fifo_size(struct snd_pcm_substream *substream, return 0; } -/** - * is sync id (clock id) empty? - */ -static inline bool pcm_sync_empty(const unsigned char *sync) -{ - return sync[0] == 0 && sync[1] == 0 && sync[2] == 0 && sync[3] == 0 && - sync[4] == 0 && sync[5] == 0 && sync[6] == 0 && sync[7] == 0; -} - static int snd_pcm_lib_ioctl_sync_id(struct snd_pcm_substream *substream, void *arg) { - struct snd_pcm_hw_params *params = arg; + static const unsigned char id[12] = { 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff }; - if (pcm_sync_empty(params->sync)) - memcpy(params->sync, substream->runtime->sync, sizeof(params->sync)); + if (substream->runtime->std_sync_id) + snd_pcm_set_sync_per_card(substream, arg, id, sizeof(id)); return 0; } diff --git a/sound/pci/emu10k1/p16v.c b/sound/pci/emu10k1/p16v.c index 773725dbdfbd..a9a75891f1da 100644 --- a/sound/pci/emu10k1/p16v.c +++ b/sound/pci/emu10k1/p16v.c @@ -174,10 +174,6 @@ static int snd_p16v_pcm_open_playback_channel(struct snd_pcm_substream *substrea if (err < 0) return err; - *(__u32 *)runtime->sync = cpu_to_le32(substream->pcm->card->number); - memset(runtime->sync + 4, 0, sizeof(runtime->sync) - 4); - strncpy(runtime->sync + 4, "P16V", 4); - return 0; } @@ -225,6 +221,17 @@ static int snd_p16v_pcm_open_capture(struct snd_pcm_substream *substream) return snd_p16v_pcm_open_capture_channel(substream, 0); } +static int snd_p16v_pcm_ioctl_playback(struct snd_pcm_substream *substream, + unsigned int cmd, void *arg) +{ + if (cmd == SNDRV_PCM_IOCTL1_SYNC_ID) { + static const unsigned char id[4] = { 'P', '1', '6', 'V' }; + snd_pcm_set_sync_per_card(substream, arg, id, 4); + return 0; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + /* prepare playback callback */ static int snd_p16v_pcm_prepare_playback(struct snd_pcm_substream *substream) { @@ -530,6 +537,7 @@ snd_p16v_pcm_pointer_capture(struct snd_pcm_substream *substream) static const struct snd_pcm_ops snd_p16v_playback_front_ops = { .open = snd_p16v_pcm_open_playback_front, .close = snd_p16v_pcm_close_playback, + .ioctl = snd_p16v_pcm_ioctl_playback, .prepare = snd_p16v_pcm_prepare_playback, .trigger = snd_p16v_pcm_trigger_playback, .pointer = snd_p16v_pcm_pointer_playback, From 5f9f982dd71b418aeba7a0b37f87312545c06df4 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Wed, 3 Jul 2024 15:07:28 +0100 Subject: [PATCH 25/29] ALSA: hda: cs35l41: Fix missing Speaker ID GPIO description in _DSD Laptop 10431A63 contains valid _DSD, but missing Speaker ID description. Add this discription, but keep the rest of the _DSD to ensure the correct firmware and tuning is loaded for this laptop. Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20240703140802.27688-1-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l41_hda.c | 86 ++++++++++++++++------------ sound/pci/hda/cs35l41_hda.h | 1 + sound/pci/hda/cs35l41_hda_property.c | 15 +++++ 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index ee9f83b737de..4b411ed8c3fe 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -1753,38 +1753,14 @@ int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int return speaker_id; } -static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) +int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id) { struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; u32 values[HDA_MAX_COMPONENTS]; - struct acpi_device *adev; - struct device *physdev; - struct spi_device *spi; - const char *sub; char *property; size_t nval; int i, ret; - adev = acpi_dev_get_first_match_dev(hid, NULL, -1); - if (!adev) { - dev_err(cs35l41->dev, "Failed to find an ACPI device for %s\n", hid); - return -ENODEV; - } - - cs35l41->dacpi = adev; - physdev = get_device(acpi_get_first_physical_node(adev)); - - sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); - if (IS_ERR(sub)) - sub = NULL; - cs35l41->acpi_subsystem_id = sub; - - ret = cs35l41_add_dsd_properties(cs35l41, physdev, id, hid); - if (!ret) { - dev_info(cs35l41->dev, "Using extra _DSD properties, bypassing _DSD in ACPI\n"); - goto out; - } - property = "cirrus,dev-index"; ret = device_property_count_u32(physdev, property); if (ret <= 0) @@ -1816,8 +1792,9 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i /* To use the same release code for all laptop variants we can't use devm_ version of * gpiod_get here, as CLSA010* don't have a fully functional bios with an _DSD node */ - cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(adev), "reset", cs35l41->index, - GPIOD_OUT_LOW, "cs35l41-reset"); + cs35l41->reset_gpio = fwnode_gpiod_get_index(acpi_fwnode_handle(cs35l41->dacpi), "reset", + cs35l41->index, GPIOD_OUT_LOW, + "cs35l41-reset"); property = "cirrus,speaker-position"; ret = device_property_read_u32_array(physdev, property, values, nval); @@ -1873,6 +1850,51 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i hw_cfg->bst_type = CS35L41_EXT_BOOST; hw_cfg->valid = true; + + return 0; +err: + dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); + hw_cfg->valid = false; + hw_cfg->gpio1.valid = false; + hw_cfg->gpio2.valid = false; + acpi_dev_put(cs35l41->dacpi); + + return ret; +} + +static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, int id) +{ + struct acpi_device *adev; + struct device *physdev; + struct spi_device *spi; + const char *sub; + int ret; + + adev = acpi_dev_get_first_match_dev(hid, NULL, -1); + if (!adev) { + dev_err(cs35l41->dev, "Failed to find an ACPI device for %s\n", hid); + return -ENODEV; + } + + cs35l41->dacpi = adev; + physdev = get_device(acpi_get_first_physical_node(adev)); + + sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + if (IS_ERR(sub)) + sub = NULL; + cs35l41->acpi_subsystem_id = sub; + + ret = cs35l41_add_dsd_properties(cs35l41, physdev, id, hid); + if (!ret) { + dev_info(cs35l41->dev, "Using extra _DSD properties, bypassing _DSD in ACPI\n"); + goto out; + } + + ret = cs35l41_hda_parse_acpi(cs35l41, physdev, id); + if (ret) { + put_device(physdev); + return ret; + } out: put_device(physdev); @@ -1888,16 +1910,6 @@ out: } return 0; - -err: - dev_err(cs35l41->dev, "Failed property %s: %d\n", property, ret); - hw_cfg->valid = false; - hw_cfg->gpio1.valid = false; - hw_cfg->gpio2.valid = false; - acpi_dev_put(cs35l41->dacpi); - put_device(physdev); - - return ret; } int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int irq, diff --git a/sound/pci/hda/cs35l41_hda.h b/sound/pci/hda/cs35l41_hda.h index b0bebb778462..c730b3351589 100644 --- a/sound/pci/hda/cs35l41_hda.h +++ b/sound/pci/hda/cs35l41_hda.h @@ -104,5 +104,6 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i struct regmap *regmap, enum control_bus control_bus); void cs35l41_hda_remove(struct device *dev); int cs35l41_get_speaker_id(struct device *dev, int amp_index, int num_amps, int fixed_gpio_id); +int cs35l41_hda_parse_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id); #endif /*__CS35L41_HDA_H__*/ diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c index 51998d1c72ff..5860379a0412 100644 --- a/sound/pci/hda/cs35l41_hda_property.c +++ b/sound/pci/hda/cs35l41_hda_property.c @@ -428,6 +428,20 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy return 0; } +static int missing_speaker_id_gpio2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, + const char *hid) +{ + int ret; + + ret = cs35l41_add_gpios(cs35l41, physdev, -1, 2, -1, 2); + if (ret) { + dev_err(cs35l41->dev, "Error adding GPIO mapping: %d\n", ret); + return ret; + } + + return cs35l41_hda_parse_acpi(cs35l41, physdev, id); +} + struct cs35l41_prop_model { const char *hid; const char *ssid; @@ -501,6 +515,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "104317F3", generic_dsd_config }, { "CSC3551", "10431863", generic_dsd_config }, { "CSC3551", "104318D3", generic_dsd_config }, + { "CSC3551", "10431A63", missing_speaker_id_gpio2 }, { "CSC3551", "10431A83", generic_dsd_config }, { "CSC3551", "10431B93", generic_dsd_config }, { "CSC3551", "10431C9F", generic_dsd_config }, From bc7540b794df29f6ec0b24996381d6e68779d02f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 5 Jul 2024 08:35:53 +0200 Subject: [PATCH 26/29] ALSA: pcm: Fix missing export of snd_pcm_set_sync_per-card() The recent change for the PCM sync API forgot to export a new API function that is currently used in emu10k1 driver. This patch adds the missing export. Fixes: d712c58c55d9 ("ALSA: pcm: optimize and clarify stream synchronization ID API") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202407051343.1Sz8AKFt-lkp@intel.com/ Reviewed-by: Jaroslav Kysela Link: https://patch.msgid.link/20240705063555.17220-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm_lib.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index e48a84f8e977..66683656fb13 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -547,6 +547,7 @@ void snd_pcm_set_sync_per_card(struct snd_pcm_substream *substream, strncpy(params->sync + 4, id, len); memset(params->sync + 4 + len, 0, 12 - len); } +EXPORT_SYMBOL_GPL(snd_pcm_set_sync_per_card); /* * Standard ioctl routine From a892b700e63bcb3679ebed1a0c3160e7b9225138 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Fri, 5 Jul 2024 09:58:28 +0200 Subject: [PATCH 27/29] ALSA: pcm: Fix id copying in snd_pcm_set_sync_per_card() Avoid to use strncpy and do proper length limiting (12 bytes) to avoid out of array access. Fixes: d712c58c55d9 ("ALSA: pcm: optimize and clarify stream synchronization ID API") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202407050501.o5Z3bibi-lkp@intel.com/ Signed-off-by: Jaroslav Kysela Link: https://patch.msgid.link/20240705075828.19746-1-perex@perex.cz Signed-off-by: Takashi Iwai --- sound/core/pcm_lib.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 66683656fb13..6e7905749c4a 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -543,8 +543,8 @@ void snd_pcm_set_sync_per_card(struct snd_pcm_substream *substream, const unsigned char *id, unsigned int len) { *(__u32 *)params->sync = cpu_to_le32(substream->pcm->card->number); - len = max(12, len); - strncpy(params->sync + 4, id, len); + len = min(12, len); + memcpy(params->sync + 4, id, len); memset(params->sync + 4 + len, 0, 12 - len); } EXPORT_SYMBOL_GPL(snd_pcm_set_sync_per_card); From fefbbdfb59d3a20fd98734363e6dd9fa7cc65c70 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 5 Jul 2024 18:03:42 +0200 Subject: [PATCH 28/29] ALSA: seq: Add tempo base unit for MIDI2 Set Tempo messages MIDI2 Set Tempo message defines the tempo in 10ns unit for finer accuracy, while MIDI1 was defined in 1us unit. For adapting this different unit, introduce "tempo_base" field to snd_seq_queue_tempo struct so that user-space can pass the proper tempo base unit. The accepted value is limited, it must be either 0, 10 or 1000. The protocol version is bumped to 1.0.4 along with this. The access with the older protocol version ignores the tempo-base value in ioctls and always treats as 1000. Reviewed-by: Jaroslav Kysela Link: https://patch.msgid.link/20240705160344.6481-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 7 ++++--- sound/core/seq/seq_clientmgr.c | 4 ++++ sound/core/seq/seq_queue.c | 6 ++++-- sound/core/seq/seq_timer.c | 21 +++++++++++++++------ sound/core/seq/seq_timer.h | 4 +++- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c85fdd8895d8..39b37edcf813 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -10,7 +10,7 @@ #include /** version of the sequencer */ -#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 3) +#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 4) /** * definition of sequencer event types @@ -523,11 +523,12 @@ struct snd_seq_queue_status { /* queue tempo */ struct snd_seq_queue_tempo { int queue; /* sequencer queue */ - unsigned int tempo; /* current tempo, us/tick */ + unsigned int tempo; /* current tempo, us/tick (or different time-base below) */ int ppq; /* time resolution, ticks/quarter */ unsigned int skew_value; /* queue skew */ unsigned int skew_base; /* queue skew base */ - char reserved[24]; /* for the future */ + unsigned short tempo_base; /* tempo base in nsec unit; either 10 or 1000 */ + char reserved[22]; /* for the future */ }; diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 42a705141050..8c4ee5066afe 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1718,6 +1718,8 @@ static int snd_seq_ioctl_get_queue_tempo(struct snd_seq_client *client, tempo->ppq = tmr->ppq; tempo->skew_value = tmr->skew; tempo->skew_base = tmr->skew_base; + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 4)) + tempo->tempo_base = tmr->tempo_base; queuefree(queue); return 0; @@ -1739,6 +1741,8 @@ static int snd_seq_ioctl_set_queue_tempo(struct snd_seq_client *client, struct snd_seq_queue_tempo *tempo = arg; int result; + if (client->user_pversion < SNDRV_PROTOCOL_VERSION(1, 0, 4)) + tempo->tempo_base = 0; result = snd_seq_set_queue_tempo(client->number, tempo); return result < 0 ? result : 0; } diff --git a/sound/core/seq/seq_queue.c b/sound/core/seq/seq_queue.c index 500ee6b19c71..5df26788dda4 100644 --- a/sound/core/seq/seq_queue.c +++ b/sound/core/seq/seq_queue.c @@ -460,7 +460,8 @@ int snd_seq_queue_timer_set_tempo(int queueid, int client, return -EPERM; } - result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq); + result = snd_seq_timer_set_tempo_ppq(q->timer, info->tempo, info->ppq, + info->tempo_base); if (result >= 0 && info->skew_base > 0) result = snd_seq_timer_set_skew(q->timer, info->skew_value, info->skew_base); @@ -724,7 +725,7 @@ void snd_seq_info_queues_read(struct snd_info_entry *entry, tmr = q->timer; if (tmr->tempo) - bpm = 60000000 / tmr->tempo; + bpm = (60000 * tmr->tempo_base) / tmr->tempo; else bpm = 0; @@ -741,6 +742,7 @@ void snd_seq_info_queues_read(struct snd_info_entry *entry, snd_iprintf(buffer, "timer state : %s\n", tmr->running ? "Running" : "Stopped"); snd_iprintf(buffer, "timer PPQ : %d\n", tmr->ppq); snd_iprintf(buffer, "current tempo : %d\n", tmr->tempo); + snd_iprintf(buffer, "tempo base : %d ns\n", tmr->tempo_base); snd_iprintf(buffer, "current BPM : %d\n", bpm); snd_iprintf(buffer, "current time : %d.%09d s\n", tmr->cur_time.tv_sec, tmr->cur_time.tv_nsec); snd_iprintf(buffer, "current tick : %d\n", tmr->tick.cur_tick); diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c index ad2b97e2762d..c9f0392ac7f1 100644 --- a/sound/core/seq/seq_timer.c +++ b/sound/core/seq/seq_timer.c @@ -20,14 +20,17 @@ static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr) { - if (tmr->tempo < 1000000) - tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq; + unsigned int threshold = + tmr->tempo_base == 1000 ? 1000000 : 10000; + + if (tmr->tempo < threshold) + tmr->tick.resolution = (tmr->tempo * tmr->tempo_base) / tmr->ppq; else { /* might overflow.. */ unsigned int s; s = tmr->tempo % tmr->ppq; - s = (s * 1000) / tmr->ppq; - tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000; + s = (s * tmr->tempo_base) / tmr->ppq; + tmr->tick.resolution = (tmr->tempo / tmr->ppq) * tmr->tempo_base; tmr->tick.resolution += s; } if (tmr->tick.resolution <= 0) @@ -79,6 +82,7 @@ void snd_seq_timer_defaults(struct snd_seq_timer * tmr) /* setup defaults */ tmr->ppq = 96; /* 96 PPQ */ tmr->tempo = 500000; /* 120 BPM */ + tmr->tempo_base = 1000; /* 1us */ snd_seq_timer_set_tick_resolution(tmr); tmr->running = 0; @@ -164,8 +168,9 @@ int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo) return 0; } -/* set current tempo and ppq in a shot */ -int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) +/* set current tempo, ppq and base in a shot */ +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq, + unsigned int tempo_base) { int changed; @@ -173,6 +178,9 @@ int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) return -EINVAL; if (tempo <= 0 || ppq <= 0) return -EINVAL; + /* allow only 10ns or 1us tempo base for now */ + if (tempo_base && tempo_base != 10 && tempo_base != 1000) + return -EINVAL; guard(spinlock_irqsave)(&tmr->lock); if (tmr->running && (ppq != tmr->ppq)) { /* refuse to change ppq on running timers */ @@ -183,6 +191,7 @@ int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq) changed = (tempo != tmr->tempo) || (ppq != tmr->ppq); tmr->tempo = tempo; tmr->ppq = ppq; + tmr->tempo_base = tempo_base ? tempo_base : 1000; if (changed) snd_seq_timer_set_tick_resolution(tmr); return 0; diff --git a/sound/core/seq/seq_timer.h b/sound/core/seq/seq_timer.h index 4bec57df8158..3b906064bde1 100644 --- a/sound/core/seq/seq_timer.h +++ b/sound/core/seq/seq_timer.h @@ -36,6 +36,7 @@ struct snd_seq_timer { unsigned int skew; unsigned int skew_base; + unsigned int tempo_base; struct timespec64 last_update; /* time of last clock update, used for interpolation */ @@ -116,7 +117,8 @@ int snd_seq_timer_stop(struct snd_seq_timer *tmr); int snd_seq_timer_start(struct snd_seq_timer *tmr); int snd_seq_timer_continue(struct snd_seq_timer *tmr); int snd_seq_timer_set_tempo(struct snd_seq_timer *tmr, int tempo); -int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq); +int snd_seq_timer_set_tempo_ppq(struct snd_seq_timer *tmr, int tempo, int ppq, + unsigned int tempo_base); int snd_seq_timer_set_position_tick(struct snd_seq_timer *tmr, snd_seq_tick_time_t position); int snd_seq_timer_set_position_time(struct snd_seq_timer *tmr, snd_seq_real_time_t position); int snd_seq_timer_set_skew(struct snd_seq_timer *tmr, unsigned int skew, unsigned int base); From d2eb433c8546363134cabca7a85c73086700aeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Mon, 8 Jul 2024 15:02:53 +0200 Subject: [PATCH 29/29] ALSA: ppc: keywest: Drop explicit initialization of struct i2c_device_id::driver_data to 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver doesn't use the driver_data member of struct i2c_device_id, so don't explicitly initialize this member. This prepares putting driver_data in an anonymous union which requires either no initialization or named designators. But it's also a nice cleanup on its own. Signed-off-by: Uwe Kleine-König Link: https://patch.msgid.link/20240708130254.9631-2-u.kleine-koenig@baylibre.com Signed-off-by: Takashi Iwai --- sound/ppc/keywest.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c index dfc1fc9b701d..2894d041b2f5 100644 --- a/sound/ppc/keywest.c +++ b/sound/ppc/keywest.c @@ -80,8 +80,8 @@ static void keywest_remove(struct i2c_client *client) static const struct i2c_device_id keywest_i2c_id[] = { - { "MAC,tas3004", 0 }, /* instantiated by i2c-powermac */ - { "keywest", 0 }, /* instantiated by us if needed */ + { "MAC,tas3004" }, /* instantiated by i2c-powermac */ + { "keywest" }, /* instantiated by us if needed */ { } }; MODULE_DEVICE_TABLE(i2c, keywest_i2c_id);