From d2a4e0d7409705a0b6010ee537c5114eac31bd13 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:35:32 +0000 Subject: ASoC: soc-utils.c: add asoc_dummy_dlc ASoC uses dummy Component, sharing snd_soc_dai_link_component for it is better idea. This patch adds it. Signed-off-by: Kuninori Morimoto driver == &dummy_codec)); } +struct snd_soc_dai_link_component asoc_dummy_dlc = { + .of_node = NULL, + .dai_name = "snd-soc-dummy-dai", + .name = "snd-soc-dummy", +}; +EXPORT_SYMBOL_GPL(asoc_dummy_dlc); + static int snd_soc_dummy_probe(struct platform_device *pdev) { int ret; -- cgit v1.2.3 From 5a6ca949350b5ae52f7e4670665e95a6137a59f4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:04 +0000 Subject: ASoC: ti: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto dai_link) return -ENOMEM; - compnent = devm_kzalloc(dev, 2 * sizeof(*compnent), GFP_KERNEL); + compnent = devm_kzalloc(dev, sizeof(*compnent), GFP_KERNEL); if (!compnent) return -ENOMEM; - card->dai_link->cpus = &compnent[0]; + card->dai_link->cpus = compnent; card->dai_link->num_cpus = 1; - card->dai_link->codecs = &compnent[1]; + card->dai_link->codecs = &asoc_dummy_dlc; card->dai_link->num_codecs = 1; card->dai_link->name = card->name; card->dai_link->stream_name = card->name; card->dai_link->cpus->dai_name = dev_name(ad->dssdev); - card->dai_link->codecs->name = "snd-soc-dummy"; - card->dai_link->codecs->dai_name = "snd-soc-dummy-dai"; card->num_links = 1; card->dev = dev; -- cgit v1.2.3 From 91cd742b22038dc794e3cb298575d92595e58f71 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:19 +0000 Subject: ASoC: sof: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto dai_name = drv[i].name; links[i].platforms->name = dev_name(dev->parent); - links[i].codecs->dai_name = "snd-soc-dummy-dai"; - links[i].codecs->name = "snd-soc-dummy"; if (drv[i].playback.channels_min) links[i].dpcm_playback = 1; if (drv[i].capture.channels_min) -- cgit v1.2.3 From 42e0861d79971655acefd1c53f206a23c309f25a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:28 +0000 Subject: ASoC: amd: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto hs_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->hs_codec_id == RT5682) { links[i].codecs = rt5682; @@ -943,8 +936,8 @@ int acp_sofdsp_dai_links_create(struct snd_soc_card *card) links[i].no_pcm = 1; if (!drv_data->hs_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->hs_codec_id == NAU8825) { links[i].codecs = nau8825; @@ -973,8 +966,8 @@ int acp_sofdsp_dai_links_create(struct snd_soc_card *card) links[i].no_pcm = 1; if (!drv_data->amp_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->amp_codec_id == RT1019) { links[i].codecs = rt1019; @@ -1005,8 +998,8 @@ int acp_sofdsp_dai_links_create(struct snd_soc_card *card) links[i].no_pcm = 1; if (!drv_data->amp_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->amp_codec_id == MAX98360A) { links[i].codecs = max98360a; @@ -1076,8 +1069,8 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card) links[i].dpcm_capture = 1; if (!drv_data->hs_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->hs_codec_id == RT5682) { links[i].codecs = rt5682; @@ -1110,8 +1103,8 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card) links[i].dpcm_capture = 1; if (!drv_data->hs_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->hs_codec_id == NAU8825) { links[i].codecs = nau8825; @@ -1138,8 +1131,8 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card) links[i].dpcm_playback = 1; if (!drv_data->amp_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->amp_codec_id == RT1019) { links[i].codecs = rt1019; @@ -1173,8 +1166,8 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card) links[i].dpcm_playback = 1; if (!drv_data->amp_codec_id) { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } if (drv_data->amp_codec_id == MAX98360A) { links[i].codecs = max98360a; @@ -1201,8 +1194,8 @@ int acp_legacy_dai_links_create(struct snd_soc_card *card) links[i].num_codecs = ARRAY_SIZE(dmic_codec); } else { /* Use dummy codec if codec id not specified */ - links[i].codecs = dummy_codec; - links[i].num_codecs = ARRAY_SIZE(dummy_codec); + links[i].codecs = &asoc_dummy_dlc; + links[i].num_codecs = 1; } links[i].cpus = pdm_dmic; links[i].num_cpus = ARRAY_SIZE(pdm_dmic); -- cgit v1.2.3 From 87e39e9b004a629f2a27497ce6c172bfcb50ed37 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:37 +0000 Subject: ASoC: fsl: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto dev, 4, sizeof(*dlc), GFP_KERNEL); + /* for CPU x 2 */ + dlc = devm_kcalloc(&pdev->dev, 2, sizeof(*dlc), GFP_KERNEL); if (!dlc) return -ENOMEM; @@ -244,7 +244,7 @@ static int imx_audmix_probe(struct platform_device *pdev) */ priv->dai[i].cpus = priv->dai[i].platforms = &dlc[0]; - priv->dai[i].codecs = &dlc[1]; + priv->dai[i].codecs = &asoc_dummy_dlc; priv->dai[i].num_cpus = 1; priv->dai[i].num_codecs = 1; @@ -252,8 +252,6 @@ static int imx_audmix_probe(struct platform_device *pdev) priv->dai[i].name = dai_name; priv->dai[i].stream_name = "HiFi-AUDMIX-FE"; - priv->dai[i].codecs->dai_name = "snd-soc-dummy-dai"; - priv->dai[i].codecs->name = "snd-soc-dummy"; priv->dai[i].cpus->of_node = args.np; priv->dai[i].cpus->dai_name = dev_name(&cpu_pdev->dev); priv->dai[i].dynamic = 1; @@ -270,15 +268,13 @@ static int imx_audmix_probe(struct platform_device *pdev) be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AUDMIX-Capture-%d", i); - priv->dai[num_dai + i].cpus = &dlc[2]; - priv->dai[num_dai + i].codecs = &dlc[3]; + priv->dai[num_dai + i].cpus = &dlc[1]; + priv->dai[num_dai + i].codecs = &asoc_dummy_dlc; priv->dai[num_dai + i].num_cpus = 1; priv->dai[num_dai + i].num_codecs = 1; priv->dai[num_dai + i].name = be_name; - priv->dai[num_dai + i].codecs->dai_name = "snd-soc-dummy-dai"; - priv->dai[num_dai + i].codecs->name = "snd-soc-dummy"; priv->dai[num_dai + i].cpus->of_node = audmix_np; priv->dai[num_dai + i].cpus->dai_name = be_name; priv->dai[num_dai + i].no_pcm = 1; diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 64a4d7e9db60..78e2e3932ba5 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -615,17 +615,8 @@ static int imx_card_parse_of(struct imx_card_data *data) plat_data->type = CODEC_AK5552; } else { - dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); - if (!dlc) { - ret = -ENOMEM; - goto err; - } - - link->codecs = dlc; + link->codecs = &asoc_dummy_dlc; link->num_codecs = 1; - - link->codecs->dai_name = "snd-soc-dummy-dai"; - link->codecs->name = "snd-soc-dummy"; } if (!strncmp(link->name, "HiFi-ASRC-FE", 12)) { diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c index 89178106fe2c..93fc976e98dc 100644 --- a/sound/soc/fsl/imx-rpmsg.c +++ b/sound/soc/fsl/imx-rpmsg.c @@ -92,8 +92,7 @@ static int imx_rpmsg_probe(struct platform_device *pdev) /* Optional codec node */ ret = of_parse_phandle_with_fixed_args(np, "audio-codec", 0, 0, &args); if (ret) { - data->dai.codecs->dai_name = "snd-soc-dummy-dai"; - data->dai.codecs->name = "snd-soc-dummy"; + *data->dai.codecs = asoc_dummy_dlc; } else { struct clk *clk; diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c index ab978431ac98..44463f92e522 100644 --- a/sound/soc/fsl/imx-spdif.c +++ b/sound/soc/fsl/imx-spdif.c @@ -26,7 +26,7 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) } data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); - comp = devm_kzalloc(&pdev->dev, 2 * sizeof(*comp), GFP_KERNEL); + comp = devm_kzalloc(&pdev->dev, sizeof(*comp), GFP_KERNEL); if (!data || !comp) { ret = -ENOMEM; goto end; @@ -37,8 +37,8 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) * platform is using soc-generic-dmaengine-pcm */ data->dai.cpus = - data->dai.platforms = &comp[0]; - data->dai.codecs = &comp[1]; + data->dai.platforms = comp; + data->dai.codecs = &asoc_dummy_dlc; data->dai.num_cpus = 1; data->dai.num_codecs = 1; @@ -46,8 +46,6 @@ static int imx_spdif_audio_probe(struct platform_device *pdev) data->dai.name = "S/PDIF PCM"; data->dai.stream_name = "S/PDIF PCM"; - data->dai.codecs->dai_name = "snd-soc-dummy-dai"; - data->dai.codecs->name = "snd-soc-dummy"; data->dai.cpus->of_node = spdif_np; data->dai.playback_only = true; data->dai.capture_only = true; -- cgit v1.2.3 From 1cef66f571a1791e2e29cc962e2c929f7f01cff1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:44 +0000 Subject: ASoC: qcom: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto codecs = dlc; + link->codecs = &asoc_dummy_dlc; link->num_codecs = 1; - - link->codecs->dai_name = "snd-soc-dummy-dai"; - link->codecs->name = "snd-soc-dummy"; link->dynamic = 1; } -- cgit v1.2.3 From ccfc8750dbe18755532ee3d7c94d918f9f5aee1f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:52 +0000 Subject: ASoC: atmel: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto cpus = &comp[0]; - dai_link->codecs = &comp[1]; + dai_link->cpus = comp; + dai_link->codecs = &asoc_dummy_dlc; dai_link->num_cpus = 1; dai_link->num_codecs = 1; dai_link->name = "CLASSD"; dai_link->stream_name = "CLASSD PCM"; - dai_link->codecs->dai_name = "snd-soc-dummy-dai"; dai_link->cpus->dai_name = dev_name(dev); - dai_link->codecs->name = "snd-soc-dummy"; card->dai_link = dai_link; card->num_links = 1; diff --git a/sound/soc/atmel/atmel-pdmic.c b/sound/soc/atmel/atmel-pdmic.c index 00c7b3a34ef5..efcbdd1a629f 100644 --- a/sound/soc/atmel/atmel-pdmic.c +++ b/sound/soc/atmel/atmel-pdmic.c @@ -496,21 +496,19 @@ static int atmel_pdmic_asoc_card_init(struct device *dev, if (!dai_link) return -ENOMEM; - comp = devm_kzalloc(dev, 2 * sizeof(*comp), GFP_KERNEL); + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); if (!comp) return -ENOMEM; - dai_link->cpus = &comp[0]; - dai_link->codecs = &comp[1]; + dai_link->cpus = comp; + dai_link->codecs = &asoc_dummy_dlc; dai_link->num_cpus = 1; dai_link->num_codecs = 1; dai_link->name = "PDMIC"; dai_link->stream_name = "PDMIC PCM"; - dai_link->codecs->dai_name = "snd-soc-dummy-dai"; dai_link->cpus->dai_name = dev_name(dev); - dai_link->codecs->name = "snd-soc-dummy"; card->dai_link = dai_link; card->num_links = 1; -- cgit v1.2.3 From 0c16ed1ab758538b4a4df8368bc1a8b22453a9b4 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:36:59 +0000 Subject: ASoC: meson: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto name) return -ENOMEM; - dlc = devm_kzalloc(card->dev, 2 * sizeof(*dlc), GFP_KERNEL); + dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); if (!dlc) return -ENOMEM; - lb->cpus = &dlc[0]; - lb->codecs = &dlc[1]; + lb->cpus = dlc; + lb->codecs = &asoc_dummy_dlc; lb->num_cpus = 1; lb->num_codecs = 1; lb->stream_name = lb->name; lb->cpus->of_node = pad->cpus->of_node; lb->cpus->dai_name = "TDM Loopback"; - lb->codecs->name = "snd-soc-dummy"; - lb->codecs->dai_name = "snd-soc-dummy-dai"; lb->dpcm_capture = 1; lb->no_pcm = 1; lb->ops = &axg_card_tdm_be_ops; diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c index 2d8d5717fd8b..ffc5111f9e3c 100644 --- a/sound/soc/meson/meson-card-utils.c +++ b/sound/soc/meson/meson-card-utils.c @@ -183,21 +183,13 @@ int meson_card_set_fe_link(struct snd_soc_card *card, struct device_node *node, bool is_playback) { - struct snd_soc_dai_link_component *codec; - - codec = devm_kzalloc(card->dev, sizeof(*codec), GFP_KERNEL); - if (!codec) - return -ENOMEM; - - link->codecs = codec; + link->codecs = &asoc_dummy_dlc; link->num_codecs = 1; link->dynamic = 1; link->dpcm_merged_format = 1; link->dpcm_merged_chan = 1; link->dpcm_merged_rate = 1; - link->codecs->dai_name = "snd-soc-dummy-dai"; - link->codecs->name = "snd-soc-dummy"; if (is_playback) link->dpcm_playback = 1; -- cgit v1.2.3 From 82528f31e6633a729772cc7366dc6529186cccea Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:37:06 +0000 Subject: ASoC: intel: avs: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-Codec", ssp_port); dl->cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); - dl->codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); - if (!dl->name || !dl->cpus || !dl->codecs) + if (!dl->name || !dl->cpus) return -ENOMEM; dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); - dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "snd-soc-dummy"); - dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "snd-soc-dummy-dai"); + dl->codecs = &asoc_dummy_dlc; if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) return -ENOMEM; -- cgit v1.2.3 From 1785af9ff65d3e7550657d979aea566385c2faa8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:37:11 +0000 Subject: ASoC: intel: sof: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto mach_params.common_hdmi_codec_drv && @@ -267,11 +266,8 @@ static void hdmi_link_init(struct snd_soc_card *card, * if HDMI is not enabled in kernel config, or * hdmi codec is not supported */ - for (i = HDMI_LINK_START; i <= HDMI_LINE_END; i++) { - link = &card->dai_link[i]; - link->codecs[0].name = "snd-soc-dummy"; - link->codecs[0].dai_name = "snd-soc-dummy-dai"; - } + for (i = HDMI_LINK_START; i <= HDMI_LINE_END; i++) + card->dai_link[i].codecs[0] = asoc_dummy_dlc; } static int snd_ehl_rt5660_probe(struct platform_device *pdev) diff --git a/sound/soc/intel/boards/sof_cs42l42.c b/sound/soc/intel/boards/sof_cs42l42.c index e9d190cb13b0..e6695e77d594 100644 --- a/sound/soc/intel/boards/sof_cs42l42.c +++ b/sound/soc/intel/boards/sof_cs42l42.c @@ -296,13 +296,6 @@ static struct snd_soc_dai_link_component dmic_component[] = { } }; -static struct snd_soc_dai_link_component dummy_component[] = { - { - .name = "snd-soc-dummy", - .dai_name = "snd-soc-dummy-dai", - } -}; - static int create_spk_amp_dai_links(struct device *dev, struct snd_soc_dai_link *links, struct snd_soc_dai_link_component *cpus, @@ -510,8 +503,8 @@ static int create_bt_offload_dai_links(struct device *dev, goto devm_err; links[*id].id = *id; - links[*id].codecs = dummy_component; - links[*id].num_codecs = ARRAY_SIZE(dummy_component); + links[*id].codecs = &asoc_dummy_dlc; + links[*id].num_codecs = 1; links[*id].platforms = platform_component; links[*id].num_platforms = ARRAY_SIZE(platform_component); diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index adf5852b2c9a..d6c38d8ea2ff 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -393,13 +393,6 @@ static struct snd_soc_dai_link_component dmic_component[] = { } }; -static struct snd_soc_dai_link_component dummy_component[] = { - { - .name = "snd-soc-dummy", - .dai_name = "snd-soc-dummy-dai", - } -}; - static int sof_es8336_late_probe(struct snd_soc_card *card) { struct sof_es8336_private *priv = snd_soc_card_get_drvdata(card); @@ -572,8 +565,8 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!links[id].name) return NULL; links[id].id = id + hdmi_id_offset; - links[id].codecs = dummy_component; - links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].codecs = &asoc_dummy_dlc; + links[id].num_codecs = 1; links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].dpcm_capture = 1; diff --git a/sound/soc/intel/boards/sof_nau8825.c b/sound/soc/intel/boards/sof_nau8825.c index 6794a0249a9a..30e798431e1f 100644 --- a/sound/soc/intel/boards/sof_nau8825.c +++ b/sound/soc/intel/boards/sof_nau8825.c @@ -346,13 +346,6 @@ static struct snd_soc_dai_link_component nau8318_components[] = { } }; -static struct snd_soc_dai_link_component dummy_component[] = { - { - .name = "snd-soc-dummy", - .dai_name = "snd-soc-dummy-dai", - } -}; - static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, int ssp_codec, int ssp_amp, @@ -532,8 +525,8 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); if (!links[id].name) goto devm_err; - links[id].codecs = dummy_component; - links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].codecs = &asoc_dummy_dlc; + links[id].num_codecs = 1; links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].dpcm_playback = 1; diff --git a/sound/soc/intel/boards/sof_pcm512x.c b/sound/soc/intel/boards/sof_pcm512x.c index 5192e02b3cee..9f673ccf81b5 100644 --- a/sound/soc/intel/boards/sof_pcm512x.c +++ b/sound/soc/intel/boards/sof_pcm512x.c @@ -331,8 +331,7 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, devm_kasprintf(dev, GFP_KERNEL, "intel-hdmi-hifi%d", i); } else { - idisp_components[i - 1].name = "snd-soc-dummy"; - idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + idisp_components[i - 1] = asoc_dummy_dlc; } if (!idisp_components[i - 1].dai_name) goto devm_err; diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 791a59c5f00d..7f4783592668 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -607,13 +607,6 @@ static struct snd_soc_dai_link_component dmic_component[] = { } }; -static struct snd_soc_dai_link_component dummy_component[] = { - { - .name = "snd-soc-dummy", - .dai_name = "snd-soc-dummy-dai", - } -}; - #define IDISP_CODEC_MASK 0x4 static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, @@ -745,8 +738,7 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!idisp_components[i - 1].dai_name) goto devm_err; } else { - idisp_components[i - 1].name = "snd-soc-dummy"; - idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + idisp_components[i - 1] = asoc_dummy_dlc; } links[id].codecs = &idisp_components[i - 1]; @@ -841,8 +833,8 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); if (!links[id].name) goto devm_err; - links[id].codecs = dummy_component; - links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].codecs = &asoc_dummy_dlc; + links[id].num_codecs = 1; links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].dpcm_playback = 1; diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 6faf4a43eaf5..c845a5cf7f4d 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -1488,8 +1488,7 @@ HDMI: if (!idisp_components[i].dai_name) return -ENOMEM; } else { - idisp_components[i].name = "snd-soc-dummy"; - idisp_components[i].dai_name = "snd-soc-dummy-dai"; + idisp_components[i] = asoc_dummy_dlc; } cpu_name = devm_kasprintf(dev, GFP_KERNEL, @@ -1514,21 +1513,13 @@ HDMI: if (!name) return -ENOMEM; - ssp_components = devm_kzalloc(dev, sizeof(*ssp_components), - GFP_KERNEL); - if (!ssp_components) - return -ENOMEM; - - ssp_components->name = "snd-soc-dummy"; - ssp_components->dai_name = "snd-soc-dummy-dai"; - cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", port); if (!cpu_name) return -ENOMEM; cpus[cpu_id].dai_name = cpu_name; init_dai_link(dev, links + link_index, be_id, name, 1, 1, - cpus + cpu_id, 1, ssp_components, 1, NULL, NULL); + cpus + cpu_id, 1, &asoc_dummy_dlc, 1, NULL, NULL); } card->dai_link = links; diff --git a/sound/soc/intel/boards/sof_ssp_amp.c b/sound/soc/intel/boards/sof_ssp_amp.c index ffd9c583dab1..b33f720b3e6d 100644 --- a/sound/soc/intel/boards/sof_ssp_amp.c +++ b/sound/soc/intel/boards/sof_ssp_amp.c @@ -167,13 +167,6 @@ static struct snd_soc_dai_link_component dmic_component[] = { } }; -static struct snd_soc_dai_link_component dummy_component[] = { - { - .name = "snd-soc-dummy", - .dai_name = "snd-soc-dummy-dai", - } -}; - static int sof_hdmi_init(struct snd_soc_pcm_runtime *rtd) { struct sof_card_private *ctx = snd_soc_card_get_drvdata(rtd->card); @@ -233,8 +226,8 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!links[id].name) return NULL; links[id].id = id; - links[id].codecs = dummy_component; - links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].codecs = &asoc_dummy_dlc; + links[id].num_codecs = 1; links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].dpcm_capture = 1; @@ -331,8 +324,7 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, if (!idisp_components[i - 1].dai_name) goto devm_err; } else { - idisp_components[i - 1].name = "snd-soc-dummy"; - idisp_components[i - 1].dai_name = "snd-soc-dummy-dai"; + idisp_components[i - 1] = asoc_dummy_dlc; } links[id].codecs = &idisp_components[i - 1]; @@ -360,8 +352,8 @@ static struct snd_soc_dai_link *sof_card_dai_links_create(struct device *dev, links[id].name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d-BT", port); if (!links[id].name) goto devm_err; - links[id].codecs = dummy_component; - links[id].num_codecs = ARRAY_SIZE(dummy_component); + links[id].codecs = &asoc_dummy_dlc; + links[id].num_codecs = 1; links[id].platforms = platform_component; links[id].num_platforms = ARRAY_SIZE(platform_component); links[id].dpcm_playback = 1; -- cgit v1.2.3 From 1a39e17813502b8cbf47c297db80838b4d701555 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:37:17 +0000 Subject: ASoC: intel: skylake: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto num_dapm_widgets = ARRAY_SIZE(skl_hda_widgets); if (!ctx->idisp_codec) { for (i = 0; i < IDISP_DAI_COUNT; i++) { - skl_hda_be_dai_links[i].codecs = dummy_codec; - skl_hda_be_dai_links[i].num_codecs = - ARRAY_SIZE(dummy_codec); + skl_hda_be_dai_links[i].codecs = &asoc_dummy_dlc; + skl_hda_be_dai_links[i].num_codecs = 1; } } } -- cgit v1.2.3 From 4d626112565f58f03de692851007d64c6a6341c5 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:37:24 +0000 Subject: ASoC: simple_card_utils.c: use asoc_dummy_dlc Now we can share asoc_dummy_dlc. This patch use it. Signed-off-by: Kuninori Morimoto link, dai_num, cnf_num); - /* dummy CPU/Codec */ - priv->dummy.of_node = NULL; - priv->dummy.dai_name = "snd-soc-dummy-dai"; - priv->dummy.name = "snd-soc-dummy"; - priv->dai_props = dai_props; priv->dai_link = dai_link; priv->dais = dais; @@ -919,7 +914,7 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, } else { /* DPCM Be's CPU = dummy */ dai_props[i].cpus = - dai_link[i].cpus = &priv->dummy; + dai_link[i].cpus = &asoc_dummy_dlc; dai_props[i].num.cpus = dai_link[i].num_cpus = 1; } @@ -943,7 +938,7 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, } else { /* DPCM Fe's Codec = dummy */ dai_props[i].codecs = - dai_link[i].codecs = &priv->dummy; + dai_link[i].codecs = &asoc_dummy_dlc; dai_props[i].num.codecs = dai_link[i].num_codecs = 1; } -- cgit v1.2.3 From 5a7bec81bd229a2512b303578e137324ba0eff5f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 24 Apr 2023 02:37:38 +0000 Subject: ASoC: soc-topology.c: add comment for Platform/Codec Not only Platform but Codec also might be overwritten on Topology. This patch adds comment about it not to use asoc_dummy_dlc here. Signed-off-by: Kuninori Morimoto cpus = &dlc[0]; - link->codecs = &dlc[1]; - link->num_cpus = 1; - link->num_codecs = 1; link->dobj.index = tplg->index; link->dobj.type = SND_SOC_DOBJ_DAI_LINK; @@ -1721,16 +1718,19 @@ static int soc_tplg_fe_link_create(struct soc_tplg *tplg, } } - link->codecs->name = "snd-soc-dummy"; - link->codecs->dai_name = "snd-soc-dummy-dai"; - /* - * Many topology is assuming link has Platform. - * This might be overwritten at soc_tplg_dai_link_load(). + * Many topology are assuming link has Codec / Platform, and + * these might be overwritten at soc_tplg_dai_link_load(). + * Don't use &asoc_dummy_dlc here. */ - link->platforms = &dlc[2]; - link->platforms->name = "snd-soc-dummy"; - link->num_platforms = 1; + link->codecs = &dlc[1]; /* Don't use &asoc_dummy_dlc here */ + link->codecs->name = "snd-soc-dummy"; + link->codecs->dai_name = "snd-soc-dummy-dai"; + link->num_codecs = 1; + + link->platforms = &dlc[2]; /* Don't use &asoc_dummy_dlc here */ + link->platforms->name = "snd-soc-dummy"; + link->num_platforms = 1; /* enable DPCM */ link->dynamic = 1; -- cgit v1.2.3 From 06ba8020287f43fc13962b158d8dec2689448a5a Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 3 May 2023 13:34:09 +0200 Subject: ASoC: SOF: mediatek: mt8195: Use snd_sof_ipc_process_reply() helper Function mt8195_get_reply() performs practically the same operation as the common snd_sof_ipc_get_reply() helper: removing the custom function allows us to simply perform a call to the sof-priv helper snd_sof_ipc_process_reply(), simplifying and shortening this driver and getting all the benefits of using a common API. Signed-off-by: AngeloGioacchino Del Regno dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); } -static void mt8195_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply has correct size? */ - if (reply.hdr.size != msg->reply_size) { - dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", - msg->reply_size, reply.hdr.size); - ret = -EINVAL; - } - - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; -} - static void mt8195_dsp_handle_reply(struct mtk_adsp_ipc *ipc) { struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); unsigned long flags; spin_lock_irqsave(&priv->sdev->ipc_lock, flags); - mt8195_get_reply(priv->sdev); - snd_sof_ipc_reply(priv->sdev, 0); + snd_sof_ipc_process_reply(priv->sdev, 0); spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); } -- cgit v1.2.3 From 709f34b41ceffedec17d1150018cf62f8ea95842 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 3 May 2023 13:34:10 +0200 Subject: ASoC: SOF: mediatek: mt8186: Use snd_sof_ipc_process_reply() helper Function mt8186_get_reply() performs practically the same operation as the common snd_sof_ipc_get_reply() helper: removing the custom function allows us to simply perform a call to the sof-priv helper snd_sof_ipc_process_reply(), simplifying and shortening this driver and getting all the benefits of using a common API. Signed-off-by: AngeloGioacchino Del Regno dsp_ipc, MTK_ADSP_IPC_REQ, MTK_ADSP_IPC_OP_REQ); } -static void mt8186_get_reply(struct snd_sof_dev *sdev) -{ - struct snd_sof_ipc_msg *msg = sdev->msg; - struct sof_ipc_reply reply; - int ret = 0; - - if (!msg) { - dev_warn(sdev->dev, "unexpected ipc interrupt\n"); - return; - } - - /* get reply */ - sof_mailbox_read(sdev, sdev->host_box.offset, &reply, sizeof(reply)); - if (reply.error < 0) { - memcpy(msg->reply_data, &reply, sizeof(reply)); - ret = reply.error; - } else { - /* reply has correct size? */ - if (reply.hdr.size != msg->reply_size) { - dev_err(sdev->dev, "error: reply expected %zu got %u bytes\n", - msg->reply_size, reply.hdr.size); - ret = -EINVAL; - } - - /* read the message */ - if (msg->reply_size > 0) - sof_mailbox_read(sdev, sdev->host_box.offset, - msg->reply_data, msg->reply_size); - } - - msg->reply_error = ret; -} - static void mt8186_dsp_handle_reply(struct mtk_adsp_ipc *ipc) { struct adsp_priv *priv = mtk_adsp_ipc_get_data(ipc); unsigned long flags; spin_lock_irqsave(&priv->sdev->ipc_lock, flags); - mt8186_get_reply(priv->sdev); - snd_sof_ipc_reply(priv->sdev, 0); + snd_sof_ipc_process_reply(priv->sdev, 0); spin_unlock_irqrestore(&priv->sdev->ipc_lock, flags); } -- cgit v1.2.3 From 686d041685639493a608bdcdb0d00551796721c9 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 3 May 2023 13:34:13 +0200 Subject: ASoC: mediatek: mt8195-afe-pcm: Clean up unnecessary functions Function mt8195_afe_init_registers() performs just a single call to regmap_multi_reg_write(), it returns int and it's not error checked; move that call to the probe function and also add some error check. While at it, also move the contents of mt8195_afe_parse_of() to the probe function as well: since this is getting a handle to topckgen and since that's optional, the ifdef for CONFIG_SND_SOC_MT6359 can also be removed. Signed-off-by: AngeloGioacchino Del Regno regmap, - mt8195_afe_reg_defaults, - ARRAY_SIZE(mt8195_afe_reg_defaults)); -} - -static void mt8195_afe_parse_of(struct mtk_base_afe *afe, - struct device_node *np) -{ -#if IS_ENABLED(CONFIG_SND_SOC_MT6359) - struct mt8195_afe_private *afe_priv = afe->platform_priv; - - afe_priv->topckgen = syscon_regmap_lookup_by_phandle(afe->dev->of_node, - "mediatek,topckgen"); - if (IS_ERR(afe_priv->topckgen)) { - dev_info(afe->dev, "%s() Cannot find topckgen controller: %ld\n", - __func__, PTR_ERR(afe_priv->topckgen)); - } -#endif -} - static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; @@ -3177,7 +3155,10 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) platform_set_drvdata(pdev, afe); - mt8195_afe_parse_of(afe, pdev->dev.of_node); + afe_priv->topckgen = syscon_regmap_lookup_by_phandle(dev->of_node, "mediatek,topckgen"); + if (IS_ERR(afe_priv->topckgen)) + dev_dbg(afe->dev, "Cannot find topckgen controller: %ld\n", + PTR_ERR(afe_priv->topckgen)); pm_runtime_enable(dev); if (!pm_runtime_enabled(dev)) { @@ -3236,7 +3217,10 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) goto err_pm_put; } - mt8195_afe_init_registers(afe); + ret = regmap_multi_reg_write(afe->regmap, mt8195_afe_reg_defaults, + ARRAY_SIZE(mt8195_afe_reg_defaults)); + if (ret) + goto err_pm_put; pm_runtime_put_sync(dev); afe_priv->pm_runtime_bypass_reg_ctl = false; -- cgit v1.2.3 From 2ca0ec01d49c9c2742c2151ae94c94bdf36bb1b8 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 3 May 2023 13:34:11 +0200 Subject: ASoC: mediatek: mt8195-afe-pcm: Simplify runtime PM during probe Use devm_pm_runtime_enable() and pm_runtime_resume_and_get() to to simplify the probe function. Signed-off-by: AngeloGioacchino Del Regno dev, "Cannot find topckgen controller: %ld\n", PTR_ERR(afe_priv->topckgen)); - pm_runtime_enable(dev); - if (!pm_runtime_enabled(dev)) { - ret = mt8195_afe_runtime_resume(dev); - if (ret) - return ret; - } - /* enable clock for regcache get default value from hw */ afe_priv->pm_runtime_bypass_reg_ctl = true; - pm_runtime_get_sync(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to resume device\n"); afe->regmap = devm_regmap_init_mmio(&pdev->dev, afe->base_addr, &mt8195_afe_regmap_config); @@ -3222,7 +3222,10 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) goto err_pm_put; - pm_runtime_put_sync(dev); + ret = pm_runtime_put_sync(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to suspend device\n"); + afe_priv->pm_runtime_bypass_reg_ctl = false; regcache_cache_only(afe->regmap, true); @@ -3232,7 +3235,6 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) err_pm_put: pm_runtime_put_sync(dev); - pm_runtime_disable(dev); return ret; } -- cgit v1.2.3 From 863da1c17616e45f1472370892b6c925cee27e24 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 3 May 2023 13:34:12 +0200 Subject: ASoC: mediatek: mt8195-afe-pcm: Simplify with dev_err_probe() Shorten the probe function by switching to dev_err_probe() where possible. Signed-off-by: AngeloGioacchino Del Regno afe_ctrl_lock); @@ -3121,30 +3112,22 @@ static int mt8195_afe_pcm_dev_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq_id, mt8195_afe_irq_handler, IRQF_TRIGGER_NONE, "asys-isr", (void *)afe); - if (ret) { - dev_err(dev, "could not request_irq for asys-isr\n"); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "could not request_irq for asys-isr\n"); /* init sub_dais */ INIT_LIST_HEAD(&afe->sub_dais); for (i = 0; i < ARRAY_SIZE(dai_register_cbs); i++) { ret = dai_register_cbs[i](afe); - if (ret) { - dev_warn(dev, "dai register i %d fail, ret %d\n", - i, ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "dai cb%i register fail\n", i); } /* init dai_driver and component_driver */ ret = mtk_afe_combine_sub_dai(afe); - if (ret) { - dev_warn(dev, "mtk_afe_combine_sub_dai fail, ret %d\n", - ret); - return ret; - } + if (ret) + return dev_err_probe(dev, ret, "mtk_afe_combine_sub_dai fail\n"); afe->mtk_afe_hardware = &mt8195_afe_hardware; afe->memif_fs = mt8195_memif_fs; -- cgit v1.2.3 From c00018cadfbf07b95d10b0e152c4edd7d5d19779 Mon Sep 17 00:00:00 2001 From: Maxim Kochetkov Date: Thu, 4 May 2023 10:16:17 +0300 Subject: ASoC: dwc: add optional reset support Some SoC may have resets for I2S subsystem. So add optional reset support. Signed-off-by: Maxim Kochetkov #include #include +#include #include #include #include @@ -648,6 +649,14 @@ static int dw_i2s_probe(struct platform_device *pdev) if (IS_ERR(dev->i2s_base)) return PTR_ERR(dev->i2s_base); + dev->reset = devm_reset_control_array_get_optional_shared(&pdev->dev); + if (IS_ERR(dev->reset)) + return PTR_ERR(dev->reset); + + ret = reset_control_deassert(dev->reset); + if (ret) + return ret; + dev->dev = &pdev->dev; irq = platform_get_irq_optional(pdev, 0); @@ -656,7 +665,7 @@ static int dw_i2s_probe(struct platform_device *pdev) pdev->name, dev); if (ret < 0) { dev_err(&pdev->dev, "failed to request irq\n"); - return ret; + goto err_assert_reset; } } @@ -676,24 +685,27 @@ static int dw_i2s_probe(struct platform_device *pdev) ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); } if (ret < 0) - return ret; + goto err_assert_reset; if (dev->capability & DW_I2S_MASTER) { if (pdata) { dev->i2s_clk_cfg = pdata->i2s_clk_cfg; if (!dev->i2s_clk_cfg) { dev_err(&pdev->dev, "no clock configure method\n"); - return -ENODEV; + ret = -ENODEV; + goto err_assert_reset; } } dev->clk = devm_clk_get(&pdev->dev, clk_id); - if (IS_ERR(dev->clk)) - return PTR_ERR(dev->clk); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + goto err_assert_reset; + } ret = clk_prepare_enable(dev->clk); if (ret < 0) - return ret; + goto err_assert_reset; } dev_set_drvdata(&pdev->dev, dev); @@ -727,6 +739,8 @@ static int dw_i2s_probe(struct platform_device *pdev) err_clk_disable: if (dev->capability & DW_I2S_MASTER) clk_disable_unprepare(dev->clk); +err_assert_reset: + reset_control_assert(dev->reset); return ret; } @@ -737,6 +751,7 @@ static void dw_i2s_remove(struct platform_device *pdev) if (dev->capability & DW_I2S_MASTER) clk_disable_unprepare(dev->clk); + reset_control_assert(dev->reset); pm_runtime_disable(&pdev->dev); } diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h index 1c361eb6127e..d64bd4f8fd34 100644 --- a/sound/soc/dwc/local.h +++ b/sound/soc/dwc/local.h @@ -89,6 +89,7 @@ union dw_i2s_snd_dma_data { struct dw_i2s_dev { void __iomem *i2s_base; struct clk *clk; + struct reset_control *reset; int active; unsigned int capability; unsigned int quirks; -- cgit v1.2.3 From 50f655429957702302c91835d84a03b9672862c2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 21 Apr 2023 17:35:46 +0100 Subject: ASoC: rt715: Use maple tree register cache regmap has introduced a maple tree based register cache which makes use of this more advanced data structure which has been added to the kernel recently. Maple trees are much flatter than rbtrees, meaning that they do not grow to such depths when the register map is sparse which makes access a bit more efficient. The maple tree cache type is still a bit of a work in progress but should be effective for some devices already. RT715 seems like a good candidate for maple tree. It is a SoundWire MBQ device and therefore supports only single register read/write operations which do not use raw I/O and will therefore save the cost of converting to and from device native format when accessing the cache while not having a negative impact from the current lack of bulk operations in maple tree cache sync. It has a moderately large and quite sparse register map which is a good fit for storing in a maple tree. Convert to use maple tree. There should be little if any visible difference at runtime. Signed-off-by: Mark Brown Date: Tue, 25 Apr 2023 18:22:46 +0100 Subject: ASoC: rt5682: Use a maple tree based register cache regmap has introduced a maple tree based register cache which makes use of this more advanced data structure which has been added to the kernel recently. Maple trees are much flatter than rbtrees, meaning that they do not grow to such depths when the register map is sparse which makes access a bit more efficient. The maple tree cache type is still a bit of a work in progress but should be effective for some devices already. RT5682 seems like a good candidate for maple tree. It only supports single register read/write operations so will gain minimal benefit from storing the register data in device native format like rbtree does (none for SoundWire) and has some sparsity in the register map which is a good fit for maple tree. Convert to use maple tree. There should be little if any visible difference at runtime. Signed-off-by: Mark Brown Date: Fri, 5 May 2023 08:35:21 +0300 Subject: ASoC: dwc: extend supported formats The COMP1_TX_WORDSIZE_0/COMP2_RX_WORDSIZE_0 fields in the comp registers indicate the maximum wordsize supported. DWC I2S controller can operate with any smaller wordsize. So extend the formats to let I2S to operate in any allowed modes. Signed-off-by: Maxim Kochetkov Date: Thu, 20 Apr 2023 18:02:12 +0000 Subject: ASoC: amd: ps: Update copyright notice The most recent changes to ASoC, such as new module parameters, date to the year 2023. Update copyright statement accordingly. Signed-off-by: Carlos Bilbao -- cgit v1.2.3 From 9abcd24002bf7997be752b6b48b137c4db3a609b Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Tue, 25 Apr 2023 11:57:16 +0200 Subject: ASoC: Switch i2c drivers back to use .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After commit b8a1a4cd5a98 ("i2c: Provide a temporary .probe_new() call-back type"), all drivers being converted to .probe_new() and then 03c835f498b5 ("i2c: Switch .probe() to not take an id parameter") convert back to (the new) .probe() to be able to eventually drop .probe_new() from struct i2c_driver. Signed-off-by: Uwe Kleine-König Date: Wed, 26 Apr 2023 17:52:01 +0530 Subject: ASoC: amd: ps: remove the register read and write wrappers. Instead of acp63_readl() and acp63_writel() wrappers readl and writel functions can be used directly. Remove acp63_readl() and acp63_writel() wrappers. Signed-off-by: Syed Saba Kareem acp63_base + ACP_EXTERNAL_INTR_STAT); + val = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT); if (val & BIT(PDM_DMA_STAT)) { pdev_index = adata->pdm_dev_index; ps_pdm_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev); - acp63_writel(BIT(PDM_DMA_STAT), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + writel(BIT(PDM_DMA_STAT), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); if (ps_pdm_data->capture_stream) snd_pcm_period_elapsed(ps_pdm_data->capture_stream); return IRQ_HANDLED; @@ -302,7 +301,7 @@ static int snd_acp63_probe(struct pci_dev *pci, dev_err(&pci->dev, "ACP PCI IRQ request failed\n"); goto de_init; } - val = acp63_readl(adata->acp63_base + ACP_PIN_CONFIG); + val = readl(adata->acp63_base + ACP_PIN_CONFIG); get_acp63_device_config(val, pci, adata); ret = create_acp63_platform_devs(pci, adata, addr); if (ret < 0) { diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index 46b91327168f..3ecc6cf3fd34 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -45,10 +45,10 @@ static const struct snd_pcm_hardware acp63_pdm_hardware_capture = { static void acp63_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size, u32 watermark_size, void __iomem *acp_base) { - acp63_writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR); - acp63_writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE); - acp63_writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); - acp63_writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL); + writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR); + writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE); + writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE); + writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL); } static void acp63_enable_pdm_clock(void __iomem *acp_base) @@ -58,11 +58,11 @@ static void acp63_enable_pdm_clock(void __iomem *acp_base) pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK; pdm_ctrl = 0x00; - acp63_writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL); - pdm_ctrl = acp63_readl(acp_base + ACP_WOV_MISC_CTRL); + writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL); + pdm_ctrl = readl(acp_base + ACP_WOV_MISC_CTRL); pdm_ctrl &= ~ACP_WOV_GAIN_CONTROL; pdm_ctrl |= FIELD_PREP(ACP_WOV_GAIN_CONTROL, clamp(pdm_gain, 0, 3)); - acp63_writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL); + writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL); } static void acp63_enable_pdm_interrupts(struct pdm_dev_data *adata) @@ -70,9 +70,9 @@ static void acp63_enable_pdm_interrupts(struct pdm_dev_data *adata) u32 ext_int_ctrl; mutex_lock(adata->acp_lock); - ext_int_ctrl = acp63_readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); ext_int_ctrl |= PDM_DMA_INTR_MASK; - acp63_writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); + writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); mutex_unlock(adata->acp_lock); } @@ -81,9 +81,9 @@ static void acp63_disable_pdm_interrupts(struct pdm_dev_data *adata) u32 ext_int_ctrl; mutex_lock(adata->acp_lock); - ext_int_ctrl = acp63_readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); + ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); ext_int_ctrl &= ~PDM_DMA_INTR_MASK; - acp63_writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); + writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL); mutex_unlock(adata->acp_lock); } @@ -93,8 +93,8 @@ static bool acp63_check_pdm_dma_status(void __iomem *acp_base) u32 pdm_enable, pdm_dma_enable; pdm_dma_status = false; - pdm_enable = acp63_readl(acp_base + ACP_WOV_PDM_ENABLE); - pdm_dma_enable = acp63_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS)) pdm_dma_status = true; @@ -111,11 +111,11 @@ static int acp63_start_pdm_dma(void __iomem *acp_base) pdm_dma_enable = 0x01; acp63_enable_pdm_clock(acp_base); - acp63_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); - acp63_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); timeout = 0; while (++timeout < ACP_COUNTER) { - pdm_dma_enable = acp63_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS) return 0; udelay(DELAY_US); @@ -131,14 +131,14 @@ static int acp63_stop_pdm_dma(void __iomem *acp_base) pdm_enable = 0x00; pdm_dma_enable = 0x00; - pdm_enable = acp63_readl(acp_base + ACP_WOV_PDM_ENABLE); - pdm_dma_enable = acp63_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE); + pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); if (pdm_dma_enable & 0x01) { pdm_dma_enable = 0x02; - acp63_writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); + writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE); timeout = 0; while (++timeout < ACP_COUNTER) { - pdm_dma_enable = acp63_readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); + pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE); if ((pdm_dma_enable & 0x02) == 0x00) break; udelay(DELAY_US); @@ -148,9 +148,9 @@ static int acp63_stop_pdm_dma(void __iomem *acp_base) } if (pdm_enable == ACP_PDM_ENABLE) { pdm_enable = ACP_PDM_DISABLE; - acp63_writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); + writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE); } - acp63_writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH); + writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH); return 0; } @@ -164,18 +164,16 @@ static void acp63_config_dma(struct pdm_stream_instance *rtd, int direction) val = PDM_PTE_OFFSET; /* Group Enable */ - acp63_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp63_base + - ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); - acp63_writel(PAGE_SIZE_4K_ENABLE, rtd->acp63_base + - ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); + writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp63_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1); + writel(PAGE_SIZE_4K_ENABLE, rtd->acp63_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1); for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) { /* Load the low address of page int ACP SRAM through SRBM */ low = lower_32_bits(addr); high = upper_32_bits(addr); - acp63_writel(low, rtd->acp63_base + ACP_SCRATCH_REG_0 + val); + writel(low, rtd->acp63_base + ACP_SCRATCH_REG_0 + val); high |= BIT(31); - acp63_writel(high, rtd->acp63_base + ACP_SCRATCH_REG_0 + val + 4); + writel(high, rtd->acp63_base + ACP_SCRATCH_REG_0 + val + 4); val += 8; addr += PAGE_SIZE; } @@ -242,9 +240,9 @@ static u64 acp63_pdm_get_byte_count(struct pdm_stream_instance *rtd, u32 high, low; u64 byte_count; - high = acp63_readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); + high = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH); byte_count = high; - low = acp63_readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); + low = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW); byte_count = (byte_count << 32) | low; return byte_count; } @@ -309,9 +307,8 @@ static int acp63_pdm_dai_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - acp63_writel(ch_mask, rtd->acp63_base + ACP_WOV_PDM_NO_OF_CHANNELS); - acp63_writel(PDM_DECIMATION_FACTOR, rtd->acp63_base + - ACP_WOV_PDM_DECIMATION_FACTOR); + writel(ch_mask, rtd->acp63_base + ACP_WOV_PDM_NO_OF_CHANNELS); + writel(PDM_DECIMATION_FACTOR, rtd->acp63_base + ACP_WOV_PDM_DECIMATION_FACTOR); rtd->bytescount = acp63_pdm_get_byte_count(rtd, substream->stream); pdm_status = acp63_check_pdm_dma_status(rtd->acp63_base); if (!pdm_status) -- cgit v1.2.3 From ea79b0a663f7bade8664d9ba5017019fc96b91f8 Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Wed, 26 Apr 2023 17:52:02 +0530 Subject: ASoC: amd: ps: refactor acp power on and reset functions. Instead of a busy waiting while loop using udelay in acp63_power_on and acp63_reset functions use readl_poll_timeout function to check the condition. Signed-off-by: Syed Saba Kareem #include #include +#include #include "acp63.h" static int acp63_power_on(void __iomem *acp_base) { u32 val; - int timeout; val = readl(acp_base + ACP_PGFSM_STATUS); @@ -29,38 +29,26 @@ static int acp63_power_on(void __iomem *acp_base) if ((val & ACP_PGFSM_STATUS_MASK) != ACP_POWER_ON_IN_PROGRESS) writel(ACP_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL); - timeout = 0; - while (++timeout < 500) { - val = readl(acp_base + ACP_PGFSM_STATUS); - if (!val) - return 0; - udelay(1); - } - return -ETIMEDOUT; + + return readl_poll_timeout(acp_base + ACP_PGFSM_STATUS, val, !val, DELAY_US, ACP_TIMEOUT); } static int acp63_reset(void __iomem *acp_base) { u32 val; - int timeout; + int ret; writel(1, acp_base + ACP_SOFT_RESET); - timeout = 0; - while (++timeout < 500) { - val = readl(acp_base + ACP_SOFT_RESET); - if (val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK) - break; - cpu_relax(); - } + + ret = readl_poll_timeout(acp_base + ACP_SOFT_RESET, val, + val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK, + DELAY_US, ACP_TIMEOUT); + if (ret) + return ret; + writel(0, acp_base + ACP_SOFT_RESET); - timeout = 0; - while (++timeout < 500) { - val = readl(acp_base + ACP_SOFT_RESET); - if (!val) - return 0; - cpu_relax(); - } - return -ETIMEDOUT; + + return readl_poll_timeout(acp_base + ACP_SOFT_RESET, val, !val, DELAY_US, ACP_TIMEOUT); } static void acp63_enable_interrupts(void __iomem *acp_base) -- cgit v1.2.3 From 101b23830d3c26e9549274d16e8d4542c8bce4af Mon Sep 17 00:00:00 2001 From: Yang Li Date: Fri, 5 May 2023 08:45:38 +0800 Subject: ASoC: codecs: wcd938x: Remove unneeded semicolon ./sound/soc/codecs/wcd938x-sdw.c:1274:2-3: Unneeded semicolon Reported-by: Abaci Robot regmap, true); - }; + } pm_runtime_set_autosuspend_delay(dev, 3000); pm_runtime_use_autosuspend(dev); -- cgit v1.2.3 From 3e4a826129980fed0e3e746a7822f2f204dfc24a Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Fri, 5 May 2023 15:55:22 +0800 Subject: ASoC: fsl_sai: MCLK bind with TX/RX enable bit On i.MX8MP, the sai MCLK is bound with TX/RX enable bit, which means the TX/RE enable bit need to be enabled then MCLK can be output on PAD. Some codec (for example: WM8962) needs the MCLK output earlier, otherwise there will be issue for codec configuration. Add new soc data "mclk_with_tere" for this platform and enable the MCLK output in startup stage. As "mclk_with_tere" only applied to i.MX8MP, currently The soc data is shared with i.MX8MN, so need to add an i.MX8MN own soc data with "mclk_with_tere" disabled. Signed-off-by: Shengjiu Wang cpu_dai_drv.symmetric_sample_bits = 0; } - if (of_property_read_bool(np, "fsl,sai-mclk-direction-output") && + sai->mclk_direction_output = of_property_read_bool(np, "fsl,sai-mclk-direction-output"); + + if (sai->mclk_direction_output && of_device_is_compatible(np, "fsl,imx6ul-sai")) { gpr = syscon_regmap_lookup_by_compatible("fsl,imx6ul-iomuxc-gpr"); if (IS_ERR(gpr)) { @@ -1443,7 +1445,7 @@ static int fsl_sai_probe(struct platform_device *pdev) dev_warn(dev, "Error reading SAI version: %d\n", ret); /* Select MCLK direction */ - if (of_property_read_bool(np, "fsl,sai-mclk-direction-output") && + if (sai->mclk_direction_output && sai->soc_data->max_register >= FSL_SAI_MCTL) { regmap_update_bits(sai->regmap, FSL_SAI_MCTL, FSL_SAI_MCTL_MCLK_EN, FSL_SAI_MCTL_MCLK_EN); @@ -1562,6 +1564,17 @@ static const struct fsl_sai_soc_data fsl_sai_imx8mm_data = { .max_register = FSL_SAI_MCTL, }; +static const struct fsl_sai_soc_data fsl_sai_imx8mn_data = { + .use_imx_pcm = true, + .use_edma = false, + .fifo_depth = 128, + .reg_offset = 8, + .mclk0_is_mclk1 = false, + .pins = 8, + .flags = 0, + .max_register = FSL_SAI_MDIV, +}; + static const struct fsl_sai_soc_data fsl_sai_imx8mp_data = { .use_imx_pcm = true, .use_edma = false, @@ -1571,6 +1584,7 @@ static const struct fsl_sai_soc_data fsl_sai_imx8mp_data = { .pins = 8, .flags = 0, .max_register = FSL_SAI_MDIV, + .mclk_with_tere = true, }; static const struct fsl_sai_soc_data fsl_sai_imx8ulp_data = { @@ -1606,7 +1620,7 @@ static const struct of_device_id fsl_sai_ids[] = { { .compatible = "fsl,imx8mm-sai", .data = &fsl_sai_imx8mm_data }, { .compatible = "fsl,imx8mp-sai", .data = &fsl_sai_imx8mp_data }, { .compatible = "fsl,imx8ulp-sai", .data = &fsl_sai_imx8ulp_data }, - { .compatible = "fsl,imx8mn-sai", .data = &fsl_sai_imx8mp_data }, + { .compatible = "fsl,imx8mn-sai", .data = &fsl_sai_imx8mn_data }, { .compatible = "fsl,imx93-sai", .data = &fsl_sai_imx93_data }, { /* sentinel */ } }; @@ -1671,6 +1685,10 @@ static int fsl_sai_runtime_resume(struct device *dev) if (ret) goto disable_rx_clk; + if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output) + regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), + FSL_SAI_CSR_TERE, FSL_SAI_CSR_TERE); + return 0; disable_rx_clk: diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h index 197748a888d5..3eb994aef36a 100644 --- a/sound/soc/fsl/fsl_sai.h +++ b/sound/soc/fsl/fsl_sai.h @@ -230,6 +230,7 @@ struct fsl_sai_soc_data { bool use_imx_pcm; bool use_edma; bool mclk0_is_mclk1; + bool mclk_with_tere; unsigned int fifo_depth; unsigned int pins; unsigned int reg_offset; @@ -287,6 +288,7 @@ struct fsl_sai { bool synchronous[2]; struct fsl_sai_dl_cfg *dl_cfg; unsigned int dl_cfg_cnt; + bool mclk_direction_output; unsigned int mclk_id[2]; unsigned int mclk_streams; -- cgit v1.2.3 From 16cafbd97759ba66c79bdede4250074f026839f3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:36 +0200 Subject: ALSA: emu10k1: remove pointless locks from timer code Contrary to its name, reg_lock locks the emu data structure, not the registers. As the functions access only data which is set once at card initialization, there is no point in locking it. Actually locking the registers would be pointless as well, as snd_emu10k1_intr_{en,dis}able() does its own locking, and TIMER is accessed only in this one place. Locking snd_emu10k1_timer_{start,stop}() against each other also wouldn't buy us anything; the functions interleaving their I/O accesses wouldn't introduce new problems. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/timer.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c index 2435d3ba68f7..993663fef885 100644 --- a/sound/pci/emu10k1/timer.c +++ b/sound/pci/emu10k1/timer.c @@ -18,29 +18,23 @@ static int snd_emu10k1_timer_start(struct snd_timer *timer) { struct snd_emu10k1 *emu; - unsigned long flags; unsigned int delay; emu = snd_timer_chip(timer); delay = timer->sticks - 1; if (delay < 5 ) /* minimum time is 5 ticks */ delay = 5; - spin_lock_irqsave(&emu->reg_lock, flags); snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB); outw(delay & TIMER_RATE_MASK, emu->port + TIMER); - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } static int snd_emu10k1_timer_stop(struct snd_timer *timer) { struct snd_emu10k1 *emu; - unsigned long flags; emu = snd_timer_chip(timer); - spin_lock_irqsave(&emu->reg_lock, flags); snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB); - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } -- cgit v1.2.3 From 71781147dabdd0b256542bb51122f376db740b0d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:37 +0200 Subject: ALSA: emu10k1: remove pointless locks from /proc code emu_lock locks the card's registers, but that's necessary only for multi-register access, incl. read-modify-write cycles. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index bec72dc60a41..c92253de881e 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -399,13 +399,10 @@ static void snd_emu_proc_io_reg_read(struct snd_info_entry *entry, { struct snd_emu10k1 *emu = entry->private_data; unsigned long value; - unsigned long flags; int i; snd_iprintf(buffer, "IO Registers:\n\n"); for(i = 0; i < 0x40; i+=4) { - spin_lock_irqsave(&emu->emu_lock, flags); value = inl(emu->port + i); - spin_unlock_irqrestore(&emu->emu_lock, flags); snd_iprintf(buffer, "%02X: %08lX\n", i, value); } } @@ -414,16 +411,13 @@ static void snd_emu_proc_io_reg_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_emu10k1 *emu = entry->private_data; - unsigned long flags; char line[64]; u32 reg, val; while (!snd_info_get_line(buffer, line, sizeof(line))) { if (sscanf(line, "%x %x", ®, &val) != 2) continue; if (reg < 0x40 && val <= 0xffffffff) { - spin_lock_irqsave(&emu->emu_lock, flags); outl(val, emu->port + (reg & 0xfffffffc)); - spin_unlock_irqrestore(&emu->emu_lock, flags); } } } -- cgit v1.2.3 From 50164f69f8c71377bfa3d356b8a6bc18b2197a45 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:38 +0200 Subject: ALSA: emu10k1: use the right lock in snd_emu10k1_shared_spdif_put() The function does read-modify-write cycles on the card's registers, and doesn't access mutable members of the emu data structure. I suppose this might have been a mixup due to the lock names being logically swapped. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 3ebc7c36a444..24052f17d81c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1654,7 +1654,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, sw = ucontrol->value.integer.value[0]; if (emu->card_capabilities->invert_shared_spdif) sw = !sw; - spin_lock_irqsave(&emu->reg_lock, flags); + spin_lock_irqsave(&emu->emu_lock, flags); if ( emu->card_capabilities->i2c_adc) { /* Do nothing for Audigy 2 ZS Notebook */ } else if (emu->audigy) { @@ -1675,7 +1675,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, reg |= val; outl(reg | val, emu->port + HCFG); } - spin_unlock_irqrestore(&emu->reg_lock, flags); + spin_unlock_irqrestore(&emu->emu_lock, flags); return change; } -- cgit v1.2.3 From 35d1d5824ffe1cfc849aa1694488d779dc79a920 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:39 +0200 Subject: ALSA: emu10k1: fix locking in snd_emu1010_fpga_link_dst_src_write() This is a multi-register operation, which must be locked in its entirety. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index cfb96a67aa35..aee84c3f9f37 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -233,16 +233,13 @@ int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, return err; } -void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) +static void snd_emu1010_fpga_write_locked(struct snd_emu10k1 *emu, u32 reg, u32 value) { - unsigned long flags; - if (snd_BUG_ON(reg > 0x3f)) return; reg += 0x40; /* 0x40 upwards are registers. */ if (snd_BUG_ON(value > 0x3f)) /* 0 to 0x3f are values */ return; - spin_lock_irqsave(&emu->emu_lock, flags); outw(reg, emu->port + A_GPIO); udelay(10); outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ @@ -250,6 +247,14 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) outw(value, emu->port + A_GPIO); udelay(10); outw(value | 0x80 , emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ +} + +void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, reg, value); spin_unlock_irqrestore(&emu->emu_lock, flags); } @@ -276,14 +281,18 @@ void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) */ void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src) { + unsigned long flags; + if (snd_BUG_ON(dst & ~0x71f)) return; if (snd_BUG_ON(src & ~0x71f)) return; - snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8); - snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f); - snd_emu1010_fpga_write(emu, EMU_HANA_SRCHI, src >> 8); - snd_emu1010_fpga_write(emu, EMU_HANA_SRCLO, src & 0x1f); + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCHI, src >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCLO, src & 0x1f); + spin_unlock_irqrestore(&emu->emu_lock, flags); } void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) -- cgit v1.2.3 From 06405d8ee8c3bac5b3a4ddda58f6431187a3be48 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:41 +0200 Subject: ALSA: emu10k1: remove now superfluous mixer locking Since commit 5bbb1ab5bd ("control: use counting semaphore as write lock for ELEM_WRITE operation"), mixer values have been fully read-write locked. This means that it is now unnecessary to apply any additional locks to values that are accessed solely by mixer callbacks. Values that are read outside mixer callbacks still need write locking. There are no cases of mixer values being written outside mixer callbacks, so no read locks remain in mixer callbacks. Note that the removed locks refer only to the emu data structure, not the card's registers as the lock's name suggests. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 7 ------- sound/pci/emu10k1/emumixer.c | 28 ---------------------------- sound/pci/emu10k1/emupcm.c | 2 -- 3 files changed, 37 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 3f64ccab0e63..98785110ef63 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -318,16 +318,12 @@ static int snd_emu10k1_gpr_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu10k1_gpr_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned long flags; unsigned int i; - spin_lock_irqsave(&emu->reg_lock, flags); for (i = 0; i < ctl->vcount; i++) ucontrol->value.integer.value[i] = ctl->value[i]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -336,12 +332,10 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned long flags; unsigned int nval, val; unsigned int i, j; int change = 0; - spin_lock_irqsave(&emu->reg_lock, flags); for (i = 0; i < ctl->vcount; i++) { nval = ucontrol->value.integer.value[i]; if (nval < ctl->min) @@ -380,7 +374,6 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl } } __error: - spin_unlock_irqrestore(&emu->reg_lock, flags); return change; } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 24052f17d81c..ab04f8be25bd 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -41,17 +41,14 @@ static int snd_emu10k1_spdif_get(struct snd_kcontrol *kcontrol, { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - unsigned long flags; /* Limit: emu->spdif_bits */ if (idx >= 3) return -EINVAL; - spin_lock_irqsave(&emu->reg_lock, flags); ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff; ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff; ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff; ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1070,10 +1067,7 @@ static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol, { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int tmp; - unsigned long flags; - - spin_lock_irqsave(&emu->reg_lock, flags); tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0); switch (tmp & A_SPDIF_RATE_MASK) { case A_SPDIF_44100: @@ -1088,7 +1082,6 @@ static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol, default: ucontrol->value.enumerated.item[0] = 1; } - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1146,7 +1139,6 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); int change; unsigned int val; - unsigned long flags; /* Limit: emu->spdif_bits */ if (idx >= 3) @@ -1155,13 +1147,11 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, (ucontrol->value.iec958.status[1] << 8) | (ucontrol->value.iec958.status[2] << 16) | (ucontrol->value.iec958.status[3] << 24); - spin_lock_irqsave(&emu->reg_lock, flags); change = val != emu->spdif_bits[idx]; if (change) { snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val); emu->spdif_bits[idx] = val; } - spin_unlock_irqrestore(&emu->reg_lock, flags); return change; } @@ -1229,7 +1219,6 @@ static int snd_emu10k1_send_routing_info(struct snd_kcontrol *kcontrol, struct s static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; @@ -1237,12 +1226,10 @@ static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol, int num_efx = emu->audigy ? 8 : 4; int mask = emu->audigy ? 0x3f : 0x0f; - spin_lock_irqsave(&emu->reg_lock, flags); for (voice = 0; voice < 3; voice++) for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[(voice * num_efx) + idx] = mix->send_routing[voice][idx] & mask; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1305,17 +1292,14 @@ static int snd_emu10k1_send_volume_info(struct snd_kcontrol *kcontrol, struct sn static int snd_emu10k1_send_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; int idx; int num_efx = emu->audigy ? 8 : 4; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3*num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1378,13 +1362,10 @@ static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - unsigned long flags; int idx; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3; idx++) ucontrol->value.integer.value[idx] = mix->attn[idx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1443,7 +1424,6 @@ static int snd_emu10k1_efx_send_routing_info(struct snd_kcontrol *kcontrol, stru static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; @@ -1451,11 +1431,9 @@ static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol, int num_efx = emu->audigy ? 8 : 4; int mask = emu->audigy ? 0x3f : 0x0f; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_routing[0][idx] & mask; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1513,17 +1491,14 @@ static int snd_emu10k1_efx_send_volume_info(struct snd_kcontrol *kcontrol, struc static int snd_emu10k1_efx_send_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; int idx; int num_efx = emu->audigy ? 8 : 4; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_volume[0][idx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1582,11 +1557,8 @@ static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - unsigned long flags; - spin_lock_irqsave(&emu->reg_lock, flags); ucontrol->value.integer.value[0] = mix->attn[0]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index e8d2f0f6fbb3..5ed404e8ed39 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1433,10 +1433,8 @@ static int snd_emu10k1_pcm_efx_voices_mask_get(struct snd_kcontrol *kcontrol, st int nefx = emu->audigy ? 64 : 32; int idx; - spin_lock_irq(&emu->reg_lock); for (idx = 0; idx < nefx; idx++) ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0; - spin_unlock_irq(&emu->reg_lock); return 0; } -- cgit v1.2.3 From 946233bb23becc2898db31ad785d94fe80aa15dc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:41 +0200 Subject: ALSA: emu10k1: minor E-MU naming fixups - Fix mixer source port names. These will require some users to re-adjust their mixer settings, which seems acceptable: - The S/PDIF port is on the main 1010 card, not the 0202 daughter card - The 1616m CardBus card has all inputs on the dock, so there is no point in specifying it - Conversely, the 1010 card has "dispersed" inputs, so say where the ADAT port is, consistently with the S/PDIF port - The 1616m CardBus card is actually named E-MU 02 (due to the headphone output jack it has) - Fix capitalization of "E-MU" Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706335-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_main.c | 16 ++++++++-------- sound/pci/emu10k1/emumixer.c | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 192208c291d6..068cb6624e36 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -1310,7 +1310,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Does NOT support sync daughter card (obviously). */ /* Tested by James@superbug.co.uk 4th Nov 2007. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102, - .driver = "Audigy2", .name = "E-mu 1010 Notebook [MAEM8950]", + .driver = "Audigy2", .name = "E-MU 02 CardBus [MAEM8950]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1323,7 +1323,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * MicroDock[M] to make it an E-MU 1616[m]. */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102, - .driver = "Audigy2", .name = "E-mu 1010b PCI [MAEM8960]", + .driver = "Audigy2", .name = "E-MU 1010b PCI [MAEM8960]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1337,7 +1337,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * still work. */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40071102, - .driver = "Audigy2", .name = "E-mu 1010 PCIe [MAEM8986]", + .driver = "Audigy2", .name = "E-MU 1010 PCIe [MAEM8986]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1349,7 +1349,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * AudioDock[M] to make it an E-MU 1820[m]. */ /* Supports sync daughter card. */ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40011102, - .driver = "Audigy2", .name = "E-mu 1010 [MAEM8810]", + .driver = "Audigy2", .name = "E-MU 1010 [MAEM8810]", .id = "EMU1010", .emu10k2_chip = 1, .ca0102_chip = 1, @@ -1359,7 +1359,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Supports sync daughter card. */ /* Tested by oswald.buddenhagen@gmx.de Mar 2023. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102, - .driver = "Audigy2", .name = "E-mu 0404b PCI [MAEM8852]", + .driver = "Audigy2", .name = "E-MU 0404b PCI [MAEM8852]", .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1369,7 +1369,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Supports sync daughter card. */ /* Tested by James@superbug.co.uk 20-3-2007. */ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40021102, - .driver = "Audigy2", .name = "E-mu 0404 [MAEM8850]", + .driver = "Audigy2", .name = "E-MU 0404 [MAEM8850]", .id = "EMU0404", .emu10k2_chip = 1, .ca0102_chip = 1, @@ -1378,7 +1378,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* EMU0404 PCIe */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40051102, - .driver = "Audigy2", .name = "E-mu 0404 PCIe [MAEM8984]", + .driver = "Audigy2", .name = "E-MU 0404 PCIe [MAEM8984]", .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1645,7 +1645,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .ac97_chip = 1, .sblive51 = 1} , {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x40011102, - .driver = "EMU10K1", .name = "E-mu APS [PC545]", + .driver = "EMU10K1", .name = "E-MU APS [PC545]", .id = "APS", .emu10k1_chip = 1, .ecard = 1} , diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index ab04f8be25bd..610700be1e37 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -78,16 +78,16 @@ static const char * const emu1010_src_texts[] = { "Dock ADC3 Right", "0202 ADC Left", "0202 ADC Right", - "0202 SPDIF Left", - "0202 SPDIF Right", - "ADAT 0", - "ADAT 1", - "ADAT 2", - "ADAT 3", - "ADAT 4", - "ADAT 5", - "ADAT 6", - "ADAT 7", + "1010 SPDIF Left", + "1010 SPDIF Right", + "1010 ADAT 0", + "1010 ADAT 1", + "1010 ADAT 2", + "1010 ADAT 3", + "1010 ADAT 4", + "1010 ADAT 5", + "1010 ADAT 6", + "1010 ADAT 7", "DSP 0", "DSP 1", "DSP 2", @@ -126,14 +126,14 @@ static const char * const emu1010_src_texts[] = { static const char * const emu1616_src_texts[] = { "Silence", - "Dock Mic A", - "Dock Mic B", - "Dock ADC1 Left", - "Dock ADC1 Right", - "Dock ADC2 Left", - "Dock ADC2 Right", - "Dock SPDIF Left", - "Dock SPDIF Right", + "Mic A", + "Mic B", + "ADC1 Left", + "ADC1 Right", + "ADC2 Left", + "ADC2 Right", + "SPDIF Left", + "SPDIF Right", "ADAT 0", "ADAT 1", "ADAT 2", -- cgit v1.2.3 From 7f5d6036ca0059f749414380e19bfc346961353c Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Fri, 21 Apr 2023 11:01:16 +0800 Subject: ASoC: rt722-sdca: Add RT722 SDCA driver This is the initial codec driver for rt722-sdca. Signed-off-by: Jack Yu +#include +#include +#include +#include +#include + +#include "rt722-sdca.h" +#include "rt722-sdca-sdw.h" + +static bool rt722_sdca_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2f01 ... 0x2f0a: + case 0x2f35 ... 0x2f36: + case 0x2f50: + case 0x2f54: + case 0x2f58 ... 0x2f5d: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, RT722_SDCA_CTL_SELECTED_MODE, + 0): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, RT722_SDCA_CTL_DETECTED_MODE, + 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, RT722_SDCA_CTL_HIDTX_CURRENT_OWNER, + 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): + case RT722_BUF_ADDR_HID1 ... RT722_BUF_ADDR_HID2: + return true; + default: + return false; + } +} + +static bool rt722_sdca_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2f01: + case 0x2f54: + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, RT722_SDCA_CTL_DETECTED_MODE, + 0): + case SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, RT722_SDCA_CTL_HIDTX_CURRENT_OWNER, + 0) ... SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): + case RT722_BUF_ADDR_HID1 ... RT722_BUF_ADDR_HID2: + return true; + default: + return false; + } +} + +static bool rt722_sdca_mbq_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000 ... 0x2000024: + case 0x2000029 ... 0x200004a: + case 0x2000051 ... 0x2000052: + case 0x200005a ... 0x200005b: + case 0x2000061 ... 0x2000069: + case 0x200006b: + case 0x2000070: + case 0x200007f: + case 0x2000082 ... 0x200008e: + case 0x2000090 ... 0x2000094: + case 0x5300000 ... 0x5300002: + case 0x5400002: + case 0x5600000 ... 0x5600007: + case 0x5700000 ... 0x5700004: + case 0x5800000 ... 0x5800004: + case 0x5b00003: + case 0x5c00011: + case 0x5d00006: + case 0x5f00000 ... 0x5f0000d: + case 0x5f00030: + case 0x6100000 ... 0x6100051: + case 0x6100055 ... 0x6100057: + case 0x6100062: + case 0x6100064 ... 0x6100065: + case 0x6100067: + case 0x6100070 ... 0x610007c: + case 0x6100080: + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_01): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_02): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_03): + case SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_04): + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_VOLUME, CH_L): + case SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_VOLUME, CH_R): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_VOLUME, + CH_L): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_VOLUME, + CH_R): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_VOLUME, + CH_L): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_VOLUME, + CH_R): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, + RT722_SDCA_CTL_FU_CH_GAIN, CH_L): + case SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, + RT722_SDCA_CTL_FU_CH_GAIN, CH_R): + return true; + default: + return false; + } +} + +static bool rt722_sdca_mbq_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000: + case 0x200000d: + case 0x2000019: + case 0x2000020: + case 0x2000030: + case 0x2000046: + case 0x2000067: + case 0x2000084: + case 0x2000086: + return true; + default: + return false; + } +} + +static const struct regmap_config rt722_sdca_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = rt722_sdca_readable_register, + .volatile_reg = rt722_sdca_volatile_register, + .max_register = 0x44ffffff, + .reg_defaults = rt722_sdca_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rt722_sdca_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static const struct regmap_config rt722_sdca_mbq_regmap = { + .name = "sdw-mbq", + .reg_bits = 32, + .val_bits = 16, + .readable_reg = rt722_sdca_mbq_readable_register, + .volatile_reg = rt722_sdca_mbq_volatile_register, + .max_register = 0x41000312, + .reg_defaults = rt722_sdca_mbq_defaults, + .num_reg_defaults = ARRAY_SIZE(rt722_sdca_mbq_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt722_sdca_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt722->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt722->hw_init = false; + + if (status == SDW_SLAVE_ATTACHED) { + if (rt722->hs_jack) { + /* + * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then + * if the device attached again, we will need to set the setting back. + * It could avoid losing the jack detection interrupt. + * This also could sync with the cache value as the rt722_sdca_jack_init set. + */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0 | SDW_SCP_SDCA_INTMASK_SDCA_6); + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + } + } + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt722->hw_init || rt722->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt722_sdca_io_init(&slave->dev, slave); +} + +static int rt722_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval; + int i, j; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + + prop->paging_support = true; + + /* + * port = 1 for headphone playback + * port = 2 for headset-mic capture + * port = 3 for speaker playback + * port = 6 for digital-mic capture + */ + prop->source_ports = BIT(6) | BIT(2); /* BITMAP: 01000100 */ + prop->sink_ports = BIT(3) | BIT(1); /* BITMAP: 00001010 */ + + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + i = 0; + dpn = prop->src_dpn_prop; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].type = SDW_DPN_FULL; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* do this again for sink now */ + nval = hweight32(prop->sink_ports); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + j = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[j].num = bit; + dpn[j].type = SDW_DPN_FULL; + dpn[j].simple_ch_prep_sm = true; + dpn[j].ch_prep_timeout = 10; + j++; + } + + /* set the timeout values */ + prop->clk_stop_timeout = 200; + + /* wake-up event */ + prop->wake_capable = 1; + + return 0; +} + +static int rt722_sdca_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); + int ret, stat; + int count = 0, retry = 3; + unsigned int sdca_cascade, scp_sdca_stat1, scp_sdca_stat2 = 0; + + if (cancel_delayed_work_sync(&rt722->jack_detect_work)) { + dev_warn(&slave->dev, "%s the pending delayed_work was cancelled", __func__); + /* avoid the HID owner doesn't change to device */ + if (rt722->scp_sdca_stat2) + scp_sdca_stat2 = rt722->scp_sdca_stat2; + } + + /* + * The critical section below intentionally protects a rather large piece of code. + * We don't want to allow the system suspend to disable an interrupt while we are + * processing it, which could be problematic given the quirky SoundWire interrupt + * scheme. We do want however to prevent new workqueues from being scheduled if + * the disable_irq flag was set during system suspend. + */ + mutex_lock(&rt722->disable_irq_lock); + + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + goto io_error; + rt722->scp_sdca_stat1 = ret; + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + goto io_error; + rt722->scp_sdca_stat2 = ret; + if (scp_sdca_stat2) + rt722->scp_sdca_stat2 |= scp_sdca_stat2; + do { + /* clear flag */ + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + goto io_error; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_0) { + ret = sdw_update_no_pm(rt722->slave, SDW_SCP_SDCA_INT1, + SDW_SCP_SDCA_INT_SDCA_0, SDW_SCP_SDCA_INT_SDCA_0); + if (ret < 0) + goto io_error; + } else if (ret & SDW_SCP_SDCA_INTMASK_SDCA_6) { + ret = sdw_update_no_pm(rt722->slave, SDW_SCP_SDCA_INT1, + SDW_SCP_SDCA_INT_SDCA_6, SDW_SCP_SDCA_INT_SDCA_6); + if (ret < 0) + goto io_error; + } + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + goto io_error; + if (ret & SDW_SCP_SDCA_INTMASK_SDCA_8) { + ret = sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INT2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + if (ret < 0) + goto io_error; + } + + /* check if flag clear or not */ + ret = sdw_read_no_pm(rt722->slave, SDW_DP0_INT); + if (ret < 0) + goto io_error; + sdca_cascade = ret & SDW_DP0_SDCA_CASCADE; + + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT1); + if (ret < 0) + goto io_error; + scp_sdca_stat1 = ret & SDW_SCP_SDCA_INTMASK_SDCA_0; + + ret = sdw_read_no_pm(rt722->slave, SDW_SCP_SDCA_INT2); + if (ret < 0) + goto io_error; + scp_sdca_stat2 = ret & SDW_SCP_SDCA_INTMASK_SDCA_8; + + stat = scp_sdca_stat1 || scp_sdca_stat2 || sdca_cascade; + + count++; + } while (stat != 0 && count < retry); + + if (stat) + dev_warn(&slave->dev, + "%s scp_sdca_stat1=0x%x, scp_sdca_stat2=0x%x\n", __func__, + rt722->scp_sdca_stat1, rt722->scp_sdca_stat2); + + if (status->sdca_cascade && !rt722->disable_irq) + mod_delayed_work(system_power_efficient_wq, + &rt722->jack_detect_work, msecs_to_jiffies(30)); + + mutex_unlock(&rt722->disable_irq_lock); + + return 0; + +io_error: + mutex_unlock(&rt722->disable_irq_lock); + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static struct sdw_slave_ops rt722_sdca_slave_ops = { + .read_prop = rt722_sdca_read_prop, + .interrupt_callback = rt722_sdca_interrupt_callback, + .update_status = rt722_sdca_update_status, +}; + +static int rt722_sdca_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap, *mbq_regmap; + + /* Regmap Initialization */ + mbq_regmap = devm_regmap_init_sdw_mbq(slave, &rt722_sdca_mbq_regmap); + if (IS_ERR(mbq_regmap)) + return PTR_ERR(mbq_regmap); + + regmap = devm_regmap_init_sdw(slave, &rt722_sdca_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return rt722_sdca_init(&slave->dev, regmap, mbq_regmap, slave); +} + +static int rt722_sdca_sdw_remove(struct sdw_slave *slave) +{ + struct rt722_sdca_priv *rt722 = dev_get_drvdata(&slave->dev); + + if (rt722->hw_init) { + cancel_delayed_work_sync(&rt722->jack_detect_work); + cancel_delayed_work_sync(&rt722->jack_btn_check_work); + } + + if (rt722->first_hw_init) + pm_runtime_disable(&slave->dev); + + mutex_destroy(&rt722->calibrate_mutex); + mutex_destroy(&rt722->disable_irq_lock); + + return 0; +} + +static const struct sdw_device_id rt722_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x722, 0x3, 0x1, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt722_sdca_id); + +static int __maybe_unused rt722_sdca_dev_suspend(struct device *dev) +{ + struct rt722_sdca_priv *rt722 = dev_get_drvdata(dev); + + if (!rt722->hw_init) + return 0; + + cancel_delayed_work_sync(&rt722->jack_detect_work); + cancel_delayed_work_sync(&rt722->jack_btn_check_work); + + regcache_cache_only(rt722->regmap, true); + regcache_cache_only(rt722->mbq_regmap, true); + + return 0; +} + +static int __maybe_unused rt722_sdca_dev_system_suspend(struct device *dev) +{ + struct rt722_sdca_priv *rt722_sdca = dev_get_drvdata(dev); + struct sdw_slave *slave = dev_to_sdw_dev(dev); + int ret1, ret2; + + if (!rt722_sdca->hw_init) + return 0; + + /* + * prevent new interrupts from being handled after the + * deferred work completes and before the parent disables + * interrupts on the link + */ + mutex_lock(&rt722_sdca->disable_irq_lock); + rt722_sdca->disable_irq = true; + ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0 | SDW_SCP_SDCA_INTMASK_SDCA_6, 0); + ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK2, + SDW_SCP_SDCA_INTMASK_SDCA_8, 0); + mutex_unlock(&rt722_sdca->disable_irq_lock); + + if (ret1 < 0 || ret2 < 0) { + /* log but don't prevent suspend from happening */ + dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__); + } + + return rt722_sdca_dev_suspend(dev); +} + +#define RT722_PROBE_TIMEOUT 5000 + +static int __maybe_unused rt722_sdca_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct rt722_sdca_priv *rt722 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt722->first_hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT722_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Initialization not complete, timed out\n"); + sdw_show_ping_status(slave->bus, true); + + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt722->regmap, false); + regcache_sync(rt722->regmap); + regcache_cache_only(rt722->mbq_regmap, false); + regcache_sync(rt722->mbq_regmap); + return 0; +} + +static const struct dev_pm_ops rt722_sdca_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt722_sdca_dev_system_suspend, rt722_sdca_dev_resume) + SET_RUNTIME_PM_OPS(rt722_sdca_dev_suspend, rt722_sdca_dev_resume, NULL) +}; + +static struct sdw_driver rt722_sdca_sdw_driver = { + .driver = { + .name = "rt722-sdca", + .owner = THIS_MODULE, + .pm = &rt722_sdca_pm, + }, + .probe = rt722_sdca_sdw_probe, + .remove = rt722_sdca_sdw_remove, + .ops = &rt722_sdca_slave_ops, + .id_table = rt722_sdca_id, +}; +module_sdw_driver(rt722_sdca_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT722 SDCA SDW driver"); +MODULE_AUTHOR("Jack Yu "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt722-sdca-sdw.h b/sound/soc/codecs/rt722-sdca-sdw.h new file mode 100644 index 000000000000..5b43e86f75d1 --- /dev/null +++ b/sound/soc/codecs/rt722-sdca-sdw.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt722-sdca-sdw.h -- RT722 SDCA ALSA SoC audio driver header + * + * Copyright(c) 2023 Realtek Semiconductor Corp. + */ + +#ifndef __RT722_SDW_H__ +#define __RT722_SDW_H__ + +#include +#include + +static const struct reg_default rt722_sdca_reg_defaults[] = { + { 0x202d, 0x00 }, + { 0x2f01, 0x00 }, + { 0x2f02, 0x09 }, + { 0x2f03, 0x00 }, + { 0x2f04, 0x00 }, + { 0x2f05, 0x0b }, + { 0x2f06, 0x01 }, + { 0x2f08, 0x00 }, + { 0x2f09, 0x00 }, + { 0x2f0a, 0x00 }, + { 0x2f35, 0x00 }, + { 0x2f36, 0x00 }, + { 0x2f50, 0xf0 }, + { 0x2f58, 0x07 }, + { 0x2f59, 0x07 }, + { 0x2f5a, 0x07 }, + { 0x2f5b, 0x07 }, + { 0x2f5c, 0x27 }, + { 0x2f5d, 0x07 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_CS01, RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, + 0), 0x09 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_CS11, RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, + 0), 0x09 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE12, RT722_SDCA_CTL_REQ_POWER_STATE, + 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE40, RT722_SDCA_CTL_REQ_POWER_STATE, + 0), 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_MUTE, CH_L), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_MUTE, CH_R), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_MUTE, CH_L), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_MUTE, CH_R), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_CS1F, RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, + 0), 0x09 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_MUTE, CH_01), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_MUTE, CH_02), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_MUTE, CH_03), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_MUTE, CH_04), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_PDE2A, RT722_SDCA_CTL_REQ_POWER_STATE, 0), + 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_IT26, RT722_SDCA_CTL_VENDOR_DEF, 0), + 0x00 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_CS31, RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), + 0x09 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_MUTE, CH_L), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_MUTE, CH_R), + 0x01 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_PDE23, RT722_SDCA_CTL_REQ_POWER_STATE, 0), + 0x03 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_OT23, RT722_SDCA_CTL_VENDOR_DEF, 0), 0x00 }, +}; + +static const struct reg_default rt722_sdca_mbq_defaults[] = { + { 0x200003c, 0xc214 }, + { 0x2000046, 0x8004 }, + { 0x6100006, 0x0005 }, + { 0x6100010, 0x2630 }, + { 0x6100011, 0x152f }, + { 0x6100013, 0x0102 }, + { 0x6100015, 0x2200 }, + { 0x6100017, 0x0102 }, + { 0x6100025, 0x2a29 }, + { 0x6100026, 0x2a00 }, + { 0x6100028, 0x2a2a }, + { 0x6100029, 0x4141 }, + { 0x6100055, 0x0000 }, + { 0x5810000, 0x702d }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_VOLUME, + CH_L), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, RT722_SDCA_CTL_FU_VOLUME, + CH_R), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_VOLUME, + CH_L), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, RT722_SDCA_CTL_FU_VOLUME, + CH_R), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, RT722_SDCA_CTL_FU_CH_GAIN, + CH_L), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, RT722_SDCA_CTL_FU_CH_GAIN, + CH_R), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_01), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_02), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_03), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, RT722_SDCA_CTL_FU_VOLUME, + CH_04), 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_FU15, RT722_SDCA_CTL_FU_CH_GAIN, CH_01), + 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_FU15, RT722_SDCA_CTL_FU_CH_GAIN, CH_02), + 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_FU15, RT722_SDCA_CTL_FU_CH_GAIN, CH_03), + 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_FU15, RT722_SDCA_CTL_FU_CH_GAIN, CH_04), + 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_VOLUME, CH_L), + 0x0000 }, + { SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, RT722_SDCA_CTL_FU_VOLUME, CH_R), + 0x0000 }, +}; + +#endif /* __RT722_SDW_H__ */ diff --git a/sound/soc/codecs/rt722-sdca.c b/sound/soc/codecs/rt722-sdca.c new file mode 100644 index 000000000000..9c0d34366c9e --- /dev/null +++ b/sound/soc/codecs/rt722-sdca.c @@ -0,0 +1,1555 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt722-sdca.c -- rt722 SDCA ALSA SoC audio driver +// +// Copyright(c) 2023 Realtek Semiconductor Corp. +// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rt722-sdca.h" + +int rt722_sdca_index_write(struct rt722_sdca_priv *rt722, + unsigned int nid, unsigned int reg, unsigned int value) +{ + struct regmap *regmap = rt722->mbq_regmap; + unsigned int addr = (nid << 20) | reg; + int ret; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) + dev_err(&rt722->slave->dev, + "Failed to set private value: %06x <= %04x ret=%d\n", + addr, value, ret); + + return ret; +} + +int rt722_sdca_index_read(struct rt722_sdca_priv *rt722, + unsigned int nid, unsigned int reg, unsigned int *value) +{ + int ret; + struct regmap *regmap = rt722->mbq_regmap; + unsigned int addr = (nid << 20) | reg; + + ret = regmap_read(regmap, addr, value); + if (ret < 0) + dev_err(&rt722->slave->dev, + "Failed to get private value: %06x => %04x ret=%d\n", + addr, *value, ret); + + return ret; +} + +static int rt722_sdca_index_update_bits(struct rt722_sdca_priv *rt722, + unsigned int nid, unsigned int reg, unsigned int mask, unsigned int val) +{ + unsigned int tmp; + int ret; + + ret = rt722_sdca_index_read(rt722, nid, reg, &tmp); + if (ret < 0) + return ret; + + set_mask_bits(&tmp, mask, val); + return rt722_sdca_index_write(rt722, nid, reg, tmp); +} + +static int rt722_sdca_btn_type(unsigned char *buffer) +{ + if ((*buffer & 0xf0) == 0x10 || (*buffer & 0x0f) == 0x01 || (*(buffer + 1) == 0x01) || + (*(buffer + 1) == 0x10)) + return SND_JACK_BTN_2; + else if ((*buffer & 0xf0) == 0x20 || (*buffer & 0x0f) == 0x02 || (*(buffer + 1) == 0x02) || + (*(buffer + 1) == 0x20)) + return SND_JACK_BTN_3; + else if ((*buffer & 0xf0) == 0x40 || (*buffer & 0x0f) == 0x04 || (*(buffer + 1) == 0x04) || + (*(buffer + 1) == 0x40)) + return SND_JACK_BTN_0; + else if ((*buffer & 0xf0) == 0x80 || (*buffer & 0x0f) == 0x08 || (*(buffer + 1) == 0x08) || + (*(buffer + 1) == 0x80)) + return SND_JACK_BTN_1; + + return 0; +} + +static unsigned int rt722_sdca_button_detect(struct rt722_sdca_priv *rt722) +{ + unsigned int btn_type = 0, offset, idx, val, owner; + int ret; + unsigned char buf[3]; + + /* get current UMP message owner */ + ret = regmap_read(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_CURRENT_OWNER, 0), &owner); + if (ret < 0) + return 0; + + /* if owner is device then there is no button event from device */ + if (owner == 1) + return 0; + + /* read UMP message offset */ + ret = regmap_read(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0), &offset); + if (ret < 0) + goto _end_btn_det_; + + for (idx = 0; idx < sizeof(buf); idx++) { + ret = regmap_read(rt722->regmap, + RT722_BUF_ADDR_HID1 + offset + idx, &val); + if (ret < 0) + goto _end_btn_det_; + buf[idx] = val & 0xff; + } + + if (buf[0] == 0x11) + btn_type = rt722_sdca_btn_type(&buf[1]); + +_end_btn_det_: + /* Host is owner, so set back to device */ + if (owner == 0) + /* set owner to device */ + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_CURRENT_OWNER, 0), 0x01); + + return btn_type; +} + +static int rt722_sdca_headset_detect(struct rt722_sdca_priv *rt722) +{ + unsigned int det_mode; + int ret; + + /* get detected_mode */ + ret = regmap_read(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, + RT722_SDCA_CTL_DETECTED_MODE, 0), &det_mode); + if (ret < 0) + goto io_error; + + switch (det_mode) { + case 0x00: + rt722->jack_type = 0; + break; + case 0x03: + rt722->jack_type = SND_JACK_HEADPHONE; + break; + case 0x05: + rt722->jack_type = SND_JACK_HEADSET; + break; + } + + /* write selected_mode */ + if (det_mode) { + ret = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, + RT722_SDCA_CTL_SELECTED_MODE, 0), det_mode); + if (ret < 0) + goto io_error; + } + + dev_dbg(&rt722->slave->dev, + "%s, detected_mode=0x%x\n", __func__, det_mode); + + return 0; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static void rt722_sdca_jack_detect_handler(struct work_struct *work) +{ + struct rt722_sdca_priv *rt722 = + container_of(work, struct rt722_sdca_priv, jack_detect_work.work); + int btn_type = 0, ret; + + if (!rt722->hs_jack) + return; + + if (!rt722->component->card || !rt722->component->card->instantiated) + return; + + /* SDW_SCP_SDCA_INT_SDCA_6 is used for jack detection */ + if (rt722->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_6 || + rt722->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { + ret = rt722_sdca_headset_detect(rt722); + if (ret < 0) + return; + } + + /* SDW_SCP_SDCA_INT_SDCA_8 is used for button detection */ + if (rt722->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) + btn_type = rt722_sdca_button_detect(rt722); + + if (rt722->jack_type == 0) + btn_type = 0; + + dev_dbg(&rt722->slave->dev, + "in %s, jack_type=%d\n", __func__, rt722->jack_type); + dev_dbg(&rt722->slave->dev, + "in %s, btn_type=0x%x\n", __func__, btn_type); + dev_dbg(&rt722->slave->dev, + "in %s, scp_sdca_stat1=0x%x, scp_sdca_stat2=0x%x\n", __func__, + rt722->scp_sdca_stat1, rt722->scp_sdca_stat2); + + snd_soc_jack_report(rt722->hs_jack, rt722->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt722->hs_jack, rt722->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt722->jack_btn_check_work, msecs_to_jiffies(200)); + } +} + +static void rt722_sdca_btn_check_handler(struct work_struct *work) +{ + struct rt722_sdca_priv *rt722 = + container_of(work, struct rt722_sdca_priv, jack_btn_check_work.work); + int btn_type = 0, ret, idx; + unsigned int det_mode, offset, val; + unsigned char buf[3]; + + ret = regmap_read(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_GE49, + RT722_SDCA_CTL_DETECTED_MODE, 0), &det_mode); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (det_mode) { + /* read UMP message offset */ + ret = regmap_read(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_HID, RT722_SDCA_ENT_HID01, + RT722_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0), &offset); + if (ret < 0) + goto io_error; + + for (idx = 0; idx < sizeof(buf); idx++) { + ret = regmap_read(rt722->regmap, + RT722_BUF_ADDR_HID1 + offset + idx, &val); + if (ret < 0) + goto io_error; + buf[idx] = val & 0xff; + } + + if (buf[0] == 0x11) + btn_type = rt722_sdca_btn_type(&buf[1]); + } else + rt722->jack_type = 0; + + dev_dbg(&rt722->slave->dev, "%s, btn_type=0x%x\n", __func__, btn_type); + snd_soc_jack_report(rt722->hs_jack, rt722->jack_type | btn_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + if (btn_type) { + /* button released */ + snd_soc_jack_report(rt722->hs_jack, rt722->jack_type, + SND_JACK_HEADSET | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3); + + mod_delayed_work(system_power_efficient_wq, + &rt722->jack_btn_check_work, msecs_to_jiffies(200)); + } + + return; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); +} + +static void rt722_sdca_jack_init(struct rt722_sdca_priv *rt722) +{ + mutex_lock(&rt722->calibrate_mutex); + if (rt722->hs_jack) { + /* set SCP_SDCA_IntMask1[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0 | SDW_SCP_SDCA_INTMASK_SDCA_6); + /* set SCP_SDCA_IntMask2[0]=1 */ + sdw_write_no_pm(rt722->slave, SDW_SCP_SDCA_INTMASK2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + dev_dbg(&rt722->slave->dev, "in %s enable\n", __func__); + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_HDA_LEGACY_UNSOL_CTL, 0x016E); + /* set XU(et03h) & XU(et0Dh) to Not bypassed */ + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_XU03, + RT722_SDCA_CTL_SELECTED_MODE, 0), 0); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_XU0D, + RT722_SDCA_CTL_SELECTED_MODE, 0), 0); + /* trigger GE interrupt */ + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_GE_RELATED_CTL2, 0x4000, 0x4000); + } + mutex_unlock(&rt722->calibrate_mutex); +} + +static int rt722_sdca_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + int ret; + + rt722->hs_jack = hs_jack; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0) { + if (ret != -EACCES) { + dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret); + return ret; + } + /* pm_runtime not enabled yet */ + dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__); + return 0; + } + + rt722_sdca_jack_init(rt722); + + pm_runtime_mark_last_busy(component->dev); + pm_runtime_put_autosuspend(component->dev); + + return 0; +} + +/* For SDCA control DAC/ADC Gain */ +static int rt722_sdca_set_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned int read_l, read_r, gain_l_val, gain_r_val; + unsigned int adc_vol_flag = 0, changed = 0; + unsigned int lvalue, rvalue; + const unsigned int interval_offset = 0xc0; + const unsigned int tendB = 0xa00; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume") || + strstr(ucontrol->id.name, "FU0F Capture Volume")) + adc_vol_flag = 1; + + regmap_read(rt722->mbq_regmap, mc->reg, &lvalue); + regmap_read(rt722->mbq_regmap, mc->rreg, &rvalue); + + /* L Channel */ + gain_l_val = ucontrol->value.integer.value[0]; + if (gain_l_val > mc->max) + gain_l_val = mc->max; + + if (mc->shift == 8) /* boost gain */ + gain_l_val = gain_l_val * tendB; + else { + /* ADC/DAC gain */ + if (adc_vol_flag) + gain_l_val = 0x1e00 - ((mc->max - gain_l_val) * interval_offset); + else + gain_l_val = 0 - ((mc->max - gain_l_val) * interval_offset); + gain_l_val &= 0xffff; + } + + /* R Channel */ + gain_r_val = ucontrol->value.integer.value[1]; + if (gain_r_val > mc->max) + gain_r_val = mc->max; + + if (mc->shift == 8) /* boost gain */ + gain_r_val = gain_r_val * tendB; + else { + /* ADC/DAC gain */ + if (adc_vol_flag) + gain_r_val = 0x1e00 - ((mc->max - gain_r_val) * interval_offset); + else + gain_r_val = 0 - ((mc->max - gain_r_val) * interval_offset); + gain_r_val &= 0xffff; + } + + if (lvalue != gain_l_val || rvalue != gain_r_val) + changed = 1; + else + return 0; + + /* Lch*/ + regmap_write(rt722->mbq_regmap, mc->reg, gain_l_val); + + /* Rch */ + regmap_write(rt722->mbq_regmap, mc->rreg, gain_r_val); + + regmap_read(rt722->mbq_regmap, mc->reg, &read_l); + regmap_read(rt722->mbq_regmap, mc->rreg, &read_r); + if (read_r == gain_r_val && read_l == gain_l_val) + return changed; + + return -EIO; +} + +static int rt722_sdca_set_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int read_l, read_r, ctl_l = 0, ctl_r = 0; + unsigned int adc_vol_flag = 0; + const unsigned int interval_offset = 0xc0; + const unsigned int tendB = 0xa00; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume") || + strstr(ucontrol->id.name, "FU0F Capture Volume")) + adc_vol_flag = 1; + + regmap_read(rt722->mbq_regmap, mc->reg, &read_l); + regmap_read(rt722->mbq_regmap, mc->rreg, &read_r); + + if (mc->shift == 8) /* boost gain */ + ctl_l = read_l / tendB; + else { + if (adc_vol_flag) + ctl_l = mc->max - (((0x1e00 - read_l) & 0xffff) / interval_offset); + else + ctl_l = mc->max - (((0 - read_l) & 0xffff) / interval_offset); + } + + if (read_l != read_r) { + if (mc->shift == 8) /* boost gain */ + ctl_r = read_r / tendB; + else { /* ADC/DAC gain */ + if (adc_vol_flag) + ctl_r = mc->max - (((0x1e00 - read_r) & 0xffff) / interval_offset); + else + ctl_r = mc->max - (((0 - read_r) & 0xffff) / interval_offset); + } + } else { + ctl_r = ctl_l; + } + + ucontrol->value.integer.value[0] = ctl_l; + ucontrol->value.integer.value[1] = ctl_r; + + return 0; +} + +static int rt722_sdca_set_fu1e_capture_ctl(struct rt722_sdca_priv *rt722) +{ + int err, i; + unsigned int ch_mute; + + for (i = 0; i < ARRAY_SIZE(rt722->fu1e_mixer_mute); i++) { + ch_mute = rt722->fu1e_dapm_mute || rt722->fu1e_mixer_mute[i]; + err = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, + RT722_SDCA_CTL_FU_MUTE, CH_01) + i, ch_mute); + if (err < 0) + return err; + } + + return 0; +} + +static int rt722_sdca_fu1e_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct rt722_sdca_dmic_kctrl_priv *p = + (struct rt722_sdca_dmic_kctrl_priv *)kcontrol->private_value; + unsigned int i; + + for (i = 0; i < p->count; i++) + ucontrol->value.integer.value[i] = !rt722->fu1e_mixer_mute[i]; + + return 0; +} + +static int rt722_sdca_fu1e_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct rt722_sdca_dmic_kctrl_priv *p = + (struct rt722_sdca_dmic_kctrl_priv *)kcontrol->private_value; + int err, changed = 0, i; + + for (i = 0; i < p->count; i++) { + if (rt722->fu1e_mixer_mute[i] != !ucontrol->value.integer.value[i]) + changed = 1; + rt722->fu1e_mixer_mute[i] = !ucontrol->value.integer.value[i]; + } + + err = rt722_sdca_set_fu1e_capture_ctl(rt722); + if (err < 0) + return err; + + return changed; +} + +static int rt722_sdca_set_fu0f_capture_ctl(struct rt722_sdca_priv *rt722) +{ + int err; + unsigned int ch_l, ch_r; + + ch_l = (rt722->fu0f_dapm_mute || rt722->fu0f_mixer_l_mute) ? 0x01 : 0x00; + ch_r = (rt722->fu0f_dapm_mute || rt722->fu0f_mixer_r_mute) ? 0x01 : 0x00; + + err = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, + RT722_SDCA_CTL_FU_MUTE, CH_L), ch_l); + if (err < 0) + return err; + + err = regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, + RT722_SDCA_CTL_FU_MUTE, CH_R), ch_r); + if (err < 0) + return err; + + return 0; +} + +static int rt722_sdca_fu0f_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = !rt722->fu0f_mixer_l_mute; + ucontrol->value.integer.value[1] = !rt722->fu0f_mixer_r_mute; + return 0; +} + +static int rt722_sdca_fu0f_capture_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + int err, changed = 0; + + if (rt722->fu0f_mixer_l_mute != !ucontrol->value.integer.value[0] || + rt722->fu0f_mixer_r_mute != !ucontrol->value.integer.value[1]) + changed = 1; + + rt722->fu0f_mixer_l_mute = !ucontrol->value.integer.value[0]; + rt722->fu0f_mixer_r_mute = !ucontrol->value.integer.value[1]; + err = rt722_sdca_set_fu0f_capture_ctl(rt722); + if (err < 0) + return err; + + return changed; +} + +static int rt722_sdca_fu_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct rt722_sdca_dmic_kctrl_priv *p = + (struct rt722_sdca_dmic_kctrl_priv *)kcontrol->private_value; + + if (p->max == 1) + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + else + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = p->count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = p->max; + return 0; +} + +static int rt722_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct rt722_sdca_dmic_kctrl_priv *p = + (struct rt722_sdca_dmic_kctrl_priv *)kcontrol->private_value; + unsigned int boost_step = 0x0a00; + unsigned int vol_max = 0x1e00; + unsigned int regvalue, ctl, i; + unsigned int adc_vol_flag = 0; + const unsigned int interval_offset = 0xc0; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume")) + adc_vol_flag = 1; + + /* check all channels */ + for (i = 0; i < p->count; i++) { + regmap_read(rt722->mbq_regmap, p->reg_base + i, ®value); + + if (!adc_vol_flag) /* boost gain */ + ctl = regvalue / boost_step; + else { /* ADC gain */ + if (adc_vol_flag) + ctl = p->max - (((vol_max - regvalue) & 0xffff) / interval_offset); + else + ctl = p->max - (((0 - regvalue) & 0xffff) / interval_offset); + } + + ucontrol->value.integer.value[i] = ctl; + } + + return 0; +} + +static int rt722_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt722_sdca_dmic_kctrl_priv *p = + (struct rt722_sdca_dmic_kctrl_priv *)kcontrol->private_value; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned int boost_step = 0x0a00; + unsigned int vol_max = 0x1e00; + unsigned int gain_val[4]; + unsigned int i, adc_vol_flag = 0, changed = 0; + unsigned int regvalue[4]; + const unsigned int interval_offset = 0xc0; + int err; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume")) + adc_vol_flag = 1; + + /* check all channels */ + for (i = 0; i < p->count; i++) { + regmap_read(rt722->mbq_regmap, p->reg_base + i, ®value[i]); + + gain_val[i] = ucontrol->value.integer.value[i]; + if (gain_val[i] > p->max) + gain_val[i] = p->max; + + if (!adc_vol_flag) /* boost gain */ + gain_val[i] = gain_val[i] * boost_step; + else { /* ADC gain */ + gain_val[i] = vol_max - ((p->max - gain_val[i]) * interval_offset); + gain_val[i] &= 0xffff; + } + + if (regvalue[i] != gain_val[i]) + changed = 1; + } + + if (!changed) + return 0; + + for (i = 0; i < p->count; i++) { + err = regmap_write(rt722->mbq_regmap, p->reg_base + i, gain_val[i]); + if (err < 0) + dev_err(&rt722->slave->dev, "%#08x can't be set\n", p->reg_base + i); + } + + return changed; +} + +#define RT722_SDCA_PR_VALUE(xreg_base, xcount, xmax, xinvert) \ + ((unsigned long)&(struct rt722_sdca_dmic_kctrl_priv) \ + {.reg_base = xreg_base, .count = xcount, .max = xmax, \ + .invert = xinvert}) + +#define RT722_SDCA_FU_CTRL(xname, reg_base, xmax, xinvert, xcount) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = rt722_sdca_fu_info, \ + .get = rt722_sdca_fu1e_capture_get, \ + .put = rt722_sdca_fu1e_capture_put, \ + .private_value = RT722_SDCA_PR_VALUE(reg_base, xcount, xmax, xinvert)} + +#define RT722_SDCA_EXT_TLV(xname, reg_base, xhandler_get,\ + xhandler_put, xcount, xmax, tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_READWRITE, \ + .tlv.p = (tlv_array), \ + .info = rt722_sdca_fu_info, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = RT722_SDCA_PR_VALUE(reg_base, xcount, xmax, 0) } + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt722_sdca_controls[] = { + /* Headphone playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, + rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, out_vol_tlv), + /* Headset mic capture settings */ + SOC_DOUBLE_EXT("FU0F Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0, + rt722_sdca_fu0f_capture_get, rt722_sdca_fu0f_capture_put), + SOC_DOUBLE_R_EXT_TLV("FU0F Capture Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, + RT722_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU0F, + RT722_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x3f, 0, + rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU33 Boost Volume", + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, + RT722_SDCA_CTL_FU_CH_GAIN, CH_L), + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PLATFORM_FU44, + RT722_SDCA_CTL_FU_CH_GAIN, CH_R), 8, 3, 0, + rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, boost_vol_tlv), + /* AMP playback settings */ + SOC_DOUBLE_R_EXT_TLV("FU06 Playback Volume", + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_VOLUME, CH_R), 0, 0x57, 0, + rt722_sdca_set_gain_get, rt722_sdca_set_gain_put, out_vol_tlv), + /* DMIC capture settings */ + RT722_SDCA_FU_CTRL("FU1E Capture Switch", + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, + RT722_SDCA_CTL_FU_MUTE, CH_01), 1, 1, 4), + RT722_SDCA_EXT_TLV("FU1E Capture Volume", + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_USER_FU1E, + RT722_SDCA_CTL_FU_VOLUME, CH_01), + rt722_sdca_dmic_set_gain_get, rt722_sdca_dmic_set_gain_put, + 4, 0x3f, mic_vol_tlv), + RT722_SDCA_EXT_TLV("FU15 Boost Volume", + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_FU15, + RT722_SDCA_CTL_FU_CH_GAIN, CH_01), + rt722_sdca_dmic_set_gain_get, rt722_sdca_dmic_set_gain_put, + 4, 3, boost_vol_tlv), +}; + +static int rt722_sdca_adc_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned int val = 0, mask_sft; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 12; + else if (strstr(ucontrol->id.name, "ADC 24 Mux")) + mask_sft = 4; + else if (strstr(ucontrol->id.name, "ADC 25 Mux")) + mask_sft = 0; + else + return -EINVAL; + + rt722_sdca_index_read(rt722, RT722_VENDOR_HDA_CTL, + RT722_HDA_LEGACY_MUX_CTL0, &val); + + ucontrol->value.enumerated.item[0] = (val >> mask_sft) & 0x7; + + return 0; +} + +static int rt722_sdca_adc_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, mask_sft; + + if (item[0] >= e->items) + return -EINVAL; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 12; + else if (strstr(ucontrol->id.name, "ADC 24 Mux")) + mask_sft = 4; + else if (strstr(ucontrol->id.name, "ADC 25 Mux")) + mask_sft = 0; + else + return -EINVAL; + + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + rt722_sdca_index_read(rt722, RT722_VENDOR_HDA_CTL, + RT722_HDA_LEGACY_MUX_CTL0, &val2); + val2 = (0x7 << mask_sft) & val2; + + if (val == val2) + change = 0; + else + change = 1; + + if (change) + rt722_sdca_index_update_bits(rt722, RT722_VENDOR_HDA_CTL, + RT722_HDA_LEGACY_MUX_CTL0, 0x7 << mask_sft, + val << mask_sft); + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + item[0], e, NULL); + + return change; +} + +static const char * const adc22_mux_text[] = { + "MIC2", + "LINE1", + "LINE2", +}; + +static const char * const adc07_10_mux_text[] = { + "DMIC1", + "DMIC2", +}; + +static SOC_ENUM_SINGLE_DECL( + rt722_adc22_enum, SND_SOC_NOPM, 0, adc22_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt722_adc24_enum, SND_SOC_NOPM, 0, adc07_10_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt722_adc25_enum, SND_SOC_NOPM, 0, adc07_10_mux_text); + +static const struct snd_kcontrol_new rt722_sdca_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt722_adc22_enum, + rt722_sdca_adc_mux_get, rt722_sdca_adc_mux_put); + +static const struct snd_kcontrol_new rt722_sdca_adc24_mux = + SOC_DAPM_ENUM_EXT("ADC 24 Mux", rt722_adc24_enum, + rt722_sdca_adc_mux_get, rt722_sdca_adc_mux_put); + +static const struct snd_kcontrol_new rt722_sdca_adc25_mux = + SOC_DAPM_ENUM_EXT("ADC 25 Mux", rt722_adc25_enum, + rt722_sdca_adc_mux_get, rt722_sdca_adc_mux_put); + +static int rt722_sdca_fu42_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char unmute = 0x0, mute = 0x1; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_MUTE, CH_L), unmute); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_MUTE, CH_R), unmute); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_MUTE, CH_L), mute); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_USER_FU05, + RT722_SDCA_CTL_FU_MUTE, CH_R), mute); + break; + } + return 0; +} + +static int rt722_sdca_fu21_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char unmute = 0x0, mute = 0x1; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_L), unmute); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_R), unmute); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_L), mute); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_USER_FU06, + RT722_SDCA_CTL_FU_MUTE, CH_R), mute); + break; + } + return 0; +} + +static int rt722_sdca_fu113_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt722->fu1e_dapm_mute = false; + rt722_sdca_set_fu1e_capture_ctl(rt722); + break; + case SND_SOC_DAPM_PRE_PMD: + rt722->fu1e_dapm_mute = true; + rt722_sdca_set_fu1e_capture_ctl(rt722); + break; + } + return 0; +} + +static int rt722_sdca_fu36_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt722->fu0f_dapm_mute = false; + rt722_sdca_set_fu0f_capture_ctl(rt722); + break; + case SND_SOC_DAPM_PRE_PMD: + rt722->fu0f_dapm_mute = true; + rt722_sdca_set_fu0f_capture_ctl(rt722); + break; + } + return 0; +} + +static int rt722_sdca_pde47_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char ps0 = 0x0, ps3 = 0x3; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE40, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE40, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps3); + break; + } + return 0; +} + +static int rt722_sdca_pde23_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char ps0 = 0x0, ps3 = 0x3; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_PDE23, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_PDE23, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps3); + break; + } + return 0; +} + +static int rt722_sdca_pde11_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char ps0 = 0x0, ps3 = 0x3; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_PDE2A, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_PDE2A, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps3); + break; + } + return 0; +} + +static int rt722_sdca_pde12_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + unsigned char ps0 = 0x0, ps3 = 0x3; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE12, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_PDE12, + RT722_SDCA_CTL_REQ_POWER_STATE, 0), ps3); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget rt722_sdca_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_OUTPUT("SPK"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + SND_SOC_DAPM_INPUT("DMIC1_2"), + SND_SOC_DAPM_INPUT("DMIC3_4"), + + SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0, + rt722_sdca_pde23_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0, + rt722_sdca_pde47_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0, + rt722_sdca_pde11_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE 12", SND_SOC_NOPM, 0, 0, + rt722_sdca_pde12_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0, + rt722_sdca_fu21_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("FU 42", NULL, SND_SOC_NOPM, 0, 0, + rt722_sdca_fu42_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0, + rt722_sdca_fu36_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0, + rt722_sdca_fu113_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt722_sdca_adc22_mux), + SND_SOC_DAPM_MUX("ADC 24 Mux", SND_SOC_NOPM, 0, 0, + &rt722_sdca_adc24_mux), + SND_SOC_DAPM_MUX("ADC 25 Mux", SND_SOC_NOPM, 0, 0, + &rt722_sdca_adc25_mux), + + SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Headphone Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Headset Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Speaker Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP6TX", "DP6 DMic Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt722_sdca_audio_map[] = { + {"FU 42", NULL, "DP1RX"}, + {"FU 21", NULL, "DP3RX"}, + + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 22 Mux", "LINE1", "LINE1"}, + {"ADC 22 Mux", "LINE2", "LINE2"}, + {"ADC 24 Mux", "DMIC1", "DMIC1_2"}, + {"ADC 24 Mux", "DMIC2", "DMIC3_4"}, + {"ADC 25 Mux", "DMIC1", "DMIC1_2"}, + {"ADC 25 Mux", "DMIC2", "DMIC3_4"}, + {"FU 36", NULL, "PDE 12"}, + {"FU 36", NULL, "ADC 22 Mux"}, + {"FU 113", NULL, "PDE 11"}, + {"FU 113", NULL, "ADC 24 Mux"}, + {"FU 113", NULL, "ADC 25 Mux"}, + {"DP2TX", NULL, "FU 36"}, + {"DP6TX", NULL, "FU 113"}, + + {"HP", NULL, "PDE 47"}, + {"HP", NULL, "FU 42"}, + {"SPK", NULL, "PDE 23"}, + {"SPK", NULL, "FU 21"}, +}; + +static int rt722_sdca_parse_dt(struct rt722_sdca_priv *rt722, struct device *dev) +{ + device_property_read_u32(dev, "realtek,jd-src", &rt722->jd_src); + + return 0; +} + +static int rt722_sdca_probe(struct snd_soc_component *component) +{ + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + int ret; + + rt722_sdca_parse_dt(rt722, &rt722->slave->dev); + rt722->component = component; + + ret = pm_runtime_resume(component->dev); + if (ret < 0 && ret != -EACCES) + return ret; + + return 0; +} + +static const struct snd_soc_component_driver soc_sdca_dev_rt722 = { + .probe = rt722_sdca_probe, + .controls = rt722_sdca_controls, + .num_controls = ARRAY_SIZE(rt722_sdca_controls), + .dapm_widgets = rt722_sdca_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt722_sdca_dapm_widgets), + .dapm_routes = rt722_sdca_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt722_sdca_audio_map), + .set_jack = rt722_sdca_set_jack_detect, + .endianness = 1, +}; + +static int rt722_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + snd_soc_dai_dma_data_set(dai, direction, sdw_stream); + + return 0; +} + +static void rt722_sdca_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static int rt722_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_runtime *sdw_stream; + int retval, port, num_channels; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + sdw_stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!sdw_stream) + return -EINVAL; + + if (!rt722->slave) + return -EINVAL; + + /* + * RT722_AIF1 with port = 1 for headphone playback + * RT722_AIF1 with port = 2 for headset-mic capture + * RT722_AIF2 with port = 3 for speaker playback + * RT722_AIF3 with port = 6 for digital-mic capture + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + if (dai->id == RT722_AIF1) + port = 1; + else if (dai->id == RT722_AIF2) + port = 3; + else + return -EINVAL; + } else { + direction = SDW_DATA_DIR_TX; + if (dai->id == RT722_AIF1) + port = 2; + else if (dai->id == RT722_AIF3) + port = 6; + else + return -EINVAL; + } + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = GENMASK(num_channels - 1, 0); + port_config.num = port; + + retval = sdw_stream_add_slave(rt722->slave, &stream_config, + &port_config, 1, sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + if (params_channels(params) > 16) { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 44100: + sampling_rate = RT722_SDCA_RATE_44100HZ; + break; + case 48000: + sampling_rate = RT722_SDCA_RATE_48000HZ; + break; + case 96000: + sampling_rate = RT722_SDCA_RATE_96000HZ; + break; + case 192000: + sampling_rate = RT722_SDCA_RATE_192000HZ; + break; + default: + dev_err(component->dev, "Rate %d is not supported\n", + params_rate(params)); + return -EINVAL; + } + + /* set sampling frequency */ + if (dai->id == RT722_AIF1) { + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_CS01, + RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), sampling_rate); + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_JACK_CODEC, RT722_SDCA_ENT_CS11, + RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), sampling_rate); + } + + if (dai->id == RT722_AIF2) + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_CS31, + RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), sampling_rate); + + if (dai->id == RT722_AIF3) + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_CS1F, + RT722_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), sampling_rate); + + return 0; +} + +static int rt722_sdca_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt722_sdca_priv *rt722 = snd_soc_component_get_drvdata(component); + struct sdw_stream_runtime *sdw_stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt722->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt722->slave, sdw_stream); + return 0; +} + +#define RT722_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000) +#define RT722_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops rt722_sdca_ops = { + .hw_params = rt722_sdca_pcm_hw_params, + .hw_free = rt722_sdca_pcm_hw_free, + .set_stream = rt722_sdca_set_sdw_stream, + .shutdown = rt722_sdca_shutdown, +}; + +static struct snd_soc_dai_driver rt722_sdca_dai[] = { + { + .name = "rt722-sdca-aif1", + .id = RT722_AIF1, + .playback = { + .stream_name = "DP1 Headphone Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT722_STEREO_RATES, + .formats = RT722_FORMATS, + }, + .capture = { + .stream_name = "DP2 Headset Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT722_STEREO_RATES, + .formats = RT722_FORMATS, + }, + .ops = &rt722_sdca_ops, + }, + { + .name = "rt722-sdca-aif2", + .id = RT722_AIF2, + .playback = { + .stream_name = "DP3 Speaker Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT722_STEREO_RATES, + .formats = RT722_FORMATS, + }, + .ops = &rt722_sdca_ops, + }, + { + .name = "rt722-sdca-aif3", + .id = RT722_AIF3, + .capture = { + .stream_name = "DP6 DMic Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT722_STEREO_RATES, + .formats = RT722_FORMATS, + }, + .ops = &rt722_sdca_ops, + } +}; + +int rt722_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave) +{ + struct rt722_sdca_priv *rt722; + + rt722 = devm_kzalloc(dev, sizeof(*rt722), GFP_KERNEL); + if (!rt722) + return -ENOMEM; + + dev_set_drvdata(dev, rt722); + rt722->slave = slave; + rt722->regmap = regmap; + rt722->mbq_regmap = mbq_regmap; + + mutex_init(&rt722->calibrate_mutex); + mutex_init(&rt722->disable_irq_lock); + + INIT_DELAYED_WORK(&rt722->jack_detect_work, rt722_sdca_jack_detect_handler); + INIT_DELAYED_WORK(&rt722->jack_btn_check_work, rt722_sdca_btn_check_handler); + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt722->hw_init = false; + rt722->first_hw_init = false; + rt722->fu1e_dapm_mute = true; + rt722->fu0f_dapm_mute = true; + rt722->fu0f_mixer_l_mute = rt722->fu0f_mixer_r_mute = true; + rt722->fu1e_mixer_mute[0] = rt722->fu1e_mixer_mute[1] = + rt722->fu1e_mixer_mute[2] = rt722->fu1e_mixer_mute[3] = true; + + return devm_snd_soc_register_component(dev, + &soc_sdca_dev_rt722, rt722_sdca_dai, ARRAY_SIZE(rt722_sdca_dai)); +} + +static void rt722_sdca_dmic_preset(struct rt722_sdca_priv *rt722) +{ + /* Set AD07 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_ADC0A_08_PDE_FLOAT_CTL, 0x2a29); + /* Set AD10 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_ADC10_PDE_FLOAT_CTL, 0x2a00); + /* Set DMIC1/DMIC2 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_DMIC1_2_PDE_FLOAT_CTL, 0x2a2a); + /* Set DMIC2 IT entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_DMIC_ENT_FLOAT_CTL, 0x2626); + /* Set AD10 FU entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_ADC_ENT_FLOAT_CTL, 0x1e00); + /* Set DMIC2 FU entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_DMIC_GAIN_ENT_FLOAT_CTL0, 0x1515); + /* Set AD10 FU channel floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_ADC_VOL_CH_FLOAT_CTL, 0x0304); + /* Set DMIC2 FU channel floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_DMIC_GAIN_ENT_FLOAT_CTL2, 0x0304); + /* vf71f_r12_07_06 and vf71f_r13_07_06 = 2’b00 */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, + RT722_HDA_LEGACY_CONFIG_CTL0, 0x0000); + /* Enable vf707_r12_05/vf707_r13_05 */ + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_MIC_ARRAY, RT722_SDCA_ENT_IT26, + RT722_SDCA_CTL_VENDOR_DEF, 0), 0x01); + /* Fine tune PDE2A latency */ + regmap_write(rt722->regmap, 0x2f5c, 0x25); +} + +static void rt722_sdca_amp_preset(struct rt722_sdca_priv *rt722) +{ + /* Set DVQ=01 */ + rt722_sdca_index_write(rt722, RT722_VENDOR_REG, RT722_CLSD_CTRL6, + 0xc215); + /* Reset dc_cal_top */ + rt722_sdca_index_write(rt722, RT722_VENDOR_CALI, RT722_DC_CALIB_CTRL, + 0x702c); + /* W1C Trigger Calibration */ + rt722_sdca_index_write(rt722, RT722_VENDOR_CALI, RT722_DC_CALIB_CTRL, + 0xf02d); + /* Set DAC02/ClassD power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_AMP_PDE_FLOAT_CTL, + 0x2323); + /* Set EAPD high */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_EAPD_CTL, + 0x0002); + /* Enable vf707_r14 */ + regmap_write(rt722->regmap, + SDW_SDCA_CTL(FUNC_NUM_AMP, RT722_SDCA_ENT_OT23, + RT722_SDCA_CTL_VENDOR_DEF, CH_08), 0x04); +} + +static void rt722_sdca_jack_preset(struct rt722_sdca_priv *rt722) +{ + int loop_check, chk_cnt = 100, ret; + unsigned int calib_status = 0; + + /* Read eFuse */ + rt722_sdca_index_write(rt722, RT722_VENDOR_SPK_EFUSE, RT722_DC_CALIB_CTRL, + 0x4808); + /* Button A, B, C, D bypass mode */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_UMP_HID_CTL4, + 0xcf00); + /* HID1 slot enable */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_UMP_HID_CTL5, + 0x000f); + /* Report ID for HID1 */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_UMP_HID_CTL0, + 0x1100); + /* OSC/OOC for slot 2, 3 */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_UMP_HID_CTL7, + 0x0c12); + /* Set JD de-bounce clock control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_REG, RT722_JD_CTRL1, + 0x7002); + /* Set DVQ=01 */ + rt722_sdca_index_write(rt722, RT722_VENDOR_REG, RT722_CLSD_CTRL6, + 0xc215); + /* FSM switch to calibration manual mode */ + rt722_sdca_index_write(rt722, RT722_VENDOR_REG, RT722_FSM_CTL, + 0x4100); + /* W1C Trigger DC calibration (HP) */ + rt722_sdca_index_write(rt722, RT722_VENDOR_CALI, RT722_DAC_DC_CALI_CTL3, + 0x008d); + /* check HP calibration FSM status */ + for (loop_check = 0; loop_check < chk_cnt; loop_check++) { + ret = rt722_sdca_index_read(rt722, RT722_VENDOR_CALI, + RT722_DAC_DC_CALI_CTL3, &calib_status); + if (ret < 0 || loop_check == chk_cnt) + dev_dbg(&rt722->slave->dev, "calibration failed!, ret=%d\n", ret); + if ((calib_status & 0x0040) == 0x0) + break; + } + /* Release HP-JD, EN_CBJ_TIE_GL/R open, en_osw gating auto done bit */ + rt722_sdca_index_write(rt722, RT722_VENDOR_REG, RT722_DIGITAL_MISC_CTRL4, + 0x0010); + /* Set ADC09 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_ADC0A_08_PDE_FLOAT_CTL, + 0x2a12); + /* Set MIC2 and LINE1 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_MIC2_LINE2_PDE_FLOAT_CTL, + 0x3429); + /* Set ET41h and LINE2 power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_ET41_LINE2_PDE_FLOAT_CTL, + 0x4112); + /* Set DAC03 and HP power entity floating control */ + rt722_sdca_index_write(rt722, RT722_VENDOR_HDA_CTL, RT722_DAC03_HP_PDE_FLOAT_CTL, + 0x4040); + /* Fine tune PDE40 latency */ + regmap_write(rt722->regmap, 0x2f58, 0x07); +} + +int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt722_sdca_priv *rt722 = dev_get_drvdata(dev); + + rt722->disable_irq = false; + + if (rt722->hw_init) + return 0; + + if (rt722->first_hw_init) { + regcache_cache_only(rt722->regmap, false); + regcache_cache_bypass(rt722->regmap, true); + regcache_cache_only(rt722->mbq_regmap, false); + regcache_cache_bypass(rt722->mbq_regmap, true); + } else { + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + rt722_sdca_dmic_preset(rt722); + rt722_sdca_amp_preset(rt722); + rt722_sdca_jack_preset(rt722); + + if (rt722->first_hw_init) { + regcache_cache_bypass(rt722->regmap, false); + regcache_mark_dirty(rt722->regmap); + regcache_cache_bypass(rt722->mbq_regmap, false); + regcache_mark_dirty(rt722->mbq_regmap); + } else + rt722->first_hw_init = true; + + /* Mark Slave initialization complete */ + rt722->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + return 0; +} + +MODULE_DESCRIPTION("ASoC RT722 SDCA SDW driver"); +MODULE_AUTHOR("Jack Yu "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt722-sdca.h b/sound/soc/codecs/rt722-sdca.h new file mode 100644 index 000000000000..5bc6184d09aa --- /dev/null +++ b/sound/soc/codecs/rt722-sdca.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt722-sdca.h -- RT722 SDCA ALSA SoC audio driver header + * + * Copyright(c) 2023 Realtek Semiconductor Corp. + */ + +#ifndef __RT722_H__ +#define __RT722_H__ + +#include +#include +#include +#include +#include +#include + +struct rt722_sdca_priv { + struct regmap *regmap; + struct regmap *mbq_regmap; + struct snd_soc_component *component; + struct sdw_slave *slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_hw_init; + struct mutex calibrate_mutex; + struct mutex disable_irq_lock; + bool disable_irq; + /* For Headset jack & Headphone */ + unsigned int scp_sdca_stat1; + unsigned int scp_sdca_stat2; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct delayed_work jack_btn_check_work; + int jack_type; + int jd_src; + bool fu0f_dapm_mute; + bool fu0f_mixer_l_mute; + bool fu0f_mixer_r_mute; + /* For DMIC */ + bool fu1e_dapm_mute; + bool fu1e_mixer_mute[4]; +}; + +struct rt722_sdca_dmic_kctrl_priv { + unsigned int reg_base; + unsigned int count; + unsigned int max; + unsigned int invert; +}; + +/* NID */ +#define RT722_VENDOR_REG 0x20 +#define RT722_VENDOR_CALI 0x58 +#define RT722_VENDOR_SPK_EFUSE 0x5c +#define RT722_VENDOR_IMS_DRE 0x5b +#define RT722_VENDOR_ANALOG_CTL 0x5f +#define RT722_VENDOR_HDA_CTL 0x61 + +/* Index (NID:20h) */ +#define RT722_JD_PRODUCT_NUM 0x00 +#define RT722_ANALOG_BIAS_CTL3 0x04 +#define RT722_JD_CTRL1 0x09 +#define RT722_LDO2_3_CTL1 0x0e +#define RT722_LDO1_CTL 0x1a +#define RT722_HP_JD_CTRL 0x24 +#define RT722_CLSD_CTRL6 0x3c +#define RT722_COMBO_JACK_AUTO_CTL1 0x45 +#define RT722_COMBO_JACK_AUTO_CTL2 0x46 +#define RT722_COMBO_JACK_AUTO_CTL3 0x47 +#define RT722_DIGITAL_MISC_CTRL4 0x4a +#define RT722_FSM_CTL 0x67 +#define RT722_SDCA_INTR_REC 0x82 +#define RT722_SW_CONFIG1 0x8a +#define RT722_SW_CONFIG2 0x8b + +/* Index (NID:58h) */ +#define RT722_DAC_DC_CALI_CTL0 0x00 +#define RT722_DAC_DC_CALI_CTL1 0x01 +#define RT722_DAC_DC_CALI_CTL2 0x02 +#define RT722_DAC_DC_CALI_CTL3 0x03 + +/* Index (NID:59h) */ +#define RT722_ULTRA_SOUND_DETECTOR6 0x1e + +/* Index (NID:5bh) */ +#define RT722_IMS_DIGITAL_CTL1 0x00 +#define RT722_IMS_DIGITAL_CTL5 0x05 +#define RT722_HP_DETECT_RLDET_CTL1 0x29 +#define RT722_HP_DETECT_RLDET_CTL2 0x2a + +/* Index (NID:5fh) */ +#define RT722_MISC_POWER_CTL0 0x00 +#define RT722_MISC_POWER_CTL7 0x08 + +/* Index (NID:61h) */ +#define RT722_HDA_LEGACY_MUX_CTL0 0x00 +#define RT722_HDA_LEGACY_UNSOL_CTL 0x03 +#define RT722_HDA_LEGACY_CONFIG_CTL0 0x06 +#define RT722_HDA_LEGACY_RESET_CTL 0x08 +#define RT722_HDA_LEGACY_GPIO_WAKE_EN_CTL 0x0e +#define RT722_DMIC_ENT_FLOAT_CTL 0x10 +#define RT722_DMIC_GAIN_ENT_FLOAT_CTL0 0x11 +#define RT722_DMIC_GAIN_ENT_FLOAT_CTL2 0x13 +#define RT722_ADC_ENT_FLOAT_CTL 0x15 +#define RT722_ADC_VOL_CH_FLOAT_CTL 0x17 +#define RT722_ADC_SAMPLE_RATE_FLOAT 0x18 +#define RT722_DAC03_HP_PDE_FLOAT_CTL 0x22 +#define RT722_MIC2_LINE2_PDE_FLOAT_CTL 0x23 +#define RT722_ET41_LINE2_PDE_FLOAT_CTL 0x24 +#define RT722_ADC0A_08_PDE_FLOAT_CTL 0x25 +#define RT722_ADC10_PDE_FLOAT_CTL 0x26 +#define RT722_DMIC1_2_PDE_FLOAT_CTL 0x28 +#define RT722_AMP_PDE_FLOAT_CTL 0x29 +#define RT722_I2S_IN_OUT_PDE_FLOAT_CTL 0x2f +#define RT722_GE_RELATED_CTL1 0x45 +#define RT722_GE_RELATED_CTL2 0x46 +#define RT722_MIXER_CTL0 0x52 +#define RT722_MIXER_CTL1 0x53 +#define RT722_EAPD_CTL 0x55 +#define RT722_UMP_HID_CTL0 0x60 +#define RT722_UMP_HID_CTL1 0x61 +#define RT722_UMP_HID_CTL2 0x62 +#define RT722_UMP_HID_CTL3 0x63 +#define RT722_UMP_HID_CTL4 0x64 +#define RT722_UMP_HID_CTL5 0x65 +#define RT722_UMP_HID_CTL6 0x66 +#define RT722_UMP_HID_CTL7 0x67 +#define RT722_UMP_HID_CTL8 0x68 + +/* Parameter & Verb control 01 (0x1a)(NID:20h) */ +#define RT722_HIDDEN_REG_SW_RESET (0x1 << 14) + +/* combo jack auto switch control 2 (0x46)(NID:20h) */ +#define RT722_COMBOJACK_AUTO_DET_STATUS (0x1 << 11) +#define RT722_COMBOJACK_AUTO_DET_TRS (0x1 << 10) +#define RT722_COMBOJACK_AUTO_DET_CTIA (0x1 << 9) +#define RT722_COMBOJACK_AUTO_DET_OMTP (0x1 << 8) + +/* DAC calibration control (0x00)(NID:58h) */ +#define RT722_DC_CALIB_CTRL (0x1 << 16) +/* DAC DC offset calibration control-1 (0x01)(NID:58h) */ +#define RT722_PDM_DC_CALIB_STATUS (0x1 << 15) + +#define RT722_EAPD_HIGH 0x2 +#define RT722_EAPD_LOW 0x0 + +/* Buffer address for HID */ +#define RT722_BUF_ADDR_HID1 0x44030000 +#define RT722_BUF_ADDR_HID2 0x44030020 + +/* RT722 SDCA Control - function number */ +#define FUNC_NUM_JACK_CODEC 0x01 +#define FUNC_NUM_MIC_ARRAY 0x02 +#define FUNC_NUM_HID 0x03 +#define FUNC_NUM_AMP 0x04 + +/* RT722 SDCA entity */ +#define RT722_SDCA_ENT_HID01 0x01 +#define RT722_SDCA_ENT_GE49 0x49 +#define RT722_SDCA_ENT_USER_FU05 0x05 +#define RT722_SDCA_ENT_USER_FU06 0x06 +#define RT722_SDCA_ENT_USER_FU0F 0x0f +#define RT722_SDCA_ENT_USER_FU10 0x19 +#define RT722_SDCA_ENT_USER_FU1E 0x1e +#define RT722_SDCA_ENT_FU15 0x15 +#define RT722_SDCA_ENT_PDE23 0x23 +#define RT722_SDCA_ENT_PDE40 0x40 +#define RT722_SDCA_ENT_PDE11 0x11 +#define RT722_SDCA_ENT_PDE12 0x12 +#define RT722_SDCA_ENT_PDE2A 0x2a +#define RT722_SDCA_ENT_CS01 0x01 +#define RT722_SDCA_ENT_CS11 0x11 +#define RT722_SDCA_ENT_CS1F 0x1f +#define RT722_SDCA_ENT_CS1C 0x1c +#define RT722_SDCA_ENT_CS31 0x31 +#define RT722_SDCA_ENT_OT23 0x42 +#define RT722_SDCA_ENT_IT26 0x26 +#define RT722_SDCA_ENT_IT09 0x09 +#define RT722_SDCA_ENT_PLATFORM_FU15 0x15 +#define RT722_SDCA_ENT_PLATFORM_FU44 0x44 +#define RT722_SDCA_ENT_XU03 0x03 +#define RT722_SDCA_ENT_XU0D 0x0d + +/* RT722 SDCA control */ +#define RT722_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 +#define RT722_SDCA_CTL_FU_MUTE 0x01 +#define RT722_SDCA_CTL_FU_VOLUME 0x02 +#define RT722_SDCA_CTL_HIDTX_CURRENT_OWNER 0x10 +#define RT722_SDCA_CTL_HIDTX_SET_OWNER_TO_DEVICE 0x11 +#define RT722_SDCA_CTL_HIDTX_MESSAGE_OFFSET 0x12 +#define RT722_SDCA_CTL_HIDTX_MESSAGE_LENGTH 0x13 +#define RT722_SDCA_CTL_SELECTED_MODE 0x01 +#define RT722_SDCA_CTL_DETECTED_MODE 0x02 +#define RT722_SDCA_CTL_REQ_POWER_STATE 0x01 +#define RT722_SDCA_CTL_VENDOR_DEF 0x30 +#define RT722_SDCA_CTL_FU_CH_GAIN 0x0b + +/* RT722 SDCA channel */ +#define CH_L 0x01 +#define CH_R 0x02 +#define CH_01 0x01 +#define CH_02 0x02 +#define CH_03 0x03 +#define CH_04 0x04 +#define CH_08 0x08 + +/* sample frequency index */ +#define RT722_SDCA_RATE_16000HZ 0x04 +#define RT722_SDCA_RATE_32000HZ 0x07 +#define RT722_SDCA_RATE_44100HZ 0x08 +#define RT722_SDCA_RATE_48000HZ 0x09 +#define RT722_SDCA_RATE_96000HZ 0x0b +#define RT722_SDCA_RATE_192000HZ 0x0d + +enum { + RT722_AIF1, /* For headset mic and headphone */ + RT722_AIF2, /* For speaker */ + RT722_AIF3, /* For dmic */ + RT722_AIFS, +}; + +enum rt722_sdca_jd_src { + RT722_JD_NULL, + RT722_JD1, +}; + +int rt722_sdca_io_init(struct device *dev, struct sdw_slave *slave); +int rt722_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); +int rt722_sdca_index_write(struct rt722_sdca_priv *rt722, + unsigned int nid, unsigned int reg, unsigned int value); +int rt722_sdca_index_read(struct rt722_sdca_priv *rt722, + unsigned int nid, unsigned int reg, unsigned int *value); + +int rt722_sdca_jack_detect(struct rt722_sdca_priv *rt722, bool *hp, bool *mic); +#endif /* __RT722_H__ */ -- cgit v1.2.3 From 4c2be53f411c25b569c8fe3f91d0acfc4c5b8392 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 9 May 2023 12:21:59 +0100 Subject: ASoC: qcom: q6dsp-common: move channel allocation to common move hdmi/dp channel allocation to a common function q6dsp_get_channel_allocation() so that we can reuse this across q6afe and q6apm drivers. Signed-off-by: Srinivas Kandagatla #include #include "q6dsp-lpass-ports.h" +#include "q6dsp-common.h" #include "q6afe.h" @@ -69,6 +70,7 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); int channels = params_channels(params); struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; + int ret; hdmi->sample_rate = params_rate(params); switch (params_format(params)) { @@ -80,33 +82,11 @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, break; } - /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */ - switch (channels) { - case 2: - hdmi->channel_allocation = 0; - break; - case 3: - hdmi->channel_allocation = 0x02; - break; - case 4: - hdmi->channel_allocation = 0x06; - break; - case 5: - hdmi->channel_allocation = 0x0A; - break; - case 6: - hdmi->channel_allocation = 0x0B; - break; - case 7: - hdmi->channel_allocation = 0x12; - break; - case 8: - hdmi->channel_allocation = 0x13; - break; - default: - dev_err(dai->dev, "invalid Channels = %u\n", channels); - return -EINVAL; - } + ret = q6dsp_get_channel_allocation(channels); + if (ret < 0) + return ret; + + hdmi->channel_allocation = (u16) ret; return 0; } diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c index d393003492c7..95585dea2b36 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-common.c +++ b/sound/soc/qcom/qdsp6/q6dsp-common.c @@ -63,4 +63,39 @@ int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch) return 0; } EXPORT_SYMBOL_GPL(q6dsp_map_channels); + +int q6dsp_get_channel_allocation(int channels) +{ + int channel_allocation; + + /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */ + switch (channels) { + case 2: + channel_allocation = 0; + break; + case 3: + channel_allocation = 0x02; + break; + case 4: + channel_allocation = 0x06; + break; + case 5: + channel_allocation = 0x0A; + break; + case 6: + channel_allocation = 0x0B; + break; + case 7: + channel_allocation = 0x12; + break; + case 8: + channel_allocation = 0x13; + break; + default: + return -EINVAL; + } + + return channel_allocation; +} +EXPORT_SYMBOL_GPL(q6dsp_get_channel_allocation); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.h b/sound/soc/qcom/qdsp6/q6dsp-common.h index 01094d108b8a..9e704db5f604 100644 --- a/sound/soc/qcom/qdsp6/q6dsp-common.h +++ b/sound/soc/qcom/qdsp6/q6dsp-common.h @@ -20,5 +20,6 @@ #define PCM_CHANNELS 10 /* Top surround channel. */ int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch); +int q6dsp_get_channel_allocation(int channels); #endif /* __Q6DSP_COMMON_H__ */ -- cgit v1.2.3 From a8ab65417d92803d15cc9aca461ecd9fdb3f2d81 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 9 May 2023 12:22:00 +0100 Subject: ASoC: qcom: audioreach: add support for DISPLAY PORT SINK module Add support for DISPLAY PORT SINK module and associated configuration. Signed-off-by: Srinivas Kandagatla param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_MF_CFG; + param_data->param_size = ep_sz - APM_MODULE_PARAM_DATA_SIZE; + + hw_cfg->mf.sample_rate = cfg->sample_rate; + hw_cfg->mf.bit_width = cfg->bit_width; + hw_cfg->mf.num_channels = cfg->num_channels; + hw_cfg->mf.data_format = module->data_format; + p += ep_sz; + + fs_cfg = p; + param_data = &fs_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_HW_EP_FRAME_SIZE_FACTOR; + param_data->param_size = fs_sz - APM_MODULE_PARAM_DATA_SIZE; + fs_cfg->frame_size_factor = 1; + p += fs_sz; + + intf_cfg = p; + param_data = &intf_cfg->param_data; + param_data->module_instance_id = module->instance_id; + param_data->error_code = 0; + param_data->param_id = PARAM_ID_DISPLAY_PORT_INTF_CFG; + param_data->param_size = ic_sz - APM_MODULE_PARAM_DATA_SIZE; + + intf_cfg->cfg.channel_allocation = cfg->channel_allocation; + intf_cfg->cfg.mst_idx = 0; + intf_cfg->cfg.dptx_idx = cfg->dp_idx; + + rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); + + kfree(pkt); + + return rc; +} + /* LPASS Codec DMA port Module Media Format Setup */ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, @@ -1122,6 +1194,9 @@ int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_mod case MODULE_ID_PCM_CNV: rc = audioreach_pcm_set_media_format(graph, module, cfg); break; + case MODULE_ID_DISPLAY_PORT_SINK: + rc = audioreach_display_port_set_media_format(graph, module, cfg); + break; case MODULE_ID_I2S_SOURCE: case MODULE_ID_I2S_SINK: rc = audioreach_i2s_set_media_format(graph, module, cfg); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 1d1d47d47d40..3ebb81cd7cb0 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -22,6 +22,7 @@ struct q6apm_graph; #define MODULE_ID_I2S_SINK 0x0700100A #define MODULE_ID_I2S_SOURCE 0x0700100B #define MODULE_ID_DATA_LOGGING 0x0700101A +#define MODULE_ID_DISPLAY_PORT_SINK 0x07001069 #define APM_CMD_GET_SPF_STATE 0x01001021 #define APM_CMD_RSP_GET_SPF_STATE 0x02001007 @@ -444,6 +445,15 @@ struct param_id_i2s_intf_cfg { #define PORT_ID_I2S_OUPUT 1 #define I2S_STACK_SIZE 2048 +#define PARAM_ID_DISPLAY_PORT_INTF_CFG 0x08001154 + +struct param_id_display_port_intf_cfg { + uint32_t channel_allocation; + /* Multi-Steam Transport index */ + uint32_t mst_idx; + uint32_t dptx_idx; +} __packed; + #define PARAM_ID_HW_EP_MF_CFG 0x08001017 struct param_id_hw_ep_mf { uint32_t sample_rate; @@ -702,6 +712,8 @@ struct audioreach_module_config { u16 data_format; u16 num_channels; u16 active_channels_mask; + u16 dp_idx; + u32 channel_allocation; u32 sd_line_mask; int fmt; u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; -- cgit v1.2.3 From 90848a2557fec0a6f1a35e58031a1f6f5e44e7d6 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 9 May 2023 12:22:01 +0100 Subject: ASoC: qcom: q6dsp: add support to more display ports Existing code base only supports one display port, this patch adds support upto 8 display ports. This support is required to allow platforms like X13s which have 3 display ports, and some of the Qualcomm SoCs there are upto 7 Display ports. Signed-off-by: Srinivas Kandagatla q6hdmi_ops; break; + case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7: + q6dsp_audio_fe_dais[i].ops = cfg->q6hdmi_ops; + break; case SLIMBUS_0_RX ... SLIMBUS_6_TX: q6dsp_audio_fe_dais[i].ops = cfg->q6slim_ops; break; -- cgit v1.2.3 From 2f6860e6133fca937d18b66faa32c460cef7ddad Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 9 May 2023 12:22:02 +0100 Subject: ASoC: qcom: q6apm: add support to display ports in lpass dais This patch adds support to q6apm lpass display port dais. This support is required to get DP audio on x13s. Signed-off-by: Srinivas Kandagatla #include #include "q6dsp-lpass-ports.h" +#include "q6dsp-common.h" #include "audioreach.h" #include "q6apm.h" @@ -91,6 +92,36 @@ static int q6dma_set_channel_map(struct snd_soc_dai *dai, return 0; } +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; + int channels = params_channels(params); + int ret; + + cfg->bit_width = params_width(params); + cfg->sample_rate = params_rate(params); + cfg->num_channels = channels; + + switch (dai->id) { + case DISPLAY_PORT_RX_0: + cfg->dp_idx = 0; + break; + case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7: + cfg->dp_idx = dai->id - DISPLAY_PORT_RX_1 + 1; + break; + } + + ret = q6dsp_get_channel_allocation(channels); + if (ret < 0) + return ret; + + cfg->channel_allocation = ret; + + return 0; +} + static int q6dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -215,6 +246,13 @@ static const struct snd_soc_dai_ops q6i2s_ops = { .shutdown = q6apm_lpass_dai_shutdown, .set_channel_map = q6dma_set_channel_map, .hw_params = q6dma_hw_params, +}; + +static const struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6apm_lpass_dai_prepare, + .startup = q6apm_lpass_dai_startup, + .shutdown = q6apm_lpass_dai_shutdown, + .hw_params = q6hdmi_hw_params, .set_fmt = q6i2s_set_fmt, }; @@ -242,6 +280,7 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) memset(&cfg, 0, sizeof(cfg)); cfg.q6i2s_ops = &q6i2s_ops; cfg.q6dma_ops = &q6dma_ops; + cfg.q6hdmi_ops = &q6hdmi_ops; dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); -- cgit v1.2.3 From bb1b282da4be8af998de7b5a2c600af6ef01aa4f Mon Sep 17 00:00:00 2001 From: Aidan MacDonald Date: Tue, 9 May 2023 13:42:36 +0100 Subject: ASoC: jz4740-i2s: Add support for X1000 SoC The X1000's AIC is similar to the AIC found on other Ingenic SoCs. It has symmetric playback/capture rates like the JZ4740, but more flexible clocking when outputting the system or bit clocks. Signed-off-by: Aidan MacDonald Date: Thu, 11 May 2023 17:05:46 +0200 Subject: ASoC: ssm3515: Add new amp driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Analog Devices' SSM3515 is a mono audio amplifier with digital input, equipped on Apple's 2021 iMacs. Add an ASoC driver for it, and register both the driver code and schema in MAINTAINERS. Signed-off-by: Martin Povišer L: asahi@lists.linux.dev L: alsa-devel@alsa-project.org (moderated for non-subscribers) S: Maintained +F: Documentation/devicetree/bindings/sound/adi,ssm3515.yaml F: Documentation/devicetree/bindings/sound/apple,* F: sound/soc/apple/* F: sound/soc/codecs/cs42l83-i2c.c +F: sound/soc/codecs/ssm3515.c ARM/ARTPEC MACHINE SUPPORT M: Jesper Nilsson diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 031443ff8414..bd9bc0f66647 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1659,6 +1659,12 @@ config SND_SOC_SSM2602_I2C select SND_SOC_SSM2602 select REGMAP_I2C +config SND_SOC_SSM3515 + tristate "Analog Devices SSM3515 amplifier driver" + select REGMAP_I2C + depends on I2C + depends on OF + config SND_SOC_SSM4567 tristate "Analog Devices ssm4567 amplifier driver support" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index cd799306331c..90c754926b37 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -257,6 +257,7 @@ snd-soc-ssm2518-objs := ssm2518.o snd-soc-ssm2602-objs := ssm2602.o snd-soc-ssm2602-spi-objs := ssm2602-spi.o snd-soc-ssm2602-i2c-objs := ssm2602-i2c.o +snd-soc-ssm3515-objs := ssm3515.o snd-soc-ssm4567-objs := ssm4567.o snd-soc-sta32x-objs := sta32x.o snd-soc-sta350-objs := sta350.o @@ -625,6 +626,7 @@ obj-$(CONFIG_SND_SOC_SSM2518) += snd-soc-ssm2518.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o obj-$(CONFIG_SND_SOC_SSM2602_SPI) += snd-soc-ssm2602-spi.o obj-$(CONFIG_SND_SOC_SSM2602_I2C) += snd-soc-ssm2602-i2c.o +obj-$(CONFIG_SND_SOC_SSM3515) += snd-soc-ssm3515.o obj-$(CONFIG_SND_SOC_SSM4567) += snd-soc-ssm4567.o obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o obj-$(CONFIG_SND_SOC_STA350) += snd-soc-sta350.o diff --git a/sound/soc/codecs/ssm3515.c b/sound/soc/codecs/ssm3515.c new file mode 100644 index 000000000000..784e890031a4 --- /dev/null +++ b/sound/soc/codecs/ssm3515.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +// +// Analog Devices' SSM3515 audio amp driver +// +// Copyright (C) The Asahi Linux Contributors + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define SSM3515_PWR 0x00 +#define SSM3515_PWR_APWDN_EN BIT(7) +#define SSM3515_PWR_BSNS_PWDN BIT(6) +#define SSM3515_PWR_S_RST BIT(1) +#define SSM3515_PWR_SPWDN BIT(0) + +#define SSM3515_GEC 0x01 +#define SSM3515_GEC_EDGE BIT(4) +#define SSM3515_GEC_EDGE_SHIFT 4 +#define SSM3515_GEC_ANA_GAIN GENMASK(1, 0) + +#define SSM3515_DAC 0x02 +#define SSM3515_DAC_HV BIT(7) +#define SSM3515_DAC_MUTE BIT(6) +#define SSM3515_DAC_HPF BIT(5) +#define SSM3515_DAC_LPM BIT(4) +#define SSM3515_DAC_FS GENMASK(2, 0) + +#define SSM3515_DAC_VOL 0x03 + +#define SSM3515_SAI1 0x04 +#define SSM3515_SAI1_DAC_POL BIT(7) +#define SSM3515_SAI1_BCLK_POL BIT(6) +#define SSM3515_SAI1_TDM_BCLKS GENMASK(5, 3) +#define SSM3515_SAI1_FSYNC_MODE BIT(2) +#define SSM3515_SAI1_SDATA_FMT BIT(1) +#define SSM3515_SAI1_SAI_MODE BIT(0) + +#define SSM3515_SAI2 0x05 +#define SSM3515_SAI2_DATA_WIDTH BIT(7) +#define SSM3515_SAI2_AUTO_SLOT BIT(4) +#define SSM3515_SAI2_TDM_SLOT GENMASK(3, 0) + +#define SSM3515_VBAT_OUT 0x06 + +#define SSM3515_STATUS 0x0a +#define SSM3515_STATUS_UVLO_REG BIT(6) +#define SSM3515_STATUS_LIM_EG BIT(5) +#define SSM3515_STATUS_CLIP BIT(4) +#define SSM3515_STATUS_AMP_OC BIT(3) +#define SSM3515_STATUS_OTF BIT(2) +#define SSM3515_STATUS_OTW BIT(1) +#define SSM3515_STATUS_BAT_WARN BIT(0) + +static bool ssm3515_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SSM3515_STATUS: + case SSM3515_VBAT_OUT: + return true; + + default: + return false; + } +} + +static const struct reg_default ssm3515_reg_defaults[] = { + { SSM3515_PWR, 0x81 }, + { SSM3515_GEC, 0x01 }, + { SSM3515_DAC, 0x32 }, + { SSM3515_DAC_VOL, 0x40 }, + { SSM3515_SAI1, 0x11 }, + { SSM3515_SAI2, 0x00 }, +}; + +static const struct regmap_config ssm3515_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = ssm3515_volatile_reg, + .max_register = 0xb, + .reg_defaults = ssm3515_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ssm3515_reg_defaults), + .cache_type = REGCACHE_FLAT, +}; + +struct ssm3515_data { + struct device *dev; + struct regmap *regmap; +}; + +// The specced range is -71.25...24.00 dB with step size of 0.375 dB, +// and a mute item below that. This is represented by -71.62...24.00 dB +// with the mute item mapped onto the low end. +static DECLARE_TLV_DB_MINMAX_MUTE(ssm3515_dac_volume, -7162, 2400); + +static const char * const ssm3515_ana_gain_text[] = { + "8.4 V Span", "12.6 V Span", "14 V Span", "15 V Span", +}; + +static SOC_ENUM_SINGLE_DECL(ssm3515_ana_gain_enum, SSM3515_GEC, + __bf_shf(SSM3515_GEC_ANA_GAIN), + ssm3515_ana_gain_text); + +static const struct snd_kcontrol_new ssm3515_snd_controls[] = { + SOC_SINGLE_TLV("DAC Playback Volume", SSM3515_DAC_VOL, + 0, 255, 1, ssm3515_dac_volume), + SOC_SINGLE("Low EMI Mode Switch", SSM3515_GEC, + __bf_shf(SSM3515_GEC_EDGE), 1, 0), + SOC_SINGLE("Soft Volume Ramping Switch", SSM3515_DAC, + __bf_shf(SSM3515_DAC_HV), 1, 1), + SOC_SINGLE("HPF Switch", SSM3515_DAC, + __bf_shf(SSM3515_DAC_HPF), 1, 0), + SOC_SINGLE("DAC Invert Switch", SSM3515_SAI1, + __bf_shf(SSM3515_SAI1_DAC_POL), 1, 0), + SOC_ENUM("DAC Analog Gain Select", ssm3515_ana_gain_enum), +}; + +static void ssm3515_read_faults(struct snd_soc_component *component) +{ + int ret; + + ret = snd_soc_component_read(component, SSM3515_STATUS); + if (ret <= 0) { + /* + * If the read was erroneous, ASoC core has printed a message, + * and that's all that's appropriate in handling the error here. + */ + return; + } + + dev_err(component->dev, "device reports:%s%s%s%s%s%s%s\n", + FIELD_GET(SSM3515_STATUS_UVLO_REG, ret) ? " voltage regulator fault" : "", + FIELD_GET(SSM3515_STATUS_LIM_EG, ret) ? " limiter engaged" : "", + FIELD_GET(SSM3515_STATUS_CLIP, ret) ? " clipping detected" : "", + FIELD_GET(SSM3515_STATUS_AMP_OC, ret) ? " amp over-current fault" : "", + FIELD_GET(SSM3515_STATUS_OTF, ret) ? " overtemperature fault" : "", + FIELD_GET(SSM3515_STATUS_OTW, ret) ? " overtemperature warning" : "", + FIELD_GET(SSM3515_STATUS_BAT_WARN, ret) ? " bat voltage low warning" : ""); +} + +static int ssm3515_probe(struct snd_soc_component *component) +{ + int ret; + + /* Start out muted */ + ret = snd_soc_component_update_bits(component, SSM3515_DAC, + SSM3515_DAC_MUTE, SSM3515_DAC_MUTE); + if (ret < 0) + return ret; + + /* Disable the 'master power-down' */ + ret = snd_soc_component_update_bits(component, SSM3515_PWR, + SSM3515_PWR_SPWDN, 0); + if (ret < 0) + return ret; + + return 0; +} + +static int ssm3515_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + int ret; + + ret = snd_soc_component_update_bits(dai->component, + SSM3515_DAC, + SSM3515_DAC_MUTE, + FIELD_PREP(SSM3515_DAC_MUTE, mute)); + if (ret < 0) + return ret; + return 0; +} + +static int ssm3515_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + int ret, rateval; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16: + case SNDRV_PCM_FORMAT_S24: + ret = snd_soc_component_update_bits(component, + SSM3515_SAI2, SSM3515_SAI2_DATA_WIDTH, + FIELD_PREP(SSM3515_SAI2_DATA_WIDTH, + params_width(params) == 16)); + if (ret < 0) + return ret; + break; + + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 8000 ... 12000: + rateval = 0; + break; + case 16000 ... 24000: + rateval = 1; + break; + case 32000 ... 48000: + rateval = 2; + break; + case 64000 ... 96000: + rateval = 3; + break; + case 128000 ... 192000: + rateval = 4; + break; + case 48001 ... 63999: /* this is ...72000 but overlaps */ + rateval = 5; + break; + default: + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, + SSM3515_DAC, SSM3515_DAC_FS, + FIELD_PREP(SSM3515_DAC_FS, rateval)); + if (ret < 0) + return ret; + + return 0; +} + +static int ssm3515_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + bool fpol_inv = false; /* non-inverted: frame starts with low-to-high FSYNC */ + int ret; + u8 sai1 = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_IB_IF: + sai1 |= SSM3515_SAI1_BCLK_POL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + fpol_inv = 1; + sai1 &= ~SSM3515_SAI1_SDATA_FMT; /* 1 bit start delay */ + break; + case SND_SOC_DAIFMT_LEFT_J: + fpol_inv = 0; + sai1 |= SSM3515_SAI1_SDATA_FMT; /* no start delay */ + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_IF: + case SND_SOC_DAIFMT_IB_IF: + fpol_inv ^= 1; + break; + } + + /* Set the serial input to 'TDM mode' */ + sai1 |= SSM3515_SAI1_SAI_MODE; + + if (fpol_inv) { + /* + * We configure the codec in a 'TDM mode', in which the + * FSYNC_MODE bit of SAI1 is supposed to select between + * what the datasheet calls 'Pulsed FSYNC mode' and '50% + * FSYNC mode'. + * + * Experiments suggest that this bit in fact simply selects + * the FSYNC polarity, so go with that. + */ + sai1 |= SSM3515_SAI1_FSYNC_MODE; + } + + ret = snd_soc_component_update_bits(component, SSM3515_SAI1, + SSM3515_SAI1_BCLK_POL | SSM3515_SAI1_SDATA_FMT | + SSM3515_SAI1_SAI_MODE | SSM3515_SAI1_FSYNC_MODE, sai1); + + if (ret < 0) + return ret; + return 0; +} + +static int ssm3515_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + int slot, tdm_bclks_val, ret; + + if (tx_mask == 0 || rx_mask != 0) + return -EINVAL; + + slot = __ffs(tx_mask); + + if (tx_mask & ~BIT(slot)) + return -EINVAL; + + switch (slot_width) { + case 16: + tdm_bclks_val = 0; + break; + case 24: + tdm_bclks_val = 1; + break; + case 32: + tdm_bclks_val = 2; + break; + case 48: + tdm_bclks_val = 3; + break; + case 64: + tdm_bclks_val = 4; + break; + default: + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, SSM3515_SAI1, + SSM3515_SAI1_TDM_BCLKS, + FIELD_PREP(SSM3515_SAI1_TDM_BCLKS, tdm_bclks_val)); + if (ret < 0) + return ret; + + ret = snd_soc_component_update_bits(component, SSM3515_SAI2, + SSM3515_SAI2_TDM_SLOT, + FIELD_PREP(SSM3515_SAI2_TDM_SLOT, slot)); + if (ret < 0) + return ret; + + return 0; +} + +static int ssm3515_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /* + * We don't get live notification of faults, so at least at + * this time, when playback is over, check if we have tripped + * over anything and if so, log it. + */ + ssm3515_read_faults(dai->component); + return 0; +} + +static const struct snd_soc_dai_ops ssm3515_dai_ops = { + .mute_stream = ssm3515_mute, + .hw_params = ssm3515_hw_params, + .set_fmt = ssm3515_set_fmt, + .set_tdm_slot = ssm3515_set_tdm_slot, + .hw_free = ssm3515_hw_free, +}; + +static struct snd_soc_dai_driver ssm3515_dai_driver = { + .name = "SSM3515 SAI", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ssm3515_dai_ops, +}; + +static const struct snd_soc_dapm_widget ssm3515_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUTPUT("OUT"), +}; + +static const struct snd_soc_dapm_route ssm3515_dapm_routes[] = { + {"OUT", NULL, "DAC"}, + {"DAC", NULL, "Playback"}, +}; + +static const struct snd_soc_component_driver ssm3515_asoc_component = { + .probe = ssm3515_probe, + .controls = ssm3515_snd_controls, + .num_controls = ARRAY_SIZE(ssm3515_snd_controls), + .dapm_widgets = ssm3515_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm3515_dapm_widgets), + .dapm_routes = ssm3515_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ssm3515_dapm_routes), + .endianness = 1, +}; + +static int ssm3515_i2c_probe(struct i2c_client *client) +{ + struct ssm3515_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &client->dev; + i2c_set_clientdata(client, data); + + data->regmap = devm_regmap_init_i2c(client, &ssm3515_i2c_regmap); + if (IS_ERR(data->regmap)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap), + "initializing register map\n"); + + /* Perform a reset */ + ret = regmap_update_bits(data->regmap, SSM3515_PWR, + SSM3515_PWR_S_RST, SSM3515_PWR_S_RST); + if (ret < 0) + return dev_err_probe(data->dev, ret, + "performing software reset\n"); + regmap_reinit_cache(data->regmap, &ssm3515_i2c_regmap); + + return devm_snd_soc_register_component(data->dev, + &ssm3515_asoc_component, + &ssm3515_dai_driver, 1); +} + +static const struct of_device_id ssm3515_of_match[] = { + { .compatible = "adi,ssm3515" }, + {} +}; +MODULE_DEVICE_TABLE(of, ssm3515_of_match); + +static struct i2c_driver ssm3515_i2c_driver = { + .driver = { + .name = "ssm3515", + .of_match_table = of_match_ptr(ssm3515_of_match), + }, + .probe_new = ssm3515_i2c_probe, +}; +module_i2c_driver(ssm3515_i2c_driver); + +MODULE_AUTHOR("Martin Povišer "); +MODULE_DESCRIPTION("ASoC SSM3515 audio amp driver"); +MODULE_LICENSE("Dual MIT/GPL"); -- cgit v1.2.3 From deeb7855f5d7e21deef25c7fdbeb8512564bdea6 Mon Sep 17 00:00:00 2001 From: Rsplwe Date: Thu, 11 May 2023 23:04:11 +0800 Subject: ASoC: amd: yc: Add MECHREVO Jiaolong Series MRID6 into DMI table This model requires an additional detection quirk to enable the internal microphone. Signed-off-by: Rsplwe Date: Wed, 10 May 2023 19:37:22 +0200 Subject: ALSA: emu10k1: don't create regular S/PDIF controls for E-MU cards These ports are unused on these cards. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173722.3072439-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 610700be1e37..48f0d3f8b8e7 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -2055,7 +2055,7 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, mix->attn[0] = 0xffff; } - if (! emu->card_capabilities->ecard) { /* FIXME: APS has these controls? */ + if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) { /* sb live! and audigy */ kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu); if (!kctl) -- cgit v1.2.3 From 2a3fa40aefbe7de6cca3c6d56711ab336dfe34ae Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:04 +0200 Subject: ALSA: emu10k1: make tone control switch mono It controls the whole surround set, so stereo can't work. As a consequence, only the left channel was paid attention to. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 98785110ef63..6442028fe48a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1543,13 +1543,13 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) #undef TREBLE_GPR for (z = 0; z < 8; z++) { - A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0); + A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); + A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr); A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); } - snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); - gpr += 2; + snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); + gpr++; /* Master volume (will be renamed later) */ A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS)); @@ -2263,13 +2263,13 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) #undef TREBLE_GPR for (z = 0; z < 6; z++) { - SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0); - SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0); + SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); + SWITCH_NEG(icode, &ptr, tmp + 1, gpr); SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000); } - snd_emu10k1_init_stereo_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); - gpr += 2; + snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); + gpr++; /* * Process outputs -- cgit v1.2.3 From 8cabf83c7aa54530e699be56249fb44f9505c4f3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:05 +0200 Subject: ALSA: emu10k1: roll up loops in DSP setup code for Audigy There is no apparent reason for the massive code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 112 ++++------------------------------------------ 1 file changed, 9 insertions(+), 103 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 6442028fe48a..eb40159a2eeb 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1552,14 +1552,8 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) gpr++; /* Master volume (will be renamed later) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS)); + for (z = 0; z < 8; z++) + A_OP(icode, &ptr, iMAC0, A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr += 2; @@ -1646,102 +1640,14 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_dbg(emu->card->dev, "emufx.c: gpr=0x%x, tmp=0x%x\n", gpr, tmp); */ - /* For the EMU1010: How to get 32bit values from the DSP. High 16bits into L, low 16bits into R. */ - /* A_P16VIN(0) is delayed by one sample, - * so all other A_P16VIN channels will need to also be delayed - */ - /* Left ADC in. 1 of 2 */ snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) ); - /* Right ADC in 1 of 2 */ - gpr_map[gpr++] = 0x00000000; - /* Delaying by one sample: instead of copying the input - * value A_P16VIN to output A_FXBUS2 as in the first channel, - * we use an auxiliary register, delaying the value by one - * sample - */ - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(2) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x1), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(4) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x2), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(6) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x3), A_C_00000000, A_C_00000000); - /* For 96kHz mode */ - /* Left ADC in. 2 of 2 */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0x8) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x4), A_C_00000000, A_C_00000000); - /* Right ADC in 2 of 2 */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xa) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x5), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xc) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x6), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xe) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x7), A_C_00000000, A_C_00000000); - /* Pavel Hofman - we still have voices, A_FXBUS2s, and - * A_P16VINs available - - * let's add 8 more capture channels - total of 16 - */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x10)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x8), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x12)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x9), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x14)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xa), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x16)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xb), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x18)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xc), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1a)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xd), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1c)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xe), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1e)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xf), - A_C_00000000, A_C_00000000); + /* A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels + * will need to also be delayed; we use an auxiliary register for that. */ + for (z = 1; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_FXBUS2(z * 2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A_P16VIN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + } } #if 0 -- cgit v1.2.3 From 4102ac29759586e86cf129d66fc5ad5406a431a8 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:06 +0200 Subject: ALSA: emu10k1: fix+optimize E-MU stereo capture DSP code Presumably, JDC added the seemingly superfluous indirection over the temporary register because without it he'd get only zero readings. However, switching the X and Y operands (or using EMU32 as the A operand in the temporary load) works just fine. Presumably a DSP bug? The original code was also actually buggy, though: both channels used the left volume control. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index eb40159a2eeb..795b2573fef4 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1324,11 +1324,9 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* EMU1010 DSP 0 and DSP 1 Capture */ // The 24 MSB hold the actual value. We implicitly discard the 16 LSB. if (emu->card_capabilities->ca0108_chip) { - /* Note:JCD:No longer bit shift lower 16bits to upper 16bits of 32bit value. */ - A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x0), A_C_00000001); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_GPR(tmp)); - A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x1), A_C_00000001); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr), A_GPR(tmp)); + // For unclear reasons, the EMU32IN cannot be the Y operand! + A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A3_EMU32IN(0x1), A_GPR(gpr+1)); } else { A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1)); -- cgit v1.2.3 From 4c7bfbcf7516b0804ba0204e865f885baca604e4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:07 +0200 Subject: ALSA: emu10k1: simplify snd_emu10k1_audigy_dsp_convert_32_to_2x16() Instead of spending lots of instructions on masking and transplanting the sign bit, sidestep the issue by replacing the last bit shift with a wrapping addition to self. Solution stolen from kX-project, after I pondered other ideas first. Also, the function really doesn't need to return a constant int value. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 795b2573fef4..8ba294138dfe 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1184,20 +1184,22 @@ snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl * to 2 x 16-bit registers in Audigy - their values are read via DMA. * Conversion is performed by Audigy DSP instructions of FX8010. */ -static int snd_emu10k1_audigy_dsp_convert_32_to_2x16( +static void snd_emu10k1_audigy_dsp_convert_32_to_2x16( struct snd_emu10k1_fx8010_code *icode, u32 *ptr, int tmp, int bit_shifter16, int reg_in, int reg_out) { - A_OP(icode, ptr, iACC3, A_GPR(tmp + 1), reg_in, A_C_00000000, A_C_00000000); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp + 1), A_GPR(bit_shifter16 - 1), A_C_00000000); - A_OP(icode, ptr, iTSTNEG, A_GPR(tmp + 2), A_GPR(tmp), A_C_80000000, A_GPR(bit_shifter16 - 2)); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_C_80000000, A_C_00000000); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp), A_GPR(bit_shifter16 - 3), A_C_00000000); - A_OP(icode, ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A_GPR(tmp), A_C_00010000); - A_OP(icode, ptr, iANDXOR, reg_out, A_GPR(tmp), A_C_ffffffff, A_GPR(tmp + 2)); - A_OP(icode, ptr, iACC3, reg_out + 1, A_GPR(tmp + 1), A_C_00000000, A_C_00000000); - return 1; + // This leaves the low word in place, which is fine, + // as the low bits are completely ignored subsequently. + // reg_out[1] = reg_in + A_OP(icode, ptr, iACC3, reg_out + 1, reg_in, A_C_00000000, A_C_00000000); + // It is fine to read reg_in multiple times. + // tmp = reg_in << 15 + A_OP(icode, ptr, iMACINT1, A_GPR(tmp), A_C_00000000, reg_in, A_GPR(bit_shifter16)); + // Left-shift once more. This is a separate step, as the + // signed multiplication would clobber the MSB. + // reg_out[0] = tmp + ((tmp << 31) >> 31) + A_OP(icode, ptr, iMAC3, reg_out, A_GPR(tmp), A_GPR(tmp), A_C_80000000); } /* @@ -1247,10 +1249,8 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) ptr = 0; nctl = 0; gpr = stereo_mix + 10; - gpr_map[gpr++] = 0x00007fff; - gpr_map[gpr++] = 0x00008000; - gpr_map[gpr++] = 0x0000ffff; bit_shifter16 = gpr; + gpr_map[gpr++] = 0x00008000; #if 1 /* PCM front Playback Volume (independent from stereo mix) -- cgit v1.2.3 From f549466b8b8519260e49460543dff9b37f280cc9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:08 +0200 Subject: ALSA: emu10k1: apply channel delay hack to all E-MU cards Evidently, the channel delay bug exists in all E-MU cards; it's in the Hana FPGA program, and was never fixed. Note that the implementation is somewhat lazy - to localize the code paths, we actually waste a GPR and a DSP instruction by keeping two delay registers for the same physical source. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emufx.c | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 8fe80dcee71b..7129b9249eb3 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1192,6 +1192,7 @@ * emumixer.c - snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[] */ #define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */ + /* This channel is delayed by one sample. */ #define EMU_DST_ALICE2_EMU32_1 0x0000 /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_2 0x0001 /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_3 0x0002 /* 16 EMU32 channels to Alice2 +0 to +0xf */ diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 8ba294138dfe..2e139ae8b41b 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1326,13 +1326,20 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) if (emu->card_capabilities->ca0108_chip) { // For unclear reasons, the EMU32IN cannot be the Y operand! A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A3_EMU32IN(0x1), A_GPR(gpr+1)); + // A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels + // need to be delayed as well; we use an auxiliary register for that. + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A3_EMU32IN(0x1), A_C_00000000, A_C_00000000); } else { A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1)); + // A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels + // need to be delayed as well; we use an auxiliary register for that. + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_GPR(gpr+2)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A_P16VIN(0x1), A_C_00000000, A_C_00000000); } snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); - gpr += 2; + gpr_map[gpr + 2] = 0x00000000; + gpr += 3; } /* AC'97 Playback Volume - used only for mic (renamed later) */ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); @@ -1624,11 +1631,17 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_info(emu->card->dev, "EMU2 inputs on\n"); /* Note that the Tina[2] DSPs have 16 more EMU32 inputs which we don't use. */ - for (z = 0; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( + icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_FXBUS2(0)); + // A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels + // need to be delayed as well; we use an auxiliary register for that. + for (z = 1; z < 0x10; z++) { snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, - A3_EMU32IN(z), + A_GPR(gpr), A_FXBUS2(z*2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A3_EMU32IN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; } } else { dev_info(emu->card->dev, "EMU inputs on\n"); -- cgit v1.2.3 From 59f038a09c62d77de91387ddcff66e54d99f2ec9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:09 +0200 Subject: ALSA: emu10k1: simplify tone control switch DSP code Instead of using lots of instructions to mix wet and dry signals, simply skip over the whole code block if tone control is disabled. This also allows us doing away with the "shadow" playback channels. Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 123 +++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 73 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 2e139ae8b41b..2da1f9f1fb5a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1211,10 +1211,10 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) int err, z, gpr, nctl; int bit_shifter16; const int playback = 10; - const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */ + const int capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; /* we reserve 10 voices */ const int stereo_mix = capture + 2; const int tmp = 0x88; - u32 ptr; + u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; u32 *gpr_map; @@ -1474,18 +1474,6 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* * Process tone control */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), A_GPR(playback + 0), A_C_00000000, A_C_00000000); /* left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), A_GPR(playback + 1), A_C_00000000, A_C_00000000); /* right */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), A_GPR(playback + 2), A_C_00000000, A_C_00000000); /* rear left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), A_GPR(playback + 3), A_C_00000000, A_C_00000000); /* rear right */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), A_GPR(playback + 4), A_C_00000000, A_C_00000000); /* center */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), A_GPR(playback + 5), A_C_00000000, A_C_00000000); /* LFE */ - if (emu->card_capabilities->spk71) { - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 6), A_GPR(playback + 6), A_C_00000000, A_C_00000000); /* side left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 7), A_GPR(playback + 7), A_C_00000000, A_C_00000000); /* side right */ - } - - ctl = &controls[nctl + 0]; ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, "Tone Control - Bass"); @@ -1515,12 +1503,19 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; } } + nctl += 2; + + A_OP(icode, &ptr, iACC3, A_C_00000000, A_GPR(gpr), A_C_00000000, A_C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); + gpr++; + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_CC_REG_ZERO, A_GPR(gpr)); + ptr_skip = ptr; for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ k = 0xb0 + (z * 8) + (j * 4); l = 0xe0 + (z * 8) + (j * 4); - d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j; + d = playback + z * 2 + j; A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j)); A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j)); @@ -1542,36 +1537,27 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) break; } } - nctl += 2; + gpr_map[gpr++] = ptr - ptr_skip; #undef BASS_GPR #undef TREBLE_GPR - for (z = 0; z < 8; z++) { - A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr); - A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); - } - snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); - gpr++; - /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) - A_OP(icode, &ptr, iMAC0, A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr += 2; /* analog speakers */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); if (emu->card_capabilities->spk71) - A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); /* headphone */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); /* digital outputs */ /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */ @@ -1580,9 +1566,9 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_info(emu->card->dev, "EMU outputs on\n"); for (z = 0; z < 8; z++) { if (emu->card_capabilities->ca0108_chip) { - A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } else { - A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } } } @@ -1598,7 +1584,7 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); - A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1); + A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); if ((z==1) && (emu->card_capabilities->spdif_bug)) { /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ dev_info(emu->card->dev, @@ -1613,13 +1599,13 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); gpr += 2; - A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); /* ADC buffer */ #ifdef EMU10K1_CAPTURE_DIGITAL_OUT - A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback); #else A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture); A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1); @@ -1762,7 +1748,7 @@ static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) { int err, i, z, gpr, tmp, playback, capture; - u32 ptr; + u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode; struct snd_emu10k1_fx8010_pcm_rec *ipcm = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; @@ -1805,7 +1791,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* we have 12 inputs */ playback = SND_EMU10K1_INPUTS; /* we have 6 playback channels and tone control doubles */ - capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); + capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS; tmp = 0x88; /* we need 4 temporary GPR */ /* from 0x8c to 0xff is the area for tone control */ @@ -2109,13 +2095,6 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* * Process tone control */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), GPR(playback + 0), C_00000000, C_00000000); /* left */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), GPR(playback + 1), C_00000000, C_00000000); /* right */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), GPR(playback + 2), C_00000000, C_00000000); /* rear left */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), GPR(playback + 3), C_00000000, C_00000000); /* rear right */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), GPR(playback + 4), C_00000000, C_00000000); /* center */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), GPR(playback + 5), C_00000000, C_00000000); /* LFE */ - ctl = &controls[i + 0]; ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, "Tone Control - Bass"); @@ -2147,12 +2126,19 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) controls[i + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; } } + i += 2; + + OP(icode, &ptr, iACC3, C_00000000, GPR(gpr), C_00000000, C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); + gpr++; + OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr)); + ptr_skip = ptr; for (z = 0; z < 3; z++) { /* front/rear/center-lfe */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ k = 0xa0 + (z * 8) + (j * 4); l = 0xd0 + (z * 8) + (j * 4); - d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j; + d = playback + z * 2 + j; OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(d), GPR(BASS_GPR + 0 + j)); OP(icode, &ptr, iMACMV, GPR(k+1), GPR(k), GPR(k+1), GPR(BASS_GPR + 4 + j)); @@ -2174,20 +2160,11 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) break; } } - i += 2; + gpr_map[gpr++] = ptr - ptr_skip; #undef BASS_GPR #undef TREBLE_GPR - for (z = 0; z < 6; z++) { - SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); - SWITCH_NEG(icode, &ptr, tmp + 1, gpr); - SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000); - } - snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); - gpr++; - /* * Process outputs */ @@ -2195,7 +2172,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* AC'97 Playback Volume */ for (z = 0; z < 2; z++) - OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), C_00000000, C_00000000); + OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + z), C_00000000, C_00000000); } if (emu->fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & (1<fx8010.extout_mask & (1< Date: Wed, 10 May 2023 11:55:18 +0800 Subject: ASoC: mediatek: mt8188: remove supply AUDIO_HIRES AUDIO_HIRES is not required in MT8188. Because top_audio_h is disabled when hires clock is not used, set_parent is a redundant operation. Signed-off-by: Trevor Wu dapm); - struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct clk *clk = afe_priv->clk[MT8188_CLK_TOP_AUDIO_H_SEL]; - struct clk *clk_parent; - - dev_dbg(afe->dev, "%s(), name %s, event 0x%x\n", - __func__, w->name, event); - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - clk_parent = afe_priv->clk[MT8188_CLK_APMIXED_APLL1]; - break; - case SND_SOC_DAPM_POST_PMD: - clk_parent = afe_priv->clk[MT8188_CLK_XTAL_26M]; - break; - default: - return 0; - } - mt8188_afe_set_clk_parent(afe, clk, clk_parent); - - return 0; -} - static int mtk_afe_adc_hires_connect(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { @@ -364,12 +335,6 @@ static const struct snd_soc_dapm_widget mtk_dai_adda_widgets[] = { mtk_adda_ul_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY_S("AUDIO_HIRES", SUPPLY_SEQ_CLOCK_SEL, - SND_SOC_NOPM, - 0, 0, - mtk_audio_hires_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_SUPPLY_S("ADDA_MTKAIF_CFG", SUPPLY_SEQ_ADDA_MTKAIF_CFG, SND_SOC_NOPM, 0, 0, @@ -397,7 +362,6 @@ static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = { {"ADDA Capture", NULL, "ADDA_MTKAIF_CFG"}, {"ADDA Capture", NULL, "aud_adc"}, {"ADDA Capture", NULL, "aud_adc_hires", mtk_afe_adc_hires_connect}, - {"aud_adc_hires", NULL, "AUDIO_HIRES"}, {"I168", NULL, "ADDA Capture"}, {"I169", NULL, "ADDA Capture"}, @@ -406,7 +370,6 @@ static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = { {"ADDA Playback", NULL, "ADDA Playback Enable"}, {"ADDA Playback", NULL, "aud_dac"}, {"ADDA Playback", NULL, "aud_dac_hires", mtk_afe_dac_hires_connect}, - {"aud_dac_hires", NULL, "AUDIO_HIRES"}, {"DL_GAIN", NULL, "O176"}, {"DL_GAIN", NULL, "O177"}, -- cgit v1.2.3 From 2664c8790cfdcaa81ff8b3b9f649a6635955d636 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:19 +0800 Subject: ASoC: mediatek: mt8188: complete set_tdm_slot function User can configures slot number of TDM mode via set_tdm_slot callback. Signed-off-by: Trevor Wu dai_priv[mst_dai_id]; + if (mst_etdm_data->slots) + channels = mst_etdm_data->slots; + ret = mtk_dai_etdm_mclk_configure(afe, mst_dai_id); if (ret) return ret; @@ -1918,7 +1922,6 @@ static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, if (ret) return ret; - mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { slv_dai_id = mst_etdm_data->cowork_slv_id[i]; ret = mtk_dai_etdm_configure(afe, rate, channels, @@ -1931,6 +1934,12 @@ static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, return ret; } } else { + if (!is_valid_etdm_dai(dai->id)) + return -EINVAL; + mst_etdm_data = afe_priv->dai_priv[dai->id]; + if (mst_etdm_data->slots) + channels = mst_etdm_data->slots; + ret = mtk_dai_etdm_mclk_configure(afe, dai->id); if (ret) return ret; @@ -2073,10 +2082,16 @@ static int mtk_dai_etdm_set_tdm_slot(struct snd_soc_dai *dai, struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); struct mt8188_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_etdm_priv *etdm_data; + int dai_id; - if (!is_valid_etdm_dai(dai->id)) + if (is_cowork_mode(dai)) + dai_id = get_etdm_cowork_master_id(dai); + else + dai_id = dai->id; + + if (!is_valid_etdm_dai(dai_id)) return -EINVAL; - etdm_data = afe_priv->dai_priv[dai->id]; + etdm_data = afe_priv->dai_priv[dai_id]; dev_dbg(dai->dev, "%s id %d slot_width %d\n", __func__, dai->id, slot_width); -- cgit v1.2.3 From e5d2bd4103df419fd33131f1aa7a8dea35e3638c Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:20 +0800 Subject: ASoC: mediatek: mt8188: revise ETDM control flow Replace register controls in snd_soc_dai_ops with snd_soc_dapm_widgets. startup, shutdown and trigger ops are removed, and create DAPM_SUPPLY to handle mclk, clock gating and etdm enabling. Additionally, mclk setup sequence is also updated because of new supply enabling sequence. Signed-off-by: Trevor Wu platform_priv; + int dai_id = get_etdm_id_by_name(afe, name); + + if (dai_id < MT8188_AFE_IO_ETDM_START || + dai_id >= MT8188_AFE_IO_ETDM_END) + return NULL; + + return afe_priv->dai_priv[dai_id]; +} + static int mtk_dai_etdm_enable_mclk(struct mtk_base_afe *afe, int dai_id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_data; + struct etdm_con_reg etdm_reg; + unsigned int val = 0; + unsigned int mask; + int clkmux_id = mtk_dai_etdm_get_clk_id_by_dai_id(dai_id); int clkdiv_id = mtk_dai_etdm_get_clkdiv_id_by_dai_id(dai_id); + int apll_clk_id; + int apll; + int ret; + + if (!is_valid_etdm_dai(dai_id)) + return -EINVAL; + etdm_data = afe_priv->dai_priv[dai_id]; - if (clkdiv_id < 0) + apll = etdm_data->mclk_apll; + apll_clk_id = mt8188_afe_get_mclk_source_clk_id(apll); + + if (clkmux_id < 0 || clkdiv_id < 0) return -EINVAL; + if (apll_clk_id < 0) + return apll_clk_id; + + ret = get_etdm_reg(dai_id, &etdm_reg); + if (ret < 0) + return ret; + + mask = ETDM_CON1_MCLK_OUTPUT; + if (etdm_data->mclk_dir == SND_SOC_CLOCK_OUT) + val = ETDM_CON1_MCLK_OUTPUT; + regmap_update_bits(afe->regmap, etdm_reg.con1, mask, val); + + /* enable parent clock before select apll*/ + mt8188_afe_enable_clk(afe, afe_priv->clk[clkmux_id]); + + /* select apll */ + ret = mt8188_afe_set_clk_parent(afe, afe_priv->clk[clkmux_id], + afe_priv->clk[apll_clk_id]); + if (ret) + return ret; + + /* set rate */ + ret = mt8188_afe_set_clk_rate(afe, afe_priv->clk[clkdiv_id], + etdm_data->mclk_freq); + mt8188_afe_enable_clk(afe, afe_priv->clk[clkdiv_id]); return 0; @@ -361,12 +433,207 @@ static int mtk_dai_etdm_enable_mclk(struct mtk_base_afe *afe, int dai_id) static int mtk_dai_etdm_disable_mclk(struct mtk_base_afe *afe, int dai_id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int clkmux_id = mtk_dai_etdm_get_clk_id_by_dai_id(dai_id); int clkdiv_id = mtk_dai_etdm_get_clkdiv_id_by_dai_id(dai_id); - if (clkdiv_id < 0) + if (clkmux_id < 0 || clkdiv_id < 0) return -EINVAL; mt8188_afe_disable_clk(afe, afe_priv->clk[clkdiv_id]); + mt8188_afe_disable_clk(afe, afe_priv->clk[clkmux_id]); + + return 0; +} + +static int mtk_etdm_mclk_connect(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_widget *w = sink; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_priv; + int mclk_id; + + mclk_id = get_etdm_id_by_name(afe, source->name); + if (mclk_id < 0) { + dev_dbg(afe->dev, "mclk_id < 0\n"); + return 0; + } + + etdm_priv = get_etdm_priv_by_name(afe, w->name); + if (!etdm_priv) { + dev_dbg(afe->dev, "etdm_priv == NULL\n"); + return 0; + } + + if (get_etdm_id_by_name(afe, sink->name) == mclk_id) + return !!(etdm_priv->mclk_freq > 0); + + if (etdm_priv->cowork_source_id == mclk_id) { + etdm_priv = afe_priv->dai_priv[mclk_id]; + return !!(etdm_priv->mclk_freq > 0); + } + + return 0; +} + +static int mtk_etdm_cowork_connect(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_widget *w = sink; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + struct mtk_dai_etdm_priv *etdm_priv; + int source_id; + int i; + + source_id = get_etdm_id_by_name(afe, source->name); + if (source_id < 0) { + dev_dbg(afe->dev, "%s() source_id < 0\n", __func__); + return 0; + } + + etdm_priv = get_etdm_priv_by_name(afe, w->name); + if (!etdm_priv) { + dev_dbg(afe->dev, "%s() etdm_priv == NULL\n", __func__); + return 0; + } + + if (etdm_priv->cowork_source_id != COWORK_ETDM_NONE) { + if (etdm_priv->cowork_source_id == source_id) + return 1; + + etdm_priv = afe_priv->dai_priv[etdm_priv->cowork_source_id]; + for (i = 0; i < etdm_priv->cowork_slv_count; i++) { + if (etdm_priv->cowork_slv_id[i] == source_id) + return 1; + } + } else { + for (i = 0; i < etdm_priv->cowork_slv_count; i++) { + if (etdm_priv->cowork_slv_id[i] == source_id) + return 1; + } + } + + return 0; +} + +static int mtk_etdm_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + int mclk_id = get_etdm_id_by_name(afe, w->name); + + if (mclk_id < 0) { + dev_dbg(afe->dev, "%s() mclk_id < 0\n", __func__); + return 0; + } + + dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mtk_dai_etdm_enable_mclk(afe, mclk_id); + break; + case SND_SOC_DAPM_POST_PMD: + mtk_dai_etdm_disable_mclk(afe, mclk_id); + break; + default: + break; + } + + return 0; +} + +static int mtk_dptx_mclk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + + dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mtk_dai_etdm_enable_mclk(afe, MT8188_AFE_IO_DPTX); + break; + case SND_SOC_DAPM_POST_PMD: + mtk_dai_etdm_disable_mclk(afe, MT8188_AFE_IO_DPTX); + break; + default: + break; + } + + return 0; +} + +static int mtk_etdm_cg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + int etdm_id; + int cg_id; + + etdm_id = get_etdm_id_by_name(afe, w->name); + if (etdm_id < 0) { + dev_dbg(afe->dev, "%s() etdm_id < 0\n", __func__); + return 0; + } + + cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(etdm_id); + if (cg_id < 0) { + dev_dbg(afe->dev, "%s() cg_id < 0\n", __func__); + return 0; + } + + dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mt8188_afe_enable_clk(afe, afe_priv->clk[cg_id]); + break; + case SND_SOC_DAPM_POST_PMD: + mt8188_afe_disable_clk(afe, afe_priv->clk[cg_id]); + break; + default: + break; + } + + return 0; +} + +static int mtk_etdm3_cg_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + + dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_HDMI_OUT]); + break; + case SND_SOC_DAPM_POST_PMD: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_HDMI_OUT]); + break; + default: + break; + } return 0; } @@ -906,11 +1173,141 @@ static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = { SND_SOC_DAPM_MUX("HDMI_CH7_MUX", SND_SOC_NOPM, 0, 0, &hdmi_ch7_mux_control), + /* mclk en */ + SND_SOC_DAPM_SUPPLY_S("ETDM1_IN_MCLK", SUPPLY_SEQ_ETDM_MCLK, + SND_SOC_NOPM, 0, 0, + mtk_etdm_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM2_IN_MCLK", SUPPLY_SEQ_ETDM_MCLK, + SND_SOC_NOPM, 0, 0, + mtk_etdm_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM1_OUT_MCLK", SUPPLY_SEQ_ETDM_MCLK, + SND_SOC_NOPM, 0, 0, + mtk_etdm_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM2_OUT_MCLK", SUPPLY_SEQ_ETDM_MCLK, + SND_SOC_NOPM, 0, 0, + mtk_etdm_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("DPTX_MCLK", SUPPLY_SEQ_ETDM_MCLK, + SND_SOC_NOPM, 0, 0, + mtk_dptx_mclk_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* cg */ + SND_SOC_DAPM_SUPPLY_S("ETDM1_IN_CG", SUPPLY_SEQ_ETDM_CG, + SND_SOC_NOPM, 0, 0, + mtk_etdm_cg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM2_IN_CG", SUPPLY_SEQ_ETDM_CG, + SND_SOC_NOPM, 0, 0, + mtk_etdm_cg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM1_OUT_CG", SUPPLY_SEQ_ETDM_CG, + SND_SOC_NOPM, 0, 0, + mtk_etdm_cg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM2_OUT_CG", SUPPLY_SEQ_ETDM_CG, + SND_SOC_NOPM, 0, 0, + mtk_etdm_cg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S("ETDM3_OUT_CG", SUPPLY_SEQ_ETDM_CG, + SND_SOC_NOPM, 0, 0, + mtk_etdm3_cg_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* en */ + SND_SOC_DAPM_SUPPLY_S("ETDM1_IN_EN", SUPPLY_SEQ_ETDM_EN, + ETDM_IN1_CON0, ETDM_CON0_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ETDM2_IN_EN", SUPPLY_SEQ_ETDM_EN, + ETDM_IN2_CON0, ETDM_CON0_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ETDM1_OUT_EN", SUPPLY_SEQ_ETDM_EN, + ETDM_OUT1_CON0, ETDM_CON0_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ETDM2_OUT_EN", SUPPLY_SEQ_ETDM_EN, + ETDM_OUT2_CON0, ETDM_CON0_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("ETDM3_OUT_EN", SUPPLY_SEQ_ETDM_EN, + ETDM_OUT3_CON0, ETDM_CON0_EN_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY_S("DPTX_EN", SUPPLY_SEQ_DPTX_EN, + AFE_DPTX_CON, AFE_DPTX_CON_ON_SHIFT, 0, NULL, 0), + SND_SOC_DAPM_INPUT("ETDM_INPUT"), SND_SOC_DAPM_OUTPUT("ETDM_OUTPUT"), }; static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { + /* mclk */ + {"ETDM1_IN", NULL, "ETDM1_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_IN", NULL, "ETDM2_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_IN", NULL, "ETDM1_OUT_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_IN", NULL, "ETDM2_OUT_MCLK", mtk_etdm_mclk_connect}, + + {"ETDM2_IN", NULL, "ETDM1_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_IN", NULL, "ETDM2_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_IN", NULL, "ETDM1_OUT_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_IN", NULL, "ETDM2_OUT_MCLK", mtk_etdm_mclk_connect}, + + {"ETDM1_OUT", NULL, "ETDM1_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_OUT", NULL, "ETDM2_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_OUT", NULL, "ETDM1_OUT_MCLK", mtk_etdm_mclk_connect}, + {"ETDM1_OUT", NULL, "ETDM2_OUT_MCLK", mtk_etdm_mclk_connect}, + + {"ETDM2_OUT", NULL, "ETDM1_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_OUT", NULL, "ETDM2_IN_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_OUT", NULL, "ETDM1_OUT_MCLK", mtk_etdm_mclk_connect}, + {"ETDM2_OUT", NULL, "ETDM2_OUT_MCLK", mtk_etdm_mclk_connect}, + + {"DPTX", NULL, "DPTX_MCLK"}, + + /* cg */ + {"ETDM1_IN", NULL, "ETDM1_IN_CG"}, + {"ETDM1_IN", NULL, "ETDM2_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM1_IN", NULL, "ETDM1_OUT_CG", mtk_etdm_cowork_connect}, + {"ETDM1_IN", NULL, "ETDM2_OUT_CG", mtk_etdm_cowork_connect}, + + {"ETDM2_IN", NULL, "ETDM1_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM2_IN", NULL, "ETDM2_IN_CG"}, + {"ETDM2_IN", NULL, "ETDM1_OUT_CG", mtk_etdm_cowork_connect}, + {"ETDM2_IN", NULL, "ETDM2_OUT_CG", mtk_etdm_cowork_connect}, + + {"ETDM1_OUT", NULL, "ETDM1_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM1_OUT", NULL, "ETDM2_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM1_OUT", NULL, "ETDM1_OUT_CG"}, + {"ETDM1_OUT", NULL, "ETDM2_OUT_CG", mtk_etdm_cowork_connect}, + + {"ETDM2_OUT", NULL, "ETDM1_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM2_IN_CG", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM1_OUT_CG", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM2_OUT_CG"}, + + {"ETDM3_OUT", NULL, "ETDM3_OUT_CG"}, + {"DPTX", NULL, "ETDM3_OUT_CG"}, + + /* en */ + {"ETDM1_IN", NULL, "ETDM1_IN_EN"}, + {"ETDM1_IN", NULL, "ETDM2_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM1_IN", NULL, "ETDM1_OUT_EN", mtk_etdm_cowork_connect}, + {"ETDM1_IN", NULL, "ETDM2_OUT_EN", mtk_etdm_cowork_connect}, + + {"ETDM2_IN", NULL, "ETDM1_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM2_IN", NULL, "ETDM2_IN_EN"}, + {"ETDM2_IN", NULL, "ETDM1_OUT_EN", mtk_etdm_cowork_connect}, + {"ETDM2_IN", NULL, "ETDM2_OUT_EN", mtk_etdm_cowork_connect}, + + {"ETDM1_OUT", NULL, "ETDM1_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM1_OUT", NULL, "ETDM2_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM1_OUT", NULL, "ETDM1_OUT_EN"}, + {"ETDM1_OUT", NULL, "ETDM2_OUT_EN", mtk_etdm_cowork_connect}, + + {"ETDM2_OUT", NULL, "ETDM1_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM2_IN_EN", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM1_OUT_EN", mtk_etdm_cowork_connect}, + {"ETDM2_OUT", NULL, "ETDM2_OUT_EN"}, + + {"ETDM3_OUT", NULL, "ETDM3_OUT_EN"}, + {"DPTX", NULL, "ETDM3_OUT_EN"}, + {"DPTX", NULL, "DPTX_EN"}, + {"I012", NULL, "ETDM2_IN"}, {"I013", NULL, "ETDM2_IN"}, {"I014", NULL, "ETDM2_IN"}, @@ -1163,64 +1560,6 @@ static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { {"ETDM2_IN", NULL, "ETDM_INPUT"}, }; -static int mt8188_afe_enable_etdm(struct mtk_base_afe *afe, int dai_id) -{ - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *etdm_data; - struct etdm_con_reg etdm_reg; - unsigned long flags; - int ret = 0; - - if (!is_valid_etdm_dai(dai_id)) - return -EINVAL; - etdm_data = afe_priv->dai_priv[dai_id]; - - dev_dbg(afe->dev, "%s [%d]%d\n", __func__, dai_id, etdm_data->en_ref_cnt); - spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags); - etdm_data->en_ref_cnt++; - if (etdm_data->en_ref_cnt == 1) { - ret = get_etdm_reg(dai_id, &etdm_reg); - if (ret < 0) - goto out; - - regmap_set_bits(afe->regmap, etdm_reg.con0, ETDM_CON0_EN); - } - -out: - spin_unlock_irqrestore(&afe_priv->afe_ctrl_lock, flags); - return ret; -} - -static int mt8188_afe_disable_etdm(struct mtk_base_afe *afe, int dai_id) -{ - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *etdm_data; - struct etdm_con_reg etdm_reg; - unsigned long flags; - int ret = 0; - - if (!is_valid_etdm_dai(dai_id)) - return -EINVAL; - etdm_data = afe_priv->dai_priv[dai_id]; - - dev_dbg(afe->dev, "%s [%d]%d\n", __func__, dai_id, etdm_data->en_ref_cnt); - spin_lock_irqsave(&afe_priv->afe_ctrl_lock, flags); - if (etdm_data->en_ref_cnt > 0) { - etdm_data->en_ref_cnt--; - if (etdm_data->en_ref_cnt == 0) { - ret = get_etdm_reg(dai_id, &etdm_reg); - if (ret < 0) - goto out; - regmap_clear_bits(afe->regmap, etdm_reg.con0, - ETDM_CON0_EN); - } - } - -out: - spin_unlock_irqrestore(&afe_priv->afe_ctrl_lock, flags); - return ret; -} - static int etdm_cowork_slv_sel(int id, int slave_mode) { if (slave_mode) { @@ -1408,121 +1747,6 @@ static int mt8188_etdm_sync_mode_configure(struct mtk_base_afe *afe, int dai_id) } /* dai ops */ -static int mtk_dai_etdm_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *mst_etdm_data; - int mst_dai_id; - int slv_dai_id; - int cg_id; - int i; - - if (is_cowork_mode(dai)) { - mst_dai_id = get_etdm_cowork_master_id(dai); - if (!is_valid_etdm_dai(mst_dai_id)) - return -EINVAL; - mtk_dai_etdm_enable_mclk(afe, mst_dai_id); - - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); - if (cg_id >= 0) - mt8188_afe_enable_clk(afe, afe_priv->clk[cg_id]); - - mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; - - for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { - slv_dai_id = mst_etdm_data->cowork_slv_id[i]; - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); - if (cg_id >= 0) - mt8188_afe_enable_clk(afe, - afe_priv->clk[cg_id]); - } - } else { - mtk_dai_etdm_enable_mclk(afe, dai->id); - - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - if (cg_id >= 0) - mt8188_afe_enable_clk(afe, afe_priv->clk[cg_id]); - } - - return 0; -} - -static void mtk_dai_etdm_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *mst_etdm_data; - int mst_dai_id; - int slv_dai_id; - int cg_id; - int ret; - int i; - - if (!is_valid_etdm_dai(dai->id)) - return; - mst_etdm_data = afe_priv->dai_priv[dai->id]; - - dev_dbg(afe->dev, "%s(), dai id %d, prepared %d\n", __func__, dai->id, - mst_etdm_data->is_prepared); - - if (mst_etdm_data->is_prepared) { - mst_etdm_data->is_prepared = false; - - if (is_cowork_mode(dai)) { - mst_dai_id = get_etdm_cowork_master_id(dai); - if (!is_valid_etdm_dai(mst_dai_id)) - return; - mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; - - ret = mt8188_afe_disable_etdm(afe, mst_dai_id); - if (ret) - dev_dbg(afe->dev, "%s disable %d failed\n", - __func__, mst_dai_id); - - for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { - slv_dai_id = mst_etdm_data->cowork_slv_id[i]; - ret = mt8188_afe_disable_etdm(afe, slv_dai_id); - if (ret) - dev_dbg(afe->dev, "%s disable %d failed\n", - __func__, slv_dai_id); - } - } else { - ret = mt8188_afe_disable_etdm(afe, dai->id); - if (ret) - dev_dbg(afe->dev, "%s disable %d failed\n", - __func__, dai->id); - } - } - - if (is_cowork_mode(dai)) { - mst_dai_id = get_etdm_cowork_master_id(dai); - if (!is_valid_etdm_dai(mst_dai_id)) - return; - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(mst_dai_id); - if (cg_id >= 0) - mt8188_afe_disable_clk(afe, afe_priv->clk[cg_id]); - - mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; - for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { - slv_dai_id = mst_etdm_data->cowork_slv_id[i]; - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(slv_dai_id); - if (cg_id >= 0) - mt8188_afe_disable_clk(afe, - afe_priv->clk[cg_id]); - } - mtk_dai_etdm_disable_mclk(afe, mst_dai_id); - } else { - cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - if (cg_id >= 0) - mt8188_afe_disable_clk(afe, afe_priv->clk[cg_id]); - - mtk_dai_etdm_disable_mclk(afe, dai->id); - } -} - static int mtk_dai_etdm_fifo_mode(struct mtk_base_afe *afe, int dai_id, unsigned int rate) { @@ -1759,60 +1983,6 @@ static int mtk_dai_etdm_out_configure(struct mtk_base_afe *afe, return 0; } -static int mtk_dai_etdm_mclk_configure(struct mtk_base_afe *afe, int dai_id) -{ - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *etdm_data; - struct etdm_con_reg etdm_reg; - int clk_id = mtk_dai_etdm_get_clk_id_by_dai_id(dai_id); - int clkdiv_id = mtk_dai_etdm_get_clkdiv_id_by_dai_id(dai_id); - int apll_clk_id; - int apll; - int ret; - - if (clk_id < 0 || clkdiv_id < 0) - return -EINVAL; - - if (!is_valid_etdm_dai(dai_id)) - return -EINVAL; - etdm_data = afe_priv->dai_priv[dai_id]; - - ret = get_etdm_reg(dai_id, &etdm_reg); - if (ret < 0) - return ret; - - if (etdm_data->mclk_dir == SND_SOC_CLOCK_OUT) - regmap_set_bits(afe->regmap, etdm_reg.con1, - ETDM_CON1_MCLK_OUTPUT); - else - regmap_clear_bits(afe->regmap, etdm_reg.con1, - ETDM_CON1_MCLK_OUTPUT); - - if (etdm_data->mclk_freq) { - apll = etdm_data->mclk_apll; - apll_clk_id = mt8188_afe_get_mclk_source_clk_id(apll); - if (apll_clk_id < 0) - return apll_clk_id; - - /* select apll */ - ret = mt8188_afe_set_clk_parent(afe, afe_priv->clk[clk_id], - afe_priv->clk[apll_clk_id]); - if (ret) - return ret; - - /* set rate */ - ret = mt8188_afe_set_clk_rate(afe, afe_priv->clk[clkdiv_id], - etdm_data->mclk_freq); - if (ret) - return ret; - } else { - if (etdm_data->mclk_dir == SND_SOC_CLOCK_OUT) - dev_dbg(afe->dev, "%s mclk freq = 0\n", __func__); - } - - return 0; -} - static int mtk_dai_etdm_configure(struct mtk_base_afe *afe, unsigned int rate, unsigned int channels, @@ -1839,10 +2009,10 @@ static int mtk_dai_etdm_configure(struct mtk_base_afe *afe, if (ret < 0) return ret; - dev_dbg(afe->dev, "%s fmt %u data %u lrck %d-%u bck %d, clock %u slv %u\n", + dev_dbg(afe->dev, "%s fmt %u data %u lrck %d-%u bck %d, slv %u\n", __func__, etdm_data->format, etdm_data->data_mode, etdm_data->lrck_inv, etdm_data->lrck_width, etdm_data->bck_inv, - etdm_data->clock_mode, etdm_data->slave_mode); + etdm_data->slave_mode); dev_dbg(afe->dev, "%s rate %u channels %u bitwidth %u, id %d\n", __func__, rate, channels, bit_width, dai_id); @@ -1913,10 +2083,6 @@ static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, if (mst_etdm_data->slots) channels = mst_etdm_data->slots; - ret = mtk_dai_etdm_mclk_configure(afe, mst_dai_id); - if (ret) - return ret; - ret = mtk_dai_etdm_configure(afe, rate, channels, bit_width, mst_dai_id); if (ret) @@ -1940,10 +2106,6 @@ static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, if (mst_etdm_data->slots) channels = mst_etdm_data->slots; - ret = mtk_dai_etdm_mclk_configure(afe, dai->id); - if (ret) - return ret; - ret = mtk_dai_etdm_configure(afe, rate, channels, bit_width, dai->id); if (ret) @@ -1953,66 +2115,6 @@ static int mtk_dai_etdm_hw_params(struct snd_pcm_substream *substream, return 0; } -static int mtk_dai_etdm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *mst_etdm_data; - int mst_dai_id; - int slv_dai_id; - int ret; - int i; - - if (!is_valid_etdm_dai(dai->id)) - return -EINVAL; - mst_etdm_data = afe_priv->dai_priv[dai->id]; - - dev_dbg(afe->dev, "%s(), dai id %d, prepared %d\n", __func__, dai->id, - mst_etdm_data->is_prepared); - - if (mst_etdm_data->is_prepared) - return 0; - - mst_etdm_data->is_prepared = true; - - if (is_cowork_mode(dai)) { - mst_dai_id = get_etdm_cowork_master_id(dai); - if (!is_valid_etdm_dai(mst_dai_id)) - return -EINVAL; - mst_etdm_data = afe_priv->dai_priv[mst_dai_id]; - - for (i = 0; i < mst_etdm_data->cowork_slv_count; i++) { - slv_dai_id = mst_etdm_data->cowork_slv_id[i]; - ret = mt8188_afe_enable_etdm(afe, slv_dai_id); - if (ret) { - dev_dbg(afe->dev, "%s enable %d failed\n", - __func__, slv_dai_id); - - return ret; - } - } - - ret = mt8188_afe_enable_etdm(afe, mst_dai_id); - if (ret) { - dev_dbg(afe->dev, "%s enable %d failed\n", - __func__, mst_dai_id); - - return ret; - } - } else { - ret = mt8188_afe_enable_etdm(afe, dai->id); - if (ret) { - dev_dbg(afe->dev, "%s enable %d failed\n", - __func__, dai->id); - - return ret; - } - } - - return 0; -} - static int mtk_dai_etdm_cal_mclk(struct mtk_base_afe *afe, int freq, int dai_id) { struct mt8188_afe_private *afe_priv = afe->platform_priv; @@ -2166,53 +2268,6 @@ static int mtk_dai_etdm_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -static int mtk_dai_hdmitx_dptx_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - int cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - - if (cg_id >= 0) - mt8188_afe_enable_clk(afe, afe_priv->clk[cg_id]); - - mtk_dai_etdm_enable_mclk(afe, dai->id); - - return 0; -} - -static void mtk_dai_hdmitx_dptx_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - int cg_id = mtk_dai_etdm_get_cg_id_by_dai_id(dai->id); - struct mtk_dai_etdm_priv *etdm_data; - int ret; - - if (!is_valid_etdm_dai(dai->id)) - return; - etdm_data = afe_priv->dai_priv[dai->id]; - - if (etdm_data->is_prepared) { - etdm_data->is_prepared = false; - /* disable etdm_out3 */ - ret = mt8188_afe_disable_etdm(afe, dai->id); - if (ret) - dev_dbg(afe->dev, "%s disable failed\n", __func__); - - /* disable dptx interface */ - if (dai->id == MT8188_AFE_IO_DPTX) - regmap_clear_bits(afe->regmap, AFE_DPTX_CON, - AFE_DPTX_CON_ON); - } - - mtk_dai_etdm_disable_mclk(afe, dai->id); - - if (cg_id >= 0) - mt8188_afe_disable_clk(afe, afe_priv->clk[cg_id]); -} - static unsigned int mtk_dai_get_dptx_ch_en(unsigned int channel) { switch (channel) { @@ -2280,42 +2335,11 @@ static int mtk_dai_hdmitx_dptx_hw_params(struct snd_pcm_substream *substream, etdm_data->data_mode = MTK_DAI_ETDM_DATA_MULTI_PIN; } - ret = mtk_dai_etdm_mclk_configure(afe, dai->id); - if (ret) - return ret; - ret = mtk_dai_etdm_configure(afe, rate, channels, width, dai->id); return ret; } -static int mtk_dai_hdmitx_dptx_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct mtk_base_afe *afe = snd_soc_dai_get_drvdata(dai); - struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_etdm_priv *etdm_data; - - if (!is_valid_etdm_dai(dai->id)) - return -EINVAL; - etdm_data = afe_priv->dai_priv[dai->id]; - - dev_dbg(afe->dev, "%s(), dai id %d, prepared %d\n", __func__, dai->id, - etdm_data->is_prepared); - - if (etdm_data->is_prepared) - return 0; - - etdm_data->is_prepared = true; - - /* enable dptx interface */ - if (dai->id == MT8188_AFE_IO_DPTX) - regmap_set_bits(afe->regmap, AFE_DPTX_CON, AFE_DPTX_CON_ON); - - /* enable etdm_out3 */ - return mt8188_afe_enable_etdm(afe, dai->id); -} - static int mtk_dai_hdmitx_dptx_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, @@ -2337,20 +2361,14 @@ static int mtk_dai_hdmitx_dptx_set_sysclk(struct snd_soc_dai *dai, } static const struct snd_soc_dai_ops mtk_dai_etdm_ops = { - .startup = mtk_dai_etdm_startup, - .shutdown = mtk_dai_etdm_shutdown, .hw_params = mtk_dai_etdm_hw_params, - .prepare = mtk_dai_etdm_prepare, .set_sysclk = mtk_dai_etdm_set_sysclk, .set_fmt = mtk_dai_etdm_set_fmt, .set_tdm_slot = mtk_dai_etdm_set_tdm_slot, }; static const struct snd_soc_dai_ops mtk_dai_hdmitx_dptx_ops = { - .startup = mtk_dai_hdmitx_dptx_startup, - .shutdown = mtk_dai_hdmitx_dptx_shutdown, .hw_params = mtk_dai_hdmitx_dptx_hw_params, - .prepare = mtk_dai_hdmitx_dptx_prepare, .set_sysclk = mtk_dai_hdmitx_dptx_set_sysclk, .set_fmt = mtk_dai_etdm_set_fmt, }; diff --git a/sound/soc/mediatek/mt8188/mt8188-reg.h b/sound/soc/mediatek/mt8188/mt8188-reg.h index 51cd1a83dd9d..bdd885419ff3 100644 --- a/sound/soc/mediatek/mt8188/mt8188-reg.h +++ b/sound/soc/mediatek/mt8188/mt8188-reg.h @@ -3007,6 +3007,7 @@ #define ETDM_CON0_SLAVE_MODE BIT(5) #define ETDM_CON0_SYNC_MODE BIT(1) #define ETDM_CON0_EN BIT(0) +#define ETDM_CON0_EN_SHIFT 0 #define ETDM_OUT_CON0_RELATCH_DOMAIN_MASK GENMASK(29, 28) @@ -3108,6 +3109,7 @@ #define AFE_DPTX_CON_CH_NUM_8CH (0x1 << 1) #define AFE_DPTX_CON_CH_NUM_MASK BIT(1) #define AFE_DPTX_CON_ON BIT(0) +#define AFE_DPTX_CON_ON_SHIFT 0 /* AFE_ADDA_DL_SRC2_CON0 */ #define DL_2_INPUT_MODE_CTL_MASK GENMASK(31, 28) -- cgit v1.2.3 From 9be0213a6858d0084a9e800d2b451678f014f337 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:21 +0800 Subject: ASoC: mediatek: mt8188: refine APLL control Currently, APLL is only used in ETDM module, so APLL and APLL tuner don't need to be enabled when AFE is used. Integrate APLL control into ETDM DAPM routes, so that APLL can be enabled when it is really required. Signed-off-by: Trevor Wu platform_priv; @@ -477,8 +491,8 @@ int mt8188_afe_set_clk_parent(struct mtk_base_afe *afe, struct clk *clk, if (clk && parent) { ret = clk_set_parent(clk, parent); if (ret) { - dev_dbg(afe->dev, "%s(), failed to set clk parent\n", - __func__); + dev_dbg(afe->dev, "%s(), failed to set clk parent %d\n", + __func__, ret); return ret; } } @@ -605,54 +619,132 @@ static int mt8188_afe_disable_afe_on(struct mtk_base_afe *afe) return 0; } -static int mt8188_afe_enable_timing_sys(struct mtk_base_afe *afe) +static int mt8188_afe_enable_a1sys(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); - mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); + if (ret) + return ret; - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); - mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); +} + +static int mt8188_afe_disable_a1sys(struct mtk_base_afe *afe) +{ + struct mt8188_afe_private *afe_priv = afe->platform_priv; + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); return 0; } -static int mt8188_afe_disable_timing_sys(struct mtk_base_afe *afe) +static int mt8188_afe_enable_a2sys(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; - mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A1SYS]); - mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); + if (ret) + return ret; - mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); - mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); - mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_A1SYS_TIMING); + return mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); +} + +static int mt8188_afe_disable_a2sys(struct mtk_base_afe *afe) +{ + struct mt8188_afe_private *afe_priv = afe->platform_priv; + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_A2SYS_TIMING); + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_AUD_A2SYS]); return 0; } -int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe) +int mt8188_apll1_enable(struct mtk_base_afe *afe) { - mt8188_afe_enable_timing_sys(afe); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + int ret; - mt8188_afe_enable_afe_on(afe); + ret = mt8188_afe_enable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_APLL1_D4]); + if (ret) + return ret; + + ret = mt8188_afe_set_clk_parent(afe, afe_priv->clk[MT8188_CLK_TOP_A1SYS_HP_SEL], + afe_priv->clk[MT8188_CLK_TOP_APLL1_D4]); + if (ret) + goto err_clk_parent; - mt8188_afe_enable_apll_tuner(afe, MT8188_AUD_PLL1); - mt8188_afe_enable_apll_tuner(afe, MT8188_AUD_PLL2); + ret = mt8188_afe_enable_apll_tuner(afe, MT8188_AUD_PLL1); + if (ret) + goto err_apll_tuner; + + ret = mt8188_afe_enable_a1sys(afe); + if (ret) + goto err_a1sys; return 0; + +err_a1sys: + mt8188_afe_disable_apll_tuner(afe, MT8188_AUD_PLL1); +err_apll_tuner: + mt8188_afe_set_clk_parent(afe, afe_priv->clk[MT8188_CLK_TOP_A1SYS_HP_SEL], + afe_priv->clk[MT8188_CLK_XTAL_26M]); +err_clk_parent: + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_APLL1_D4]); + + return ret; } -int mt8188_afe_disable_main_clock(struct mtk_base_afe *afe) +int mt8188_apll1_disable(struct mtk_base_afe *afe) { - mt8188_afe_disable_apll_tuner(afe, MT8188_AUD_PLL2); + struct mt8188_afe_private *afe_priv = afe->platform_priv; + + mt8188_afe_disable_a1sys(afe); mt8188_afe_disable_apll_tuner(afe, MT8188_AUD_PLL1); + mt8188_afe_set_clk_parent(afe, afe_priv->clk[MT8188_CLK_TOP_A1SYS_HP_SEL], + afe_priv->clk[MT8188_CLK_XTAL_26M]); + mt8188_afe_disable_clk(afe, afe_priv->clk[MT8188_CLK_TOP_APLL1_D4]); - mt8188_afe_disable_afe_on(afe); + return 0; +} + +int mt8188_apll2_enable(struct mtk_base_afe *afe) +{ + int ret; - mt8188_afe_disable_timing_sys(afe); + ret = mt8188_afe_enable_apll_tuner(afe, MT8188_AUD_PLL2); + if (ret) + return ret; + ret = mt8188_afe_enable_a2sys(afe); + if (ret) + goto err_a2sys; + + return 0; +err_a2sys: + mt8188_afe_disable_apll_tuner(afe, MT8188_AUD_PLL2); + + return ret; +} + +int mt8188_apll2_disable(struct mtk_base_afe *afe) +{ + mt8188_afe_disable_a2sys(afe); + mt8188_afe_disable_apll_tuner(afe, MT8188_AUD_PLL2); + return 0; +} + +int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe) +{ + mt8188_afe_enable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); + mt8188_afe_enable_afe_on(afe); + return 0; +} + +int mt8188_afe_disable_main_clock(struct mtk_base_afe *afe) +{ + mt8188_afe_disable_afe_on(afe); + mt8188_afe_disable_top_cg(afe, MT8188_TOP_CG_26M_TIMING); return 0; } diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-clk.h b/sound/soc/mediatek/mt8188/mt8188-afe-clk.h index 084fdfb1d877..04cb476f0bcb 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-clk.h +++ b/sound/soc/mediatek/mt8188/mt8188-afe-clk.h @@ -11,6 +11,10 @@ #ifndef _MT8188_AFE_CLK_H_ #define _MT8188_AFE_CLK_H_ +/* APLL */ +#define APLL1_W_NAME "APLL1" +#define APLL2_W_NAME "APLL2" + enum { /* xtal */ MT8188_CLK_XTAL_26M, @@ -18,6 +22,7 @@ enum { MT8188_CLK_APMIXED_APLL1, MT8188_CLK_APMIXED_APLL2, /* divider */ + MT8188_CLK_TOP_APLL1_D4, MT8188_CLK_TOP_APLL12_DIV0, MT8188_CLK_TOP_APLL12_DIV1, MT8188_CLK_TOP_APLL12_DIV2, @@ -99,6 +104,8 @@ struct mtk_base_afe; int mt8188_afe_get_mclk_source_clk_id(int sel); int mt8188_afe_get_mclk_source_rate(struct mtk_base_afe *afe, int apll); int mt8188_afe_get_default_mclk_source_by_rate(int rate); +int mt8188_get_apll_by_rate(struct mtk_base_afe *afe, int rate); +int mt8188_get_apll_by_name(struct mtk_base_afe *afe, const char *name); int mt8188_afe_init_clock(struct mtk_base_afe *afe); void mt8188_afe_deinit_clock(void *priv); int mt8188_afe_enable_clk(struct mtk_base_afe *afe, struct clk *clk); @@ -107,6 +114,10 @@ int mt8188_afe_set_clk_rate(struct mtk_base_afe *afe, struct clk *clk, unsigned int rate); int mt8188_afe_set_clk_parent(struct mtk_base_afe *afe, struct clk *clk, struct clk *parent); +int mt8188_apll1_enable(struct mtk_base_afe *afe); +int mt8188_apll1_disable(struct mtk_base_afe *afe); +int mt8188_apll2_enable(struct mtk_base_afe *afe); +int mt8188_apll2_disable(struct mtk_base_afe *afe); int mt8188_afe_enable_main_clock(struct mtk_base_afe *afe); int mt8188_afe_disable_main_clock(struct mtk_base_afe *afe); int mt8188_afe_enable_reg_rw_clk(struct mtk_base_afe *afe); diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-etdm.c b/sound/soc/mediatek/mt8188/mt8188-dai-etdm.c index fd6e39a1e4c1..16440dd0a89c 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-etdm.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-etdm.c @@ -22,6 +22,7 @@ #define ENUM_TO_STR(x) #x enum { + SUPPLY_SEQ_APLL, SUPPLY_SEQ_ETDM_MCLK, SUPPLY_SEQ_ETDM_CG, SUPPLY_SEQ_DPTX_EN, @@ -95,6 +96,7 @@ struct mtk_dai_etdm_priv { bool slave_mode; bool lrck_inv; bool bck_inv; + unsigned int rate; unsigned int format; unsigned int slots; unsigned int lrck_width; @@ -360,6 +362,10 @@ static int get_etdm_id_by_name(struct mtk_base_afe *afe, return MT8188_AFE_IO_ETDM1_OUT; else if (!strncmp(name, "ETDM2_OUT", strlen("ETDM2_OUT"))) return MT8188_AFE_IO_ETDM2_OUT; + else if (!strncmp(name, "ETDM3_OUT", strlen("ETDM3_OUT"))) + return MT8188_AFE_IO_ETDM3_OUT; + else if (!strncmp(name, "DPTX", strlen("DPTX"))) + return MT8188_AFE_IO_ETDM3_OUT; else return -EINVAL; } @@ -445,6 +451,44 @@ static int mtk_dai_etdm_disable_mclk(struct mtk_base_afe *afe, int dai_id) return 0; } +static int mtk_afe_etdm_apll_connect(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_widget *w = sink; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mtk_dai_etdm_priv *etdm_priv; + int cur_apll; + int need_apll; + + etdm_priv = get_etdm_priv_by_name(afe, w->name); + if (!etdm_priv) { + dev_dbg(afe->dev, "etdm_priv == NULL\n"); + return 0; + } + + cur_apll = mt8188_get_apll_by_name(afe, source->name); + need_apll = mt8188_get_apll_by_rate(afe, etdm_priv->rate); + + return (need_apll == cur_apll) ? 1 : 0; +} + +static int mtk_afe_mclk_apll_connect(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_dapm_widget *w = sink; + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + struct mtk_dai_etdm_priv *etdm_priv; + int cur_apll; + + etdm_priv = get_etdm_priv_by_name(afe, w->name); + + cur_apll = mt8188_get_apll_by_name(afe, source->name); + + return (etdm_priv->mclk_apll == cur_apll) ? 1 : 0; +} + static int mtk_etdm_mclk_connect(struct snd_soc_dapm_widget *source, struct snd_soc_dapm_widget *sink) { @@ -520,6 +564,36 @@ static int mtk_etdm_cowork_connect(struct snd_soc_dapm_widget *source, return 0; } +static int mtk_apll_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, + int event) +{ + struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); + struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); + + dev_dbg(cmpnt->dev, "%s(), name %s, event 0x%x\n", + __func__, w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (strcmp(w->name, APLL1_W_NAME) == 0) + mt8188_apll1_enable(afe); + else + mt8188_apll2_enable(afe); + break; + case SND_SOC_DAPM_POST_PMD: + if (strcmp(w->name, APLL1_W_NAME) == 0) + mt8188_apll1_disable(afe); + else + mt8188_apll2_disable(afe); + break; + default: + break; + } + + return 0; +} + static int mtk_etdm_mclk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -1231,6 +1305,16 @@ static const struct snd_soc_dapm_widget mtk_dai_etdm_widgets[] = { SND_SOC_DAPM_SUPPLY_S("DPTX_EN", SUPPLY_SEQ_DPTX_EN, AFE_DPTX_CON, AFE_DPTX_CON_ON_SHIFT, 0, NULL, 0), + /* apll */ + SND_SOC_DAPM_SUPPLY_S(APLL1_W_NAME, SUPPLY_SEQ_APLL, + SND_SOC_NOPM, 0, 0, + mtk_apll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY_S(APLL2_W_NAME, SUPPLY_SEQ_APLL, + SND_SOC_NOPM, 0, 0, + mtk_apll_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_INPUT("ETDM_INPUT"), SND_SOC_DAPM_OUTPUT("ETDM_OUTPUT"), }; @@ -1259,6 +1343,21 @@ static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { {"DPTX", NULL, "DPTX_MCLK"}, + {"ETDM1_IN_MCLK", NULL, APLL1_W_NAME, mtk_afe_mclk_apll_connect}, + {"ETDM1_IN_MCLK", NULL, APLL2_W_NAME, mtk_afe_mclk_apll_connect}, + + {"ETDM2_IN_MCLK", NULL, APLL1_W_NAME, mtk_afe_mclk_apll_connect}, + {"ETDM2_IN_MCLK", NULL, APLL2_W_NAME, mtk_afe_mclk_apll_connect}, + + {"ETDM1_OUT_MCLK", NULL, APLL1_W_NAME, mtk_afe_mclk_apll_connect}, + {"ETDM1_OUT_MCLK", NULL, APLL2_W_NAME, mtk_afe_mclk_apll_connect}, + + {"ETDM2_OUT_MCLK", NULL, APLL1_W_NAME, mtk_afe_mclk_apll_connect}, + {"ETDM2_OUT_MCLK", NULL, APLL2_W_NAME, mtk_afe_mclk_apll_connect}, + + {"DPTX_MCLK", NULL, APLL1_W_NAME, mtk_afe_mclk_apll_connect}, + {"DPTX_MCLK", NULL, APLL2_W_NAME, mtk_afe_mclk_apll_connect}, + /* cg */ {"ETDM1_IN", NULL, "ETDM1_IN_CG"}, {"ETDM1_IN", NULL, "ETDM2_IN_CG", mtk_etdm_cowork_connect}, @@ -1308,6 +1407,21 @@ static const struct snd_soc_dapm_route mtk_dai_etdm_routes[] = { {"DPTX", NULL, "ETDM3_OUT_EN"}, {"DPTX", NULL, "DPTX_EN"}, + {"ETDM1_IN_EN", NULL, APLL1_W_NAME, mtk_afe_etdm_apll_connect}, + {"ETDM1_IN_EN", NULL, APLL2_W_NAME, mtk_afe_etdm_apll_connect}, + + {"ETDM2_IN_EN", NULL, APLL1_W_NAME, mtk_afe_etdm_apll_connect}, + {"ETDM2_IN_EN", NULL, APLL2_W_NAME, mtk_afe_etdm_apll_connect}, + + {"ETDM1_OUT_EN", NULL, APLL1_W_NAME, mtk_afe_etdm_apll_connect}, + {"ETDM1_OUT_EN", NULL, APLL2_W_NAME, mtk_afe_etdm_apll_connect}, + + {"ETDM2_OUT_EN", NULL, APLL1_W_NAME, mtk_afe_etdm_apll_connect}, + {"ETDM2_OUT_EN", NULL, APLL2_W_NAME, mtk_afe_etdm_apll_connect}, + + {"ETDM3_OUT_EN", NULL, APLL1_W_NAME, mtk_afe_etdm_apll_connect}, + {"ETDM3_OUT_EN", NULL, APLL2_W_NAME, mtk_afe_etdm_apll_connect}, + {"I012", NULL, "ETDM2_IN"}, {"I013", NULL, "ETDM2_IN"}, {"I014", NULL, "ETDM2_IN"}, @@ -2004,6 +2118,7 @@ static int mtk_dai_etdm_configure(struct mtk_base_afe *afe, return -EINVAL; etdm_data = afe_priv->dai_priv[dai_id]; slave_mode = etdm_data->slave_mode; + etdm_data->rate = rate; ret = get_etdm_reg(dai_id, &etdm_reg); if (ret < 0) -- cgit v1.2.3 From e9eab4bed0436a7b7268114ecf55fe4989855d47 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:22 +0800 Subject: ASoC: mediatek: mt8188: combine afe component registration There is no benefit to separate two components for AFE, so DAI driver registration is moved to dev_snd_soc_register_component to merge these two components. Signed-off-by: Trevor Wu dev, DMA_BIT_MASK(33)); if (ret) @@ -3280,34 +3275,12 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) /* register component */ ret = devm_snd_soc_register_component(dev, &mt8188_afe_component, - NULL, 0); + afe->dai_drivers, afe->num_dai_drivers); if (ret) { dev_warn(dev, "err_platform\n"); goto err_pm_put; } - component = devm_kzalloc(&pdev->dev, sizeof(*component), GFP_KERNEL); - if (!component) { - ret = -ENOMEM; - goto err_pm_put; - } - - ret = snd_soc_component_initialize(component, - &mt8188_afe_pcm_dai_component, - &pdev->dev); - if (ret) - goto err_pm_put; -#ifdef CONFIG_DEBUG_FS - component->debugfs_prefix = "pcm"; -#endif - ret = snd_soc_add_component(component, - afe->dai_drivers, - afe->num_dai_drivers); - if (ret) { - dev_warn(dev, "err_add_component\n"); - goto err_pm_put; - } - mt8188_afe_init_registers(afe); pm_runtime_put_sync(&pdev->dev); @@ -3323,11 +3296,6 @@ err_pm_put: return ret; } -static void mt8188_afe_pcm_dev_remove(struct platform_device *pdev) -{ - snd_soc_unregister_component(&pdev->dev); -} - static const struct of_device_id mt8188_afe_pcm_dt_match[] = { { .compatible = "mediatek,mt8188-afe", }, {}, @@ -3346,7 +3314,6 @@ static struct platform_driver mt8188_afe_pcm_driver = { .pm = &mt8188_afe_pm_ops, }, .probe = mt8188_afe_pcm_dev_probe, - .remove_new = mt8188_afe_pcm_dev_remove, }; module_platform_driver(mt8188_afe_pcm_driver); -- cgit v1.2.3 From fb167449cec1d2f34ef4c6d17ff860076ac0fc44 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:23 +0800 Subject: ASoC: mediatek: mt8188: add bus protection Add bus protection for reset controller. Signed-off-by: Trevor Wu #include #include +#include #include #include #include "mt8188-afe-common.h" @@ -3133,12 +3134,68 @@ static int mt8188_afe_parse_of(struct mtk_base_afe *afe, return 0; } +#define MT8188_DELAY_US 10 +#define MT8188_TIMEOUT_US USEC_PER_SEC + +static int bus_protect_enable(struct regmap *regmap) +{ + int ret; + u32 val; + u32 mask; + + val = 0; + mask = MT8188_TOP_AXI_PROT_EN_2_AUDIO_STEP1; + regmap_write(regmap, MT8188_TOP_AXI_PROT_EN_2_SET, mask); + + ret = regmap_read_poll_timeout(regmap, MT8188_TOP_AXI_PROT_EN_2_STA, + val, (val & mask) == mask, + MT8188_DELAY_US, MT8188_TIMEOUT_US); + if (ret) + return ret; + + val = 0; + mask = MT8188_TOP_AXI_PROT_EN_2_AUDIO_STEP2; + regmap_write(regmap, MT8188_TOP_AXI_PROT_EN_2_SET, mask); + + ret = regmap_read_poll_timeout(regmap, MT8188_TOP_AXI_PROT_EN_2_STA, + val, (val & mask) == mask, + MT8188_DELAY_US, MT8188_TIMEOUT_US); + return ret; +} + +static int bus_protect_disable(struct regmap *regmap) +{ + int ret; + u32 val; + u32 mask; + + val = 0; + mask = MT8188_TOP_AXI_PROT_EN_2_AUDIO_STEP2; + regmap_write(regmap, MT8188_TOP_AXI_PROT_EN_2_CLR, mask); + + ret = regmap_read_poll_timeout(regmap, MT8188_TOP_AXI_PROT_EN_2_STA, + val, !(val & mask), + MT8188_DELAY_US, MT8188_TIMEOUT_US); + if (ret) + return ret; + + val = 0; + mask = MT8188_TOP_AXI_PROT_EN_2_AUDIO_STEP1; + regmap_write(regmap, MT8188_TOP_AXI_PROT_EN_2_CLR, mask); + + ret = regmap_read_poll_timeout(regmap, MT8188_TOP_AXI_PROT_EN_2_STA, + val, !(val & mask), + MT8188_DELAY_US, MT8188_TIMEOUT_US); + return ret; +} + static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) { struct mtk_base_afe *afe; struct mt8188_afe_private *afe_priv; struct device *dev; struct reset_control *rstc; + struct regmap *infra_ao; int i, irq_id, ret; ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(33)); @@ -3163,18 +3220,37 @@ static int mt8188_afe_pcm_dev_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(afe->base_addr), "AFE base_addr not found\n"); + infra_ao = syscon_regmap_lookup_by_phandle(dev->of_node, + "mediatek,infracfg"); + if (IS_ERR(infra_ao)) + return dev_err_probe(dev, PTR_ERR(infra_ao), + "%s() Cannot find infra_ao controller\n", + __func__); + /* reset controller to reset audio regs before regmap cache */ rstc = devm_reset_control_get_exclusive(dev, "audiosys"); if (IS_ERR(rstc)) return dev_err_probe(dev, PTR_ERR(rstc), "could not get audiosys reset\n"); + ret = bus_protect_enable(infra_ao); + if (ret) { + dev_err(dev, "bus_protect_enable failed\n"); + return ret; + } + ret = reset_control_reset(rstc); if (ret) { dev_err(dev, "failed to trigger audio reset:%d\n", ret); return ret; } + ret = bus_protect_disable(infra_ao); + if (ret) { + dev_err(dev, "bus_protect_disable failed\n"); + return ret; + } + /* initial audio related clock */ ret = mt8188_afe_init_clock(afe); if (ret) -- cgit v1.2.3 From 2e5c422a624aa35cefb5a448a2040df6627f505b Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Wed, 10 May 2023 11:55:24 +0800 Subject: ASoC: mediatek: mt8188: add required clocks apll2_d4, apll12_div4, top_a2sys and top_aud_iec are possibly used in the future. To prevent from breaking binding ABI after any mt8188 dts upstream, add these clocks to clock list in advance. Signed-off-by: Trevor Wu Date: Fri, 12 May 2023 13:28:30 +0100 Subject: ASoC: soc-component: Add notify control helper function Add a function to allow ASoC drivers to easily notify an ALSA control change. This function will automatically add any component naming prefix into the control name. Signed-off-by: Charles Keepax name_prefix) + snprintf(name, ARRAY_SIZE(name), "%s %s", component->name_prefix, ctl); + else + snprintf(name, ARRAY_SIZE(name), "%s", ctl); + + kctl = snd_soc_card_get_kcontrol(component->card, name); + if (!kctl) + return soc_component_ret(component, -EINVAL); + + snd_ctl_notify(component->card->snd_card, + SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_component_notify_control); + /** * snd_soc_component_set_jack - configure component jack. * @component: COMPONENTs -- cgit v1.2.3 From 476d942e50d4f22d8621a18612dd6cfbf0a5d1d9 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 12 May 2023 13:28:31 +0100 Subject: ASoC: ak4118: Update to use new component control notify helper Update the driver to use the new ASoC core control notify helper. This also fixes a bug where the control would not be found if the CODEC was given a name prefix. Signed-off-by: Charles Keepax component; struct snd_kcontrol_new *kctl_new; - struct snd_kcontrol *kctl; - struct snd_ctl_elem_id *id; unsigned int i; if (!component) @@ -273,13 +271,8 @@ static irqreturn_t ak4118_irq_handler(int irq, void *data) for (i = 0; i < ARRAY_SIZE(ak4118_iec958_controls); i++) { kctl_new = &ak4118_iec958_controls[i]; - kctl = snd_soc_card_get_kcontrol(component->card, - kctl_new->name); - if (!kctl) - continue; - id = &kctl->id; - snd_ctl_notify(component->card->snd_card, - SNDRV_CTL_EVENT_MASK_VALUE, id); + + snd_soc_component_notify_control(component, kctl_new->name); } return IRQ_HANDLED; -- cgit v1.2.3 From 95d06196c83c9dc1b6fd6cda07a1bac54ca2d568 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 12 May 2023 13:28:32 +0100 Subject: ASoC: wm_adsp: Update to use new component control notify helepr Signed-off-by: Charles Keepax cs_dsp, name, type, alg); struct wm_coeff_ctl *ctl; - struct snd_kcontrol *kcontrol; - char ctl_name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int ret; ret = cs_dsp_coeff_write_ctrl(cs_ctl, 0, buf, len); @@ -699,23 +697,7 @@ int wm_adsp_write_ctl(struct wm_adsp *dsp, const char *name, int type, ctl = cs_ctl->priv; - if (dsp->component->name_prefix) - snprintf(ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s %s", - dsp->component->name_prefix, ctl->name); - else - snprintf(ctl_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "%s", - ctl->name); - - kcontrol = snd_soc_card_get_kcontrol(dsp->component->card, ctl_name); - if (!kcontrol) { - adsp_err(dsp, "Can't find kcontrol %s\n", ctl_name); - return -EINVAL; - } - - snd_ctl_notify(dsp->component->card->snd_card, - SNDRV_CTL_EVENT_MASK_VALUE, &kcontrol->id); - - return 0; + return snd_soc_component_notify_control(dsp->component, ctl->name); } EXPORT_SYMBOL_GPL(wm_adsp_write_ctl); -- cgit v1.2.3 From 8899672f8847f1ac7767b1431266c01741047e37 Mon Sep 17 00:00:00 2001 From: Terry Cheong Date: Fri, 12 May 2023 12:32:58 -0500 Subject: ASoC: Intel: Add rpl_max98373_8825 driver Boards were using this in older kernels before adl and rpl ids were split. Add this back to maintain support. Signed-off-by: Terry Cheong Date: Fri, 12 May 2023 12:32:59 -0500 Subject: ASoC: Intel: sof_sdw: remove SOF_SDW_TGL_HDMI for MeteorLake devices Topologies support three HDMI links on MeteorLake devices only. Fixes: 18489174e4fb ("ASoC: intel: sof_sdw: add RT711 SDCA card for MTL platform") Signed-off-by: Bard Liao Date: Fri, 12 May 2023 12:33:00 -0500 Subject: ASoC: Intel: sof_sdw: add quirk for MTL RVP We should use RT711_JD2_100K for on board rt711. Signed-off-by: Bard Liao Date: Fri, 12 May 2023 12:33:01 -0500 Subject: ASoC: Intel: soc-acpi: add support for MTL SDCA boards The description and board layout is changed and different from previous ones for CometLake and Tigerlake. The new codec layout for MTL is: SDW0: RT711 Headphone SDW1: RT714 DMIC SDW2: RT1316 Speaker SDW3: RT1316 Speaker The previous codec layout for CML and TGL is: SDW0: RT711 Headphone SDW1: RT1316 Speaker SDW2: RT1316 Speaker SDW3: RT714 DMIC Signed-off-by: Chao Song amp */ { @@ -122,6 +149,30 @@ static const struct snd_soc_acpi_link_adr mtl_rvp[] = { {} }; +static const struct snd_soc_acpi_link_adr mtl_3_in_1_sdca[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1316_3_group1_adr), + .adr_d = rt1316_3_group1_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt714_1_adr), + .adr_d = rt714_1_adr, + }, + {} +}; + /* this table is used when there is no I2S codec present */ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { /* mockup tests need to be first */ @@ -143,6 +194,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-mtl-rt715-rt711-rt1308-mono.tplg", }, + { + .link_mask = GENMASK(3, 0), + .links = mtl_3_in_1_sdca, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt711-l0-rt1316-l23-rt714-l1.tplg", + }, { .link_mask = BIT(0), .links = mtl_rvp, -- cgit v1.2.3 From f0c8d83ab1a3532ebeb1a89acb649be01657aed8 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 12 May 2023 12:33:02 -0500 Subject: ASoC: Intel: sof_sdw: start set codec init function with an adr index Currently, set_codec_init_func always start with link->adr_d[0] because we assumed all adr_d on the same link are the same devices. The assumption is no longer valid when different devices on the same sdw link are supported. Fixes: c8db7b50128b ("ASoC: Intel: sof_sdw: support different devices on the same sdw link") Signed-off-by: Bard Liao 0), initialize all codecs belonging to * same group. + * The first link should start with link->adr_d[adr_index] + * because that is the device that we want to initialize and + * we should end immediately if it is not aggregated (group_id=0) */ - for (i = 0; i < link->num_adr; i++) { + for ( ; i < link->num_adr; i++) { int codec_index; codec_index = find_codec_info_part(link->adr_d[i].adr); @@ -936,9 +939,12 @@ static int set_codec_init_func(struct snd_soc_card *card, dai_links, &codec_info_list[codec_index], playback); + if (!group_id) + return 0; } + i = 0; link++; - } while (link->mask && group_id); + } while (link->mask); return 0; } @@ -1188,7 +1194,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, dai_links[*link_index].nonatomic = true; ret = set_codec_init_func(card, link, dai_links + (*link_index)++, - playback, group_id); + playback, group_id, adr_index); if (ret < 0) { dev_err(dev, "failed to init codec %d", codec_index); return ret; -- cgit v1.2.3 From 49d1f3ccc876eec87be41b5ee816d723b9a53ae2 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 12:33:03 -0500 Subject: ASoC: Intel: sof_sdw: add new mapping for HP Spectre x360 A BIOS/DMI update seems to have broken some devices, let's add a new mapping. Link: https://github.com/thesofproject/linux/issues/4323 Signed-off-by: Pierre-Louis Bossart Date: Fri, 12 May 2023 12:33:04 -0500 Subject: ASoC: Intel: soc-acpi: add tables for LunarLake These tables are used for 'nocodec' and SoundWire mockups+RVP tests. The LNL RVP has a single rt711-sdca SoundWire codec. Co-developed-by: Peter Ujfalusi +#include +#include "soc-acpi-intel-sdw-mockup-match.h" + +struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_machines[] = { + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_lnl_machines); + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, +}; + +static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { + { + .adr = 0x000030025D071101ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt711" + } +}; + +static const struct snd_soc_acpi_link_adr lnl_rvp[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + {} +}; + +/* this table is used when there is no I2S codec present */ +struct snd_soc_acpi_mach snd_soc_acpi_intel_lnl_sdw_machines[] = { + /* mockup tests need to be first */ + { + .link_mask = GENMASK(3, 0), + .links = sdw_mockup_headset_2amps_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-rt1308-rt715.tplg", + }, + { + .link_mask = BIT(0) | BIT(1) | BIT(3), + .links = sdw_mockup_headset_1amp_mic, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711-rt1308-mono-rt715.tplg", + }, + { + .link_mask = GENMASK(2, 0), + .links = sdw_mockup_mic_headset_1amp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt715-rt711-rt1308-mono.tplg", + }, + { + .link_mask = BIT(0), + .links = lnl_rvp, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-lnl-rt711.tplg", + }, + {}, +}; +EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_lnl_sdw_machines); -- cgit v1.2.3 From dfe25fea968dc4884e12d471c8263f0f611b380a Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 12 May 2023 12:33:05 -0500 Subject: ASoC: Intel: sof_sdw: add quirk for LNL RVP We should use RT711_JD2_100K for on board rt711 Signed-off-by: Peter Ujfalusi Date: Mon, 15 May 2023 13:33:28 +0300 Subject: ASoC: SOF: ipc4-topology: Handle input/output audio format special case When there is only one input/output format specified in topology, there is no need to search for a matching format, simply pick the available one. This is in preparation to modify and split the selection logic for the input and output audio formats. Signed-off-by: Ranjani Sridharan dev, "no reference formats for %s\n", swidget->widget->name); @@ -1066,6 +1066,10 @@ static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; } + /* pick the only available input format */ + if (available_fmt->num_input_formats == 1) + goto in_fmt; + /* * Search supported audio formats with pin index 0 to match rate, channels ,and * sample_valid_bytes from runtime params @@ -1093,6 +1097,7 @@ static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; } +in_fmt: /* copy input format */ if (available_fmt->num_input_formats && i < available_fmt->num_input_formats) { memcpy(&base_config->audio_fmt, &available_fmt->input_pin_fmts[i].audio_fmt, @@ -1105,6 +1110,10 @@ static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->input_pin_fmts[i], 1); } + /* pick the only available output format */ + if (available_fmt->num_output_formats == 1) + i = 0; + if (available_fmt->num_output_formats && i < available_fmt->num_output_formats) base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; -- cgit v1.2.3 From 523042f63febca24cbf9cf83729044c3dbaa9706 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:29 +0300 Subject: ASoC: SOF: ipc4-topology: Add a helper function for output format selection Add a helper function to select the output format. Signed-off-by: Ranjani Sridharan num_output_formats == 1) + i = 0; + else + i = input_audio_format_index; + + if (available_fmt->num_output_formats && i < available_fmt->num_output_formats) + base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; + + /* Return the index of the chosen output format */ + return i; +} + static int sof_ipc4_init_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, @@ -1110,15 +1130,7 @@ in_fmt: sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->input_pin_fmts[i], 1); } - /* pick the only available output format */ - if (available_fmt->num_output_formats == 1) - i = 0; - - if (available_fmt->num_output_formats && i < available_fmt->num_output_formats) - base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; - - /* Return the index of the matched format */ - return i; + return sof_ipc4_init_output_audio_fmt(sdev, base_config, available_fmt, i); } static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) -- cgit v1.2.3 From a2e07c3319f79f7305641b98d32765dc5b607873 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:30 +0300 Subject: ASoC: SOF: ipc4-topology: Move the call to init output format In preparation for changing the logic for input/output format selection, move the call to sof_ipc4_init_output_audio_fmt() into the individual widget prepare ops. Signed-off-by: Ranjani Sridharan dev, &available_fmt->input_pin_fmts[i], 1); } - return sof_ipc4_init_output_audio_fmt(sdev, base_config, available_fmt, i); + return i; } static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) @@ -1375,6 +1375,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, int ipc_size, ret; u32 deep_buffer_dma_ms = 0; u32 format_list_count; + int output_fmt_index; dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id); @@ -1518,6 +1519,9 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &copier_data->base_config, + available_fmt, ret); + /* * Set the output format. Current topology defines pin 0 input and output formats in pairs. * This assumes that the pin 0 formats are defined before all other pins. @@ -1525,10 +1529,11 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, * input format. This logic will need to be updated when the format definitions * in topology change. */ - memcpy(&copier_data->out_format, &available_fmt->output_pin_fmts[ret].audio_fmt, + memcpy(&copier_data->out_format, + &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt, sizeof(struct sof_ipc4_audio_format)); dev_dbg(sdev->dev, "Output audio format for %s\n", swidget->widget->name); - sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->output_pin_fmts[ret], 1); + sof_ipc4_dbg_audio_format(sdev->dev, &available_fmt->output_pin_fmts[output_fmt_index], 1); switch (swidget->id) { case snd_soc_dapm_dai_in: @@ -1694,6 +1699,8 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + sof_ipc4_init_output_audio_fmt(sdev, &gain->base_config, available_fmt, ret); + /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config); @@ -1718,6 +1725,8 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt, ret); + /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config); @@ -1743,6 +1752,8 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + sof_ipc4_init_output_audio_fmt(sdev, &src->base_config, available_fmt, ret); + /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config); @@ -1842,6 +1853,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct sof_ipc4_process *process = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; void *cfg = process->ipc_config_data; + int output_fmt_index; int ret; ret = sof_ipc4_init_audio_fmt(sdev, swidget, &process->base_config, @@ -1851,10 +1863,15 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &process->base_config, + available_fmt, ret); + /* copy Pin 0 output format */ - if (available_fmt->num_output_formats && ret < available_fmt->num_output_formats && - !available_fmt->output_pin_fmts[ret].pin_index) { - memcpy(&process->output_format, &available_fmt->output_pin_fmts[ret].audio_fmt, + if (available_fmt->num_output_formats && + output_fmt_index < available_fmt->num_output_formats && + !available_fmt->output_pin_fmts[output_fmt_index].pin_index) { + memcpy(&process->output_format, + &available_fmt->output_pin_fmts[output_fmt_index].audio_fmt, sizeof(struct sof_ipc4_audio_format)); /* modify the pipeline params with the pin 0 output format */ -- cgit v1.2.3 From ae45aebe45600a85c410280badec6b209979cf7c Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:31 +0300 Subject: ASoC: SOF: ipc4-topology: Rename sof_ipc4_init_audio_fmt() Rename it to sof_ipc4_init_input_audio_fmt() as it only does input format selection now. Signed-off-by: Ranjani Sridharan base_config, ref_params, - available_fmt, format_list_to_search, format_list_count); + ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &copier_data->base_config, ref_params, + available_fmt, format_list_to_search, + format_list_count); if (ret < 0) return ret; @@ -1541,7 +1542,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, { /* * Only SOF_DAI_INTEL_ALH needs copier_data to set blob. - * That's why only ALH dai's blob is set after sof_ipc4_init_audio_fmt + * That's why only ALH dai's blob is set after sof_ipc4_init_input_audio_fmt */ if (ipc4_copier->dai_type == SOF_DAI_INTEL_ALH) { struct sof_ipc4_alh_configuration_blob *blob; @@ -1692,10 +1693,10 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt = &gain->available_fmt; int ret; - ret = sof_ipc4_init_audio_fmt(sdev, swidget, &gain->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &gain->base_config, + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1718,10 +1719,10 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt = &mixer->available_fmt; int ret; - ret = sof_ipc4_init_audio_fmt(sdev, swidget, &mixer->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &mixer->base_config, + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1745,10 +1746,10 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, struct snd_interval *rate; int ret; - ret = sof_ipc4_init_audio_fmt(sdev, swidget, &src->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &src->base_config, + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; @@ -1856,10 +1857,10 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, int output_fmt_index; int ret; - ret = sof_ipc4_init_audio_fmt(sdev, swidget, &process->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &process->base_config, + pipeline_params, available_fmt, + available_fmt->input_pin_fmts, + available_fmt->num_input_formats); if (ret < 0) return ret; -- cgit v1.2.3 From f1ceebdbe8d8915edb64045853ab23db8ddade60 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:32 +0300 Subject: ASoC: SOF: ipc4-topology: Handle output format special case The current topologies have input/output formats in pairs and even though there are multiple output formats, they are all the same. Handle this case as if there were only one format in topology. Also, add a check for the number of output formats and reports errors where applicable. Signed-off-by: Ranjani Sridharan num_output_formats == 1) + if (!available_fmt->num_output_formats) + return -EINVAL; + + out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; + out_rate = out_fmt->sampling_frequency; + out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg); + out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); + + /* check if all output formats in topology are the same */ + for (i = 1; i < available_fmt->num_output_formats; i++) { + u32 _out_rate, _out_channels, _out_valid_bits; + + out_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; + _out_rate = out_fmt->sampling_frequency; + _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg); + _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); + + if (_out_rate != out_rate || _out_channels != out_channels || + _out_valid_bits != out_valid_bits) { + single_format = false; + break; + } + } + + /* pick the first format if there's only one available or if all formats are the same */ + if (single_format) i = 0; else i = input_audio_format_index; @@ -1522,6 +1549,11 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &copier_data->base_config, available_fmt, ret); + if (output_fmt_index < 0) { + dev_err(sdev->dev, "No output formats in topology for copier %s", + swidget->widget->name); + return output_fmt_index; + } /* * Set the output format. Current topology defines pin 0 input and output formats in pairs. @@ -1700,7 +1732,11 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - sof_ipc4_init_output_audio_fmt(sdev, &gain->base_config, available_fmt, ret); + ret = sof_ipc4_init_output_audio_fmt(sdev, &gain->base_config, available_fmt, ret); + if (ret < 0) { + dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); + return ret; + } /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config); @@ -1726,7 +1762,11 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt, ret); + ret = sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt, ret); + if (ret < 0) { + dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); + return ret; + } /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config); @@ -1753,7 +1793,11 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - sof_ipc4_init_output_audio_fmt(sdev, &src->base_config, available_fmt, ret); + ret = sof_ipc4_init_output_audio_fmt(sdev, &src->base_config, available_fmt, ret); + if (ret < 0) { + dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); + return ret; + } /* update pipeline memory usage */ sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config); @@ -1864,6 +1908,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + /* No need to check the return value. Some processing modules do not have output pins */ output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &process->base_config, available_fmt, ret); -- cgit v1.2.3 From 35171c1a907cb1226ba190685c38f62ef02bbed8 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:33 +0300 Subject: ASoC: SOF: ipc4-topology: Add a new helper function to get the valid bits Add a new helper function sof_ipc4_get_valid_bits() to get the valid bits in the PCM params. Signed-off-by: Ranjani Sridharan dev, "invalid pcm frame format %d\n", params_format(params)); + return -EINVAL; + } +} + static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, struct sof_ipc4_base_module_cfg *base_config, @@ -1093,20 +1108,9 @@ static int sof_ipc4_init_input_audio_fmt(struct snd_sof_dev *sdev, return -EINVAL; } - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - sample_valid_bits = 16; - break; - case SNDRV_PCM_FORMAT_S24_LE: - sample_valid_bits = 24; - break; - case SNDRV_PCM_FORMAT_S32_LE: - sample_valid_bits = 32; - break; - default: - dev_err(sdev->dev, "invalid pcm frame format %d\n", params_format(params)); - return -EINVAL; - } + sample_valid_bits = sof_ipc4_get_valid_bits(sdev, params); + if (sample_valid_bits < 0) + return sample_valid_bits; if (!pin_fmts_size) { dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name); -- cgit v1.2.3 From 1af13f221ac331e2d493896df5315fb8b211b4aa Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:34 +0300 Subject: ASoC: SOF: ipc4-topology: Modify the output format selection logic Modify the output format selection when there are multiple output formats available to choose the one that matches the reference params. The reference params depend on the type of module. In the case of processing modules, the reference params are based on the selected input audio format. This would be the case when a processing module does not perform any format conversion during processing. The only special case is the copier module. The copier module is capable of format conversion but it is only used in the case when the output is fixed to a single format. In the case of a module copier, when there are multiple formats, the reference params is based on the selected input params and the output format must match that of the selected input format. In the case of host copier, the reference params should be based on the input audio format for playback and the FE hw_params for capture. In the case DAI copier, the reference params should be based on the input audio format for capture and the FE hw_params for playback when there is no format conversion in the pipeline from the host to the DAI. Signed-off-by: Ranjani Sridharan obs = available_fmt->output_pin_fmts[0].buffer_size; + return 0; + } + + /* + * if there are multiple output formats, then choose the output format that matches + * the reference params + */ + for (i = 0; i < available_fmt->num_output_formats; i++) { + u32 _out_rate, _out_channels, _out_valid_bits; - if (available_fmt->num_output_formats && i < available_fmt->num_output_formats) - base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; + out_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; + _out_rate = out_fmt->sampling_frequency; + _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg); + _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); - /* Return the index of the chosen output format */ - return i; + if (_out_rate == out_ref_rate && _out_channels == out_ref_channels && + _out_valid_bits == out_ref_valid_bits) { + base_config->obs = available_fmt->output_pin_fmts[i].buffer_size; + return i; + } + } + + return -EINVAL; } static int sof_ipc4_get_valid_bits(struct snd_sof_dev *sdev, struct snd_pcm_hw_params *params) @@ -1404,6 +1420,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, int *ipc_config_size; u32 **data; int ipc_size, ret; + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; u32 deep_buffer_dma_ms = 0; u32 format_list_count; int output_fmt_index; @@ -1551,10 +1568,42 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; + /* set the reference params for output format selection */ + switch (swidget->id) { + case snd_soc_dapm_aif_in: + case snd_soc_dapm_dai_out: + case snd_soc_dapm_buffer: + { + struct sof_ipc4_audio_format *in_fmt; + + in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt; + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + break; + } + case snd_soc_dapm_aif_out: + case snd_soc_dapm_dai_in: + out_ref_valid_bits = sof_ipc4_get_valid_bits(sdev, fe_params); + if (out_ref_valid_bits < 0) + return out_ref_valid_bits; + + out_ref_rate = params_rate(fe_params); + out_ref_channels = params_channels(fe_params); + break; + default: + /* + * Unsupported type should be caught by the former switch default + * case, this should never happen in reality. + */ + return -EINVAL; + } + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &copier_data->base_config, - available_fmt, ret); + available_fmt, out_ref_rate, + out_ref_channels, out_ref_valid_bits); if (output_fmt_index < 0) { - dev_err(sdev->dev, "No output formats in topology for copier %s", + dev_err(sdev->dev, "Failed to initialize output format for %s", swidget->widget->name); return output_fmt_index; } @@ -1727,6 +1776,8 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_gain *gain = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &gain->available_fmt; + struct sof_ipc4_audio_format *in_fmt; + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &gain->base_config, @@ -1736,9 +1787,16 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - ret = sof_ipc4_init_output_audio_fmt(sdev, &gain->base_config, available_fmt, ret); + in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt; + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + + ret = sof_ipc4_init_output_audio_fmt(sdev, &gain->base_config, available_fmt, + out_ref_rate, out_ref_channels, out_ref_valid_bits); if (ret < 0) { - dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); + dev_err(sdev->dev, "Failed to initialize output format for %s", + swidget->widget->name); return ret; } @@ -1757,6 +1815,8 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_mixer *mixer = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &mixer->available_fmt; + struct sof_ipc4_audio_format *in_fmt; + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &mixer->base_config, @@ -1766,9 +1826,16 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - ret = sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt, ret); + in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt; + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + + ret = sof_ipc4_init_output_audio_fmt(sdev, &mixer->base_config, available_fmt, + out_ref_rate, out_ref_channels, out_ref_valid_bits); if (ret < 0) { - dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); + dev_err(sdev->dev, "Failed to initialize output format for %s", + swidget->widget->name); return ret; } @@ -1787,6 +1854,8 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_src *src = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &src->available_fmt; + struct sof_ipc4_audio_format *in_fmt; + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; struct snd_interval *rate; int ret; @@ -1797,10 +1866,16 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - ret = sof_ipc4_init_output_audio_fmt(sdev, &src->base_config, available_fmt, ret); + in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt; + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + + ret = sof_ipc4_init_output_audio_fmt(sdev, &src->base_config, available_fmt, + out_ref_rate, out_ref_channels, out_ref_valid_bits); if (ret < 0) { - dev_err(sdev->dev, "No output formats for %s", swidget->widget->name); - return ret; + dev_err(sdev->dev, "Failed to initialize output format for %s", + swidget->widget->name); } /* update pipeline memory usage */ @@ -1901,6 +1976,8 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); struct sof_ipc4_process *process = swidget->private; struct sof_ipc4_available_audio_format *available_fmt = &process->available_fmt; + struct sof_ipc4_audio_format *in_fmt; + u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; void *cfg = process->ipc_config_data; int output_fmt_index; int ret; @@ -1912,9 +1989,19 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, if (ret < 0) return ret; - /* No need to check the return value. Some processing modules do not have output pins */ + in_fmt = &available_fmt->input_pin_fmts[ret].audio_fmt; + out_ref_rate = in_fmt->sampling_frequency; + out_ref_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(in_fmt->fmt_cfg); + out_ref_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(in_fmt->fmt_cfg); + output_fmt_index = sof_ipc4_init_output_audio_fmt(sdev, &process->base_config, - available_fmt, ret); + available_fmt, out_ref_rate, + out_ref_channels, out_ref_valid_bits); + if (output_fmt_index < 0 && available_fmt->num_output_formats) { + dev_err(sdev->dev, "Failed to initialize output format for %s", + swidget->widget->name); + return output_fmt_index; + } /* copy Pin 0 output format */ if (available_fmt->num_output_formats && -- cgit v1.2.3 From f37b702cb6f76963013af951737e49e61bf35771 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:35 +0300 Subject: ASoC: SOF: ipc4-topology: New helper to check if all output formats are the same Add a helper function to check if all formats are identical. Signed-off-by: Ranjani Sridharan sampling_frequency; + channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); + valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); + + /* check if all output formats in topology are the same */ + for (i = 1; i < pin_fmts_size; i++) { + u32 _rate, _channels, _valid_bits; + + fmt = &pin_fmts[i].audio_fmt; + _rate = fmt->sampling_frequency; + _channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(fmt->fmt_cfg); + _valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(fmt->fmt_cfg); + + if (_rate != rate || _channels != channels || _valid_bits != valid_bits) + return false; + } + + return true; +} + static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, struct sof_ipc4_base_module_cfg *base_config, struct sof_ipc4_available_audio_format *available_fmt, @@ -1035,33 +1063,14 @@ static int sof_ipc4_init_output_audio_fmt(struct snd_sof_dev *sdev, u32 out_ref_valid_bits) { struct sof_ipc4_audio_format *out_fmt; - u32 out_rate, out_channels, out_valid_bits; - bool single_format = true; + bool single_format; int i; if (!available_fmt->num_output_formats) return -EINVAL; - out_fmt = &available_fmt->output_pin_fmts[0].audio_fmt; - out_rate = out_fmt->sampling_frequency; - out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg); - out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); - - /* check if all output formats in topology are the same */ - for (i = 1; i < available_fmt->num_output_formats; i++) { - u32 _out_rate, _out_channels, _out_valid_bits; - - out_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; - _out_rate = out_fmt->sampling_frequency; - _out_channels = SOF_IPC4_AUDIO_FORMAT_CFG_CHANNELS_COUNT(out_fmt->fmt_cfg); - _out_valid_bits = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(out_fmt->fmt_cfg); - - if (_out_rate != out_rate || _out_channels != out_channels || - _out_valid_bits != out_valid_bits) { - single_format = false; - break; - } - } + single_format = sof_ipc4_is_single_format(sdev, available_fmt->output_pin_fmts, + available_fmt->num_output_formats); /* pick the first format if there's only one available or if all formats are the same */ if (single_format) { -- cgit v1.2.3 From 5a56c5335d36decbdcb80900c665360fbbd1042a Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 13:33:36 +0300 Subject: ASoC: SOF: ipc4-topology: Modify input audio format selection logic The current selection logic assumes that input and output formats always come in pairs in topology. Handle this special case by checking if all input formats are the same. And for the case where there are multiple supported input audio formats, modify the selection logic to pick the audio formats based on the reference params which is either the FE hw_params or the pipeline params based on the type of module. Signed-off-by: Ranjani Sridharan input_pin_fmts; + u32 pin_fmts_size = available_fmt->num_input_formats; u32 valid_bits; u32 channels; u32 rate; + bool single_format; int sample_valid_bits; int i = 0; - if (!pin_fmts) { - dev_err(sdev->dev, "no reference formats for %s\n", swidget->widget->name); + if (!available_fmt->num_input_formats) { + dev_err(sdev->dev, "no input formats for %s\n", swidget->widget->name); return -EINVAL; } + single_format = sof_ipc4_is_single_format(sdev, available_fmt->input_pin_fmts, + available_fmt->num_input_formats); + if (single_format) + goto in_fmt; + sample_valid_bits = sof_ipc4_get_valid_bits(sdev, params); if (sample_valid_bits < 0) return sample_valid_bits; - if (!pin_fmts_size) { - dev_err(sdev->dev, "no formats available for %s\n", swidget->widget->name); - return -EINVAL; - } - - /* pick the only available input format */ - if (available_fmt->num_input_formats == 1) - goto in_fmt; - /* - * Search supported audio formats with pin index 0 to match rate, channels ,and - * sample_valid_bytes from runtime params + * Search supported input audio formats with pin index 0 to match rate, channels and + * sample_valid_bits from reference params */ for (i = 0; i < pin_fmts_size; i++) { struct sof_ipc4_audio_format *fmt = &pin_fmts[i].audio_fmt; @@ -1365,50 +1363,6 @@ static int ipc4_set_fmt_mask(struct snd_mask *fmt, unsigned int bit_depth) return 0; } -static int ipc4_copier_set_capture_fmt(struct snd_sof_dev *sdev, - struct snd_pcm_hw_params *pipeline_params, - struct snd_pcm_hw_params *fe_params, - struct sof_ipc4_available_audio_format *available_fmt) -{ - struct sof_ipc4_audio_format *audio_fmt; - unsigned int sample_valid_bits; - bool multiple_formats = false; - bool fe_format_match = false; - struct snd_mask *fmt; - int i; - - for (i = 0; i < available_fmt->num_output_formats; i++) { - unsigned int val; - - audio_fmt = &available_fmt->output_pin_fmts[i].audio_fmt; - val = SOF_IPC4_AUDIO_FORMAT_CFG_V_BIT_DEPTH(audio_fmt->fmt_cfg); - - if (i == 0) - sample_valid_bits = val; - else if (sample_valid_bits != val) - multiple_formats = true; - - if (snd_pcm_format_width(params_format(fe_params)) == val) - fe_format_match = true; - } - - fmt = hw_param_mask(pipeline_params, SNDRV_PCM_HW_PARAM_FORMAT); - snd_mask_none(fmt); - - if (multiple_formats) { - if (fe_format_match) { - /* multiple formats defined and one matches FE */ - snd_mask_set_format(fmt, params_format(fe_params)); - return 0; - } - - dev_err(sdev->dev, "Multiple audio formats for single dai_out not supported\n"); - return -EINVAL; - } - - return ipc4_set_fmt_mask(fmt, sample_valid_bits); -} - static int sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_pcm_hw_params *fe_params, @@ -1418,7 +1372,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct sof_ipc4_available_audio_format *available_fmt; struct snd_soc_component *scomp = swidget->scomp; struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(scomp); - struct sof_ipc4_pin_format *format_list_to_search; struct sof_ipc4_copier_data *copier_data; struct snd_pcm_hw_params *ref_params; struct sof_ipc4_copier *ipc4_copier; @@ -1431,7 +1384,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, int ipc_size, ret; u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; u32 deep_buffer_dma_ms = 0; - u32 format_list_count; int output_fmt_index; dev_dbg(sdev->dev, "copier %s, type %d", swidget->widget->name, swidget->id); @@ -1496,13 +1448,10 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, * Use the input_pin_fmts to match pcm params for playback and the output_pin_fmts * for capture. */ - if (dir == SNDRV_PCM_STREAM_PLAYBACK) { - format_list_to_search = available_fmt->input_pin_fmts; - format_list_count = available_fmt->num_input_formats; - } else { - format_list_to_search = available_fmt->output_pin_fmts; - format_list_count = available_fmt->num_output_formats; - } + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + ref_params = fe_params; + else + ref_params = pipeline_params; copier_data->gtw_cfg.node_id &= ~SOF_IPC4_NODE_INDEX_MASK; copier_data->gtw_cfg.node_id |= @@ -1510,7 +1459,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, /* set gateway attributes */ gtw_attr->lp_buffer_alloc = pipeline->lp_mode; - ref_params = fe_params; break; } case snd_soc_dapm_dai_in: @@ -1527,20 +1475,17 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ipc4_copier = (struct sof_ipc4_copier *)dai->private; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; - if (dir == SNDRV_PCM_STREAM_CAPTURE) { - format_list_to_search = available_fmt->output_pin_fmts; - format_list_count = available_fmt->num_output_formats; - - ret = ipc4_copier_set_capture_fmt(sdev, pipeline_params, fe_params, - available_fmt); - if (ret < 0) - return ret; - } else { - format_list_to_search = available_fmt->input_pin_fmts; - format_list_count = available_fmt->num_input_formats; - } - ref_params = pipeline_params; + /* + * When there is format conversion within a pipeline, the number of supported + * output formats is typically limited to just 1 for the DAI copiers. But when there + * is no format conversion, the DAI copiers input format must match that of the + * FE hw_params for capture and the pipeline params for playback. + */ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + ref_params = pipeline_params; + else + ref_params = fe_params; ret = snd_sof_get_nhlt_endpoint_data(sdev, dai, fe_params, ipc4_copier->dai_index, ipc4_copier->dai_type, dir, @@ -1556,10 +1501,6 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ipc4_copier = (struct sof_ipc4_copier *)swidget->private; copier_data = &ipc4_copier->data; available_fmt = &ipc4_copier->available_fmt; - - /* Use the input formats to match pcm params */ - format_list_to_search = available_fmt->input_pin_fmts; - format_list_count = available_fmt->num_input_formats; ref_params = pipeline_params; break; @@ -1572,8 +1513,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, /* set input and output audio formats */ ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &copier_data->base_config, ref_params, - available_fmt, format_list_to_search, - format_list_count); + available_fmt); if (ret < 0) return ret; @@ -1790,9 +1730,7 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &gain->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + pipeline_params, available_fmt); if (ret < 0) return ret; @@ -1829,9 +1767,7 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &mixer->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + pipeline_params, available_fmt); if (ret < 0) return ret; @@ -1869,9 +1805,7 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &src->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + pipeline_params, available_fmt); if (ret < 0) return ret; @@ -1992,9 +1926,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, int ret; ret = sof_ipc4_init_input_audio_fmt(sdev, swidget, &process->base_config, - pipeline_params, available_fmt, - available_fmt->input_pin_fmts, - available_fmt->num_input_formats); + pipeline_params, available_fmt); if (ret < 0) return ret; -- cgit v1.2.3 From 3886518fdb6d4c3f5a84648474a857d63749af78 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:54 -0500 Subject: ASoC: SOF: Intel: hda-dai: simplify .prepare callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code has been cleaned-up multiple times, but while adding the new abstractions for DMIC/SSP/SoundWire it appears that we don't really need a specific sequence for .prepare, and we can reuse what .hw_params already does. Signed-off-by: Pierre-Louis Bossart stream; - - return hda_link_dma_hw_params(substream, &rtd->dpcm[stream].hw_params, cpu_dai); -} - static int hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); @@ -251,30 +243,10 @@ static int hda_dai_hw_params(struct snd_pcm_substream *substream, static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); - const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); - struct hdac_ext_stream *hext_stream; - struct snd_sof_dai_config_data data = { 0 }; - unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS; - int ret; - - hext_stream = ops->get_hext_stream(sdev, dai, substream); - if (hext_stream && hext_stream->link_prepared) - return 0; - - dev_dbg(sdev->dev, "prepare stream dir %d\n", substream->stream); - - ret = hda_link_dma_prepare(substream, dai); - if (ret < 0) - return ret; - - hext_stream = ops->get_hext_stream(sdev, dai, substream); - - flags |= SOF_DAI_CONFIG_FLAGS_2_STEP_STOP << SOF_DAI_CONFIG_FLAGS_QUIRK_SHIFT; - data.dai_data = hdac_stream(hext_stream)->stream_tag - 1; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int stream = substream->stream; - return hda_dai_config(w, flags, &data); + return hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, dai); } /* -- cgit v1.2.3 From de8e2d5d8024670eaa35ae4c9b9efb76ca6cc8de Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:55 -0500 Subject: ASoC: SOF: Intel: hda-dai: remove use of cpu_dai->component drvdata The existing code relies on conversions from cpu_dai to the sdev structure pointer based on the cpu_dai component. This works fine for HDaudio but will not work for SoundWire DAIs which are registered by a different component. That's a problem preventing reuse of the HDaudio DMA stream allocation for SoundWire DAIs starting with the LunarLake platform. This patch introduces a set of helpers to perform the conversion, and an indirect way of retrieving the sdev pointer based on the swidget->comp intermediate pointer. Suggested-by: Ranjani Sridharan dobj.private; + struct snd_soc_component *component = swidget->scomp; + + return snd_soc_component_get_drvdata(component); +} + int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags, struct snd_sof_dai_config_data *data) { struct snd_sof_widget *swidget = w->dobj.private; const struct sof_ipc_tplg_ops *tplg_ops; - struct snd_soc_component *component; struct snd_sof_dev *sdev; int ret; if (!swidget) return 0; - component = swidget->scomp; - sdev = snd_soc_component_get_drvdata(component); + sdev = widget_to_sdev(w); tplg_ops = sof_ipc_get_ops(sdev, tplg); if (tplg_ops && tplg_ops->dai_config) { @@ -57,14 +63,24 @@ int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags, #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +static struct snd_sof_dev *dai_to_sdev(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream); + + return widget_to_sdev(w); +} + static const struct hda_dai_widget_dma_ops * hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(cpu_dai, substream->stream); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); struct snd_sof_widget *swidget = w->dobj.private; + struct snd_sof_dev *sdev; struct snd_sof_dai *sdai; + sdev = widget_to_sdev(w); + /* * The swidget parameter of hda_select_dai_widget_ops() is ignored in * case of DSPless mode @@ -96,14 +112,16 @@ static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai, struct snd_soc_dai *codec_dai) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); struct hdac_stream *hstream = &hext_stream->hstream; struct hdac_bus *bus = hstream->bus; struct sof_intel_hda_stream *hda_stream; struct hdac_ext_link *hlink; + struct snd_sof_dev *sdev; int stream_tag; + sdev = dai_to_sdev(substream, cpu_dai); + hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); if (!hlink) return -EINVAL; @@ -140,7 +158,7 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, unsigned int link_bps; int stream_tag; - sdev = snd_soc_component_get_drvdata(cpu_dai->component); + sdev = dai_to_sdev(substream, cpu_dai); bus = sof_to_bus(sdev); hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); @@ -190,11 +208,11 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, static int hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(cpu_dai->component); const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct hdac_ext_stream *hext_stream; + struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai); if (!ops) { dev_err(sdev->dev, "DAI widget ops not set\n"); @@ -213,11 +231,11 @@ static int hda_dai_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_dapm_widget *w = snd_soc_dai_get_widget(dai, substream->stream); - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); struct hdac_ext_stream *hext_stream; struct snd_sof_dai_config_data data = { 0 }; unsigned int flags = SOF_DAI_CONFIG_FLAGS_HW_PARAMS; + struct snd_sof_dev *sdev = widget_to_sdev(w); int ret; if (!ops) { @@ -255,16 +273,18 @@ static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_d */ static int hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct snd_sof_dev *sdev = snd_soc_component_get_drvdata(dai->component); const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); struct hdac_ext_stream *hext_stream; struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *codec_dai; + struct snd_sof_dev *sdev; int ret; dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, dai->name, substream->stream); + sdev = dai_to_sdev(substream, dai); + hext_stream = ops->get_hext_stream(sdev, dai, substream); if (!hext_stream) return -EINVAL; @@ -344,7 +364,7 @@ static int hda_dai_suspend(struct hdac_bus *bus) codec_dai = asoc_rtd_to_codec(rtd, 0); w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction); swidget = w->dobj.private; - sdev = snd_soc_component_get_drvdata(swidget->scomp); + sdev = widget_to_sdev(w); sdai = swidget->private; ops = sdai->platform_private; -- cgit v1.2.3 From 45f3c2f83a089a1f21ea089e07e3118b87116cab Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:56 -0500 Subject: ASoC: SOF: Intel: fix DAI number mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The number of DAIs was based on a Kconfig option and the declaration on another. Fix before changing the dependencies. Signed-off-by: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:57 -0500 Subject: ASoC: SOF: Intel: clarify initialization when HDA_AUDIO_CODEC is not used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For LunarLake support, we need to enable HDA_LINK but we also want the ability to remove HDaudio codec support, e.g. for 'nocodec' tests. This requires a small change in the bus initialization without any codec-specific callbacks provided. Signed-off-by: Pierre-Louis Bossart dev = dev; @@ -87,12 +92,12 @@ void sof_hda_bus_init(struct snd_sof_dev *sdev, struct device *dev) bus->idx = 0; spin_lock_init(&bus->reg_lock); -#endif /* CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC */ +#endif /* CONFIG_SND_SOC_SOF_HDA_LINK */ } void sof_hda_bus_exit(struct snd_sof_dev *sdev) { -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) struct hdac_bus *bus = sof_to_bus(sdev); snd_hdac_ext_bus_exit(bus); -- cgit v1.2.3 From 2dddff71e9ae973e46287c4e5a7d9206faa6c5e8 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:58 -0500 Subject: ASoC: SOF: Intel: Kconfig: move selection of PROBE_WORK_QUEUE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The probe workqueue is only needed if we have a Display Audio codec. Signed-off-by: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:16:59 -0500 Subject: ASoC: SOF: Intel: hda-dai: move hda_dai_prepare() code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before we change the Kconfig support, move code around. No functionality change with this commit in isolation. Signed-off-by: Pierre-Louis Bossart stream; - - return hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, dai); -} - /* * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes * (over IPC channel) and DMA state change (direct host register changes). @@ -325,6 +317,14 @@ static int hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct return 0; } +static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int stream = substream->stream; + + return hda_dai_hw_params(substream, &rtd->dpcm[stream].hw_params, dai); +} + static const struct snd_soc_dai_ops hda_dai_ops = { .hw_params = hda_dai_hw_params, .hw_free = hda_dai_hw_free, -- cgit v1.2.3 From b7b71b8cbd48b435e7e70a27f96b43a8270ec675 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:17:00 -0500 Subject: ASoC: SOF: Intel: hda-dai: mark functions as __maybe_unused MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hda_dai_hw_params, hda_dai_trigger(), hda_dai_hw_free are currently only used for HDaudio codec support, but will be reused for SSP/DMIC/SoundWire in the LunarLake/ACE2.x case. To avoid 'defined but not used' errors or added complexity in Kconfig, mark all these functions as __maybe_used. When SSP/DMIC/SoundWire are added, some of these changes may be reverted. For now this avoids compilation warnings. Signed-off-by: Pierre-Louis Bossart stream); const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); @@ -263,7 +264,8 @@ static int hda_dai_hw_params(struct snd_pcm_substream *substream, * In contrast to IPC3, the dai trigger in IPC4 mixes pipeline state changes * (over IPC channel) and DMA state change (direct host register changes). */ -static int hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) +static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) { const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); struct hdac_ext_stream *hext_stream; -- cgit v1.2.3 From 746a78c2864ca90e4a8783838adf6d765f6282da Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:17:01 -0500 Subject: ASoC: SOF: Intel: hda-dai: use HDA_LINK instead of HDA_AUDIO_CODEC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For LunarLake support, we will have to use HDAudio DMA-based DAIs even for SSP/DMIC/SoundWire. That's completely different to the HDA_AUDIO_CODEC, the DAI ops deal with DMA configuration and that can happen in the absence of any HDAudio codec. Signed-off-by: Pierre-Louis Bossart dspless_mode_selected) diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 729f750fe746..09d8ee98581d 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -61,7 +61,7 @@ int hda_dai_config(struct snd_soc_dapm_widget *w, unsigned int flags, return 0; } -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) static struct snd_sof_dev *dai_to_sdev(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) @@ -319,6 +319,8 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i return 0; } +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) + static int hda_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); @@ -334,6 +336,8 @@ static const struct snd_soc_dai_ops hda_dai_ops = { .prepare = hda_dai_prepare, }; +#endif + static int hda_dai_suspend(struct hdac_bus *bus) { struct snd_soc_pcm_runtime *rtd; @@ -582,7 +586,7 @@ int hda_dsp_dais_suspend(struct snd_sof_dev *sdev) * Since the component suspend is called last, we can trap this corner case * and force the DAIs to release their resources. */ -#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) +#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_LINK) int ret; ret = hda_dai_suspend(sof_to_bus(sdev)); -- cgit v1.2.3 From fdecd4aaf80af23f946ad97f6fb90c1f553fcdcc Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:17:02 -0500 Subject: ASoC: SOF: Intel: remove mutual exclusion between NOCODEC and HDA_LINK MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nocodec mode served two purposes so far a) generate a test driver for DMIC/SSP without any codec connected b) make sure the use of snd_hdac_ libraries was contained b) is no longer an option for LunarLake, the HDaudio DMA is used for DMIC/SSP and the HDA_LINK option needs to be enabled. Signed-off-by: Pierre-Louis Bossart Date: Fri, 12 May 2023 13:56:42 +0300 Subject: ASoC: SOF: ipc4-topology: Use set_get_data() to send LARGE_CONFIG message Instead of open coding the sending of sink format of the copier with LARGE_CONFIG_SET message, use the proper function to do so. Signed-off-by: Peter Ujfalusi ipc->ops; struct sof_ipc4_base_module_cfg *src_config; const struct sof_ipc4_audio_format *pin_fmt; struct sof_ipc4_fw_module *fw_module; struct sof_ipc4_msg msg = {{ 0 }}; - u32 header, extension; dev_dbg(sdev->dev, "%s set copier sink %d format\n", src_widget->widget->name, sink_id); @@ -2305,22 +2305,15 @@ static int sof_ipc4_set_copier_sink_format(struct snd_sof_dev *sdev, msg.data_size = sizeof(format); msg.data_ptr = &format; - header = fw_module->man4_module_entry.id; - header |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); - header |= SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_MOD_LARGE_CONFIG_SET); - header |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); - header |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); + msg.primary = fw_module->man4_module_entry.id; + msg.primary |= SOF_IPC4_MOD_INSTANCE(src_widget->instance_id); + msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST); + msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_MODULE_MSG); - extension = SOF_IPC4_MOD_EXT_MSG_SIZE(msg.data_size); - extension |= + msg.extension = SOF_IPC4_MOD_EXT_MSG_PARAM_ID(SOF_IPC4_COPIER_MODULE_CFG_PARAM_SET_SINK_FORMAT); - extension |= SOF_IPC4_MOD_EXT_MSG_LAST_BLOCK(1); - extension |= SOF_IPC4_MOD_EXT_MSG_FIRST_BLOCK(1); - - msg.primary = header; - msg.extension = extension; - return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, msg.data_size); + return iops->set_get_data(sdev, &msg, msg.data_size, true); } static int sof_ipc4_route_setup(struct snd_sof_dev *sdev, struct snd_sof_route *sroute) -- cgit v1.2.3 From d904942aeaa6a6fe493f2825048613ee46c0e991 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Fri, 12 May 2023 14:42:25 +0800 Subject: ASoC: SOF: Simplify the calculation of variables ./sound/soc/sof/pcm.c:372:27-29: WARNING !A || A && B is equivalent to !A || B. Reported-by: Abaci Robot platform_stop_during_hw_free)) + if (!pcm_ops || !pcm_ops->platform_stop_during_hw_free) snd_sof_pcm_platform_trigger(sdev, substream, cmd); break; default: -- cgit v1.2.3 From 81a5d699217d1ae2853d6b022fc110aa95a2ff52 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 14:20:21 +0300 Subject: ASoC: SOF: Intel: hda-dai-ops: Split the get_hext_stream() op for IPC4 Introduce a separate op implementation for get_hext_stream() for IPC4. This op will also be used to set the skip_during_fe_trigger flag for the BE DAI pipeline. With this change, we can remove the flag setting in sof_ipc4_dai_config() which will further simplify support for DMIC/SSP/Soundwire in the LunarLake platform. Signed-off-by: Ranjani Sridharan stream); + swidget = w->dobj.private; + pipe_widget = swidget->spipe->pipe_widget; + pipeline = pipe_widget->private; + + /* mark pipeline so that it can be skipped during FE trigger */ + pipeline->skip_during_fe_trigger = true; + + return snd_soc_dai_get_dma_data(cpu_dai, substream); +} + static struct hdac_ext_stream *hda_assign_hext_stream(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream) @@ -267,7 +287,7 @@ static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *c } static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { - .get_hext_stream = hda_get_hext_stream, + .get_hext_stream = hda_ipc4_get_hext_stream, .assign_hext_stream = hda_assign_hext_stream, .release_hext_stream = hda_release_hext_stream, .setup_hext_stream = hda_setup_hext_stream, diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 059eebf0a687..91a89ac9cec3 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -2507,7 +2507,6 @@ static int sof_ipc4_dai_config(struct snd_sof_dev *sdev, struct snd_sof_widget * } gtw_attr = ipc4_copier->gtw_attr; gtw_attr->lp_buffer_alloc = pipeline->lp_mode; - pipeline->skip_during_fe_trigger = true; fallthrough; case SOF_DAI_INTEL_ALH: /* -- cgit v1.2.3 From 225f37b578a9f6462afd46c976e31977f765c38b Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Mon, 15 May 2023 14:20:22 +0300 Subject: ASoC: SOF: ipc4-pcm: reset all pipelines during FE DAI hw_free Do not reset pipelines during the stop/suspend triggers in the BE DAI ops as the BE DAI pipeline needs to be left in the PAUSED state. It should only be reset during hw_free. This simplification is already done for the FE pipelines and the DAI trigger only toggles the states between PAUSED and RUNNING. But because the FE DAI hw_free is invoked first and all the pipelines are freed during this op, we need to make sure that the BE DAI pipeline also gets reset before it is freed. So do not skip the pipelines that have the skip_during_fe_trigger flag set when resetting pipelines. Also, because the pipeline state changes are split between the FE and BE DAI ops now, protect the BE DAI pipeline state changes with the pipeline_state_mutex as well. Signed-off-by: Ranjani Sridharan private; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; struct snd_sof_widget *swidget; @@ -189,6 +190,8 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; + mutex_lock(&ipc4_data->pipeline_state_mutex); + switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: @@ -199,7 +202,7 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_PAUSED); if (ret < 0) - return ret; + goto out; pipeline->state = SOF_IPC4_PIPE_PAUSED; break; @@ -207,7 +210,8 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp dev_err(sdev->dev, "unknown trigger command %d\n", cmd); return -EINVAL; } - +out: + mutex_unlock(&ipc4_data->pipeline_state_mutex); return 0; } @@ -237,53 +241,62 @@ static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, static int hda_ipc4_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd) { + struct sof_ipc4_fw_data *ipc4_data = sdev->private; struct snd_sof_widget *pipe_widget; struct sof_ipc4_pipeline *pipeline; struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; - int ret; + int ret = 0; w = snd_soc_dai_get_widget(cpu_dai, substream->stream); swidget = w->dobj.private; pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; + mutex_lock(&ipc4_data->pipeline_state_mutex); + switch (cmd) { case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (pipeline->state != SOF_IPC4_PIPE_PAUSED) { ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_PAUSED); if (ret < 0) - return ret; + goto out; pipeline->state = SOF_IPC4_PIPE_PAUSED; } ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, SOF_IPC4_PIPE_RUNNING); if (ret < 0) - return ret; + goto out; pipeline->state = SOF_IPC4_PIPE_RUNNING; + swidget->spipe->started_count++; break; - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: ret = sof_ipc4_set_pipeline_state(sdev, pipe_widget->instance_id, - SOF_IPC4_PIPE_RESET); + SOF_IPC4_PIPE_RUNNING); if (ret < 0) - return ret; - - pipeline->state = SOF_IPC4_PIPE_RESET; + goto out; + pipeline->state = SOF_IPC4_PIPE_RUNNING; + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + /* + * STOP/SUSPEND trigger is invoked only once when all users of this pipeline have + * been stopped. So, clear the started_count so that the pipeline can be reset + */ + swidget->spipe->started_count = 0; break; - } case SNDRV_PCM_TRIGGER_PAUSE_PUSH: break; default: dev_err(sdev->dev, "unknown trigger command %d\n", cmd); - return -EINVAL; + ret = -EINVAL; + break; } - - return 0; +out: + mutex_unlock(&ipc4_data->pipeline_state_mutex); + return ret; } static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { diff --git a/sound/soc/sof/ipc4-pcm.c b/sound/soc/sof/ipc4-pcm.c index 9e2b6c45080d..0c905bd0fab4 100644 --- a/sound/soc/sof/ipc4-pcm.c +++ b/sound/soc/sof/ipc4-pcm.c @@ -69,7 +69,7 @@ sof_ipc4_add_pipeline_to_trigger_list(struct snd_sof_dev *sdev, int state, struct snd_sof_widget *pipe_widget = spipe->pipe_widget; struct sof_ipc4_pipeline *pipeline = pipe_widget->private; - if (pipeline->skip_during_fe_trigger) + if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) return; switch (state) { @@ -108,7 +108,7 @@ sof_ipc4_update_pipeline_state(struct snd_sof_dev *sdev, int state, int cmd, struct sof_ipc4_pipeline *pipeline = pipe_widget->private; int i; - if (pipeline->skip_during_fe_trigger) + if (pipeline->skip_during_fe_trigger && state != SOF_IPC4_PIPE_RESET) return; /* set state for pipeline if it was just triggered */ -- cgit v1.2.3 From 3676cd4bc8e69192246851edad164127d71ffee2 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:21 +0200 Subject: ALSA: emu10k1: validate parameters of snd_emu10k1_ptr_{read,write}() Rather than applying masks to the provided values, make assertions about them being valid - otherwise we'd just try to paper over bugs. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index aee84c3f9f37..ced69165d69a 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -18,14 +18,27 @@ #include #include "p17v.h" +static inline bool check_ptr_reg(struct snd_emu10k1 *emu, unsigned int reg) +{ + if (snd_BUG_ON(!emu)) + return false; + if (snd_BUG_ON(reg & (emu->audigy ? (0xffff0000 & ~A_PTR_ADDRESS_MASK) + : (0xffff0000 & ~PTR_ADDRESS_MASK)))) + return false; + if (snd_BUG_ON(reg & 0x0000ffff & ~PTR_CHANNELNUM_MASK)) + return false; + return true; +} + unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn) { unsigned long flags; unsigned int regptr, val; unsigned int mask; - mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; - regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK); + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) + return 0; if (reg & 0xff000000) { unsigned char size, offset; @@ -57,18 +70,20 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i unsigned long flags; unsigned int mask; - if (snd_BUG_ON(!emu)) + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) return; - mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; - regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK); if (reg & 0xff000000) { unsigned char size, offset; size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; - mask = ((1 << size) - 1) << offset; - data = (data << offset) & mask; + mask = (1 << size) - 1; + if (snd_BUG_ON(data & ~mask)) + return; + mask <<= offset; + data <<= offset; spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); -- cgit v1.2.3 From 2093dcfc04e1477efd47d3acf0adc4582dc5f4f6 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:22 +0200 Subject: ALSA: emu10k1: merge common paths in snd_emu10k1_ptr_{read,write}() Avoids some code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index ced69165d69a..2d6bbb77c961 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -40,6 +40,11 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un if (!check_ptr_reg(emu, regptr)) return 0; + spin_lock_irqsave(&emu->emu_lock, flags); + outl(regptr, emu->port + PTR); + val = inl(emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); + if (reg & 0xff000000) { unsigned char size, offset; @@ -47,17 +52,8 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; - spin_lock_irqsave(&emu->emu_lock, flags); - outl(regptr, emu->port + PTR); - val = inl(emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); - return (val & mask) >> offset; } else { - spin_lock_irqsave(&emu->emu_lock, flags); - outl(regptr, emu->port + PTR); - val = inl(emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); return val; } } @@ -88,14 +84,12 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); data |= inl(emu->port + DATA) & ~mask; - outl(data, emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); } else { spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); - outl(data, emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); } + outl(data, emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); } EXPORT_SYMBOL(snd_emu10k1_ptr_write); -- cgit v1.2.3 From 2e9bd50f117ea3f638802627a196949f7eefcf02 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:23 +0200 Subject: ALSA: emu10k1: optimize mask calculation in snd_emu10k1_ptr_read() Unlike in snd_emu10k1_ptr_write(), we don't need to keep the value's bits in place, so we can save one shift. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 2d6bbb77c961..59b0f4d08c6b 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -50,9 +50,9 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; - mask = ((1 << size) - 1) << offset; + mask = (1 << size) - 1; - return (val & mask) >> offset; + return (val >> offset) & mask; } else { return val; } -- cgit v1.2.3 From a746516d75fd734e6af1d9a53bacbdef790e76d1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:17 +0200 Subject: ALSA: emu10k1: polish audigy GPR allocation - Pull ahead all fixed allocations, so we don't rely on the semi- dynamic ones not crossing the arbitrarily determined limit - Use an enum for the fixed allocations - Stop arbitrarily wasting registers on unexplained "reservations" - Don't reserve two regs for the master volume control - it's mono Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 69 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 32 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 2da1f9f1fb5a..43abb6c04a7f 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1202,18 +1202,31 @@ static void snd_emu10k1_audigy_dsp_convert_32_to_2x16( A_OP(icode, ptr, iMAC3, reg_out, A_GPR(tmp), A_GPR(tmp), A_C_80000000); } +#define ENUM_GPR(name, size) name, name ## _dummy = name + (size) - 1 + /* * initial DSP configuration for Audigy */ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) { - int err, z, gpr, nctl; - int bit_shifter16; - const int playback = 10; - const int capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; /* we reserve 10 voices */ - const int stereo_mix = capture + 2; - const int tmp = 0x88; + int err, z, nctl; + enum { + ENUM_GPR(playback, SND_EMU10K1_PLAYBACK_CHANNELS), + ENUM_GPR(stereo_mix, 2), + ENUM_GPR(capture, 2), + ENUM_GPR(bit_shifter16, 1), + // The fixed allocation of these breaks the pattern, but why not. + // Splitting these into left/right is questionable, as it will break + // down for center/lfe. But it works for stereo/quadro, so whatever. + ENUM_GPR(bass_gpr, 2 * 5), // two sides, five coefficients + ENUM_GPR(treble_gpr, 2 * 5), + ENUM_GPR(bass_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), // four delay stages + ENUM_GPR(treble_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), + ENUM_GPR(tmp, 3), + num_static_gprs + }; + int gpr = num_static_gprs; u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; @@ -1248,9 +1261,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) strcpy(icode->name, "Audigy DSP code for ALSA"); ptr = 0; nctl = 0; - gpr = stereo_mix + 10; - bit_shifter16 = gpr; - gpr_map[gpr++] = 0x00008000; + gpr_map[bit_shifter16] = 0x00008000; #if 1 /* PCM front Playback Volume (independent from stereo mix) @@ -1492,15 +1503,11 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) ctl->max = 40; ctl->value[0] = ctl->value[1] = 20; ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE; - -#define BASS_GPR 0x8c -#define TREBLE_GPR 0x96 - for (z = 0; z < 5; z++) { int j; for (j = 0; j < 2; j++) { - controls[nctl + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j; - controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; + controls[nctl + 0].gpr[z * 2 + j] = bass_gpr + z * 2 + j; + controls[nctl + 1].gpr[z * 2 + j] = treble_gpr + z * 2 + j; } } nctl += 2; @@ -1513,22 +1520,22 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ - k = 0xb0 + (z * 8) + (j * 4); - l = 0xe0 + (z * 8) + (j * 4); + k = bass_tmp + (z * 8) + (j * 4); + l = treble_tmp + (z * 8) + (j * 4); d = playback + z * 2 + j; - A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(BASS_GPR + 2 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(BASS_GPR + 8 + j)); - A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(BASS_GPR + 6 + j)); + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(bass_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(bass_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(bass_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(bass_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(bass_gpr + 6 + j)); A_OP(icode, &ptr, iACC3, A_GPR(k+2), A_GPR(k+2), A_GPR(k+2), A_C_00000000); - A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(TREBLE_GPR + 0 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(TREBLE_GPR + 4 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(TREBLE_GPR + 2 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(TREBLE_GPR + 8 + j)); - A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(TREBLE_GPR + 6 + j)); + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(treble_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(treble_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(treble_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(treble_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(treble_gpr + 6 + j)); A_OP(icode, &ptr, iMACINT0, A_GPR(l+2), A_C_00000000, A_GPR(l+2), A_C_00000010); A_OP(icode, &ptr, iACC3, A_GPR(d), A_GPR(l+2), A_C_00000000, A_C_00000000); @@ -1539,14 +1546,11 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) } gpr_map[gpr++] = ptr - ptr_skip; -#undef BASS_GPR -#undef TREBLE_GPR - /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); - gpr += 2; + gpr++; /* analog speakers */ A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); @@ -1668,11 +1672,12 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) * ok, set up done.. */ - if (gpr > tmp) { + if (gpr > 512) { snd_BUG(); err = -EIO; goto __err; } + /* clear remaining instruction memory */ while (ptr < 0x400) A_OP(icode, &ptr, 0x0f, 0xc0, 0xc0, 0xcf, 0xc0); -- cgit v1.2.3 From bb5ceb43b7bfa166fd5d739d51ad46c1cfb225e3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:18 +0200 Subject: ALSA: emu10k1: fix non-zero mixer control defaults in highres mode The default value needs to be scaled. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 43abb6c04a7f..fbc1bfc122fc 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1116,18 +1116,19 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; - ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; if (high_res_gpr_volume) { ctl->min = 0; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; + defval = defval * 0x7fffffffLL / 100; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } static void @@ -1137,19 +1138,20 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; - ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; - ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; if (high_res_gpr_volume) { ctl->min = 0; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; + defval = defval * 0x7fffffffLL / 100; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; + ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; } static void -- cgit v1.2.3 From 1a38ae579606dae836dced573d5ffa78cce6fc48 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:19 +0200 Subject: ALSA: emu10k1: validate min/max values of translated controls User space could pass arbitrary ranges, which were uncritically accepted. This could lead to table lookups out of range. I don't think that this is a security issue, as it only allowed someone with CAP_SYS_ADMIN to crash the kernel, but still. Setting an invalid translation mode will also be rejected now. That did no harm, but it's still better to detect errors. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index fbc1bfc122fc..796e24b6f01a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -769,6 +769,32 @@ static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, err = -EINVAL; goto __error; } + switch (gctl->translation) { + case EMU10K1_GPR_TRANSLATION_NONE: + break; + case EMU10K1_GPR_TRANSLATION_TABLE100: + if (gctl->min != 0 || gctl->max != 100) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_BASS: + case EMU10K1_GPR_TRANSLATION_TREBLE: + if (gctl->min != 0 || gctl->max != 40) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_ONOFF: + if (gctl->min != 0 || gctl->max != 1) { + err = -EINVAL; + goto __error; + } + break; + default: + err = -EINVAL; + goto __error; + } } for (i = 0; i < icode->gpr_list_control_count; i++) { /* FIXME: we need to check the WRITE access */ -- cgit v1.2.3 From 6175ccd1a98136203bf88279cebcc6514ec15bdd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:20 +0200 Subject: ALSA: emu10k1: omit non-applicable mixer controls for E-MU cards The E-MU cards don't try very hard to be Sound Blasters. All sound I/O goes through the Hana FPGA, thus making the regular extin/out controls useless. Still showing them just serves to clutter up the interface and confuse the user. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 233 +++++++++++++++++++++++----------------------- 1 file changed, 116 insertions(+), 117 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 796e24b6f01a..8c171bbaf02c 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1379,87 +1379,88 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); gpr_map[gpr + 2] = 0x00000000; gpr += 3; - } - /* AC'97 Playback Volume - used only for mic (renamed later) */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); - gpr += 2; - /* AC'97 Capture Volume - used only for mic */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); - gpr += 2; + } else { + /* AC'97 Playback Volume - used only for mic (renamed later) */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); + gpr += 2; + /* AC'97 Capture Volume - used only for mic */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); + gpr += 2; - /* mic capture buffer */ - A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + /* mic capture buffer */ + A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); - /* Audigy CD Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume", - gpr, 0); - gpr += 2; - /* Audigy CD Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume", - gpr, 0); - gpr += 2; + /* Audigy CD Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume", + gpr, 0); + gpr += 2; + /* Audigy CD Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume", + gpr, 0); + gpr += 2; - /* Optical SPDIF Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0); - gpr += 2; - /* Optical SPDIF Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0); - gpr += 2; + /* Optical SPDIF Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0); + gpr += 2; + /* Optical SPDIF Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0); + gpr += 2; - /* Line2 Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume", - gpr, 0); - gpr += 2; - /* Line2 Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume", - gpr, 0); - gpr += 2; - - /* Philips ADC Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0); - gpr += 2; - /* Philips ADC Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0); - gpr += 2; + /* Line2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume", + gpr, 0); + gpr += 2; + /* Line2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume", + gpr, 0); + gpr += 2; - /* Aux2 Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume", - gpr, 0); - gpr += 2; - /* Aux2 Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume", - gpr, 0); - gpr += 2; + /* Philips ADC Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0); + gpr += 2; + /* Philips ADC Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0); + gpr += 2; + + /* Aux2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume", + gpr, 0); + gpr += 2; + /* Aux2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume", + gpr, 0); + gpr += 2; + } /* Stereo Mix Front Playback Volume */ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); @@ -1580,19 +1581,6 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr++; - /* analog speakers */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); - A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); - A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); - A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); - if (emu->card_capabilities->spk71) - A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); - - /* headphone */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); - - /* digital outputs */ - /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */ if (emu->card_capabilities->emu_model) { /* EMU1010 Outputs from PCM Front, Rear, Center, LFE, Side */ dev_info(emu->card->dev, "EMU outputs on\n"); @@ -1603,37 +1591,48 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } } - } + } else { + /* analog speakers */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); + if (emu->card_capabilities->spk71) + A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); - /* IEC958 Optical Raw Playback Switch */ - gpr_map[gpr++] = 0; - gpr_map[gpr++] = 0x1008; - gpr_map[gpr++] = 0xffff0000; - for (z = 0; z < 2; z++) { - A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000); - A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001); - A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2)); - A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); - A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); - A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - if ((z==1) && (emu->card_capabilities->spdif_bug)) { - /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ - dev_info(emu->card->dev, - "Installing spdif_bug patch: %s\n", - emu->card_capabilities->name); - A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); - } else { - A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + /* headphone */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); + + /* IEC958 Optical Raw Playback Switch */ + gpr_map[gpr++] = 0; + gpr_map[gpr++] = 0x1008; + gpr_map[gpr++] = 0xffff0000; + for (z = 0; z < 2; z++) { + A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001); + A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2)); + A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); + A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); + A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); + A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); + if ((z==1) && (emu->card_capabilities->spdif_bug)) { + /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ + dev_info(emu->card->dev, + "Installing spdif_bug patch: %s\n", + emu->card_capabilities->name); + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } else { + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } } + snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); + gpr += 2; + + A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); } - snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); - gpr += 2; - - A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); - A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); - A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); /* ADC buffer */ #ifdef EMU10K1_CAPTURE_DIGITAL_OUT -- cgit v1.2.3 From de0dc31070a54d146bb5e7e5a739c9588034165c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:21 +0200 Subject: ALSA: emu10k1: skip mic capture PCM for cards without AC97 codec The microphone capture device is a feature of the AC97 codec, so its availability should be coupled to the presence of that codec. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1.c | 8 +++++--- sound/pci/emu10k1/emufx.c | 28 +++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c index b8163f26004a..0c97237af922 100644 --- a/sound/pci/emu10k1/emu10k1.c +++ b/sound/pci/emu10k1/emu10k1.c @@ -107,9 +107,11 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci, err = snd_emu10k1_pcm(emu, 0); if (err < 0) return err; - err = snd_emu10k1_pcm_mic(emu, 1); - if (err < 0) - return err; + if (emu->card_capabilities->ac97_chip) { + err = snd_emu10k1_pcm_mic(emu, 1); + if (err < 0) + return err; + } err = snd_emu10k1_pcm_efx(emu, 2); if (err < 0) return err; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 8c171bbaf02c..9c9ffba7e591 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1380,19 +1380,21 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) gpr_map[gpr + 2] = 0x00000000; gpr += 3; } else { - /* AC'97 Playback Volume - used only for mic (renamed later) */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); - gpr += 2; - /* AC'97 Capture Volume - used only for mic */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); - gpr += 2; - - /* mic capture buffer */ - A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + if (emu->card_capabilities->ac97_chip) { + /* AC'97 Playback Volume - used only for mic (renamed later) */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); + gpr += 2; + /* AC'97 Capture Volume - used only for mic */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); + gpr += 2; + + /* mic capture buffer */ + A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + } /* Audigy CD Playback Volume */ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); -- cgit v1.2.3 From 1298bc978afba0a507cedd0a91e53267ca152804 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:22 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 1: DSP attenuation Fractional multiplication with the maximal value 2^31-1 causes some tiny distortion. Instead, we want to multiply with the full 2^31. The catch is of course that this cannot be represented in the DSP's signed 32 bit registers. One way to deal with this is to encode 1.0 as a negative number and special-case it. As a matter of fact, the SbLive! code path already contained such code, though the controls never actually exercised it. A more efficient approach is to use negative values, which actually extend to -2^31. Accordingly, for all the volume adjustments we now use the MAC1 instruction which negates the X operand. The range of the controls in highres mode is extended downwards, so -1 is the new zero/mute. At maximal excursion, real zero is not mute any more, but I don't think anyone will notice this behavior change. ;-) That also required making the min/max/values in the control structs signed. This technically changes the user space interface, but it seems implausible that someone would notice - the numbers were actually treated as if they were signed anyway (and in the actual mixer iface they _are_). And without this change, the min value didn't even make sense in the first place (and no-one noticed, because it was always 0). Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 6 +-- include/uapi/sound/emu10k1.h | 8 +-- sound/pci/emu10k1/emufx.c | 119 ++++++++++++++++++++----------------------- 3 files changed, 64 insertions(+), 69 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index e9b1729ade60..8e27f7074230 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1508,9 +1508,9 @@ struct snd_emu10k1_fx8010_ctl { unsigned int vcount; unsigned int count; /* count of GPR (1..16) */ unsigned short gpr[32]; /* GPR number(s) */ - unsigned int value[32]; - unsigned int min; /* minimum range */ - unsigned int max; /* maximum range */ + int value[32]; + int min; /* minimum range */ + int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ struct snd_kcontrol *kcontrol; }; diff --git a/include/uapi/sound/emu10k1.h b/include/uapi/sound/emu10k1.h index c8e131d6da00..4c32a116e7ad 100644 --- a/include/uapi/sound/emu10k1.h +++ b/include/uapi/sound/emu10k1.h @@ -308,6 +308,8 @@ struct snd_emu10k1_fx8010_info { #define EMU10K1_GPR_TRANSLATION_BASS 2 #define EMU10K1_GPR_TRANSLATION_TREBLE 3 #define EMU10K1_GPR_TRANSLATION_ONOFF 4 +#define EMU10K1_GPR_TRANSLATION_NEGATE 5 +#define EMU10K1_GPR_TRANSLATION_NEG_TABLE100 6 enum emu10k1_ctl_elem_iface { EMU10K1_CTL_ELEM_IFACE_MIXER = 2, /* virtual mixer device */ @@ -328,9 +330,9 @@ struct snd_emu10k1_fx8010_control_gpr { unsigned int vcount; /* visible count */ unsigned int count; /* count of GPR (1..16) */ unsigned short gpr[32]; /* GPR number(s) */ - unsigned int value[32]; /* initial values */ - unsigned int min; /* minimum range */ - unsigned int max; /* maximum range */ + int value[32]; /* initial values */ + int min; /* minimum range */ + int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ const unsigned int *tlv; }; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 9c9ffba7e591..4c9d67e72ae5 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -332,7 +332,7 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned int nval, val; + int nval, val; unsigned int i, j; int change = 0; @@ -349,9 +349,16 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl case EMU10K1_GPR_TRANSLATION_NONE: snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, val); break; + case EMU10K1_GPR_TRANSLATION_NEGATE: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, ~val); + break; case EMU10K1_GPR_TRANSLATION_TABLE100: snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, db_table[val]); break; + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, + val == 100 ? 0x80000000 : -(int)db_table[val]); + break; case EMU10K1_GPR_TRANSLATION_BASS: if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) { change = -EIO; @@ -771,8 +778,10 @@ static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, } switch (gctl->translation) { case EMU10K1_GPR_TRANSLATION_NONE: + case EMU10K1_GPR_TRANSLATION_NEGATE: break; case EMU10K1_GPR_TRANSLATION_TABLE100: + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: if (gctl->min != 0 || gctl->max != 100) { err = -EINVAL; goto __error; @@ -1137,44 +1146,44 @@ static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu, static void snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) + const char *name, int gpr, int defval) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; if (high_res_gpr_volume) { - ctl->min = 0; + ctl->min = -1; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; - ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; - defval = defval * 0x7fffffffLL / 100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval * 0x80000000LL / 100 - 1; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; - ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } static void snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) + const char *name, int gpr, int defval) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; if (high_res_gpr_volume) { - ctl->min = 0; + ctl->min = -1; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; - ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; - defval = defval * 0x7fffffffLL / 100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval * 0x80000000LL / 100 - 1; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; - ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; @@ -1293,36 +1302,36 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) #if 1 /* PCM front Playback Volume (independent from stereo mix) - * playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31) - * where gpr contains attenuation from corresponding mixer control + * playback = -gpr * FXBUS_PCM_LEFT_FRONT >> 31 + * where gpr contains negated attenuation from corresponding mixer control * (snd_emu10k1_init_stereo_control) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100); gpr += 2; /* PCM Surround Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Surround Playback Volume", gpr, 100); gpr += 2; /* PCM Side Playback (independent from stereo mix) */ if (emu->card_capabilities->spk71) { - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Side Playback Volume", gpr, 100); gpr += 2; } /* PCM Center Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER)); snd_emu10k1_init_mono_control(&controls[nctl++], "PCM Center Playback Volume", gpr, 100); gpr++; /* PCM LFE Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE)); snd_emu10k1_init_mono_control(&controls[nctl++], "PCM LFE Playback Volume", gpr, 100); gpr++; @@ -1330,26 +1339,26 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) * Stereo Mix */ /* Wave (PCM) Playback Volume (will be renamed later) */ - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Wave Playback Volume", gpr, 100); gpr += 2; /* Synth Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Playback Volume", gpr, 100); gpr += 2; /* Wave (PCM) Capture */ - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Capture Volume", gpr, 0); gpr += 2; /* Synth Capture */ - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0); gpr += 2; @@ -1357,23 +1366,23 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) * inputs */ #define A_ADD_VOLUME_IN(var,vol,input) \ -A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) + A_OP(icode, &ptr, iMAC1, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) if (emu->card_capabilities->emu_model) { /* EMU1010 DSP 0 and DSP 1 Capture */ // The 24 MSB hold the actual value. We implicitly discard the 16 LSB. if (emu->card_capabilities->ca0108_chip) { // For unclear reasons, the EMU32IN cannot be the Y operand! - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); // A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels // need to be delayed as well; we use an auxiliary register for that. - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A3_EMU32IN(0x1), A_C_00000000, A_C_00000000); } else { - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_P16VIN(0x0), A_GPR(gpr)); // A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels // need to be delayed as well; we use an auxiliary register for that. - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_GPR(gpr+2)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A_P16VIN(0x1), A_C_00000000, A_C_00000000); } snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); @@ -1465,33 +1474,33 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) } /* Stereo Mix Front Playback Volume */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Front Playback Volume", gpr, 100); gpr += 2; /* Stereo Mix Surround Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Surround Playback Volume", gpr, 0); gpr += 2; /* Stereo Mix Center Playback */ /* Center = sub = Left/2 + Right/2 */ A_OP(icode, &ptr, iINTERP, A_GPR(tmp), A_GPR(stereo_mix), A_C_40000000, A_GPR(stereo_mix+1)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp)); snd_emu10k1_init_mono_control(&controls[nctl++], "Center Playback Volume", gpr, 0); gpr++; /* Stereo Mix LFE Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp)); snd_emu10k1_init_mono_control(&controls[nctl++], "LFE Playback Volume", gpr, 0); gpr++; if (emu->card_capabilities->spk71) { /* Stereo Mix Side Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Side Playback Volume", gpr, 0); gpr += 2; } @@ -1579,7 +1588,7 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) - A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr++; @@ -1731,30 +1740,14 @@ __err_gpr: * initial DSP configuration for Emu10k1 */ -/* when volume = max, then copy only to avoid volume modification */ -/* with iMAC0 (negative values) */ +/* Volumes are in the [-2^31, 0] range, zero being mute. */ static void _volume(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) { - OP(icode, ptr, iMAC0, dst, C_00000000, src, vol); - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000001); - OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000); + OP(icode, ptr, iMAC1, dst, C_00000000, src, vol); } static void _volume_add(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) { - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002); - OP(icode, ptr, iMACINT0, dst, dst, src, C_00000001); - OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001); - OP(icode, ptr, iMAC0, dst, dst, src, vol); -} -static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) -{ - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002); - OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000); - OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001); - OP(icode, ptr, iMAC0, dst, C_00000000, src, vol); + OP(icode, ptr, iMAC1, dst, dst, src, vol); } #define VOLUME(icode, ptr, dst, src, vol) \ @@ -1766,7 +1759,7 @@ static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst #define VOLUME_ADDIN(icode, ptr, dst, src, vol) \ _volume_add(icode, ptr, GPR(dst), EXTIN(src), GPR(vol)) #define VOLUME_OUT(icode, ptr, dst, src, vol) \ - _volume_out(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol)) + _volume(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol)) #define _SWITCH(icode, ptr, dst, src, sw) \ OP((icode), ptr, iMACINT0, dst, C_00000000, src, sw); #define SWITCH(icode, ptr, dst, src, sw) \ -- cgit v1.2.3 From bcdbd3b7888e1db89b7b2f7c78237c9ed5c2ebb1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:23 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 2: voice attenuation The voice volume is a raw fractional multiplier that can't actually represent 1.0. To still enable real pass-through, we now set the volume to 0.5 (which results in no loss of precision, as the FX bus provides fractional values) and scale up the samples in DSP code. To maintain backwards compatibility with existing configuration files, we rescale the values in the mixer controls. The range is extended upwards from 0xffff to 0x1fffd, which actually introduces the possibility of specifying an amplification. There is still a minor incompatibility with user space, namely if someone loaded custom DSP code. They'll just get half the volume, so this doesn't seem like a big deal. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- Documentation/sound/cards/audigy-mixer.rst | 2 +- Documentation/sound/cards/sb-live-mixer.rst | 2 +- include/sound/emu10k1.h | 3 +++ sound/pci/emu10k1/emufx.c | 30 +++++++++++++++++------------ sound/pci/emu10k1/emumixer.c | 15 +++++++++------ sound/pci/emu10k1/emupcm.c | 4 ++-- 6 files changed, 34 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/cards/audigy-mixer.rst b/Documentation/sound/cards/audigy-mixer.rst index aa176451d5b5..e02dd890d089 100644 --- a/Documentation/sound/cards/audigy-mixer.rst +++ b/Documentation/sound/cards/audigy-mixer.rst @@ -227,7 +227,7 @@ PCM stream related controls name='EMU10K1 PCM Volume',index 0-31 ------------------------------------ -Channel volume attenuation in range 0-0xffff. The maximum value (no +Channel volume attenuation in range 0-0x1fffd. The middle value (no attenuation) is default. The channel mapping for three values is as follows: diff --git a/Documentation/sound/cards/sb-live-mixer.rst b/Documentation/sound/cards/sb-live-mixer.rst index 819886634400..4dd9bfe01bd8 100644 --- a/Documentation/sound/cards/sb-live-mixer.rst +++ b/Documentation/sound/cards/sb-live-mixer.rst @@ -258,7 +258,7 @@ PCM stream related controls ``name='EMU10K1 PCM Volume',index 0-31`` ---------------------------------------- -Channel volume attenuation in range 0-0xffff. The maximum value (no +Channel volume attenuation in range 0-0x1fffd. The middle value (no attenuation) is default. The channel mapping for three values is as follows: diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 8e27f7074230..7bcb1a2d779a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -415,6 +415,7 @@ SUB_REG(PTRX, PITCHTARGET, 0xffff0000) /* Pitch target of specified channel */ SUB_REG(PTRX, FXSENDAMOUNT_A, 0x0000ff00) /* Linear level of channel output sent to FX send bus A */ SUB_REG(PTRX, FXSENDAMOUNT_B, 0x000000ff) /* Linear level of channel output sent to FX send bus B */ +// Note: the volumes are raw multpliers, so real 100% is impossible. #define CVCF 0x02 /* Current volume and filter cutoff register */ SUB_REG(CVCF, CURRENTVOL, 0xffff0000) /* Current linear volume of specified channel */ SUB_REG(CVCF, CURRENTFILTER, 0x0000ffff) /* Current filter cutoff frequency of specified channel */ @@ -1477,6 +1478,8 @@ struct snd_emu10k1_pcm_mixer { /* mono, left, right x 8 sends (4 on emu10k1) */ unsigned char send_routing[3][8]; unsigned char send_volume[3][8]; + // 0x8000 is neutral. The mixer code rescales it to 0xffff to maintain + // backwards compatibility with user space. unsigned short attn[3]; struct snd_emu10k1_pcm *epcm; }; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 4c9d67e72ae5..f64b2b4eb348 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1361,7 +1361,13 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0); gpr += 2; - + + // We need to double the volume, as we configure the voices for half volume, + // which is necessary for bit-identical reproduction. + { static_assert(stereo_mix == playback + SND_EMU10K1_PLAYBACK_CHANNELS); } + for (z = 0; z < SND_EMU10K1_PLAYBACK_CHANNELS + 2; z++) + A_OP(icode, &ptr, iACC3, A_GPR(playback + z), A_GPR(playback + z), A_GPR(playback + z), A_C_00000000); + /* * inputs */ @@ -1826,18 +1832,18 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* * Process FX Buses */ - OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004); + OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000008); OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000); /* S/PDIF left */ OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000); /* S/PDIF right */ - OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000004); + OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000008); /* Raw S/PDIF PCM */ ipcm->substream = 0; @@ -1931,7 +1937,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* Wave Center/LFE Playback Volume */ OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000); - OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000002); + OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000004); VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr); snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0); VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr); diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 48f0d3f8b8e7..9fa4bc845116 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1352,7 +1352,7 @@ static int snd_emu10k1_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_e uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 3; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 0xffff; + uinfo->value.integer.max = 0x1fffd; return 0; } @@ -1365,7 +1365,7 @@ static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol, int idx; for (idx = 0; idx < 3; idx++) - ucontrol->value.integer.value[idx] = mix->attn[idx]; + ucontrol->value.integer.value[idx] = mix->attn[idx] * 0xffffU / 0x8000U; return 0; } @@ -1380,7 +1380,8 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3; idx++) { - val = ucontrol->value.integer.value[idx] & 0xffff; + unsigned uval = ucontrol->value.integer.value[idx] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; if (mix->attn[idx] != val) { mix->attn[idx] = val; change = 1; @@ -1547,7 +1548,7 @@ static int snd_emu10k1_efx_attn_info(struct snd_kcontrol *kcontrol, struct snd_c uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 0xffff; + uinfo->value.integer.max = 0x1fffd; return 0; } @@ -1558,7 +1559,7 @@ static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - ucontrol->value.integer.value[0] = mix->attn[0]; + ucontrol->value.integer.value[0] = mix->attn[0] * 0xffffU / 0x8000U; return 0; } @@ -1570,9 +1571,11 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch]; int change = 0, val; + unsigned uval; spin_lock_irqsave(&emu->reg_lock, flags); - val = ucontrol->value.integer.value[0] & 0xffff; + uval = ucontrol->value.integer.value[0] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; if (mix->attn[0] != val) { mix->attn[0] = val; change = 1; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 5ed404e8ed39..6e6d3103ed90 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1049,7 +1049,7 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) mix->send_routing[0][0] = i; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = 255; - mix->attn[0] = 0xffff; + mix->attn[0] = 0x8000; mix->epcm = epcm; snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1); } @@ -1098,7 +1098,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = mix->send_volume[0][1] = mix->send_volume[1][0] = mix->send_volume[2][1] = 255; - mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff; + mix->attn[0] = mix->attn[1] = mix->attn[2] = 0x8000; mix->epcm = epcm; snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1); return 0; -- cgit v1.2.3 From 702648721db590b3425c31ade294000e18808345 Mon Sep 17 00:00:00 2001 From: Paweł Anikiel Date: Mon, 8 May 2023 13:30:31 +0200 Subject: ASoC: Add Google Chameleon v3 i2s driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add driver for the i2s IP present on Google Chameleon v3 Signed-off-by: Paweł Anikiel +#include +#include + +#include + +/* + * The I2S interface consists of two ring buffers - one for RX and one for + * TX. A ring buffer has a producer index and a consumer index. Depending + * on which way the data is flowing, either the software or the hardware + * writes data and updates the producer index, and the other end reads data + * and updates the consumer index. + * + * The pointer managed by software is updated using the .ack callback + * (see chv3_dma_ack). This seems to be the only way to reliably obtain + * the appl_ptr from within the driver and pass it to hardware. + * + * Because of the two pointer design, the ring buffer can never be full. With + * capture this isn't a problem, because the hardware being the producer + * will wait for the consumer index to move out of the way. With playback, + * however, this is problematic, because ALSA wants to fill up the buffer + * completely when waiting for hardware. In the .ack callback, the driver + * would have to wait for the consumer index to move out of the way by + * busy-waiting, which would keep stalling the kernel for quite a long time. + * + * The workaround to this problem is to "lie" to ALSA that the hw_pointer + * is one frame behind what it actually is (see chv3_dma_pointer). This + * way, ALSA will not try to fill up the entire buffer, and all callbacks + * are wait-free. + */ + +#define I2S_TX_ENABLE 0x00 +#define I2S_TX_BASE_ADDR 0x04 +#define I2S_TX_BUFFER_SIZE 0x08 +#define I2S_TX_PRODUCER_IDX 0x0c +#define I2S_TX_CONSUMER_IDX 0x10 +#define I2S_RX_ENABLE 0x14 +#define I2S_RX_BASE_ADDR 0x18 +#define I2S_RX_BUFFER_SIZE 0x1c +#define I2S_RX_PRODUCER_IDX 0x20 +#define I2S_RX_CONSUMER_IDX 0x24 + +#define I2S_SOFT_RESET 0x2c +#define I2S_SOFT_RESET_RX_BIT 0x1 +#define I2S_SOFT_RESET_TX_BIT 0x2 + +#define I2S_RX_IRQ 0x4c +#define I2S_RX_IRQ_CONST 0x50 +#define I2S_TX_IRQ 0x54 +#define I2S_TX_IRQ_CONST 0x58 + +#define I2S_IRQ_MASK 0x8 +#define I2S_IRQ_CLR 0xc +#define I2S_IRQ_RX_BIT 0x1 +#define I2S_IRQ_TX_BIT 0x2 + +#define I2S_MAX_BUFFER_SIZE 0x200000 + +struct chv3_i2s_dev { + struct device *dev; + void __iomem *iobase; + void __iomem *iobase_irq; + struct snd_pcm_substream *rx_substream; + struct snd_pcm_substream *tx_substream; + int tx_bytes_to_fetch; +}; + +static struct snd_soc_dai_driver chv3_i2s_dai = { + .name = "chv3-i2s", + .capture = { + .channels_min = 1, + .channels_max = 128, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .playback = { + .channels_min = 1, + .channels_max = 128, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 8000, + .rate_max = 96000, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, +}; + +static const struct snd_pcm_hardware chv3_dma_hw = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .buffer_bytes_max = I2S_MAX_BUFFER_SIZE, + .period_bytes_min = 64, + .period_bytes_max = 8192, + .periods_min = 4, + .periods_max = 256, +}; + +static inline void chv3_i2s_wr(struct chv3_i2s_dev *i2s, int offset, u32 val) +{ + writel(val, i2s->iobase + offset); +} + +static inline u32 chv3_i2s_rd(struct chv3_i2s_dev *i2s, int offset) +{ + return readl(i2s->iobase + offset); +} + +static irqreturn_t chv3_i2s_isr(int irq, void *data) +{ + struct chv3_i2s_dev *i2s = data; + u32 reg; + + reg = readl(i2s->iobase_irq + I2S_IRQ_CLR); + if (!reg) + return IRQ_NONE; + + if (reg & I2S_IRQ_RX_BIT) + snd_pcm_period_elapsed(i2s->rx_substream); + + if (reg & I2S_IRQ_TX_BIT) + snd_pcm_period_elapsed(i2s->tx_substream); + + writel(reg, i2s->iobase_irq + I2S_IRQ_CLR); + + return IRQ_HANDLED; +} + +static int chv3_dma_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + int res; + + snd_soc_set_runtime_hwparams(substream, &chv3_dma_hw); + + res = snd_pcm_hw_constraint_pow2(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES); + if (res) + return res; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + i2s->rx_substream = substream; + else + i2s->tx_substream = substream; + + return 0; +} +static int chv3_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + chv3_i2s_wr(i2s, I2S_RX_ENABLE, 0); + else + chv3_i2s_wr(i2s, I2S_TX_ENABLE, 0); + + return 0; +} + +static int chv3_dma_pcm_construct(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + struct snd_pcm_substream *substream; + int res; + + substream = rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (substream) { + res = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, i2s->dev, + I2S_MAX_BUFFER_SIZE, &substream->dma_buffer); + if (res) + return res; + } + + substream = rtd->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (substream) { + res = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, i2s->dev, + I2S_MAX_BUFFER_SIZE, &substream->dma_buffer); + if (res) + return res; + } + + return 0; +} + +static int chv3_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + return 0; +} + +static int chv3_dma_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + unsigned int buffer_bytes, period_bytes, period_size; + + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + period_bytes = snd_pcm_lib_period_bytes(substream); + period_size = substream->runtime->period_size; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + chv3_i2s_wr(i2s, I2S_SOFT_RESET, I2S_SOFT_RESET_RX_BIT); + chv3_i2s_wr(i2s, I2S_RX_BASE_ADDR, substream->dma_buffer.addr); + chv3_i2s_wr(i2s, I2S_RX_BUFFER_SIZE, buffer_bytes); + chv3_i2s_wr(i2s, I2S_RX_IRQ, (period_size << 8) | 1); + chv3_i2s_wr(i2s, I2S_RX_ENABLE, 1); + } else { + chv3_i2s_wr(i2s, I2S_SOFT_RESET, I2S_SOFT_RESET_TX_BIT); + chv3_i2s_wr(i2s, I2S_TX_BASE_ADDR, substream->dma_buffer.addr); + chv3_i2s_wr(i2s, I2S_TX_BUFFER_SIZE, buffer_bytes); + chv3_i2s_wr(i2s, I2S_TX_IRQ, ((period_bytes / i2s->tx_bytes_to_fetch) << 8) | 1); + chv3_i2s_wr(i2s, I2S_TX_ENABLE, 1); + } + writel(I2S_IRQ_RX_BIT | I2S_IRQ_TX_BIT, i2s->iobase_irq + I2S_IRQ_MASK); + + return 0; +} + +static snd_pcm_uframes_t chv3_dma_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + u32 frame_bytes, buffer_bytes; + u32 idx_bytes; + + frame_bytes = substream->runtime->frame_bits * 8; + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + idx_bytes = chv3_i2s_rd(i2s, I2S_RX_PRODUCER_IDX); + } else { + idx_bytes = chv3_i2s_rd(i2s, I2S_TX_CONSUMER_IDX); + /* lag the pointer by one frame */ + idx_bytes = (idx_bytes - frame_bytes) & (buffer_bytes - 1); + } + + return bytes_to_frames(substream->runtime, idx_bytes); +} + +static int chv3_dma_ack(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct chv3_i2s_dev *i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + unsigned int bytes, idx; + + bytes = frames_to_bytes(runtime, runtime->control->appl_ptr); + idx = bytes & (snd_pcm_lib_buffer_bytes(substream) - 1); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + chv3_i2s_wr(i2s, I2S_RX_CONSUMER_IDX, idx); + else + chv3_i2s_wr(i2s, I2S_TX_PRODUCER_IDX, idx); + + return 0; +} + +static const struct snd_soc_component_driver chv3_i2s_comp = { + .name = "chv3-i2s-comp", + .open = chv3_dma_open, + .close = chv3_dma_close, + .pcm_construct = chv3_dma_pcm_construct, + .hw_params = chv3_dma_hw_params, + .prepare = chv3_dma_prepare, + .pointer = chv3_dma_pointer, + .ack = chv3_dma_ack, +}; + +static int chv3_i2s_probe(struct platform_device *pdev) +{ + struct chv3_i2s_dev *i2s; + int res; + int irq; + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->iobase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(i2s->iobase)) + return PTR_ERR(i2s->iobase); + + i2s->iobase_irq = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(i2s->iobase_irq)) + return PTR_ERR(i2s->iobase_irq); + + i2s->tx_bytes_to_fetch = (chv3_i2s_rd(i2s, I2S_TX_IRQ_CONST) >> 8) & 0xffff; + + i2s->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, i2s); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENXIO; + res = devm_request_irq(i2s->dev, irq, chv3_i2s_isr, 0, "chv3-i2s", i2s); + if (res) + return res; + + res = devm_snd_soc_register_component(&pdev->dev, &chv3_i2s_comp, + &chv3_i2s_dai, 1); + if (res) { + dev_err(&pdev->dev, "couldn't register component: %d\n", res); + return res; + } + + return 0; +} + +static const struct of_device_id chv3_i2s_of_match[] = { + { .compatible = "google,chv3-i2s" }, + {}, +}; + +static struct platform_driver chv3_i2s_driver = { + .probe = chv3_i2s_probe, + .driver = { + .name = "chv3-i2s", + .of_match_table = chv3_i2s_of_match, + }, +}; + +module_platform_driver(chv3_i2s_driver); + +MODULE_AUTHOR("Pawel Anikiel "); +MODULE_DESCRIPTION("Chameleon v3 I2S interface"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 61ed303496eb7e18491ee617dec2403f75d5168c Mon Sep 17 00:00:00 2001 From: Paweł Anikiel Date: Mon, 8 May 2023 13:30:32 +0200 Subject: ASoC: Add Google Chameleon v3 codec driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add driver for the codec IP present on Google Chameleon v3 Signed-off-by: Paweł Anikiel +#include + +static struct snd_soc_dai_driver chv3_codec_dai = { + .name = "chv3-codec-hifi", + .capture = { + .stream_name = "Capture", + .channels_min = 8, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, +}; + +static const struct snd_soc_component_driver soc_component_dev_chv3_codec = { +}; + +static int chv3_codec_probe(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, + &soc_component_dev_chv3_codec, &chv3_codec_dai, 1); +} + +static const struct of_device_id chv3_codec_of_match[] = { + { .compatible = "google,chv3-codec", }, + { } +}; + +static struct platform_driver chv3_codec_platform_driver = { + .driver = { + .name = "chv3-codec", + .of_match_table = chv3_codec_of_match, + }, + .probe = chv3_codec_probe, +}; +module_platform_driver(chv3_codec_platform_driver); + +MODULE_DESCRIPTION("ASoC Chameleon v3 codec driver"); +MODULE_AUTHOR("Pawel Anikiel "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 35f8a9d87ca4f920526e6063df570490b41295fc Mon Sep 17 00:00:00 2001 From: Min-Hua Chen Date: Wed, 17 May 2023 06:36:59 +0800 Subject: ASoC: tegra: tegra210_adx: fix snd_pcm_format_t type use snd_pcm_format_t instead of unsigned int to fix the following sparse warnings: sound/soc/tegra/tegra210_adx.c:125:14: sparse: warning: restricted snd_pcm_format_t degrades to integer sound/soc/tegra/tegra210_adx.c:128:14: sparse: warning: restricted snd_pcm_format_t degrades to integer sound/soc/tegra/tegra210_adx.c:131:14: sparse: warning: restricted snd_pcm_format_t degrades to integer Signed-off-by: Min-Hua Chen Date: Wed, 17 May 2023 06:38:05 +0800 Subject: ALSA: compat_ioctl: use correct snd_ctl_elem_type_t type SNDRV_CTL_ELEM_TYPE_* are type of snd_ctl_elem_type_t, we have to __force cast them to int when comparing them with int to fix the following sparse warnings. sound/core/control_compat.c:203:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:205:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:207:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:209:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:237:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:238:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:270:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:271:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer Signed-off-by: Min-Hua Chen Link: https://lore.kernel.org/r/20230516223806.185683-1-minhuadotchen@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_compat.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index d8a86d1a99d6..9cae5d74335c 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -197,7 +197,7 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, return err; } -static int get_elem_size(int type, int count) +static int get_elem_size(snd_ctl_elem_type_t type, int count) { switch (type) { case SNDRV_CTL_ELEM_TYPE_INTEGER64: @@ -234,8 +234,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, if (type < 0) return type; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -244,7 +244,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, data->value.integer.value[i] = val; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (size < 0) { dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); return -EINVAL; @@ -267,8 +267,8 @@ static int copy_ctl_value_to_user(void __user *userdata, struct snd_ctl_elem_value32 __user *data32 = userdata; int i, size; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -277,7 +277,7 @@ static int copy_ctl_value_to_user(void __user *userdata, return -EFAULT; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (copy_to_user(valuep, data->value.bytes.data, size)) return -EFAULT; } -- cgit v1.2.3 From 155e3d3bf0cdf88430a6e6da629316d9cf766cc7 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:07 +0200 Subject: ALSA: emu10k1: straighten out FX send init The mixer structures were filled in two places: on driver init, and when the devices are opened. The latter made the former pointless, so we remove the former. This implies that mixer dumps may now return all zeroes, which is OK, as restoring them is meaningless as well. Things were even weirder for the (generally unused) secondary sends: Some of the initialization loops were forgotten when support for Audigy was added, thus creating the technically illegal state of multiple sends being routed to the same FX accumulator (though it apparently doesn't matter when the amount is zero). The global multi-channel init used some rather bizarre values for the secondary sends, and the init on open actually forgot to re-initialize them. We now use a not really more useful, but simpler formula. The direct register init was also bogus. This doesn't really matter, as the value is overwritten when a voice comes into use, but still. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- Documentation/sound/cards/audigy-mixer.rst | 36 ++++++++++++------------ sound/pci/emu10k1/emu10k1_main.c | 2 +- sound/pci/emu10k1/emumixer.c | 44 +----------------------------- sound/pci/emu10k1/emupcm.c | 14 ++++------ 4 files changed, 26 insertions(+), 70 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/cards/audigy-mixer.rst b/Documentation/sound/cards/audigy-mixer.rst index e02dd890d089..ea66b50a2b03 100644 --- a/Documentation/sound/cards/audigy-mixer.rst +++ b/Documentation/sound/cards/audigy-mixer.rst @@ -240,30 +240,30 @@ name='EMU10K1 PCM Send Routing',index 0-31 This control specifies the destination - FX-bus accumulators. There are 24 values in this mapping: -* 0 - mono, A destination (FX-bus 0-63), default 0 -* 1 - mono, B destination (FX-bus 0-63), default 1 -* 2 - mono, C destination (FX-bus 0-63), default 2 -* 3 - mono, D destination (FX-bus 0-63), default 3 -* 4 - mono, E destination (FX-bus 0-63), default 0 -* 5 - mono, F destination (FX-bus 0-63), default 0 -* 6 - mono, G destination (FX-bus 0-63), default 0 -* 7 - mono, H destination (FX-bus 0-63), default 0 -* 8 - left, A destination (FX-bus 0-63), default 0 -* 9 - left, B destination (FX-bus 0-63), default 1 +* 0 - mono, A destination (FX-bus 0-63), default 0 +* 1 - mono, B destination (FX-bus 0-63), default 1 +* 2 - mono, C destination (FX-bus 0-63), default 2 +* 3 - mono, D destination (FX-bus 0-63), default 3 +* 4 - mono, E destination (FX-bus 0-63), default 4 +* 5 - mono, F destination (FX-bus 0-63), default 5 +* 6 - mono, G destination (FX-bus 0-63), default 6 +* 7 - mono, H destination (FX-bus 0-63), default 7 +* 8 - left, A destination (FX-bus 0-63), default 0 +* 9 - left, B destination (FX-bus 0-63), default 1 * 10 - left, C destination (FX-bus 0-63), default 2 * 11 - left, D destination (FX-bus 0-63), default 3 -* 12 - left, E destination (FX-bus 0-63), default 0 -* 13 - left, F destination (FX-bus 0-63), default 0 -* 14 - left, G destination (FX-bus 0-63), default 0 -* 15 - left, H destination (FX-bus 0-63), default 0 +* 12 - left, E destination (FX-bus 0-63), default 4 +* 13 - left, F destination (FX-bus 0-63), default 5 +* 14 - left, G destination (FX-bus 0-63), default 6 +* 15 - left, H destination (FX-bus 0-63), default 7 * 16 - right, A destination (FX-bus 0-63), default 0 * 17 - right, B destination (FX-bus 0-63), default 1 * 18 - right, C destination (FX-bus 0-63), default 2 * 19 - right, D destination (FX-bus 0-63), default 3 -* 20 - right, E destination (FX-bus 0-63), default 0 -* 21 - right, F destination (FX-bus 0-63), default 0 -* 22 - right, G destination (FX-bus 0-63), default 0 -* 23 - right, H destination (FX-bus 0-63), default 0 +* 20 - right, E destination (FX-bus 0-63), default 4 +* 21 - right, F destination (FX-bus 0-63), default 5 +* 22 - right, G destination (FX-bus 0-63), default 6 +* 23 - right, H destination (FX-bus 0-63), default 7 Don't forget that it's illegal to assign a channel to the same FX-bus accumulator more than once (it means 0=0 && 1=0 is an invalid combination). diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 068cb6624e36..5c8f38f20fcc 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -95,7 +95,7 @@ void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) snd_emu10k1_ptr_write(emu, A_CSFE, ch, 0); snd_emu10k1_ptr_write(emu, A_CSHG, ch, 0); snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100); - snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x3f3f3f3f); + snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x07060504); snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0); } } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 9fa4bc845116..e067a4066cda 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1752,7 +1752,7 @@ static int rename_ctl(struct snd_card *card, const char *src, const char *dst) int snd_emu10k1_mixer(struct snd_emu10k1 *emu, int pcm_device, int multi_device) { - int err, pcm; + int err; struct snd_kcontrol *kctl; struct snd_card *card = emu->card; const char * const *c; @@ -2016,48 +2016,6 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (err) return err; - /* initialize the routing and volume table for each pcm playback stream */ - for (pcm = 0; pcm < 32; pcm++) { - struct snd_emu10k1_pcm_mixer *mix; - int v; - - mix = &emu->pcm_mixer[pcm]; - mix->epcm = NULL; - - for (v = 0; v < 4; v++) - mix->send_routing[0][v] = - mix->send_routing[1][v] = - mix->send_routing[2][v] = v; - - memset(&mix->send_volume, 0, sizeof(mix->send_volume)); - mix->send_volume[0][0] = mix->send_volume[0][1] = - mix->send_volume[1][0] = mix->send_volume[2][1] = 255; - - mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff; - } - - /* initialize the routing and volume table for the multichannel playback stream */ - for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) { - struct snd_emu10k1_pcm_mixer *mix; - int v; - - mix = &emu->efx_pcm_mixer[pcm]; - mix->epcm = NULL; - - mix->send_routing[0][0] = pcm; - mix->send_routing[0][1] = (pcm == 0) ? 1 : 0; - for (v = 0; v < 2; v++) - mix->send_routing[0][2+v] = 13+v; - if (emu->audigy) - for (v = 0; v < 4; v++) - mix->send_routing[0][4+v] = 60+v; - - memset(&mix->send_volume, 0, sizeof(mix->send_volume)); - mix->send_volume[0][0] = 255; - - mix->attn[0] = 0xffff; - } - if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) { /* sb live! and audigy */ kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 6e6d3103ed90..c5ab0926d04f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -283,11 +283,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, /* volume parameters */ if (extra) { - memset(send_routing, 0, sizeof(send_routing)); - send_routing[0] = 0; - send_routing[1] = 1; - send_routing[2] = 2; - send_routing[3] = 3; + for (int i = 0; i < 8; i++) + send_routing[i] = i; memset(send_amount, 0, sizeof(send_amount)); } else { /* mono, left, right (master voice = left) */ @@ -1031,7 +1028,7 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm; struct snd_emu10k1_pcm_mixer *mix; struct snd_pcm_runtime *runtime = substream->runtime; - int i; + int i, j; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1046,7 +1043,8 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) for (i = 0; i < NUM_EFX_PLAYBACK; i++) { mix = &emu->efx_pcm_mixer[i]; - mix->send_routing[0][0] = i; + for (j = 0; j < 8; j++) + mix->send_routing[0][j] = i + j; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = 255; mix->attn[0] = 0x8000; @@ -1093,7 +1091,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) return err; } mix = &emu->pcm_mixer[substream->number]; - for (i = 0; i < 4; i++) + for (i = 0; i < 8; i++) mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = mix->send_volume[0][1] = -- cgit v1.2.3 From 94dabafea04e49448cfbb7c2d86ac0db2dbd5df9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:08 +0200 Subject: ALSA: emu10k1: cleanup envelope register init We (rightfully) don't enable the envelope engine for PCM voices, so any related setup is entirely pointless - the EMU8K documentation makes that very clear, and the fact that the various open drivers all use different values to no observable detriment pretty much confirms it. The remaining initializations are regrouped for clarity. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 - sound/pci/emu10k1/emu10k1_main.c | 10 +++---- sound/pci/emu10k1/emupcm.c | 42 +++++++-------------------- sound/pci/emu10k1/io.c | 61 ---------------------------------------- 4 files changed, 14 insertions(+), 100 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 7bcb1a2d779a..36687195c8f7 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1798,7 +1798,6 @@ void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data); -unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate); #ifdef CONFIG_PM_SLEEP void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu); diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 5c8f38f20fcc..793ae8797172 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -58,7 +58,6 @@ MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) { snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(emu, IP, ch, 0); snd_emu10k1_ptr_write(emu, VTFT, ch, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, ch, CVCF_CURRENTFILTER_MASK); snd_emu10k1_ptr_write(emu, PTRX, ch, 0); @@ -72,19 +71,18 @@ void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) snd_emu10k1_ptr_write(emu, Z2, ch, 0); snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000); - snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); + // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0); + snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); + snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); + snd_emu10k1_ptr_write(emu, IP, ch, 0); snd_emu10k1_ptr_write(emu, IFATN, ch, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK); snd_emu10k1_ptr_write(emu, PEFE, ch, 0); snd_emu10k1_ptr_write(emu, FMMOD, ch, 0); snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */ snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0); - - /*** these are last so OFF prevents writing ***/ snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0); snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0); snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index c5ab0926d04f..d377669a8a94 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -348,24 +348,9 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page); snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page); - /* modulation envelope */ + // Disable filter (in conjunction with CCCA_RESONANCE == 0) snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0); - snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f); - snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000); - snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000); - snd_emu10k1_ptr_write(emu, FMMOD, voice, 0); - snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0); - snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0); - snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000); - /* volume envelope */ - snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f); - snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000); - /* filter envelope */ - snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f); - /* pitch envelope */ - snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0); spin_unlock_irqrestore(&emu->reg_lock, flags); } @@ -600,12 +585,12 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e } static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - int master, int extra, + int master, struct snd_emu10k1_pcm_mixer *mix) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - unsigned int attn, vattn; + unsigned int vattn; unsigned int voice, tmp; if (evoice == NULL) /* skip second voice for mono */ @@ -614,13 +599,10 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s runtime = substream->runtime; voice = evoice->number; - attn = extra ? 0 : 0x00ff; tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; - snd_emu10k1_ptr_write(emu, IFATN, voice, attn); snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f); snd_emu10k1_voice_clear_loop_stop(emu, voice); } @@ -628,7 +610,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - unsigned int voice, pitch, pitch_target; + unsigned int voice, pitch_target; if (evoice == NULL) /* skip second voice for mono */ return; @@ -636,7 +618,6 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s runtime = substream->runtime; voice = evoice->number; - pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8; if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -644,7 +625,6 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); if (master || evoice->epcm->type == PLAYBACK_EFX) snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); - snd_emu10k1_ptr_write(emu, IP, voice, pitch); if (extra) snd_emu10k1_voice_intr_enable(emu, voice); } @@ -659,10 +639,8 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_ snd_emu10k1_voice_intr_disable(emu, voice); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); - snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff); snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, IP, voice, 0); } static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, @@ -707,9 +685,9 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); + snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); @@ -853,11 +831,11 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0, + snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, &emu->efx_pcm_mixer[0]); for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0, + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, &emu->efx_pcm_mixer[i]); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 59b0f4d08c6b..f50943913a31 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -514,64 +514,3 @@ void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned outw(data, emu->port + AC97DATA); spin_unlock_irqrestore(&emu->emu_lock, flags); } - -/* - * convert rate to pitch - */ - -unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate) -{ - static const u32 logMagTable[128] = { - 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, - 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, - 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, - 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, - 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, - 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, - 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, - 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, - 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, - 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, - 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, - 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, - 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, - 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, - 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, - 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df - }; - static const char logSlopeTable[128] = { - 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, - 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, - 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, - 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, - 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, - 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, - 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, - 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, - 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, - 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, - 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, - 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, - 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, - 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, - 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f - }; - int i; - - if (rate == 0) - return 0; /* Bail out if no leading "1" */ - rate *= 11185; /* Scale 48000 to 0x20002380 */ - for (i = 31; i > 0; i--) { - if (rate & 0x80000000) { /* Detect leading "1" */ - return (((unsigned int) (i - 15) << 20) + - logMagTable[0x7f & (rate >> 24)] + - (0x7f & (rate >> 17)) * - logSlopeTable[0x7f & (rate >> 24)]); - } - rate <<= 1; - } - - return 0; /* Should never reach this point */ -} - -- cgit v1.2.3 From a61c695aee87ba9c9f6b2996f98e933e3c33a049 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:09 +0200 Subject: ALSA: emu10k1: remove useless resets of stop-on-loop-end bits We initialize them at card init and don't touch them later, so there is no need to reset them again at voice start. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 ++ sound/pci/emu10k1/emupcm.c | 1 - sound/pci/emu10k1/io.c | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 36687195c8f7..a5e935e16651 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1792,8 +1792,10 @@ void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +#if 0 void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +#endif void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index d377669a8a94..2b6f5d2bbb3e 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -603,7 +603,6 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); - snd_emu10k1_voice_clear_loop_stop(emu, voice); } static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra) diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index f50943913a31..36fd6f7a0a2c 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -434,6 +434,7 @@ void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int spin_unlock_irqrestore(&emu->emu_lock, flags); } +#if 0 void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum) { unsigned long flags; @@ -471,6 +472,7 @@ void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voi outl(sol, emu->port + DATA); spin_unlock_irqrestore(&emu->emu_lock, flags); } +#endif void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait) { -- cgit v1.2.3 From 35a60d1edff4dec9a31862a3515676cd0fafe4e4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:10 +0200 Subject: ALSA: emu10k1: rewire {en,dis}abling interrupts for PCM playback We now enable ints even before triggering, and disable them only after stopping - otherwise there is a race condition we may plausibly run into when we pause/resume near the end of the buffer. Updating the epcm->running flag is moved the same way, as it affects the *_pointer() functions, which are called by the interrupt handler. Also, factor these out to own functions, for clarity. For multi-channel, the extra voice is now triggered after all regular voices - we wouldn't want to receive an int before all channels have passed the period boundary. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 2b6f5d2bbb3e..7b0ab4e02cfd 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -605,7 +605,9 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); } -static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra) +static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + int master) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; @@ -624,24 +626,36 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); if (master || evoice->epcm->type == PLAYBACK_EFX) snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); - if (extra) - snd_emu10k1_voice_intr_enable(emu, voice); } -static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) +static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) { unsigned int voice; if (evoice == NULL) return; voice = evoice->number; - snd_emu10k1_voice_intr_disable(emu, voice); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); } +static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + epcm->running = 1; + snd_emu10k1_voice_intr_enable(emu, epcm->extra->number); +} + +static void snd_emu10k1_playback_set_stopped(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + snd_emu10k1_voice_intr_disable(emu, epcm->extra->number); + epcm->running = 0; +} + static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, struct snd_emu10k1_pcm *epcm, struct snd_pcm_substream *substream, @@ -687,18 +701,18 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); - epcm->running = 1; + snd_emu10k1_playback_set_running(emu, epcm); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: - epcm->running = 0; snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_playback_set_stopped(emu, epcm); break; default: result = -EINVAL; @@ -836,20 +850,19 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, for (i = 1; i < NUM_EFX_PLAYBACK; i++) snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, &emu->efx_pcm_mixer[i]); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); - for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0); - epcm->running = 1; + snd_emu10k1_playback_set_running(emu, epcm); + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - epcm->running = 0; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); } snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_playback_set_stopped(emu, epcm); break; default: result = -EINVAL; -- cgit v1.2.3 From 77e067d0fa0511daec7e4c72ec3f830e5faaee9e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:11 +0200 Subject: ALSA: emu10k1: skip needless setting of some voice registers Many registers are meaningless for stereo slaves and the extra voices. This patch cleans up these unnecessary register writes. snd_emu10k1_playback_{trigger,stop}_voice() is not called for stereo slaves any more. snd_emu10k1_playback_prepare_voice() is renamed to snd_emu10k1_playback_unmute_voice(), as this better reflects its remaining function. It's not called for the extra voices any more. Accordingly, snd_emu10k1_playback_mute_voice() is factored out from snd_emu10k1_playback_stop_voice(), and is called selectively as well. This doesn't add conditionals which would avoid initializing sub-registers, as that wouldn't pull its weight. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 8 +++++ sound/pci/emu10k1/emupcm.c | 89 +++++++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 41 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index a5e935e16651..5c1e5b123362 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -404,6 +404,14 @@ SUB_REG(HCFG, LOCKTANKCACHE, 0x00000004) /* 1 = Cancel bustmaster accesses to ta // distortion), the modulation engine sets the target registers, towards // which the current registers "swerve" gradually. +// For the odd channel in a stereo pair, these registers are meaningless: +// CPF_STEREO, CPF_CURRENTPITCH, PTRX_PITCHTARGET, CCR_CACHEINVALIDSIZE, +// PSST_LOOPSTARTADDR, DSL_LOOPENDADDR, CCCA_CURRADDR +// The somewhat non-obviously still meaningful ones are: +// CPF_STOP, CPF_FRACADDRESS, CCR_READADDRESS (!), +// CCCA_INTERPROM, CCCA_8BITSELECT (!) +// (The envelope engine is ignored here, as stereo matters only for verbatim playback.) + #define CPF 0x00 /* Current pitch and fraction register */ SUB_REG(CPF, CURRENTPITCH, 0xffff0000) /* Current pitch (linear, 0x4000 == unity pitch shift) */ #define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 7b0ab4e02cfd..4ade0ef2cd1b 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -301,12 +301,12 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, start_addr += ccis; end_addr += ccis + emu->delay_pcm_irq; } - if (stereo && !extra) { - snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); - snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK); - } else { - snd_emu10k1_ptr_write(emu, CPF, voice, 0); - } + } + if (stereo && !extra) { + // Not really necessary for the slave, but it doesn't hurt + snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); + } else { + snd_emu10k1_ptr_write(emu, CPF, voice, 0); } /* setup routing */ @@ -325,6 +325,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_compose_send_routing(send_routing)); /* Assumption that PT is already 0 so no harm overwriting */ snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); + // Stereo slaves don't need to have the addresses set, but it doesn't hurt snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); snd_emu10k1_ptr_write(emu, PSST, voice, (start_addr + (extra ? emu->delay_pcm_irq : 0)) | @@ -554,8 +555,6 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e struct snd_pcm_runtime *runtime; unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; - if (evoice == NULL) - return; runtime = evoice->epcm->substream->runtime; voice = evoice->number; stereo = (!extra && runtime->channels == 2); @@ -575,6 +574,7 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra); if (stereo) { snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0); + // The engine goes haywire if this one is out of sync snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra); } /* fill cache */ @@ -584,37 +584,49 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e } } -static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - int master, - struct snd_emu10k1_pcm_mixer *mix) +static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + unsigned int vattn) +{ + snd_emu10k1_ptr_write(emu, VTFT, evoice->number, vattn | VTFT_FILTERTARGET_MASK); + snd_emu10k1_ptr_write(emu, CVCF, evoice->number, vattn | CVCF_CURRENTFILTER_MASK); +} + +static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool master, + struct snd_emu10k1_pcm_mixer *mix) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; unsigned int vattn; - unsigned int voice, tmp; + unsigned int tmp; if (evoice == NULL) /* skip second voice for mono */ return; substream = evoice->epcm->substream; runtime = substream->runtime; - voice = evoice->number; tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; - vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; - snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); + vattn = mix->attn[tmp] << 16; + snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } +static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) +{ + if (evoice == NULL) + return; + snd_emu10k1_playback_commit_volume(emu, evoice, 0); +} + static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice, - int master) + struct snd_emu10k1_voice *evoice) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; unsigned int voice, pitch_target; - if (evoice == NULL) /* skip second voice for mono */ - return; substream = evoice->epcm->substream; runtime = substream->runtime; voice = evoice->number; @@ -624,8 +636,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, else pitch_target = emu10k1_calc_pitch_target(runtime->rate); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); - if (master || evoice->epcm->type == PLAYBACK_EFX) - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); + snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -633,13 +644,9 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, { unsigned int voice; - if (evoice == NULL) - return; voice = evoice->number; snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); - snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); } static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, @@ -698,21 +705,20 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); snd_emu10k1_playback_set_running(emu, epcm); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); - snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); + snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_mute_voice(emu, epcm->voices[1]); break; default: result = -EINVAL; @@ -844,16 +850,14 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, - &emu->efx_pcm_mixer[0]); - for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, - &emu->efx_pcm_mixer[i]); + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, + &emu->efx_pcm_mixer[i]); + snd_emu10k1_playback_set_running(emu, epcm); for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i]); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: @@ -863,6 +867,9 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, } snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); + + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); break; default: result = -EINVAL; -- cgit v1.2.3 From 51d652f4587f22b619028f4113dd262b80a82489 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:12 +0200 Subject: ALSA: emu10k1: factor out snd_emu10k1_compose_audigy_sendamounts() Saves a bit of code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +++ sound/pci/emu10k1/emumixer.c | 7 ++----- sound/pci/emu10k1/emupcm.c | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 5c1e5b123362..456af84735a8 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1501,6 +1501,9 @@ struct snd_emu10k1_pcm_mixer { #define snd_emu10k1_compose_audigy_fxrt2(route) \ ((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 24)) +#define snd_emu10k1_compose_audigy_sendamounts(vol) \ +(((unsigned int)vol[4] << 24) | ((unsigned int)vol[5] << 16) | ((unsigned int)vol[6] << 8) | (unsigned int)vol[7]) + struct snd_emu10k1_memblk { struct snd_util_memblk mem; /* private part */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index e067a4066cda..1ebf161d410e 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1196,11 +1196,8 @@ static void update_emu10k1_send_volume(struct snd_emu10k1 *emu, int voice, unsig snd_emu10k1_ptr_write(emu, PSST_FXSENDAMOUNT_C, voice, volume[2]); snd_emu10k1_ptr_write(emu, DSL_FXSENDAMOUNT_D, voice, volume[3]); if (emu->audigy) { - unsigned int val = ((unsigned int)volume[4] << 24) | - ((unsigned int)volume[5] << 16) | - ((unsigned int)volume[6] << 8) | - (unsigned int)volume[7]; - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, val); + snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, + snd_emu10k1_compose_audigy_sendamounts(volume)); } } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4ade0ef2cd1b..d669f93d8930 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -316,10 +316,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, A_FXRT2, voice, snd_emu10k1_compose_audigy_fxrt2(send_routing)); snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, - ((unsigned int)send_amount[4] << 24) | - ((unsigned int)send_amount[5] << 16) | - ((unsigned int)send_amount[6] << 8) | - (unsigned int)send_amount[7]); + snd_emu10k1_compose_audigy_sendamounts(send_amount)); } else snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); -- cgit v1.2.3 From 9b00a1e9b1aedd70fd397335f5e41609b6e6109b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:03 +0200 Subject: ALSA: emu10k1: make some initializer arrays less wasteful - Use bit fields in struct snd_emu_chip_details - Use shorts in the E-MU routing register arrays Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 36 ++++++++++++++++++------------------ sound/pci/emu10k1/emumixer.c | 10 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 456af84735a8..03850fa186fc 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1612,24 +1612,24 @@ struct snd_emu_chip_details { u32 device; u32 subsystem; unsigned char revision; - unsigned char emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */ - /* Redundant with emu10k2_chip being unset. */ - unsigned char emu10k2_chip; /* Audigy 1 or Audigy 2. */ - unsigned char ca0102_chip; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ - /* Redundant with ca0108_chip being unset. */ - unsigned char ca0108_chip; /* Audigy 2 Value */ - unsigned char ca_cardbus_chip; /* Audigy 2 ZS Notebook */ - unsigned char ca0151_chip; /* P16V */ - unsigned char spk71; /* Has 7.1 speakers */ - unsigned char sblive51; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ - unsigned char spdif_bug; /* Has Spdif phasing bug */ - unsigned char ac97_chip; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ - unsigned char ecard; /* APS EEPROM */ - unsigned char emu_model; /* EMU model type */ - unsigned char spi_dac; /* SPI interface for DAC; requires ca0108_chip */ - unsigned char i2c_adc; /* I2C interface for ADC; requires ca0108_chip */ - unsigned char adc_1361t; /* Use Philips 1361T ADC */ - unsigned char invert_shared_spdif; /* analog/digital switch inverted */ + unsigned char emu_model; /* EMU model type */ + unsigned int emu10k1_chip:1; /* Original SB Live. Not SB Live 24bit. */ + /* Redundant with emu10k2_chip being unset. */ + unsigned int emu10k2_chip:1; /* Audigy 1 or Audigy 2. */ + unsigned int ca0102_chip:1; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ + /* Redundant with ca0108_chip being unset. */ + unsigned int ca0108_chip:1; /* Audigy 2 Value */ + unsigned int ca_cardbus_chip:1; /* Audigy 2 ZS Notebook */ + unsigned int ca0151_chip:1; /* P16V */ + unsigned int spk71:1; /* Has 7.1 speakers */ + unsigned int sblive51:1; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ + unsigned int spdif_bug:1; /* Has Spdif phasing bug */ + unsigned int ac97_chip:2; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ + unsigned int ecard:1; /* APS EEPROM */ + unsigned int spi_dac:1; /* SPI interface for DAC; requires ca0108_chip */ + unsigned int i2c_adc:1; /* I2C interface for ADC; requires ca0108_chip */ + unsigned int adc_1361t:1; /* Use Philips 1361T ADC */ + unsigned int invert_shared_spdif:1; /* analog/digital switch inverted */ const char *driver; const char *name; const char *id; /* for backward compatibility - can be NULL if not needed */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 1ebf161d410e..4d28a917aa16 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -180,7 +180,7 @@ static const char * const emu1616_src_texts[] = { /* * List of data sources available for each destination */ -static const unsigned int emu1010_src_regs[] = { +static const unsigned short emu1010_src_regs[] = { EMU_SRC_SILENCE,/* 0 */ EMU_SRC_DOCK_MIC_A1, /* 1 */ EMU_SRC_DOCK_MIC_B1, /* 2 */ @@ -237,7 +237,7 @@ static const unsigned int emu1010_src_regs[] = { }; /* 1616(m) cardbus */ -static const unsigned int emu1616_src_regs[] = { +static const unsigned short emu1616_src_regs[] = { EMU_SRC_SILENCE, EMU_SRC_DOCK_MIC_A1, EMU_SRC_DOCK_MIC_B1, @@ -293,7 +293,7 @@ static const unsigned int emu1616_src_regs[] = { * Data destinations - physical EMU outputs. * Each destination has an enum mixer control to choose a data source */ -static const unsigned int emu1010_output_dst[] = { +static const unsigned short emu1010_output_dst[] = { EMU_DST_DOCK_DAC1_LEFT1, /* 0 */ EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */ EMU_DST_DOCK_DAC2_LEFT1, /* 2 */ @@ -321,7 +321,7 @@ static const unsigned int emu1010_output_dst[] = { }; /* 1616(m) cardbus */ -static const unsigned int emu1616_output_dst[] = { +static const unsigned short emu1616_output_dst[] = { EMU_DST_DOCK_DAC1_LEFT1, EMU_DST_DOCK_DAC1_RIGHT1, EMU_DST_DOCK_DAC2_LEFT1, @@ -347,7 +347,7 @@ static const unsigned int emu1616_output_dst[] = { * capture (EMU32 + I2S links) * Each destination has an enum mixer control to choose a data source */ -static const unsigned int emu1010_input_dst[] = { +static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_2, -- cgit v1.2.3 From dc39bb3e4c25b784899cce572e539055898b2c73 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:04 +0200 Subject: ALSA: emu10k1: compactize E-MU routing source arrays Use macros to avoid duplication. Arguably, this is somewhat less legible, but future additions would grow this part of the file to completely unmanageable dimensions. The EMU*_COMMON_TEXTS macros will save duplication in a future commit; I pulled them ahead to reduce churn. While rewriting the tables anyway, rearrange them such that each card's strings and registers are adjacent. Also, add some static asserts to verify that the array sizes match. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 315 ++++++++++++++----------------------------- 1 file changed, 103 insertions(+), 212 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 4d28a917aa16..fd5fcacfe0d5 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -62,232 +62,123 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, return 0; } -/* - * Items labels in enum mixer controls assigning source data to - * each destination - */ -static const char * const emu1010_src_texts[] = { - "Silence", - "Dock Mic A", - "Dock Mic B", - "Dock ADC1 Left", - "Dock ADC1 Right", - "Dock ADC2 Left", - "Dock ADC2 Right", - "Dock ADC3 Left", - "Dock ADC3 Right", - "0202 ADC Left", - "0202 ADC Right", - "1010 SPDIF Left", - "1010 SPDIF Right", - "1010 ADAT 0", - "1010 ADAT 1", - "1010 ADAT 2", - "1010 ADAT 3", - "1010 ADAT 4", - "1010 ADAT 5", - "1010 ADAT 6", - "1010 ADAT 7", - "DSP 0", - "DSP 1", - "DSP 2", - "DSP 3", - "DSP 4", - "DSP 5", - "DSP 6", - "DSP 7", - "DSP 8", - "DSP 9", - "DSP 10", - "DSP 11", - "DSP 12", - "DSP 13", - "DSP 14", - "DSP 15", - "DSP 16", - "DSP 17", - "DSP 18", - "DSP 19", - "DSP 20", - "DSP 21", - "DSP 22", - "DSP 23", - "DSP 24", - "DSP 25", - "DSP 26", - "DSP 27", - "DSP 28", - "DSP 29", - "DSP 30", - "DSP 31", -}; +#define PAIR_PS(base, one, two, sfx) base " " one sfx, base " " two sfx +#define LR_PS(base, sfx) PAIR_PS(base, "Left", "Right", sfx) -/* 1616(m) cardbus */ +#define ADAT_PS(pfx, sfx) \ + pfx "ADAT 0" sfx, pfx "ADAT 1" sfx, pfx "ADAT 2" sfx, pfx "ADAT 3" sfx, \ + pfx "ADAT 4" sfx, pfx "ADAT 5" sfx, pfx "ADAT 6" sfx, pfx "ADAT 7" sfx -static const char * const emu1616_src_texts[] = { - "Silence", - "Mic A", - "Mic B", - "ADC1 Left", - "ADC1 Right", - "ADC2 Left", - "ADC2 Right", - "SPDIF Left", - "SPDIF Right", - "ADAT 0", - "ADAT 1", - "ADAT 2", - "ADAT 3", - "ADAT 4", - "ADAT 5", - "ADAT 6", - "ADAT 7", - "DSP 0", - "DSP 1", - "DSP 2", - "DSP 3", - "DSP 4", - "DSP 5", - "DSP 6", - "DSP 7", - "DSP 8", - "DSP 9", - "DSP 10", - "DSP 11", - "DSP 12", - "DSP 13", - "DSP 14", - "DSP 15", - "DSP 16", - "DSP 17", - "DSP 18", - "DSP 19", - "DSP 20", - "DSP 21", - "DSP 22", - "DSP 23", - "DSP 24", - "DSP 25", - "DSP 26", - "DSP 27", - "DSP 28", - "DSP 29", - "DSP 30", - "DSP 31", -}; +#define PAIR_REGS(base, one, two) \ + base ## one ## 1, \ + base ## two ## 1 + +#define LR_REGS(base) PAIR_REGS(base, _LEFT, _RIGHT) +#define ADAT_REGS(base) \ + base+0, base+1, base+2, base+3, base+4, base+5, base+6, base+7 /* * List of data sources available for each destination */ + +#define DSP_TEXTS \ + "DSP 0", "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", \ + "DSP 8", "DSP 9", "DSP 10", "DSP 11", "DSP 12", "DSP 13", "DSP 14", "DSP 15", \ + "DSP 16", "DSP 17", "DSP 18", "DSP 19", "DSP 20", "DSP 21", "DSP 22", "DSP 23", \ + "DSP 24", "DSP 25", "DSP 26", "DSP 27", "DSP 28", "DSP 29", "DSP 30", "DSP 31" + +#define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "") +#define LR_TEXTS(base) LR_PS(base, "") +#define ADAT_TEXTS(pfx) ADAT_PS(pfx, "") + +#define EMU32_SRC_REGS \ + EMU_SRC_ALICE_EMU32A, \ + EMU_SRC_ALICE_EMU32A+1, \ + EMU_SRC_ALICE_EMU32A+2, \ + EMU_SRC_ALICE_EMU32A+3, \ + EMU_SRC_ALICE_EMU32A+4, \ + EMU_SRC_ALICE_EMU32A+5, \ + EMU_SRC_ALICE_EMU32A+6, \ + EMU_SRC_ALICE_EMU32A+7, \ + EMU_SRC_ALICE_EMU32A+8, \ + EMU_SRC_ALICE_EMU32A+9, \ + EMU_SRC_ALICE_EMU32A+0xa, \ + EMU_SRC_ALICE_EMU32A+0xb, \ + EMU_SRC_ALICE_EMU32A+0xc, \ + EMU_SRC_ALICE_EMU32A+0xd, \ + EMU_SRC_ALICE_EMU32A+0xe, \ + EMU_SRC_ALICE_EMU32A+0xf, \ + EMU_SRC_ALICE_EMU32B, \ + EMU_SRC_ALICE_EMU32B+1, \ + EMU_SRC_ALICE_EMU32B+2, \ + EMU_SRC_ALICE_EMU32B+3, \ + EMU_SRC_ALICE_EMU32B+4, \ + EMU_SRC_ALICE_EMU32B+5, \ + EMU_SRC_ALICE_EMU32B+6, \ + EMU_SRC_ALICE_EMU32B+7, \ + EMU_SRC_ALICE_EMU32B+8, \ + EMU_SRC_ALICE_EMU32B+9, \ + EMU_SRC_ALICE_EMU32B+0xa, \ + EMU_SRC_ALICE_EMU32B+0xb, \ + EMU_SRC_ALICE_EMU32B+0xc, \ + EMU_SRC_ALICE_EMU32B+0xd, \ + EMU_SRC_ALICE_EMU32B+0xe, \ + EMU_SRC_ALICE_EMU32B+0xf + +#define EMU1010_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Dock Mic", "A", "B"), \ + LR_TEXTS("Dock ADC1"), \ + LR_TEXTS("Dock ADC2"), \ + LR_TEXTS("Dock ADC3"), \ + LR_TEXTS("0202 ADC"), \ + LR_TEXTS("1010 SPDIF"), \ + ADAT_TEXTS("1010 ") + +static const char * const emu1010_src_texts[] = { + EMU1010_COMMON_TEXTS, + DSP_TEXTS, +}; + static const unsigned short emu1010_src_regs[] = { - EMU_SRC_SILENCE,/* 0 */ - EMU_SRC_DOCK_MIC_A1, /* 1 */ - EMU_SRC_DOCK_MIC_B1, /* 2 */ - EMU_SRC_DOCK_ADC1_LEFT1, /* 3 */ - EMU_SRC_DOCK_ADC1_RIGHT1, /* 4 */ - EMU_SRC_DOCK_ADC2_LEFT1, /* 5 */ - EMU_SRC_DOCK_ADC2_RIGHT1, /* 6 */ - EMU_SRC_DOCK_ADC3_LEFT1, /* 7 */ - EMU_SRC_DOCK_ADC3_RIGHT1, /* 8 */ - EMU_SRC_HAMOA_ADC_LEFT1, /* 9 */ - EMU_SRC_HAMOA_ADC_RIGHT1, /* 10 */ - EMU_SRC_HANA_SPDIF_LEFT1, /* 11 */ - EMU_SRC_HANA_SPDIF_RIGHT1, /* 12 */ - EMU_SRC_HANA_ADAT, /* 13 */ - EMU_SRC_HANA_ADAT+1, /* 14 */ - EMU_SRC_HANA_ADAT+2, /* 15 */ - EMU_SRC_HANA_ADAT+3, /* 16 */ - EMU_SRC_HANA_ADAT+4, /* 17 */ - EMU_SRC_HANA_ADAT+5, /* 18 */ - EMU_SRC_HANA_ADAT+6, /* 19 */ - EMU_SRC_HANA_ADAT+7, /* 20 */ - EMU_SRC_ALICE_EMU32A, /* 21 */ - EMU_SRC_ALICE_EMU32A+1, /* 22 */ - EMU_SRC_ALICE_EMU32A+2, /* 23 */ - EMU_SRC_ALICE_EMU32A+3, /* 24 */ - EMU_SRC_ALICE_EMU32A+4, /* 25 */ - EMU_SRC_ALICE_EMU32A+5, /* 26 */ - EMU_SRC_ALICE_EMU32A+6, /* 27 */ - EMU_SRC_ALICE_EMU32A+7, /* 28 */ - EMU_SRC_ALICE_EMU32A+8, /* 29 */ - EMU_SRC_ALICE_EMU32A+9, /* 30 */ - EMU_SRC_ALICE_EMU32A+0xa, /* 31 */ - EMU_SRC_ALICE_EMU32A+0xb, /* 32 */ - EMU_SRC_ALICE_EMU32A+0xc, /* 33 */ - EMU_SRC_ALICE_EMU32A+0xd, /* 34 */ - EMU_SRC_ALICE_EMU32A+0xe, /* 35 */ - EMU_SRC_ALICE_EMU32A+0xf, /* 36 */ - EMU_SRC_ALICE_EMU32B, /* 37 */ - EMU_SRC_ALICE_EMU32B+1, /* 38 */ - EMU_SRC_ALICE_EMU32B+2, /* 39 */ - EMU_SRC_ALICE_EMU32B+3, /* 40 */ - EMU_SRC_ALICE_EMU32B+4, /* 41 */ - EMU_SRC_ALICE_EMU32B+5, /* 42 */ - EMU_SRC_ALICE_EMU32B+6, /* 43 */ - EMU_SRC_ALICE_EMU32B+7, /* 44 */ - EMU_SRC_ALICE_EMU32B+8, /* 45 */ - EMU_SRC_ALICE_EMU32B+9, /* 46 */ - EMU_SRC_ALICE_EMU32B+0xa, /* 47 */ - EMU_SRC_ALICE_EMU32B+0xb, /* 48 */ - EMU_SRC_ALICE_EMU32B+0xc, /* 49 */ - EMU_SRC_ALICE_EMU32B+0xd, /* 50 */ - EMU_SRC_ALICE_EMU32B+0xe, /* 51 */ - EMU_SRC_ALICE_EMU32B+0xf, /* 52 */ + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_DOCK_ADC3), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, }; +static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts)); /* 1616(m) cardbus */ + +#define EMU1616_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Mic", "A", "B"), \ + LR_TEXTS("ADC1"), \ + LR_TEXTS("ADC2"), \ + LR_TEXTS("SPDIF"), \ + ADAT_TEXTS("") + +static const char * const emu1616_src_texts[] = { + EMU1616_COMMON_TEXTS, + DSP_TEXTS, +}; + static const unsigned short emu1616_src_regs[] = { EMU_SRC_SILENCE, - EMU_SRC_DOCK_MIC_A1, - EMU_SRC_DOCK_MIC_B1, - EMU_SRC_DOCK_ADC1_LEFT1, - EMU_SRC_DOCK_ADC1_RIGHT1, - EMU_SRC_DOCK_ADC2_LEFT1, - EMU_SRC_DOCK_ADC2_RIGHT1, - EMU_SRC_MDOCK_SPDIF_LEFT1, - EMU_SRC_MDOCK_SPDIF_RIGHT1, - EMU_SRC_MDOCK_ADAT, - EMU_SRC_MDOCK_ADAT+1, - EMU_SRC_MDOCK_ADAT+2, - EMU_SRC_MDOCK_ADAT+3, - EMU_SRC_MDOCK_ADAT+4, - EMU_SRC_MDOCK_ADAT+5, - EMU_SRC_MDOCK_ADAT+6, - EMU_SRC_MDOCK_ADAT+7, - EMU_SRC_ALICE_EMU32A, - EMU_SRC_ALICE_EMU32A+1, - EMU_SRC_ALICE_EMU32A+2, - EMU_SRC_ALICE_EMU32A+3, - EMU_SRC_ALICE_EMU32A+4, - EMU_SRC_ALICE_EMU32A+5, - EMU_SRC_ALICE_EMU32A+6, - EMU_SRC_ALICE_EMU32A+7, - EMU_SRC_ALICE_EMU32A+8, - EMU_SRC_ALICE_EMU32A+9, - EMU_SRC_ALICE_EMU32A+0xa, - EMU_SRC_ALICE_EMU32A+0xb, - EMU_SRC_ALICE_EMU32A+0xc, - EMU_SRC_ALICE_EMU32A+0xd, - EMU_SRC_ALICE_EMU32A+0xe, - EMU_SRC_ALICE_EMU32A+0xf, - EMU_SRC_ALICE_EMU32B, - EMU_SRC_ALICE_EMU32B+1, - EMU_SRC_ALICE_EMU32B+2, - EMU_SRC_ALICE_EMU32B+3, - EMU_SRC_ALICE_EMU32B+4, - EMU_SRC_ALICE_EMU32B+5, - EMU_SRC_ALICE_EMU32B+6, - EMU_SRC_ALICE_EMU32B+7, - EMU_SRC_ALICE_EMU32B+8, - EMU_SRC_ALICE_EMU32B+9, - EMU_SRC_ALICE_EMU32B+0xa, - EMU_SRC_ALICE_EMU32B+0xb, - EMU_SRC_ALICE_EMU32B+0xc, - EMU_SRC_ALICE_EMU32B+0xd, - EMU_SRC_ALICE_EMU32B+0xe, - EMU_SRC_ALICE_EMU32B+0xf, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + EMU32_SRC_REGS, }; +static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); /* * Data destinations - physical EMU outputs. -- cgit v1.2.3 From 536438f1def68eb56fe611c07d2a6ec73ab4a5b1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:05 +0200 Subject: ALSA: emu10k1: make mixer control mass creation less wasteful Define arrays of strings instead of snd_kcontrol_new. While at it, move the E-MU source & destination enum defs next to their hardware defs, which is a lot more logical and will come in handy in a followup commit. And add some static asserts to verify that the array sizes match. This also applies the compactization from the previous commit to the destination registers. While reshuffling the arrays anyway, switch the order of the HAMOA_DAC & HANA_SPDIF output destinations for the 1010 card, so they follow a more regular pattern. This should have no functional impact. The code is somewhat de-duplicated by the extraction of add_ctls(). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 +- sound/pci/emu10k1/emumixer.c | 449 +++++++++++++++++++------------------------ 2 files changed, 203 insertions(+), 248 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 03850fa186fc..b263c762c01a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1195,7 +1195,7 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM * physical outputs of Hana, or outputs going to Alice2/Tina for capture - * 16 x EMU_DST_ALICE2_EMU32_X (2x on rev2 boards). Which data is fed into * a channel depends on the mixer control setting for each destination - see - * emumixer.c - snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[] + * the register arrays in emumixer.c. */ #define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */ /* This channel is delayed by one sample. */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index fd5fcacfe0d5..92545559a36c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -29,6 +29,24 @@ static const DECLARE_TLV_DB_SCALE(snd_audigy_db_scale2, -10350, 50, 1); /* WM8775 gain scale */ + +static int add_ctls(struct snd_emu10k1 *emu, const struct snd_kcontrol_new *tpl, + const char * const *ctls, unsigned nctls) +{ + struct snd_kcontrol_new kctl = *tpl; + int err; + + for (unsigned i = 0; i < nctls; i++) { + kctl.name = ctls[i]; + kctl.private_value = i; + err = snd_ctl_add(emu->card, snd_ctl_new1(&kctl, emu)); + if (err < 0) + return err; + } + return 0; +} + + static int snd_emu10k1_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; @@ -184,60 +202,88 @@ static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); * Data destinations - physical EMU outputs. * Each destination has an enum mixer control to choose a data source */ + +#define LR_CTLS(base) LR_PS(base, " Playback Enum") +#define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum") + +static const char * const emu1010_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock DAC4"), + LR_CTLS("Dock Phones"), + LR_CTLS("Dock SPDIF"), + LR_CTLS("0202 DAC"), + LR_CTLS("1010 SPDIF"), + ADAT_CTLS("1010 "), +}; + static const unsigned short emu1010_output_dst[] = { - EMU_DST_DOCK_DAC1_LEFT1, /* 0 */ - EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */ - EMU_DST_DOCK_DAC2_LEFT1, /* 2 */ - EMU_DST_DOCK_DAC2_RIGHT1, /* 3 */ - EMU_DST_DOCK_DAC3_LEFT1, /* 4 */ - EMU_DST_DOCK_DAC3_RIGHT1, /* 5 */ - EMU_DST_DOCK_DAC4_LEFT1, /* 6 */ - EMU_DST_DOCK_DAC4_RIGHT1, /* 7 */ - EMU_DST_DOCK_PHONES_LEFT1, /* 8 */ - EMU_DST_DOCK_PHONES_RIGHT1, /* 9 */ - EMU_DST_DOCK_SPDIF_LEFT1, /* 10 */ - EMU_DST_DOCK_SPDIF_RIGHT1, /* 11 */ - EMU_DST_HANA_SPDIF_LEFT1, /* 12 */ - EMU_DST_HANA_SPDIF_RIGHT1, /* 13 */ - EMU_DST_HAMOA_DAC_LEFT1, /* 14 */ - EMU_DST_HAMOA_DAC_RIGHT1, /* 15 */ - EMU_DST_HANA_ADAT, /* 16 */ - EMU_DST_HANA_ADAT+1, /* 17 */ - EMU_DST_HANA_ADAT+2, /* 18 */ - EMU_DST_HANA_ADAT+3, /* 19 */ - EMU_DST_HANA_ADAT+4, /* 20 */ - EMU_DST_HANA_ADAT+5, /* 21 */ - EMU_DST_HANA_ADAT+6, /* 22 */ - EMU_DST_HANA_ADAT+7, /* 23 */ + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_DOCK_DAC4), + LR_REGS(EMU_DST_DOCK_PHONES), + LR_REGS(EMU_DST_DOCK_SPDIF), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), }; +static_assert(ARRAY_SIZE(emu1010_output_dst) == ARRAY_SIZE(emu1010_output_texts)); /* 1616(m) cardbus */ + +static const char * const snd_emu1616_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("Mana DAC"), +}; + static const unsigned short emu1616_output_dst[] = { - EMU_DST_DOCK_DAC1_LEFT1, - EMU_DST_DOCK_DAC1_RIGHT1, - EMU_DST_DOCK_DAC2_LEFT1, - EMU_DST_DOCK_DAC2_RIGHT1, - EMU_DST_DOCK_DAC3_LEFT1, - EMU_DST_DOCK_DAC3_RIGHT1, - EMU_DST_MDOCK_SPDIF_LEFT1, - EMU_DST_MDOCK_SPDIF_RIGHT1, - EMU_DST_MDOCK_ADAT, - EMU_DST_MDOCK_ADAT+1, - EMU_DST_MDOCK_ADAT+2, - EMU_DST_MDOCK_ADAT+3, - EMU_DST_MDOCK_ADAT+4, - EMU_DST_MDOCK_ADAT+5, - EMU_DST_MDOCK_ADAT+6, - EMU_DST_MDOCK_ADAT+7, - EMU_DST_MANA_DAC_LEFT, - EMU_DST_MANA_DAC_RIGHT, + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + EMU_DST_MANA_DAC_LEFT, EMU_DST_MANA_DAC_RIGHT, }; +static_assert(ARRAY_SIZE(emu1616_output_dst) == ARRAY_SIZE(snd_emu1616_output_texts)); /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) * Each destination has an enum mixer control to choose a data source */ + +static const char * const emu1010_input_texts[] = { + "DSP 0 Capture Enum", + "DSP 1 Capture Enum", + "DSP 2 Capture Enum", + "DSP 3 Capture Enum", + "DSP 4 Capture Enum", + "DSP 5 Capture Enum", + "DSP 6 Capture Enum", + "DSP 7 Capture Enum", + "DSP 8 Capture Enum", + "DSP 9 Capture Enum", + "DSP A Capture Enum", + "DSP B Capture Enum", + "DSP C Capture Enum", + "DSP D Capture Enum", + "DSP E Capture Enum", + "DSP F Capture Enum", + /* These exist only on rev1 EMU1010 cards. */ + "DSP 10 Capture Enum", + "DSP 11 Capture Enum", + "DSP 12 Capture Enum", + "DSP 13 Capture Enum", + "DSP 14 Capture Enum", + "DSP 15 Capture Enum", +}; + static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_1, @@ -263,6 +309,7 @@ static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE_I2S2_LEFT, EMU_DST_ALICE_I2S2_RIGHT, }; +static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -321,6 +368,14 @@ static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol, return 1; } +static const struct snd_kcontrol_new emu1010_output_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_output_source_get, + .put = snd_emu1010_output_source_put +}; + static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -363,110 +418,36 @@ static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol, return 1; } -#define EMU1010_SOURCE_OUTPUT(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_input_output_source_info, \ - .get = snd_emu1010_output_source_get, \ - .put = snd_emu1010_output_source_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_output_enum_ctls[] = { - EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0), - EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5), - EMU1010_SOURCE_OUTPUT("Dock DAC4 Left Playback Enum", 6), - EMU1010_SOURCE_OUTPUT("Dock DAC4 Right Playback Enum", 7), - EMU1010_SOURCE_OUTPUT("Dock Phones Left Playback Enum", 8), - EMU1010_SOURCE_OUTPUT("Dock Phones Right Playback Enum", 9), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 0xa), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 0xb), - EMU1010_SOURCE_OUTPUT("1010 SPDIF Left Playback Enum", 0xc), - EMU1010_SOURCE_OUTPUT("1010 SPDIF Right Playback Enum", 0xd), - EMU1010_SOURCE_OUTPUT("0202 DAC Left Playback Enum", 0xe), - EMU1010_SOURCE_OUTPUT("0202 DAC Right Playback Enum", 0xf), - EMU1010_SOURCE_OUTPUT("1010 ADAT 0 Playback Enum", 0x10), - EMU1010_SOURCE_OUTPUT("1010 ADAT 1 Playback Enum", 0x11), - EMU1010_SOURCE_OUTPUT("1010 ADAT 2 Playback Enum", 0x12), - EMU1010_SOURCE_OUTPUT("1010 ADAT 3 Playback Enum", 0x13), - EMU1010_SOURCE_OUTPUT("1010 ADAT 4 Playback Enum", 0x14), - EMU1010_SOURCE_OUTPUT("1010 ADAT 5 Playback Enum", 0x15), - EMU1010_SOURCE_OUTPUT("1010 ADAT 6 Playback Enum", 0x16), - EMU1010_SOURCE_OUTPUT("1010 ADAT 7 Playback Enum", 0x17), +static const struct snd_kcontrol_new emu1010_input_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_input_source_get, + .put = snd_emu1010_input_source_put }; -/* 1616(m) cardbus */ -static const struct snd_kcontrol_new snd_emu1616_output_enum_ctls[] = { - EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0), - EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 6), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 7), - EMU1010_SOURCE_OUTPUT("Dock ADAT 0 Playback Enum", 8), - EMU1010_SOURCE_OUTPUT("Dock ADAT 1 Playback Enum", 9), - EMU1010_SOURCE_OUTPUT("Dock ADAT 2 Playback Enum", 0xa), - EMU1010_SOURCE_OUTPUT("Dock ADAT 3 Playback Enum", 0xb), - EMU1010_SOURCE_OUTPUT("Dock ADAT 4 Playback Enum", 0xc), - EMU1010_SOURCE_OUTPUT("Dock ADAT 5 Playback Enum", 0xd), - EMU1010_SOURCE_OUTPUT("Dock ADAT 6 Playback Enum", 0xe), - EMU1010_SOURCE_OUTPUT("Dock ADAT 7 Playback Enum", 0xf), - EMU1010_SOURCE_OUTPUT("Mana DAC Left Playback Enum", 0x10), - EMU1010_SOURCE_OUTPUT("Mana DAC Right Playback Enum", 0x11), +static const char * const snd_emu1010_adc_pads[] = { + "ADC1 14dB PAD Audio Dock Capture Switch", + "ADC2 14dB PAD Audio Dock Capture Switch", + "ADC3 14dB PAD Audio Dock Capture Switch", + "ADC1 14dB PAD 0202 Capture Switch", }; - -#define EMU1010_SOURCE_INPUT(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_input_output_source_info, \ - .get = snd_emu1010_input_source_get, \ - .put = snd_emu1010_input_source_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_input_enum_ctls[] = { - EMU1010_SOURCE_INPUT("DSP 0 Capture Enum", 0), - EMU1010_SOURCE_INPUT("DSP 1 Capture Enum", 1), - EMU1010_SOURCE_INPUT("DSP 2 Capture Enum", 2), - EMU1010_SOURCE_INPUT("DSP 3 Capture Enum", 3), - EMU1010_SOURCE_INPUT("DSP 4 Capture Enum", 4), - EMU1010_SOURCE_INPUT("DSP 5 Capture Enum", 5), - EMU1010_SOURCE_INPUT("DSP 6 Capture Enum", 6), - EMU1010_SOURCE_INPUT("DSP 7 Capture Enum", 7), - EMU1010_SOURCE_INPUT("DSP 8 Capture Enum", 8), - EMU1010_SOURCE_INPUT("DSP 9 Capture Enum", 9), - EMU1010_SOURCE_INPUT("DSP A Capture Enum", 0xa), - EMU1010_SOURCE_INPUT("DSP B Capture Enum", 0xb), - EMU1010_SOURCE_INPUT("DSP C Capture Enum", 0xc), - EMU1010_SOURCE_INPUT("DSP D Capture Enum", 0xd), - EMU1010_SOURCE_INPUT("DSP E Capture Enum", 0xe), - EMU1010_SOURCE_INPUT("DSP F Capture Enum", 0xf), - EMU1010_SOURCE_INPUT("DSP 10 Capture Enum", 0x10), - EMU1010_SOURCE_INPUT("DSP 11 Capture Enum", 0x11), - EMU1010_SOURCE_INPUT("DSP 12 Capture Enum", 0x12), - EMU1010_SOURCE_INPUT("DSP 13 Capture Enum", 0x13), - EMU1010_SOURCE_INPUT("DSP 14 Capture Enum", 0x14), - EMU1010_SOURCE_INPUT("DSP 15 Capture Enum", 0x15), +static const unsigned short snd_emu1010_adc_pad_regs[] = { + EMU_HANA_DOCK_ADC_PAD1, + EMU_HANA_DOCK_ADC_PAD2, + EMU_HANA_DOCK_ADC_PAD3, + EMU_HANA_0202_ADC_PAD1, }; - - #define snd_emu1010_adc_pads_info snd_ctl_boolean_mono_info static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; + ucontrol->value.integer.value[0] = (emu->emu1010.adc_pads & mask) ? 1 : 0; return 0; } @@ -474,7 +455,7 @@ static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; unsigned int val, cache; val = ucontrol->value.integer.value[0]; cache = emu->emu1010.adc_pads; @@ -490,23 +471,29 @@ static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct return 0; } +static const struct snd_kcontrol_new emu1010_adc_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_adc_pads_info, + .get = snd_emu1010_adc_pads_get, + .put = snd_emu1010_adc_pads_put +}; -#define EMU1010_ADC_PADS(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_adc_pads_info, \ - .get = snd_emu1010_adc_pads_get, \ - .put = snd_emu1010_adc_pads_put, \ - .private_value = chid \ -} +static const char * const snd_emu1010_dac_pads[] = { + "DAC1 Audio Dock 14dB PAD Playback Switch", + "DAC2 Audio Dock 14dB PAD Playback Switch", + "DAC3 Audio Dock 14dB PAD Playback Switch", + "DAC4 Audio Dock 14dB PAD Playback Switch", + "DAC1 0202 14dB PAD Playback Switch", +}; -static const struct snd_kcontrol_new snd_emu1010_adc_pads[] = { - EMU1010_ADC_PADS("ADC1 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD1), - EMU1010_ADC_PADS("ADC2 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD2), - EMU1010_ADC_PADS("ADC3 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD3), - EMU1010_ADC_PADS("ADC1 14dB PAD 0202 Capture Switch", EMU_HANA_0202_ADC_PAD1), +static const unsigned short snd_emu1010_dac_regs[] = { + EMU_HANA_DOCK_DAC_PAD1, + EMU_HANA_DOCK_DAC_PAD2, + EMU_HANA_DOCK_DAC_PAD3, + EMU_HANA_DOCK_DAC_PAD4, + EMU_HANA_0202_DAC_PAD1, }; #define snd_emu1010_dac_pads_info snd_ctl_boolean_mono_info @@ -514,7 +501,8 @@ static const struct snd_kcontrol_new snd_emu1010_adc_pads[] = { static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; + ucontrol->value.integer.value[0] = (emu->emu1010.dac_pads & mask) ? 1 : 0; return 0; } @@ -522,7 +510,7 @@ static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; unsigned int val, cache; val = ucontrol->value.integer.value[0]; cache = emu->emu1010.dac_pads; @@ -538,24 +526,12 @@ static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct return 0; } - - -#define EMU1010_DAC_PADS(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_dac_pads_info, \ - .get = snd_emu1010_dac_pads_get, \ - .put = snd_emu1010_dac_pads_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_dac_pads[] = { - EMU1010_DAC_PADS("DAC1 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD1), - EMU1010_DAC_PADS("DAC2 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD2), - EMU1010_DAC_PADS("DAC3 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD3), - EMU1010_DAC_PADS("DAC4 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD4), - EMU1010_DAC_PADS("DAC1 0202 14dB PAD Playback Switch", EMU_HANA_0202_DAC_PAD1), +static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_dac_pads_info, + .get = snd_emu1010_dac_pads_get, + .put = snd_emu1010_dac_pads_put }; @@ -927,22 +903,19 @@ static int snd_audigy_i2c_volume_put(struct snd_kcontrol *kcontrol, return change; } -#define I2C_VOLUME(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ - SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ - .info = snd_audigy_i2c_volume_info, \ - .get = snd_audigy_i2c_volume_get, \ - .put = snd_audigy_i2c_volume_put, \ - .tlv = { .p = snd_audigy_db_scale2 }, \ - .private_value = chid \ -} - +static const struct snd_kcontrol_new i2c_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_audigy_i2c_volume_info, + .get = snd_audigy_i2c_volume_get, + .put = snd_audigy_i2c_volume_put, + .tlv = { .p = snd_audigy_db_scale2 } +}; -static const struct snd_kcontrol_new snd_audigy_i2c_volume_ctls[] = { - I2C_VOLUME("Mic Capture Volume", 0), - I2C_VOLUME("Line Capture Volume", 0) +static const char * const snd_audigy_i2c_volume_ctls[] = { + "Mic Capture Volume", + "Line Capture Volume", }; #if 0 @@ -1958,34 +1931,26 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { /* 1616(m) cardbus */ - int i; - - for (i = 0; i < ARRAY_SIZE(snd_emu1616_output_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1616_output_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_input_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads) - 2; i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_adc_pads[i], emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads) - 2; i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_dac_pads[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &emu1010_output_source_ctl, + snd_emu1616_output_texts, + ARRAY_SIZE(snd_emu1616_output_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, + ARRAY_SIZE(emu1010_input_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_adc_pads_ctl, + snd_emu1010_adc_pads, + ARRAY_SIZE(snd_emu1010_adc_pads) - 2); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_dac_pads_ctl, + snd_emu1010_dac_pads, + ARRAY_SIZE(snd_emu1010_dac_pads) - 2); + if (err < 0) + return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) @@ -2001,34 +1966,26 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, } else if (emu->card_capabilities->emu_model) { /* all other e-mu cards for now */ - int i; - - for (i = 0; i < ARRAY_SIZE(snd_emu1010_output_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_output_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_input_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_adc_pads[i], emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_dac_pads[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &emu1010_output_source_ctl, + emu1010_output_texts, + ARRAY_SIZE(emu1010_output_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, + ARRAY_SIZE(emu1010_input_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_adc_pads_ctl, + snd_emu1010_adc_pads, + ARRAY_SIZE(snd_emu1010_adc_pads)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_dac_pads_ctl, + snd_emu1010_dac_pads, + ARRAY_SIZE(snd_emu1010_dac_pads)); + if (err < 0) + return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) @@ -2044,17 +2001,15 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, } if ( emu->card_capabilities->i2c_adc) { - int i; - err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_capture_source, emu)); if (err < 0) return err; - for (i = 0; i < ARRAY_SIZE(snd_audigy_i2c_volume_ctls); i++) { - err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_volume_ctls[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &i2c_volume_ctl, + snd_audigy_i2c_volume_ctls, + ARRAY_SIZE(snd_audigy_i2c_volume_ctls)); + if (err < 0) + return err; } if (emu->card_capabilities->ac97_chip && emu->audigy) { -- cgit v1.2.3 From 511cbe8f59e30cd09a04c1dbafe6337d9111e88a Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:06 +0200 Subject: ALSA: emu10k1: un-hardcode E-MU mixer control callbacks somewhat Instead of hard-coding the card-specific arrays and their sizes in each function, use a more data-driven approach. As a drive-by, also hide the unavailable I2S input destinations on the 1616 cardbus card. Also as a drive-by, use more assignments at variable declaration for brevity. This also removes the pointless masking of kctl.private_value. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 155 ++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 54 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 92545559a36c..c5ebec52c8cf 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -311,28 +311,89 @@ static const unsigned short emu1010_input_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); +struct snd_emu1010_routing_info { + const char * const *src_texts; + const unsigned short *src_regs; + const unsigned short *out_regs; + const unsigned short *in_regs; + unsigned n_srcs; + unsigned n_outs; + unsigned n_ins; +}; + +const struct snd_emu1010_routing_info emu1010_routing_info[] = { + { + .src_regs = emu1010_src_regs, + .src_texts = emu1010_src_texts, + .n_srcs = ARRAY_SIZE(emu1010_src_texts), + + .out_regs = emu1010_output_dst, + .n_outs = ARRAY_SIZE(emu1010_output_dst), + + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst), + }, + { + /* 1616(m) cardbus */ + .src_regs = emu1616_src_regs, + .src_texts = emu1616_src_texts, + .n_srcs = ARRAY_SIZE(emu1616_src_texts), + + .out_regs = emu1616_output_dst, + .n_outs = ARRAY_SIZE(emu1616_output_dst), + + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, +}; + +static unsigned emu1010_idx(struct snd_emu10k1 *emu) +{ + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) + return 1; + else + return 0; +} + +static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu, + int channel, int src) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->out_regs[channel], emu_ri->src_regs[src]); +} + +static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu, + int channel, int src) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->in_regs[channel], emu_ri->src_regs[src]); +} + static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - return snd_ctl_enum_info(uinfo, 1, 49, emu1616_src_texts); - else - return snd_ctl_enum_info(uinfo, 1, 53, emu1010_src_texts); + return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs, emu_ri->src_texts); } static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_output_dst, emu->emu1010.output_source */ - if (channel >= 24 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - channel >= 18)) + if (channel >= emu_ri->n_outs) return -EINVAL; ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel]; return 0; @@ -342,30 +403,22 @@ static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int val; - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; - val = ucontrol->value.enumerated.item[0]; - if (val >= 53 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - val >= 49)) + if (val >= emu_ri->n_srcs) return -EINVAL; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_output_dst, emu->emu1010.output_source */ - if (channel >= 24 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - channel >= 18)) + if (channel >= emu_ri->n_outs) return -EINVAL; - if (emu->emu1010.output_source[channel] == val) - return 0; - emu->emu1010.output_source[channel] = val; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - snd_emu1010_fpga_link_dst_src_write(emu, - emu1616_output_dst[channel], emu1616_src_regs[val]); - else - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_output_dst[channel], emu1010_src_regs[val]); - return 1; + change = (emu->emu1010.output_source[channel] != val); + if (change) { + emu->emu1010.output_source[channel] = val; + snd_emu1010_output_source_apply(emu, channel, val); + } + return change; } static const struct snd_kcontrol_new emu1010_output_source_ctl = { @@ -380,11 +433,11 @@ static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_input_dst, emu->emu1010.input_source */ - if (channel >= 22) + if (channel >= emu_ri->n_ins) return -EINVAL; ucontrol->value.enumerated.item[0] = emu->emu1010.input_source[channel]; return 0; @@ -394,28 +447,22 @@ static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int val; - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; - val = ucontrol->value.enumerated.item[0]; - if (val >= 53 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - val >= 49)) + if (val >= emu_ri->n_srcs) return -EINVAL; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_input_dst, emu->emu1010.input_source */ - if (channel >= 22) + if (channel >= emu_ri->n_ins) return -EINVAL; - if (emu->emu1010.input_source[channel] == val) - return 0; - emu->emu1010.input_source[channel] = val; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_input_dst[channel], emu1616_src_regs[val]); - else - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_input_dst[channel], emu1010_src_regs[val]); - return 1; + change = (emu->emu1010.input_source[channel] != val); + if (change) { + emu->emu1010.input_source[channel] = val; + snd_emu1010_input_source_apply(emu, channel, val); + } + return change; } static const struct snd_kcontrol_new emu1010_input_source_ctl = { -- cgit v1.2.3 From cc766807a208bfa06c204be784ec099fb25c87a4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:07 +0200 Subject: ALSA: emu10k1: fix return value of snd_emu1010_dac_pads_put() It returned zero even if the value had changed. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index c5ebec52c8cf..ef05d4f3316c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -559,18 +559,21 @@ static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; unsigned int val, cache; + int change; + val = ucontrol->value.integer.value[0]; cache = emu->emu1010.dac_pads; if (val == 1) cache = cache | mask; else cache = cache & ~mask; - if (cache != emu->emu1010.dac_pads) { + change = (cache != emu->emu1010.dac_pads); + if (change) { snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, cache ); emu->emu1010.dac_pads = cache; } - return 0; + return change; } static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { -- cgit v1.2.3 From 1fc710f06aa8f33abab4fdded9463eefdff7d390 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:08 +0200 Subject: ALSA: emu10k1: make E-MU I/O routing init data-driven ... and move it to the mixer init, as it's logically part of it. As a side effect, this fixes the initial values of the input destination mixer controls, which would have previously remained at "Silent" despite different defaults. This didn't really matter, though, as ALSA state restoration would hide that bug beyond first use. Note that this completely does away with clearing the output routing registers, as it was rather pointless - we just programmed the FPGA (resetting it first if necessary), so everything is zeroed anyway (that's documented by Xilinx, and as further evidence, some of the loops terminated too early, and we didn't bother clearing the high channels of the input routes at all, all with no observed adverse effects). As a drive-by, this also fixes some capture channel defaults - any EMU_SRC_*2 isn't a sensible value in 1x clock mode. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_main.c | 248 +-------------------------------------- sound/pci/emu10k1/emumixer.c | 97 +++++++++++++++ 2 files changed, 99 insertions(+), 246 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 793ae8797172..6a3476de74e6 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -796,7 +796,6 @@ static void emu1010_firmware_work(struct work_struct *work) */ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) { - unsigned int i; u32 tmp, tmp2, reg; int err; @@ -887,253 +886,10 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* Audio Dock LEDs. */ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_LOCK | EMU_HANA_DOCK_LEDS_2_48K); -#if 0 - /* For 96kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT2); -#endif -#if 0 - /* For 192kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_RIGHT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_HAMOA_ADC_LEFT4); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_HAMOA_ADC_RIGHT4); -#endif -#if 1 - /* For 48kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_DOCK_ADC2_RIGHT1); - /* Pavel Hofman - setting defaults for 8 more capture channels - * Defaults only, users will set their own values anyways, let's - * just copy/paste. - */ - - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_8, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_9, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_A, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_B, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_E, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_F, EMU_SRC_DOCK_ADC2_RIGHT1); -#endif -#if 0 - /* Original */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HANA_ADAT); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HANA_ADAT + 1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_HANA_ADAT + 2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_HANA_ADAT + 3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_8, EMU_SRC_HANA_ADAT + 4); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_9, EMU_SRC_HANA_ADAT + 5); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_A, EMU_SRC_HANA_ADAT + 6); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_B, EMU_SRC_HANA_ADAT + 7); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_E, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_F, EMU_SRC_HAMOA_ADC_LEFT2); -#endif - for (i = 0; i < 0x20; i++) { - /* AudioDock Elink <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0100 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 4; i++) { - /* Hana SPDIF Out <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0200 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 7; i++) { - /* Hamoa DAC <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0300 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 7; i++) { - /* Hana ADAT Out <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, EMU_DST_HANA_ADAT + i, EMU_SRC_SILENCE); - } - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S0_LEFT, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S0_RIGHT, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S1_LEFT, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S1_RIGHT, EMU_SRC_DOCK_ADC2_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S2_LEFT, EMU_SRC_DOCK_ADC3_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S2_RIGHT, EMU_SRC_DOCK_ADC3_RIGHT1); + // The routes are all set to EMU_SRC_SILENCE due to the reset, + // so it is safe to simply enable the outputs. snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); -#if 0 - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32B + 2); /* ALICE2 bus 0xa2 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32B + 3); /* ALICE2 bus 0xa3 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 2); /* ALICE2 bus 0xb2 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); /* ALICE2 bus 0xb3 */ -#endif - /* Default outputs */ - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { - /* 1616(M) cardbus default outputs */ - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[0] = 17; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[1] = 18; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[2] = 19; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[3] = 20; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[4] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[5] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_MANA_DAC_LEFT, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[16] = 17; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_MANA_DAC_RIGHT, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[17] = 18; - } else { - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[0] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[1] = 22; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[2] = 23; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[3] = 24; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[4] = 25; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[5] = 26; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC4_LEFT1, EMU_SRC_ALICE_EMU32A + 6); - emu->emu1010.output_source[6] = 27; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC4_RIGHT1, EMU_SRC_ALICE_EMU32A + 7); - emu->emu1010.output_source[7] = 28; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_PHONES_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[8] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_PHONES_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[9] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[10] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[11] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[12] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[13] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[14] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[15] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[16] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[17] = 22; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 2, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[18] = 23; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 3, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[19] = 24; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 4, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[20] = 25; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 5, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[21] = 26; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 6, EMU_SRC_ALICE_EMU32A + 6); - emu->emu1010.output_source[22] = 27; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 7, EMU_SRC_ALICE_EMU32A + 7); - emu->emu1010.output_source[23] = 28; - } - return 0; } /* diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index ef05d4f3316c..b3e5a842ac17 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -231,6 +231,20 @@ static const unsigned short emu1010_output_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_output_dst) == ARRAY_SIZE(emu1010_output_texts)); +static const unsigned short emu1010_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, +}; +static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst)); + /* 1616(m) cardbus */ static const char * const snd_emu1616_output_texts[] = { @@ -252,6 +266,17 @@ static const unsigned short emu1616_output_dst[] = { }; static_assert(ARRAY_SIZE(emu1616_output_dst) == ARRAY_SIZE(snd_emu1616_output_texts)); +static const unsigned short emu1616_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst)); + /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) @@ -311,11 +336,43 @@ static const unsigned short emu1010_input_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); +static const unsigned short emu1010_input_dflt[] = { + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + /* Pavel Hofman - setting defaults for all capture channels. + * Defaults only, users will set their own values anyways, let's + * just copy/paste. */ + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + EMU_SRC_DOCK_ADC3_LEFT1, + EMU_SRC_DOCK_ADC3_RIGHT1, +}; +static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); + struct snd_emu1010_routing_info { const char * const *src_texts; const unsigned short *src_regs; const unsigned short *out_regs; const unsigned short *in_regs; + const unsigned short *out_dflts; + const unsigned short *in_dflts; unsigned n_srcs; unsigned n_outs; unsigned n_ins; @@ -327,9 +384,11 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .src_texts = emu1010_src_texts, .n_srcs = ARRAY_SIZE(emu1010_src_texts), + .out_dflts = emu1010_output_dflt, .out_regs = emu1010_output_dst, .n_outs = ARRAY_SIZE(emu1010_output_dst), + .in_dflts = emu1010_input_dflt, .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst), }, @@ -339,9 +398,11 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .src_texts = emu1616_src_texts, .n_srcs = ARRAY_SIZE(emu1616_src_texts), + .out_dflts = emu1616_output_dflt, .out_regs = emu1616_output_dst, .n_outs = ARRAY_SIZE(emu1616_output_dst), + .in_dflts = emu1010_input_dflt, .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, }, @@ -375,6 +436,28 @@ static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu, emu_ri->in_regs[channel], emu_ri->src_regs[src]); } +static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + for (unsigned i = 0; i < emu_ri->n_outs; i++) + snd_emu1010_output_source_apply( + emu, i, emu->emu1010.output_source[i]); + for (unsigned i = 0; i < emu_ri->n_ins; i++) + snd_emu1010_input_source_apply( + emu, i, emu->emu1010.input_source[i]); +} + +static u8 emu1010_map_source(const struct snd_emu1010_routing_info *emu_ri, + unsigned val) +{ + for (unsigned i = 0; i < emu_ri->n_srcs; i++) + if (val == emu_ri->src_regs[i]) + return i; + return 0; +} + static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -1979,6 +2062,20 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, return err; } + if (emu->card_capabilities->emu_model) { + unsigned i, emu_idx = emu1010_idx(emu); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu_idx]; + + for (i = 0; i < emu_ri->n_ins; i++) + emu->emu1010.input_source[i] = + emu1010_map_source(emu_ri, emu_ri->in_dflts[i]); + for (i = 0; i < emu_ri->n_outs; i++) + emu->emu1010.output_source[i] = + emu1010_map_source(emu_ri, emu_ri->out_dflts[i]); + snd_emu1010_apply_sources(emu); + } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { /* 1616(m) cardbus */ err = add_ctls(emu, &emu1010_output_source_ctl, -- cgit v1.2.3 From 97f1582e92c91e77bcf4af3dbf445c6694eb2dff Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:09 +0200 Subject: ALSA: emu10k1: make E-MU mixer control creation more data-driven The more card models are handled separately, the more code duplication this saves. add_emu1010_source_mixers() is factored out the save duplication in a later commit. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 100 +++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 51 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index b3e5a842ac17..4fda6c2829ac 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -368,6 +368,7 @@ static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); struct snd_emu1010_routing_info { const char * const *src_texts; + const char * const *out_texts; const unsigned short *src_regs; const unsigned short *out_regs; const unsigned short *in_regs; @@ -386,6 +387,7 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .out_dflts = emu1010_output_dflt, .out_regs = emu1010_output_dst, + .out_texts = emu1010_output_texts, .n_outs = ARRAY_SIZE(emu1010_output_dst), .in_dflts = emu1010_input_dflt, @@ -400,6 +402,7 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .out_dflts = emu1616_output_dflt, .out_regs = emu1616_output_dst, + .out_texts = snd_emu1616_output_texts, .n_outs = ARRAY_SIZE(emu1616_output_dst), .in_dflts = emu1010_input_dflt, @@ -556,6 +559,21 @@ static const struct snd_kcontrol_new emu1010_input_source_ctl = { .put = snd_emu1010_input_source_put }; +static int add_emu1010_source_mixers(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + int err; + + err = add_ctls(emu, &emu1010_output_source_ctl, + emu_ri->out_texts, emu_ri->n_outs); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, emu_ri->n_ins); + return err; +} + static const char * const snd_emu1010_adc_pads[] = { "ADC1 14dB PAD Audio Dock Capture Switch", @@ -668,6 +686,29 @@ static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { }; +struct snd_emu1010_pads_info { + const char * const *adc_ctls, * const *dac_ctls; + unsigned n_adc_ctls, n_dac_ctls; +}; + +const struct snd_emu1010_pads_info emu1010_pads_info[] = { + { + /* all other e-mu cards for now */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads), + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads), + }, + { + /* 1616(m) cardbus */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, + }, +}; + + static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -2066,6 +2107,7 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, unsigned i, emu_idx = emu1010_idx(emu); const struct snd_emu1010_routing_info *emu_ri = &emu1010_routing_info[emu_idx]; + const struct snd_emu1010_pads_info *emu_pi = &emu1010_pads_info[emu_idx]; for (i = 0; i < emu_ri->n_ins; i++) emu->emu1010.input_source[i] = @@ -2074,69 +2116,21 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, emu->emu1010.output_source[i] = emu1010_map_source(emu_ri, emu_ri->out_dflts[i]); snd_emu1010_apply_sources(emu); - } - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { - /* 1616(m) cardbus */ - err = add_ctls(emu, &emu1010_output_source_ctl, - snd_emu1616_output_texts, - ARRAY_SIZE(snd_emu1616_output_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_input_source_ctl, - emu1010_input_texts, - ARRAY_SIZE(emu1010_input_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_adc_pads_ctl, - snd_emu1010_adc_pads, - ARRAY_SIZE(snd_emu1010_adc_pads) - 2); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_dac_pads_ctl, - snd_emu1010_dac_pads, - ARRAY_SIZE(snd_emu1010_dac_pads) - 2); - if (err < 0) - return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_out, emu)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_in, emu)); - if (err < 0) - return err; - } else if (emu->card_capabilities->emu_model) { - /* all other e-mu cards for now */ - err = add_ctls(emu, &emu1010_output_source_ctl, - emu1010_output_texts, - ARRAY_SIZE(emu1010_output_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_input_source_ctl, - emu1010_input_texts, - ARRAY_SIZE(emu1010_input_texts)); - if (err < 0) - return err; err = add_ctls(emu, &emu1010_adc_pads_ctl, - snd_emu1010_adc_pads, - ARRAY_SIZE(snd_emu1010_adc_pads)); + emu_pi->adc_ctls, emu_pi->n_adc_ctls); if (err < 0) return err; err = add_ctls(emu, &emu1010_dac_pads_ctl, - snd_emu1010_dac_pads, - ARRAY_SIZE(snd_emu1010_dac_pads)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_internal_clock, emu)); + emu_pi->dac_ctls, emu_pi->n_dac_ctls); if (err < 0) return err; + err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_optical_out, emu)); if (err < 0) @@ -2145,6 +2139,10 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, snd_ctl_new1(&snd_emu1010_optical_in, emu)); if (err < 0) return err; + + err = add_emu1010_source_mixers(emu); + if (err < 0) + return err; } if ( emu->card_capabilities->i2c_adc) { -- cgit v1.2.3 From f69d705d3972fae19b4b00c7643efdd3d2953f25 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:10 +0200 Subject: ALSA: emu10k1: improve mixer controls for E-MU 1010 rev2 card This card has rather different inputs/outputs due to switching from the AudioDock to the MicroDock. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 106 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 4fda6c2829ac..0e3007120fb8 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -172,6 +172,38 @@ static const unsigned short emu1010_src_regs[] = { }; static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts)); +/* 1010 rev2 */ + +#define EMU1010b_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Dock Mic", "A", "B"), \ + LR_TEXTS("Dock ADC1"), \ + LR_TEXTS("Dock ADC2"), \ + LR_TEXTS("0202 ADC"), \ + LR_TEXTS("Dock SPDIF"), \ + LR_TEXTS("1010 SPDIF"), \ + ADAT_TEXTS("Dock "), \ + ADAT_TEXTS("1010 ") + +static const char * const emu1010b_src_texts[] = { + EMU1010b_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu1010b_src_regs[] = { + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu1010b_src_regs) == ARRAY_SIZE(emu1010b_src_texts)); + /* 1616(m) cardbus */ #define EMU1616_COMMON_TEXTS \ @@ -245,6 +277,44 @@ static const unsigned short emu1010_output_dflt[] = { }; static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst)); +/* 1010 rev2 */ + +static const char * const snd_emu1010b_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("0202 DAC"), + LR_CTLS("1010 SPDIF"), + ADAT_CTLS("1010 "), +}; + +static const unsigned short emu1010b_output_dst[] = { + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), +}; +static_assert(ARRAY_SIZE(emu1010b_output_dst) == ARRAY_SIZE(snd_emu1010b_output_texts)); + +static const unsigned short emu1010b_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, EMU_SRC_ALICE_EMU32A+2, EMU_SRC_ALICE_EMU32A+3, + EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7, +}; + /* 1616(m) cardbus */ static const char * const snd_emu1616_output_texts[] = { @@ -394,6 +464,21 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst), }, + { + /* rev2 1010 */ + .src_regs = emu1010b_src_regs, + .src_texts = emu1010b_src_texts, + .n_srcs = ARRAY_SIZE(emu1010b_src_texts), + + .out_dflts = emu1010b_output_dflt, + .out_regs = emu1010b_output_dst, + .out_texts = snd_emu1010b_output_texts, + .n_outs = ARRAY_SIZE(emu1010b_output_dst), + + .in_dflts = emu1010_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, { /* 1616(m) cardbus */ .src_regs = emu1616_src_regs, @@ -414,6 +499,8 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { static unsigned emu1010_idx(struct snd_emu10k1 *emu) { if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) + return 2; + else if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) return 1; else return 0; @@ -576,17 +663,17 @@ static int add_emu1010_source_mixers(struct snd_emu10k1 *emu) static const char * const snd_emu1010_adc_pads[] = { + "ADC1 14dB PAD 0202 Capture Switch", "ADC1 14dB PAD Audio Dock Capture Switch", "ADC2 14dB PAD Audio Dock Capture Switch", "ADC3 14dB PAD Audio Dock Capture Switch", - "ADC1 14dB PAD 0202 Capture Switch", }; static const unsigned short snd_emu1010_adc_pad_regs[] = { + EMU_HANA_0202_ADC_PAD1, EMU_HANA_DOCK_ADC_PAD1, EMU_HANA_DOCK_ADC_PAD2, EMU_HANA_DOCK_ADC_PAD3, - EMU_HANA_0202_ADC_PAD1, }; #define snd_emu1010_adc_pads_info snd_ctl_boolean_mono_info @@ -629,19 +716,19 @@ static const struct snd_kcontrol_new emu1010_adc_pads_ctl = { static const char * const snd_emu1010_dac_pads[] = { + "DAC1 0202 14dB PAD Playback Switch", "DAC1 Audio Dock 14dB PAD Playback Switch", "DAC2 Audio Dock 14dB PAD Playback Switch", "DAC3 Audio Dock 14dB PAD Playback Switch", "DAC4 Audio Dock 14dB PAD Playback Switch", - "DAC1 0202 14dB PAD Playback Switch", }; static const unsigned short snd_emu1010_dac_regs[] = { + EMU_HANA_0202_DAC_PAD1, EMU_HANA_DOCK_DAC_PAD1, EMU_HANA_DOCK_DAC_PAD2, EMU_HANA_DOCK_DAC_PAD3, EMU_HANA_DOCK_DAC_PAD4, - EMU_HANA_0202_DAC_PAD1, }; #define snd_emu1010_dac_pads_info snd_ctl_boolean_mono_info @@ -700,10 +787,17 @@ const struct snd_emu1010_pads_info emu1010_pads_info[] = { .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads), }, { - /* 1616(m) cardbus */ + /* rev2 1010 */ .adc_ctls = snd_emu1010_adc_pads, - .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 1, .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 1, + }, + { + /* 1616(m) cardbus */ + .adc_ctls = snd_emu1010_adc_pads + 1, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .dac_ctls = snd_emu1010_dac_pads + 1, .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, }, }; -- cgit v1.2.3 From 6f3609f8a3da1214cd78f8a8a2ee2dab8fcc4505 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:11 +0200 Subject: ALSA: emu10k1: add explicit support for E-MU 0404 Unlike the other models, this is actually a distinct card, rather than an E-MU 1010 with different "dongles". It is stereo only, and supports no ADAT (there is no trace of ADAT in the manual, switching the output mode to ADAT has no effect, and switching the input mode to ADAT just breaks input (presumably ... my only ADAT source is the card's output)). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-10-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 + sound/pci/emu10k1/emu10k1_main.c | 20 ++++--- sound/pci/emu10k1/emumixer.c | 112 +++++++++++++++++++++++++++++++++------ sound/pci/emu10k1/emuproc.c | 18 ++++--- 4 files changed, 123 insertions(+), 29 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index b263c762c01a..aab45a23320e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1621,7 +1621,9 @@ struct snd_emu_chip_details { unsigned int ca0108_chip:1; /* Audigy 2 Value */ unsigned int ca_cardbus_chip:1; /* Audigy 2 ZS Notebook */ unsigned int ca0151_chip:1; /* P16V */ + unsigned int spk20:1; /* Stereo only */ unsigned int spk71:1; /* Has 7.1 speakers */ + unsigned int no_adat:1; /* Has no ADAT, only SPDIF */ unsigned int sblive51:1; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ unsigned int spdif_bug:1; /* Has Spdif phasing bug */ unsigned int ac97_chip:2; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 6a3476de74e6..da7c988b5c97 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -852,9 +852,14 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg); - /* Optical -> ADAT I/O */ - emu->emu1010.optical_in = 1; /* IN_ADAT */ - emu->emu1010.optical_out = 1; /* OUT_ADAT */ + if (emu->card_capabilities->no_adat) { + emu->emu1010.optical_in = 0; /* IN_SPDIF */ + emu->emu1010.optical_out = 0; /* OUT_SPDIF */ + } else { + /* Optical -> ADAT I/O */ + emu->emu1010.optical_in = 1; /* IN_ADAT */ + emu->emu1010.optical_out = 1; /* OUT_ADAT */ + } tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : EMU_HANA_OPTICAL_IN_SPDIF) | (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : EMU_HANA_OPTICAL_OUT_SPDIF); snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp); @@ -1117,7 +1122,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */ /* This is MAEM8850 "HanaLite" */ /* Supports sync daughter card. */ @@ -1127,7 +1133,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0102_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */ /* EMU0404 PCIe */ /* Does NOT support sync daughter card. */ @@ -1136,7 +1143,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 PCIe ver_03 */ {.vendor = 0x1102, .device = 0x0008, .driver = "Audigy2", .name = "SB Audigy 2 Value [Unknown]", diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 0e3007120fb8..41a1cf10c6d8 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -144,6 +144,8 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, EMU_SRC_ALICE_EMU32B+0xe, \ EMU_SRC_ALICE_EMU32B+0xf +/* 1010 rev1 */ + #define EMU1010_COMMON_TEXTS \ "Silence", \ PAIR_TEXTS("Dock Mic", "A", "B"), \ @@ -230,6 +232,26 @@ static const unsigned short emu1616_src_regs[] = { }; static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); +/* 0404 rev1 & rev2 */ + +#define EMU0404_COMMON_TEXTS \ + "Silence", \ + LR_TEXTS("ADC"), \ + LR_TEXTS("SPDIF") + +static const char * const emu0404_src_texts[] = { + EMU0404_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu0404_src_regs[] = { + EMU_SRC_SILENCE, + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu0404_src_regs) == ARRAY_SIZE(emu0404_src_texts)); + /* * Data destinations - physical EMU outputs. * Each destination has an enum mixer control to choose a data source @@ -238,6 +260,8 @@ static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); #define LR_CTLS(base) LR_PS(base, " Playback Enum") #define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum") +/* 1010 rev1 */ + static const char * const emu1010_output_texts[] = { LR_CTLS("Dock DAC1"), LR_CTLS("Dock DAC2"), @@ -347,6 +371,25 @@ static const unsigned short emu1616_output_dflt[] = { }; static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst)); +/* 0404 rev1 & rev2 */ + +static const char * const snd_emu0404_output_texts[] = { + LR_CTLS("DAC"), + LR_CTLS("SPDIF"), +}; + +static const unsigned short emu0404_output_dst[] = { + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), +}; +static_assert(ARRAY_SIZE(emu0404_output_dst) == ARRAY_SIZE(snd_emu0404_output_texts)); + +static const unsigned short emu0404_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu0404_output_dflt) == ARRAY_SIZE(emu0404_output_dst)); + /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) @@ -436,6 +479,25 @@ static const unsigned short emu1010_input_dflt[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); +static const unsigned short emu0404_input_dflt[] = { + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_HANA_SPDIF_LEFT1, + EMU_SRC_HANA_SPDIF_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, +}; + struct snd_emu1010_routing_info { const char * const *src_texts; const char * const *out_texts; @@ -451,6 +513,7 @@ struct snd_emu1010_routing_info { const struct snd_emu1010_routing_info emu1010_routing_info[] = { { + /* rev1 1010 */ .src_regs = emu1010_src_regs, .src_texts = emu1010_src_texts, .n_srcs = ARRAY_SIZE(emu1010_src_texts), @@ -494,16 +557,26 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, }, + { + /* 0404 */ + .src_regs = emu0404_src_regs, + .src_texts = emu0404_src_texts, + .n_srcs = ARRAY_SIZE(emu0404_src_texts), + + .out_dflts = emu0404_output_dflt, + .out_regs = emu0404_output_dst, + .out_texts = snd_emu0404_output_texts, + .n_outs = ARRAY_SIZE(emu0404_output_dflt), + + .in_dflts = emu0404_input_dflt, + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, }; static unsigned emu1010_idx(struct snd_emu10k1 *emu) { - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - return 2; - else if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) - return 1; - else - return 0; + return emu->card_capabilities->emu_model - 1; } static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu, @@ -780,7 +853,7 @@ struct snd_emu1010_pads_info { const struct snd_emu1010_pads_info emu1010_pads_info[] = { { - /* all other e-mu cards for now */ + /* rev1 1010 */ .adc_ctls = snd_emu1010_adc_pads, .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads), .dac_ctls = snd_emu1010_dac_pads, @@ -800,6 +873,13 @@ const struct snd_emu1010_pads_info emu1010_pads_info[] = { .dac_ctls = snd_emu1010_dac_pads + 1, .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, }, + { + /* 0404 */ + .adc_ctls = NULL, + .n_adc_ctls = 0, + .dac_ctls = NULL, + .n_dac_ctls = 0, + }, }; @@ -2225,14 +2305,16 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (err < 0) return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_out, emu)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_in, emu)); - if (err < 0) - return err; + if (!emu->card_capabilities->no_adat) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_out, emu)); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_in, emu)); + if (err < 0) + return err; + } err = add_emu1010_source_mixers(emu); if (err < 0) diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index c92253de881e..708aff6cf09a 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -229,14 +229,16 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, u32 rate; if (emu->card_capabilities->emu_model) { - snd_emu1010_fpga_read(emu, 0x38, &value); - if ((value & 0x1) == 0) { - snd_emu1010_fpga_read(emu, 0x2a, &value); - snd_emu1010_fpga_read(emu, 0x2b, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "ADAT Locked : %u\n", rate); - } else { - snd_iprintf(buffer, "ADAT Unlocked\n"); + if (!emu->card_capabilities->no_adat) { + snd_emu1010_fpga_read(emu, 0x38, &value); + if ((value & 0x1) == 0) { + snd_emu1010_fpga_read(emu, 0x2a, &value); + snd_emu1010_fpga_read(emu, 0x2b, &value2); + rate = 0x1770000 / (((value << 5) | value2)+1); + snd_iprintf(buffer, "ADAT Locked : %u\n", rate); + } else { + snd_iprintf(buffer, "ADAT Unlocked\n"); + } } snd_emu1010_fpga_read(emu, 0x20, &value); if ((value & 0x4) == 0) { -- cgit v1.2.3 From 216abe45cf4addba4e4c1eb2fae24762ffdefe9e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:12 +0200 Subject: ALSA: emu10k1: make struct snd_emu1010 less wasteful Shrink the {in,out}put_source arrays and their data type to what is actually necessary. To be still on the safe side, add some static asserts. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-11-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 7 +++++-- sound/pci/emu10k1/emumixer.c | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index aab45a23320e..5ad2144fa530 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1637,9 +1637,12 @@ struct snd_emu_chip_details { const char *id; /* for backward compatibility - can be NULL if not needed */ }; +#define NUM_OUTPUT_DESTS 28 +#define NUM_INPUT_DESTS 22 + struct snd_emu1010 { - unsigned int output_source[64]; - unsigned int input_source[64]; + unsigned char output_source[NUM_OUTPUT_DESTS]; + unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ unsigned int internal_clock; /* 44100 or 48000 */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 41a1cf10c6d8..3a7f25f81504 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -273,6 +273,7 @@ static const char * const emu1010_output_texts[] = { LR_CTLS("1010 SPDIF"), ADAT_CTLS("1010 "), }; +static_assert(ARRAY_SIZE(emu1010_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1010_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -313,6 +314,7 @@ static const char * const snd_emu1010b_output_texts[] = { LR_CTLS("1010 SPDIF"), ADAT_CTLS("1010 "), }; +static_assert(ARRAY_SIZE(snd_emu1010b_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1010b_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -349,6 +351,7 @@ static const char * const snd_emu1616_output_texts[] = { ADAT_CTLS("Dock "), LR_CTLS("Mana DAC"), }; +static_assert(ARRAY_SIZE(snd_emu1616_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1616_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -377,6 +380,7 @@ static const char * const snd_emu0404_output_texts[] = { LR_CTLS("DAC"), LR_CTLS("SPDIF"), }; +static_assert(ARRAY_SIZE(snd_emu0404_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu0404_output_dst[] = { LR_REGS(EMU_DST_HAMOA_DAC), @@ -421,6 +425,7 @@ static const char * const emu1010_input_texts[] = { "DSP 14 Capture Enum", "DSP 15 Capture Enum", }; +static_assert(ARRAY_SIZE(emu1010_input_texts) <= NUM_INPUT_DESTS); static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, -- cgit v1.2.3 From 9fe0731bc345230e8ce125056b9407c63960f74e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 18:48:00 +0200 Subject: ALSA: emu10k1: remove runtime 64-bit divisions 32-bit platforms don't like these. As we're actually dealing with constants, factor out the calculations and pass them in as additional arguments. To keep the call sites clean, wrap the actual functions in macros which generate the arguments. Fixes: bb5ceb43b7bf ("ALSA: emu10k1: fix non-zero mixer control defaults in highres mode") Fixes: 1298bc978afb ("ALSA: emu10k1: enable bit-exact playback, part 1: DSP attenuation") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202305171622.jKTovBvy-lkp@intel.com/ Reported-by: Linux Kernel Functional Testing Reported-by: Christophe Leroy Closes: https://lore.kernel.org/r/CA+G9fYsShNP=LALHdMd-Btx3PBtO_CjyBNgpStr9fPGXNbRvdg@mail.gmail.com Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517164800.3650699-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index f64b2b4eb348..e9855d37fa5c 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1144,9 +1144,11 @@ static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu, #define SND_EMU10K1_PLAYBACK_CHANNELS 8 #define SND_EMU10K1_CAPTURE_CHANNELS 4 +#define HR_VAL(v) ((v) * 0x80000000LL / 100 - 1) + static void -snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) +snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); @@ -1156,7 +1158,7 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; - defval = defval * 0x80000000LL / 100 - 1; + defval = defval_hr; } else { ctl->min = 0; ctl->max = 100; @@ -1165,10 +1167,12 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } +#define snd_emu10k1_init_mono_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_mono_control2(ctl, name, gpr, defval, HR_VAL(defval)) static void -snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) +snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); @@ -1178,7 +1182,7 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; - defval = defval * 0x80000000LL / 100 - 1; + defval = defval_hr; } else { ctl->min = 0; ctl->max = 100; @@ -1188,6 +1192,8 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; } +#define snd_emu10k1_init_stereo_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_stereo_control2(ctl, name, gpr, defval, HR_VAL(defval)) static void snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, -- cgit v1.2.3 From af7fd0276ed76357974ebb0e5b5968b4b4e84781 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:48 +0200 Subject: ALSA: emu10k1: pass frame instead of byte addresses ... to snd_emu10k1_pcm_init_voice(). This makes the code arguably less convoluted. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index d669f93d8930..9f151a0a7756 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -270,15 +270,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, stereo = runtime->channels == 2; w_16 = snd_pcm_format_width(runtime->format) == 16; - if (!extra && stereo) { - start_addr >>= 1; - end_addr >>= 1; - } - if (w_16) { - start_addr >>= 1; - end_addr >>= 1; - } - spin_lock_irqsave(&emu->reg_lock, flags); /* volume parameters */ @@ -424,19 +415,16 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; + bool w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; unsigned int start_addr, end_addr; - start_addr = epcm->start_addr; - end_addr = snd_pcm_lib_period_bytes(substream); - if (runtime->channels == 2) { - start_addr >>= 1; - end_addr >>= 1; - } - end_addr += start_addr; + start_addr = epcm->start_addr >> w_16; + end_addr = start_addr + runtime->period_size; snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, start_addr, end_addr, NULL); - start_addr = epcm->start_addr; - end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream); + start_addr >>= stereo; + end_addr = start_addr + runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], start_addr, end_addr, &emu->pcm_mixer[substream->number]); @@ -452,14 +440,13 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; - unsigned int start_addr, end_addr; + unsigned int start_addr; unsigned int channel_size; int i; - start_addr = epcm->start_addr; - end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream); + start_addr = epcm->start_addr >> 1; // 16-bit voices - channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK; + channel_size = runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, start_addr, start_addr + (channel_size / 2), NULL); -- cgit v1.2.3 From 1e5323bd7725c1e3a5bd65af210ea7d54ccdbd00 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:49 +0200 Subject: Revert "ALSA: emu10k1 - delay the PCM interrupts (add pcm_irq_delay parameter)" This workaround fails to address the underlying problem, which is actually wholly self-made. Subsequent patches will fix it. This reverts commit 56385a12d9bb9e173751f74b6c430742018cafc0. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 - sound/core/pcm_native.c | 4 ---- sound/pci/emu10k1/emu10k1.c | 4 ---- sound/pci/emu10k1/emupcm.c | 25 ++----------------------- sound/pci/emu10k1/memory.c | 4 +--- 5 files changed, 3 insertions(+), 35 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 5ad2144fa530..2d64f07e3883 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1670,7 +1670,6 @@ struct snd_emu10k1 { unsigned int address_mode; /* address mode */ unsigned long dma_mask; /* PCI DMA mask */ bool iommu_workaround; /* IOMMU workaround needed */ - unsigned int delay_pcm_irq; /* in samples */ int max_cache_pages; /* max memory size / PAGE_SIZE */ struct snd_dma_buffer silent_page; /* silent page */ struct snd_dma_buffer ptb_pages; /* page table pages */ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 39a65d1415ab..95fc56e403b1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1605,10 +1605,6 @@ static int snd_pcm_do_pause(struct snd_pcm_substream *substream, { if (substream->runtime->trigger_master != substream) return 0; - /* some drivers might use hw_ptr to recover from the pause - - update the hw_ptr now */ - if (pause_pushed(state)) - snd_pcm_update_hw_ptr(substream); /* The jiffies check in snd_pcm_update_hw_ptr*() is done by * a delta between the current jiffies, this gives a large enough * delta, effectively to skip the check once. diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c index 0c97237af922..23adace1b969 100644 --- a/sound/pci/emu10k1/emu10k1.c +++ b/sound/pci/emu10k1/emu10k1.c @@ -34,7 +34,6 @@ static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64}; static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128}; static bool enable_ir[SNDRV_CARDS]; static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */ -static uint delay_pcm_irq[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard."); @@ -56,8 +55,6 @@ module_param_array(enable_ir, bool, NULL, 0444); MODULE_PARM_DESC(enable_ir, "Enable IR."); module_param_array(subsystem, uint, NULL, 0444); MODULE_PARM_DESC(subsystem, "Force card subsystem model."); -module_param_array(delay_pcm_irq, uint, NULL, 0444); -MODULE_PARM_DESC(delay_pcm_irq, "Delay PCM interrupt by specified number of samples (default 0)."); /* * Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value Model:SB0400 */ @@ -103,7 +100,6 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci, enable_ir[dev], subsystem[dev]); if (err < 0) return err; - emu->delay_pcm_irq = delay_pcm_irq[dev] & 0x1f; err = snd_emu10k1_pcm(emu, 0); if (err < 0) return err; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9f151a0a7756..27977d03e323 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -290,7 +290,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, evoice->epcm->ccca_start_addr = start_addr + ccis; if (extra) { start_addr += ccis; - end_addr += ccis + emu->delay_pcm_irq; + end_addr += ccis; } } if (stereo && !extra) { @@ -315,9 +315,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); // Stereo slaves don't need to have the addresses set, but it doesn't hurt snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); - snd_emu10k1_ptr_write(emu, PSST, voice, - (start_addr + (extra ? emu->delay_pcm_irq : 0)) | - (send_amount[2] << 24)); + snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24)); if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -647,23 +645,6 @@ static void snd_emu10k1_playback_set_stopped(struct snd_emu10k1 *emu, epcm->running = 0; } -static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, - struct snd_emu10k1_pcm *epcm, - struct snd_pcm_substream *substream, - struct snd_pcm_runtime *runtime) -{ - unsigned int ptr, period_pos; - - /* try to sychronize the current position for the interrupt - source voice */ - period_pos = runtime->status->hw_ptr - runtime->hw_ptr_interrupt; - period_pos %= runtime->period_size; - ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->extra->number); - ptr &= ~0x00ffffff; - ptr |= epcm->ccca_start_addr + period_pos; - snd_emu10k1_ptr_write(emu, CCCA, epcm->extra->number, ptr); -} - static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -686,8 +667,6 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) - snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c index edb3f1763719..20b07117574b 100644 --- a/sound/pci/emu10k1/memory.c +++ b/sound/pci/emu10k1/memory.c @@ -315,10 +315,8 @@ snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *subst if (snd_BUG_ON(!hdr)) return NULL; - idx = runtime->period_size >= runtime->buffer_size ? - (emu->delay_pcm_irq * 2) : 0; mutex_lock(&hdr->block_mutex); - blk = search_empty(emu, runtime->dma_bytes + idx); + blk = search_empty(emu, runtime->dma_bytes); if (blk == NULL) { mutex_unlock(&hdr->block_mutex); return NULL; -- cgit v1.2.3 From be3b7629e13a5861b6988d46912212ac9f24c369 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:50 +0200 Subject: ALSA: emu10k1: remove pointless displacement of the extra voices The idea is to make the extra voice lag behind the "real" voices, but moving the buffer address around doesn't contribute to that, as the CCCA write below uses the same address. The exact address is unimportant, as the data is discarded anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 27977d03e323..16e7d0ff97a4 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -288,10 +288,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, if (master) { evoice->epcm->ccca_start_addr = start_addr + ccis; - if (extra) { - start_addr += ccis; - end_addr += ccis; - } } if (stereo && !extra) { // Not really necessary for the slave, but it doesn't hurt -- cgit v1.2.3 From cd6dceb197ca5ec70a3ed4c6aec50f9abdf85f8e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:51 +0200 Subject: ALSA: emu10k1: skip pointless cache setup for extra voices Given that the data is going to be ignored anyway, and that the cache does not influence interrupt timing (which is the purpose of the extra voices), it's pointless to pre-fill the cache. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 16e7d0ff97a4..a6c4f1895a08 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -528,14 +528,15 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) return 0; } -static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int extra, struct snd_emu10k1_voice *evoice) +static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) { struct snd_pcm_runtime *runtime; unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; runtime = evoice->epcm->substream->runtime; voice = evoice->number; - stereo = (!extra && runtime->channels == 2); + stereo = (runtime->channels == 2); sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; ccis = emu10k1_ccis(stereo, sample == 0); /* set cs to 2 * number of cache registers beside the invalidated */ @@ -658,8 +659,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra); /* do we need this? */ - snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[0]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0]); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: @@ -803,9 +803,8 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* prepare voices */ for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[i]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]); } - snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: -- cgit v1.2.3 From 5b1cd21f0f05757e724e18a599b391689f8565fc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:52 +0200 Subject: ALSA: emu10k1: fix PCM playback cache and interrupt handling The cache causes a fixed delay regardless of stream parameters. Consequently, all that "cache invalidate size" calculation stuff was garbage (which can be traced right back to Creative's OSS driver). This also removes the definitions of registers CD1..CDF, because they are accessed only relative to CD0 anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 38 +++++++++++++------------- sound/pci/emu10k1/emupcm.c | 67 ++++++++++++++++------------------------------ 2 files changed, 43 insertions(+), 62 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 2d64f07e3883..ee662a1b0dc7 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -116,6 +116,10 @@ #define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ #define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ #define IPR_CHANNELLOOP 0x00000040 /* Channel (half) loop interrupt(s) pending */ + /* The interrupt is triggered shortly after */ + /* CCR_READADDRESS has crossed the boundary; */ + /* due to the cache, this runs ahead of the */ + /* actual playback position. */ #define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ /* highest set channel in CLIPL, CLIPH, HLIPL, */ /* or HLIPH. When IPR is written with CL set, */ @@ -586,24 +590,22 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ /* 0x1f: not used */ -#define CD0 0x20 /* Cache data 0 register */ -#define CD1 0x21 /* Cache data 1 register */ -#define CD2 0x22 /* Cache data 2 register */ -#define CD3 0x23 /* Cache data 3 register */ -#define CD4 0x24 /* Cache data 4 register */ -#define CD5 0x25 /* Cache data 5 register */ -#define CD6 0x26 /* Cache data 6 register */ -#define CD7 0x27 /* Cache data 7 register */ -#define CD8 0x28 /* Cache data 8 register */ -#define CD9 0x29 /* Cache data 9 register */ -#define CDA 0x2a /* Cache data A register */ -#define CDB 0x2b /* Cache data B register */ -#define CDC 0x2c /* Cache data C register */ -#define CDD 0x2d /* Cache data D register */ -#define CDE 0x2e /* Cache data E register */ -#define CDF 0x2f /* Cache data F register */ - -/* 0x30-3f seem to be the same as 0x20-2f */ +// 32 cache registers (== 128 bytes) per channel follow. +// In stereo mode, the two channels' caches are concatenated into one, +// and hold the interleaved frames. +// The cache holds 64 frames, so the upper half is not used in 8-bit mode. +// All registers mentioned below count in frames. +// The cache is a ring buffer; CCR_READADDRESS operates modulo 64. +// The cache is filled from (CCCA_CURRADDR - CCR_CACHEINVALIDSIZE) +// into (CCR_READADDRESS - CCR_CACHEINVALIDSIZE). +// The engine has a fetch threshold of 32 bytes, so it tries to keep +// CCR_CACHEINVALIDSIZE below 8 (16-bit stereo), 16 (16-bit mono, +// 8-bit stereo), or 32 (8-bit mono). The actual transfers are pretty +// unpredictable, especially if several voices are running. +// Frames are consumed at CCR_READADDRESS, which is incremented afterwards, +// along with CCCA_CURRADDR and CCR_CACHEINVALIDSIZE. This implies that the +// actual playback position always lags CCCA_CURRADDR by exactly 64 frames. +#define CD0 0x20 /* Cache data registers 0 .. 0x1f */ #define PTB 0x40 /* Page table base register */ #define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index a6c4f1895a08..feb575922738 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -112,6 +112,10 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic } } if (epcm->extra == NULL) { + // The hardware supports only (half-)loop interrupts, so to support an + // arbitrary number of periods per buffer, we use an extra voice with a + // period-sized loop as the interrupt source. Additionally, the interrupt + // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, 1, @@ -232,23 +236,6 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) return CCCA_INTERPROM_2; } -/* - * calculate cache invalidate size - * - * stereo: channel is stereo - * w_16: using 16bit samples - * - * returns: cache invalidate size in samples - */ -static inline int emu10k1_ccis(int stereo, int w_16) -{ - if (w_16) { - return stereo ? 24 : 26; - } else { - return stereo ? 24*2 : 26*2; - } -} - static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, @@ -264,7 +251,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, unsigned char send_routing[8]; unsigned long flags; unsigned int pitch_target; - unsigned int ccis; voice = evoice->number; stereo = runtime->channels == 2; @@ -284,10 +270,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - ccis = emu10k1_ccis(stereo, w_16); - if (master) { - evoice->epcm->ccca_start_addr = start_addr + ccis; + evoice->epcm->ccca_start_addr = start_addr + 64 - 3; } if (stereo && !extra) { // Not really necessary for the slave, but it doesn't hurt @@ -317,11 +301,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, else pitch_target = emu10k1_calc_pitch_target(runtime->rate); if (extra) - snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr | + snd_emu10k1_ptr_write(emu, CCCA, voice, (end_addr - 3) | emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); else - snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) | + snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + 64 - 3) | emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); /* Clear filter delay memory */ @@ -532,35 +516,30 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { struct snd_pcm_runtime *runtime; - unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; + unsigned voice, stereo, sample; + u32 ccr; runtime = evoice->epcm->substream->runtime; voice = evoice->number; stereo = (runtime->channels == 2); sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; - ccis = emu10k1_ccis(stereo, sample == 0); - /* set cs to 2 * number of cache registers beside the invalidated */ - cs = (sample == 0) ? (32-ccis) : (64-ccis+1) >> 1; - if (cs > 16) cs = 16; - for (i = 0; i < cs; i++) { + + // We assume that the cache is resting at this point (i.e., + // CCR_CACHEINVALIDSIZE is very small). + + // Clear leading frames. For simplicitly, this does too much, + // except for 16-bit stereo. And the interpolator will actually + // access them at all only when we're pitch-shifting. + for (int i = 0; i < 3; i++) snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample); - if (stereo) { - snd_emu10k1_ptr_write(emu, CD0 + i, voice + 1, sample); - } - } - /* reset cache */ - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0); - snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra); - if (stereo) { - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0); - // The engine goes haywire if this one is out of sync - snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra); - } - /* fill cache */ - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, ccis); + + // Fill cache + ccr = (64 - 3) << REG_SHIFT(CCR_CACHEINVALIDSIZE); if (stereo) { - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice+1, ccis); + // The engine goes haywire if CCR_READADDRESS is out of sync + snd_emu10k1_ptr_write(emu, CCR, voice + 1, ccr); } + snd_emu10k1_ptr_write(emu, CCR, voice, ccr); } static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, -- cgit v1.2.3 From 9e72666b9ee1140b7ecc66abc30b1c694ed7a6a0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:54 +0200 Subject: ALSA: emu10k1: improve API of low-level voice manipulation functions Originally, there was a 1:1 relationship between the PCM streams' and the low-level voices' parameters. The addition of multi-channel playback partially invalidated that, but didn't introduce proper layering, so things kept working only by virtue of the multi-channel device never having two channels (yet). The upcoming addition of 32-bit playback would complete upending the relationships. So this patch detaches the low-level parameters from the high-level ones: we pass pre-calculated bit width and stereo flags to the low-level manipulation functions instead of calculating them in-place from the stream parameters. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 50 ++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index feb575922738..bbe054be2448 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -239,6 +239,7 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, unsigned int start_addr, unsigned int end_addr, struct snd_emu10k1_pcm_mixer *mix) @@ -246,15 +247,13 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream = evoice->epcm->substream; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int silent_page, tmp; - int voice, stereo, w_16; + int voice; unsigned char send_amount[8]; unsigned char send_routing[8]; unsigned long flags; unsigned int pitch_target; voice = evoice->number; - stereo = runtime->channels == 2; - w_16 = snd_pcm_format_width(runtime->format) == 16; spin_lock_irqsave(&emu->reg_lock, flags); @@ -273,7 +272,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, if (master) { evoice->epcm->ccca_start_addr = start_addr + 64 - 3; } - if (stereo && !extra) { + if (stereo) { // Not really necessary for the slave, but it doesn't hurt snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); } else { @@ -399,15 +398,15 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, + snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, start_addr, end_addr, NULL); start_addr >>= stereo; end_addr = start_addr + runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], + snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, &emu->pcm_mixer[substream->number]); if (epcm->voices[1]) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], w_16, true, start_addr, end_addr, &emu->pcm_mixer[substream->number]); return 0; @@ -426,17 +425,17 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) channel_size = runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, + snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, start_addr, start_addr + (channel_size / 2), NULL); /* only difference with the master voice is we use it for the pointer */ - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], + snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[0]); start_addr += channel_size; for (i = 1; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[i]); start_addr += channel_size; @@ -513,16 +512,14 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) } static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice) + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo) { - struct snd_pcm_runtime *runtime; - unsigned voice, stereo, sample; + unsigned voice, sample; u32 ccr; - runtime = evoice->epcm->substream->runtime; voice = evoice->number; - stereo = (runtime->channels == 2); - sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; + sample = w_16 ? 0 : 0x80808080; // We assume that the cache is resting at this point (i.e., // CCR_CACHEINVALIDSIZE is very small). @@ -552,20 +549,15 @@ static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - bool master, + bool stereo, bool master, struct snd_emu10k1_pcm_mixer *mix) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; unsigned int vattn; unsigned int tmp; if (evoice == NULL) /* skip second voice for mono */ return; - substream = evoice->epcm->substream; - runtime = substream->runtime; - - tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; + tmp = stereo ? (master ? 1 : 2) : 0; vattn = mix->attn[tmp] << 16; snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } @@ -628,6 +620,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; struct snd_emu10k1_pcm_mixer *mix; + bool w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; int result = 0; /* @@ -638,13 +632,13 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0], w_16, stereo); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], stereo, false, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -782,13 +776,13 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* prepare voices */ for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i], true, false); } fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, &emu->efx_pcm_mixer[i]); snd_emu10k1_playback_set_running(emu, epcm); -- cgit v1.2.3 From 9581128a213461cf2f82dd09558b7066d363360c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:55 +0200 Subject: ALSA: emu10k1: refactor PCM playback cache filling Rename snd_emu10k1_playback_invalidate_cache() to the more apt snd_emu10k1_playback_fill_cache(), and factor out snd_emu10k1_playback_prepare_voices(), which calls the former for all channels. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index bbe054be2448..063918397a5f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -511,16 +511,12 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) return 0; } -static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice, - bool w_16, bool stereo) +static void snd_emu10k1_playback_fill_cache(struct snd_emu10k1 *emu, + unsigned voice, + u32 sample, bool stereo) { - unsigned voice, sample; u32 ccr; - voice = evoice->number; - sample = w_16 ? 0 : 0x80808080; - // We assume that the cache is resting at this point (i.e., // CCR_CACHEINVALIDSIZE is very small). @@ -539,6 +535,22 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, CCR, voice, ccr); } +static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + bool w_16, bool stereo, + int channels) +{ + u32 sample = w_16 ? 0 : 0x80808080; + + for (int i = 0; i < channels; i++) { + unsigned voice = epcm->voices[i]->number; + snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo); + } + + // It takes a moment until the cache fills complete, + // but the unmuting takes long enough for that. +} + static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, unsigned int vattn) @@ -632,7 +644,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0], w_16, stereo); + snd_emu10k1_playback_prepare_voices(emu, epcm, w_16, stereo, 1); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: @@ -774,10 +786,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - /* prepare voices */ - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i], true, false); - } + snd_emu10k1_playback_prepare_voices(emu, epcm, true, false, NUM_EFX_PLAYBACK); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: -- cgit v1.2.3 From fa75064d92fdec157d75375bca06c77fb30c25df Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:56 +0200 Subject: ALSA: emu10k1: refactor PCM playback address handling Pull the special handling of extra voices out of snd_emu10k1_pcm_init_voice(), simplify snd_emu10k1_playback_pointer(), and make the logic overall clearer. Also, add verbose comments. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 81 +++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 29 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 063918397a5f..89f7e85034b6 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -269,9 +269,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - if (master) { - evoice->epcm->ccca_start_addr = start_addr + 64 - 3; - } if (stereo) { // Not really necessary for the slave, but it doesn't hurt snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); @@ -299,12 +296,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else pitch_target = emu10k1_calc_pitch_target(runtime->rate); - if (extra) - snd_emu10k1_ptr_write(emu, CCCA, voice, (end_addr - 3) | - emu10k1_select_interprom(pitch_target) | - (w_16 ? 0 : CCCA_8BITSELECT)); - else - snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + 64 - 3) | + snd_emu10k1_ptr_write(emu, CCCA, voice, emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); /* Clear filter delay memory */ @@ -401,6 +393,7 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, start_addr, end_addr, NULL); start_addr >>= stereo; + epcm->ccca_start_addr = start_addr; end_addr = start_addr + runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, @@ -428,13 +421,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, start_addr, start_addr + (channel_size / 2), NULL); - /* only difference with the master voice is we use it for the pointer */ - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], true, false, - start_addr, start_addr + channel_size, - &emu->efx_pcm_mixer[0]); - - start_addr += channel_size; - for (i = 1; i < NUM_EFX_PLAYBACK; i++) { + epcm->ccca_start_addr = start_addr; + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[i]); @@ -540,13 +528,45 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, bool w_16, bool stereo, int channels) { + struct snd_pcm_substream *substream = epcm->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned eloop_start = epcm->start_addr >> w_16; + unsigned loop_start = eloop_start >> stereo; + unsigned eloop_size = runtime->period_size; + unsigned loop_size = runtime->buffer_size; u32 sample = w_16 ? 0 : 0x80808080; + // To make the playback actually start at the 1st frame, + // we need to compensate for two circumstances: + // - The actual position is delayed by the cache size (64 frames) + // - The interpolator is centered around the 4th frame + loop_start += 64 - 3; for (int i = 0; i < channels; i++) { unsigned voice = epcm->voices[i]->number; + snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, voice, loop_start); + loop_start += loop_size; snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo); } + // The interrupt is triggered when CCCA_CURRADDR (CA) wraps around, + // which is ahead of the actual playback position, so the interrupt + // source needs to be delayed. + // + // In principle, this wouldn't need to be the cache's entire size - in + // practice, CCR_CACHEINVALIDSIZE (CIS) > `fetch threshold` has never + // been observed, and assuming 40 _bytes_ should be safe. + // + // The cache fills are somewhat random, which makes it impossible to + // align them with the interrupts. This makes a non-delayed interrupt + // source not practical, as the interrupt handler would have to wait + // for (CA - CIS) >= period_boundary for every channel in the stream. + // + // This is why all other (open) drivers for these chips use timer-based + // interrupts. + // + eloop_start += eloop_size - 3; + snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, epcm->extra->number, eloop_start); + // It takes a moment until the cache fills complete, // but the unmuting takes long enough for that. } @@ -746,24 +766,27 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream * struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; - unsigned int ptr; + int ptr; if (!epcm->running) return 0; + ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff; -#if 0 /* Perex's code */ - ptr += runtime->buffer_size; ptr -= epcm->ccca_start_addr; - ptr %= runtime->buffer_size; -#else /* EMU10K1 Open Source code from Creative */ - if (ptr < epcm->ccca_start_addr) - ptr += runtime->buffer_size - epcm->ccca_start_addr; - else { - ptr -= epcm->ccca_start_addr; - if (ptr >= runtime->buffer_size) - ptr -= runtime->buffer_size; - } -#endif + + // This is the size of the whole cache minus the interpolator read-ahead, + // which leads us to the actual playback position. + // + // The cache is constantly kept mostly filled, so in principle we could + // return a more advanced position representing how far the hardware has + // already read the buffer, and set runtime->delay accordingly. However, + // this would be slightly different for every channel (and remarkably slow + // to obtain), so only a fixed worst-case value would be practical. + // + ptr -= 64 - 3; + if (ptr < 0) + ptr += runtime->buffer_size; + /* dev_dbg(emu->card->dev, "ptr = 0x%lx, buffer_size = 0x%lx, period_size = 0x%lx\n", -- cgit v1.2.3 From cc3ff544a296b5b4bb021f4dc415b53a6955b980 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:42 +0200 Subject: ASoC: codecs: rt1308: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt1308->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt1308->hw_init = false; @@ -314,7 +311,7 @@ static int rt1308_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt1308->hw_init || rt1308->status != SDW_SLAVE_ATTACHED) + if (rt1308->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt1308-sdw.h b/sound/soc/codecs/rt1308-sdw.h index 04ff18fa18e2..f816c73e247e 100644 --- a/sound/soc/codecs/rt1308-sdw.h +++ b/sound/soc/codecs/rt1308-sdw.h @@ -159,7 +159,6 @@ struct rt1308_sdw_priv { struct snd_soc_component *component; struct regmap *regmap; struct sdw_slave *sdw_slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 70207b95b2245502496443475c9fc4eb72ba3b66 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:43 +0200 Subject: ASoC: codecs: rt1316: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt1316->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt1316->hw_init = false; @@ -333,7 +330,7 @@ static int rt1316_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt1316->hw_init || rt1316->status != SDW_SLAVE_ATTACHED) + if (rt1316->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt1316-sdw.h b/sound/soc/codecs/rt1316-sdw.h index e37121655bc1..dc1bfe40edd3 100644 --- a/sound/soc/codecs/rt1316-sdw.h +++ b/sound/soc/codecs/rt1316-sdw.h @@ -42,7 +42,6 @@ struct rt1316_sdw_priv { struct snd_soc_component *component; struct regmap *regmap; struct sdw_slave *sdw_slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 28eb1e4224c3b3ff29fe4c29bcdc011d3a0ffd07 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:44 +0200 Subject: ASoC: codecs: rt1318: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt1318->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt1318->hw_init = false; @@ -466,7 +463,7 @@ static int rt1318_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt1318->hw_init || rt1318->status != SDW_SLAVE_ATTACHED) + if (rt1318->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt1318-sdw.h b/sound/soc/codecs/rt1318-sdw.h index 85918c184f16..86e83d63a017 100644 --- a/sound/soc/codecs/rt1318-sdw.h +++ b/sound/soc/codecs/rt1318-sdw.h @@ -88,7 +88,6 @@ struct rt1318_sdw_priv { struct snd_soc_component *component; struct regmap *regmap; struct sdw_slave *sdw_slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 758665b15acc1adb21a833c6456746ffbce07ed7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:45 +0200 Subject: ASoC: codecs: rt5682: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt5682->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt5682->hw_init = false; @@ -510,7 +507,7 @@ static int rt5682_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt5682->hw_init || rt5682->status != SDW_SLAVE_ATTACHED) + if (rt5682->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt5682.h b/sound/soc/codecs/rt5682.h index d568c6993c33..301d1817f8f1 100644 --- a/sound/soc/codecs/rt5682.h +++ b/sound/soc/codecs/rt5682.h @@ -1440,7 +1440,6 @@ struct rt5682_priv { bool disable_irq; struct mutex calibrate_mutex; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 9564c9f691128bc2dc69de02f7eed205d9b2513f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:46 +0200 Subject: ASoC: codecs: rt700: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt700->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt700->hw_init = false; @@ -325,7 +322,7 @@ static int rt700_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt700->hw_init || rt700->status != SDW_SLAVE_ATTACHED) + if (rt700->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt700.h b/sound/soc/codecs/rt700.h index 93c44005d38c..491774d207de 100644 --- a/sound/soc/codecs/rt700.h +++ b/sound/soc/codecs/rt700.h @@ -15,7 +15,6 @@ struct rt700_priv { struct regmap *regmap; struct regmap *sdw_regmap; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 8322947e9228ef7f8c3dd13822d32c491f9488e7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:47 +0200 Subject: ASoC: codecs: rt711-sdca: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt711->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt711->hw_init = false; @@ -168,7 +165,7 @@ static int rt711_sdca_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt711->hw_init || rt711->status != SDW_SLAVE_ATTACHED) + if (rt711->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt711-sdca.h b/sound/soc/codecs/rt711-sdca.h index 22076f268577..11d421e8ab2b 100644 --- a/sound/soc/codecs/rt711-sdca.h +++ b/sound/soc/codecs/rt711-sdca.h @@ -19,7 +19,6 @@ struct rt711_sdca_priv { struct regmap *regmap, *mbq_regmap; struct snd_soc_component *component; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 22e15c18b4a91c71bf66de06187b8a3199bb8cad Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:48 +0200 Subject: ASoC: codecs: rt711: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt711->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt711->hw_init = false; @@ -329,7 +326,7 @@ static int rt711_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt711->hw_init || rt711->status != SDW_SLAVE_ATTACHED) + if (rt711->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt711.h b/sound/soc/codecs/rt711.h index b31351f11df9..491e357191f9 100644 --- a/sound/soc/codecs/rt711.h +++ b/sound/soc/codecs/rt711.h @@ -15,7 +15,6 @@ struct rt711_priv { struct regmap *sdw_regmap; struct snd_soc_component *component; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From d7a79616fc723305094fd7391085428b7a893636 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:49 +0200 Subject: ASoC: codecs: rt712-sdca-dmic: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt712->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt712->hw_init = false; @@ -813,7 +810,7 @@ static int rt712_sdca_dmic_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt712->hw_init || rt712->status != SDW_SLAVE_ATTACHED) + if (rt712->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt712-sdca-dmic.h b/sound/soc/codecs/rt712-sdca-dmic.h index 74c29677c251..110154e74efe 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.h +++ b/sound/soc/codecs/rt712-sdca-dmic.h @@ -16,7 +16,6 @@ struct rt712_sdca_dmic_priv { struct regmap *mbq_regmap; struct snd_soc_component *component; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 5cd02f96f49a7e6d2f8b96ddc42092776b554873 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:50 +0200 Subject: ASoC: codecs: rt712-sdca: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt712->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt712->hw_init = false; @@ -165,7 +162,7 @@ static int rt712_sdca_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt712->hw_init || rt712->status != SDW_SLAVE_ATTACHED) + if (rt712->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt712-sdca.h b/sound/soc/codecs/rt712-sdca.h index c6a94a23f46e..ff79e03118ce 100644 --- a/sound/soc/codecs/rt712-sdca.h +++ b/sound/soc/codecs/rt712-sdca.h @@ -20,7 +20,6 @@ struct rt712_sdca_priv { struct regmap *mbq_regmap; struct snd_soc_component *component; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From cda72c89d082f5953fab9948fc1212ca0df11d96 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:51 +0200 Subject: ASoC: codecs: rt715-sdca: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt715->status = status; - /* * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt715->hw_init || rt715->status != SDW_SLAVE_ATTACHED) + if (rt715->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt715-sdca.h b/sound/soc/codecs/rt715-sdca.h index 7577f3151934..e5d6928ecaba 100644 --- a/sound/soc/codecs/rt715-sdca.h +++ b/sound/soc/codecs/rt715-sdca.h @@ -24,7 +24,6 @@ struct rt715_sdca_priv { int dbg_nid; int dbg_vid; int dbg_payload; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From 0315dac5406c9c0b8e334195aa01c4ec155adf47 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:52 +0200 Subject: ASoC: codecs: rt715: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt715->status = status; /* * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt715->hw_init || rt715->status != SDW_SLAVE_ATTACHED) + if (rt715->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt715.h b/sound/soc/codecs/rt715.h index 17a8d041c1c3..12a0ae656d09 100644 --- a/sound/soc/codecs/rt715.h +++ b/sound/soc/codecs/rt715.h @@ -18,7 +18,6 @@ struct rt715_priv { int dbg_nid; int dbg_vid; int dbg_payload; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From b932f21f6678659bd434c0d47e3bebc94bae0a51 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 18 May 2023 09:27:53 +0200 Subject: ASoC: codecs: rt722-sdca: do not store status in state container Driver in its update status callback stores Soundwire device status in state container but it never uses it later. Simplify the code a bit. Signed-off-by: Krzysztof Kozlowski dev); - /* Update the status */ - rt722->status = status; - if (status == SDW_SLAVE_UNATTACHED) rt722->hw_init = false; @@ -188,7 +185,7 @@ static int rt722_sdca_update_status(struct sdw_slave *slave, * Perform initialization only if slave status is present and * hw_init flag is false */ - if (rt722->hw_init || rt722->status != SDW_SLAVE_ATTACHED) + if (rt722->hw_init || status != SDW_SLAVE_ATTACHED) return 0; /* perform I/O transfers required for Slave initialization */ diff --git a/sound/soc/codecs/rt722-sdca.h b/sound/soc/codecs/rt722-sdca.h index 5bc6184d09aa..44af8901352e 100644 --- a/sound/soc/codecs/rt722-sdca.h +++ b/sound/soc/codecs/rt722-sdca.h @@ -20,7 +20,6 @@ struct rt722_sdca_priv { struct regmap *mbq_regmap; struct snd_soc_component *component; struct sdw_slave *slave; - enum sdw_slave_status status; struct sdw_bus_params params; bool hw_init; bool first_hw_init; -- cgit v1.2.3 From cbbc0ec6dea09c815f1d1ef0abaf3f2ec89ff11f Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Wed, 17 May 2023 17:15:16 +0200 Subject: ASoC: mediatek: mt8192-mt6359: Remove " Jack" from Headphone pin name Function jack_kctl_name_gen() will remove the redundant " Jack" from the name, if present, and then it will add it back, so that all of the controls are named "(pin-name) Jack". Remove " Jack" from the Headphone pin name to spare some CPU cycles. This commit brings no functional changes. Signed-off-by: AngeloGioacchino Del Regno Date: Thu, 18 May 2023 11:22:24 +0200 Subject: ALSA: emu10k1: fix PCM playback buffer size constraints The period_bytes_min parameter and the buffer_bytes minimum constraint made no sense at all, as they didn't reflect any hardware limitation. Instead, apply a frame-based period_size minimum constraint, which is derived from the cache size (it would be actually possible to go below that, but it would require special handling, and it would be practically impossible to keep up with the IRQ rate anyway). Sync up the constraints of the EFX playback with those of the regular playback, as there is no reason for them to diverge. N.b., the maximum buffer size is actually arbitrary - the hardware could go waay beyond 128 KiB. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518092224.3696958-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 89f7e85034b6..9045359bb461 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -444,9 +444,8 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .rate_max = 48000, .channels_min = NUM_EFX_PLAYBACK, .channels_max = NUM_EFX_PLAYBACK, - .buffer_bytes_max = (64*1024), - .period_bytes_min = 64, - .period_bytes_max = (64*1024), + .buffer_bytes_max = (128*1024), + .period_bytes_max = (128*1024), .periods_min = 2, .periods_max = 2, .fifo_size = 0, @@ -877,7 +876,6 @@ static const struct snd_pcm_hardware snd_emu10k1_playback = .channels_min = 1, .channels_max = 2, .buffer_bytes_max = (128*1024), - .period_bytes_min = 64, .period_bytes_max = (128*1024), .periods_min = 1, .periods_max = 1024, @@ -982,13 +980,29 @@ static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream) return 0; } +static int snd_emu10k1_playback_set_constraints(struct snd_pcm_runtime *runtime) +{ + int err; + + // The buffer size must be a multiple of the period size, to avoid a + // mismatch between the extra voice and the regular voices. + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + // The hardware is typically the cache's size of 64 frames ahead. + // Leave enough time for actually filling up the buffer. + err = snd_pcm_hw_constraint_minmax( + runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 128, UINT_MAX); + return err; +} + static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) { struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_emu10k1_pcm *epcm; struct snd_emu10k1_pcm_mixer *mix; struct snd_pcm_runtime *runtime = substream->runtime; - int i, j; + int i, j, err; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1000,7 +1014,12 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_efx_playback; - + err = snd_emu10k1_playback_set_constraints(runtime); + if (err < 0) { + kfree(epcm); + return err; + } + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { mix = &emu->efx_pcm_mixer[i]; for (j = 0; j < 8; j++) @@ -1031,12 +1050,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_playback; - err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (err < 0) { - kfree(epcm); - return err; - } - err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX); + err = snd_emu10k1_playback_set_constraints(runtime); if (err < 0) { kfree(epcm); return err; -- cgit v1.2.3 From 583307bafb264a1a42a1ffc8cdf6493f9deda414 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:44 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 1 IPR_CHANNELNUMBERMASK cannot be non-zero when IPR_CHANNELLOOP is unset, so join marking them as handled. This logically reverts part of commit f453e20d8a0 ("ALSA update 0.9.3a"), which made the inverse change with no explanation. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index dfb44e5e69a7..0cb89bd8c16b 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -79,9 +79,8 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) val >>= 1; pvoice++; } - status &= ~IPR_CHANNELLOOP; + status &= ~(IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK); } - status &= ~IPR_CHANNELNUMBERMASK; if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) { if (emu->capture_interrupt) emu->capture_interrupt(emu, status); -- cgit v1.2.3 From 016027741f97457087b81bf304f1cb807bdeffe0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:45 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 2 Remove weird INTE_* clearing code. The bits were a subset of the actually handled interrupts, which kind of contradicted the stated purpose. I suppose it would make sense to complete the set and negate it, but interrupts being enabled out of the blue is neither something that happens a lot, nor should it result in just one error message, IMO. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 0cb89bd8c16b..312511300053 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -146,26 +146,8 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) } if (status) { - unsigned int bits; dev_err(emu->card->dev, "unhandled interrupt: 0x%08x\n", status); - //make sure any interrupts we don't handle are disabled: - bits = INTE_FXDSPENABLE | - INTE_PCIERRORENABLE | - INTE_VOLINCRENABLE | - INTE_VOLDECRENABLE | - INTE_MUTEENABLE | - INTE_MICBUFENABLE | - INTE_ADCBUFENABLE | - INTE_EFXBUFENABLE | - INTE_GPSPDIFENABLE | - INTE_CDSPDIFENABLE | - INTE_INTERVALTIMERENB | - INTE_MIDITXENABLE | - INTE_MIDIRXENABLE; - if (emu->audigy) - bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2; - snd_emu10k1_intr_disable(emu, bits); } outl(orig_status, emu->port + IPR); /* ack all */ } -- cgit v1.2.3 From 9436f0151d30b80873eda80341304524db9e8149 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:46 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 3 Handle the "timeout" (actually the retry counter) such that it's more obvious and causes less cost in the normal case. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 312511300053..7dc803aaa850 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -22,15 +22,18 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) int handled = 0; int timeout = 0; - while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) { - timeout++; - orig_status = status; + while ((status = inl(emu->port + IPR)) != 0) { handled = 1; if ((status & 0xffffffff) == 0xffffffff) { dev_info(emu->card->dev, "Suspected sound card removal\n"); break; } + if (++timeout == 1000) { + dev_info(emu->card->dev, "emu10k1 irq routine failure\n"); + break; + } + orig_status = status; if (status & IPR_PCIERROR) { dev_err(emu->card->dev, "interrupt: PCI error\n"); snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE); @@ -151,8 +154,6 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) } outl(orig_status, emu->port + IPR); /* ack all */ } - if (timeout == 1000) - dev_info(emu->card->dev, "emu10k1 irq routine failure\n"); return IRQ_RETVAL(handled); } -- cgit v1.2.3 From 6797400ef4abb4359c225a207c1f3ca28591f51c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:47 +0200 Subject: ALSA: emu10k1: fix handling of half-loop interrupts We'd try to iterate the voices twice without resetting the pointer. This went unnoticed, because the code isn't actually in use. Amends commit 27ae958cf6 ("emu10k1 driver - add multichannel device hw:x,3 [2-8/8]"). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 7dc803aaa850..a813ef8c2f8d 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -47,12 +47,13 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE); } if (status & IPR_CHANNELLOOP) { + struct snd_emu10k1_voice *pvoice; int voice; int voice_max = status & IPR_CHANNELNUMBERMASK; u32 val; - struct snd_emu10k1_voice *pvoice = emu->voices; val = snd_emu10k1_ptr_read(emu, CLIPL, 0); + pvoice = emu->voices; for (voice = 0; voice <= voice_max; voice++) { if (voice == 0x20) val = snd_emu10k1_ptr_read(emu, CLIPH, 0); @@ -68,6 +69,7 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) pvoice++; } val = snd_emu10k1_ptr_read(emu, HLIPL, 0); + pvoice = emu->voices; for (voice = 0; voice <= voice_max; voice++) { if (voice == 0x20) val = snd_emu10k1_ptr_read(emu, HLIPH, 0); -- cgit v1.2.3 From 46055699e5f81db8c70946609f445c572983eca5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:31:34 +0200 Subject: ALSA: emu10k1: introduce and use snd_emu10k1_ptr_write_multiple() While this nicely denoises the code, the real intent is being able to write many registers pseudo-atomically, which will come in handy later. Idea stolen from kX-project. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093134.3697955-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 4 + sound/pci/emu10k1/emu10k1_callback.c | 209 +++++++++++++++++------------------ sound/pci/emu10k1/emu10k1_main.c | 168 +++++++++++++++------------- sound/pci/emu10k1/emumixer.c | 8 +- sound/pci/emu10k1/emupcm.c | 108 +++++++++--------- sound/pci/emu10k1/io.c | 31 ++++++ 6 files changed, 293 insertions(+), 235 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index ee662a1b0dc7..9c5de1f45566 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -64,6 +64,9 @@ #define REG_VAL_GET(r, v) ((v & REG_MASK(r)) >> REG_SHIFT(r)) #define REG_VAL_PUT(r, v) ((v) << REG_SHIFT(r)) +// List terminator for snd_emu10k1_ptr_write_multiple() +#define REGLIST_END ~0 + // Audigy specify registers are prefixed with 'A_' /************************************************************************************************/ @@ -1793,6 +1796,7 @@ int snd_emu10k1_done(struct snd_emu10k1 * emu); /* I/O functions */ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...); unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, unsigned int data); diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 9455df18f7b2..06440b97b5d7 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -33,9 +33,8 @@ static void release_voice(struct snd_emux_voice *vp); static void update_voice(struct snd_emux_voice *vp, int update); static void terminate_voice(struct snd_emux_voice *vp); static void free_voice(struct snd_emux_voice *vp); -static void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); -static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); -static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); +static u32 make_fmmod(struct snd_emux_voice *vp); +static u32 make_fm2frq2(struct snd_emux_voice *vp); /* * Ensure a value is between two points @@ -116,14 +115,13 @@ snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw) static void release_voice(struct snd_emux_voice *vp) { - int dcysusv; struct snd_emu10k1 *hw; hw = vp->hw; - dcysusv = (unsigned char)vp->reg.parm.modrelease | DCYSUSM_PHASE1_MASK; - snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv); - dcysusv = (unsigned char)vp->reg.parm.volrelease | DCYSUSV_PHASE1_MASK | DCYSUSV_CHANNELENABLE_MASK; - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSM, (unsigned char)vp->reg.parm.modrelease | DCYSUSM_PHASE1_MASK, + DCYSUSV, (unsigned char)vp->reg.parm.volrelease | DCYSUSV_PHASE1_MASK | DCYSUSV_CHANNELENABLE_MASK, + REGLIST_END); } @@ -192,13 +190,13 @@ update_voice(struct snd_emux_voice *vp, int update) snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux); } if (update & SNDRV_EMUX_UPDATE_FMMOD) - set_fmmod(hw, vp); + snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, make_fmmod(vp)); if (update & SNDRV_EMUX_UPDATE_TREMFREQ) snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) - set_fm2frq2(hw, vp); + snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, make_fm2frq2(vp)); if (update & SNDRV_EMUX_UPDATE_Q) - set_filterQ(hw, vp); + snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ); } @@ -310,6 +308,7 @@ start_voice(struct snd_emux_voice *vp) { unsigned int temp; int ch; + u32 psst, dsl, map, ccca, vtarget; unsigned int addr, mapped_offset; struct snd_midi_channel *chan; struct snd_emu10k1 *hw; @@ -347,66 +346,93 @@ start_voice(struct snd_emux_voice *vp) snd_emu10k1_ptr_write(hw, FXRT, ch, temp); } - /* channel to be silent and idle */ - snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(hw, VTFT, ch, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(hw, CVCF, ch, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(hw, PTRX, ch, 0); - snd_emu10k1_ptr_write(hw, CPF, ch, 0); - - /* set pitch offset */ - snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch); - - /* set envelope parameters */ - snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay); - snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld); - snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus); - snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay); - snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld); - /* decay/sustain parameter for volume envelope is used - for triggerg the voice */ - - /* cutoff and volume */ - temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol; - snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp); - - /* modulation envelope heights */ - snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe); - - /* lfo1/2 delay */ - snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay); - snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay); - - /* lfo1 pitch & cutoff shift */ - set_fmmod(hw, vp); - /* lfo1 volume & freq */ - snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); - /* lfo2 pitch & freq */ - set_fm2frq2(hw, vp); - - /* reverb and loop start (reverb 8bit, MSB) */ temp = vp->reg.parm.reverb; temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; LIMITMAX(temp, 255); addr = vp->reg.loopstart; - snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr); + psst = (temp << 24) | addr; - /* chorus & loop end (chorus 8bit, MSB) */ addr = vp->reg.loopend; temp = vp->reg.parm.chorus; temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; LIMITMAX(temp, 255); - temp = (temp <<24) | addr; - snd_emu10k1_ptr_write(hw, DSL, ch, temp); + dsl = (temp << 24) | addr; - /* clear filter delay memory */ - snd_emu10k1_ptr_write(hw, Z1, ch, 0); - snd_emu10k1_ptr_write(hw, Z2, ch, 0); + map = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - /* invalidate maps */ - temp = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(hw, MAPA, ch, temp); - snd_emu10k1_ptr_write(hw, MAPB, ch, temp); + addr = vp->reg.start; + temp = vp->reg.parm.filterQ; + ccca = (temp << 28) | addr; + if (vp->apitch < 0xe400) + ccca |= CCCA_INTERPROM_0; + else { + unsigned int shift = (vp->apitch - 0xe000) >> 10; + ccca |= shift << 25; + } + if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) + ccca |= CCCA_8BITSELECT; + + vtarget = (unsigned int)vp->vtarget << 16; + + snd_emu10k1_ptr_write_multiple(hw, ch, + /* channel to be silent and idle */ + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + + /* set pitch offset */ + IP, vp->apitch, + + /* set envelope parameters */ + ENVVAL, vp->reg.parm.moddelay, + ATKHLDM, vp->reg.parm.modatkhld, + DCYSUSM, vp->reg.parm.moddcysus, + ENVVOL, vp->reg.parm.voldelay, + ATKHLDV, vp->reg.parm.volatkhld, + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + + /* cutoff and volume */ + IFATN, (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol, + + /* modulation envelope heights */ + PEFE, vp->reg.parm.pefe, + + /* lfo1/2 delay */ + LFOVAL1, vp->reg.parm.lfo1delay, + LFOVAL2, vp->reg.parm.lfo2delay, + + /* lfo1 pitch & cutoff shift */ + FMMOD, make_fmmod(vp), + /* lfo1 volume & freq */ + TREMFRQ, vp->reg.parm.tremfrq, + /* lfo2 pitch & freq */ + FM2FRQ2, make_fm2frq2(vp), + + /* reverb and loop start (reverb 8bit, MSB) */ + PSST, psst, + + /* chorus & loop end (chorus 8bit, MSB) */ + DSL, dsl, + + /* clear filter delay memory */ + Z1, 0, + Z2, 0, + + /* invalidate maps */ + MAPA, map, + MAPB, map, + + /* Q & current address (Q 4bit value, MSB) */ + CCCA, ccca, + + /* reset volume */ + VTFT, vtarget | vp->ftarget, + CVCF, vtarget | CVCF_CURRENTFILTER_MASK, + + REGLIST_END); #if 0 /* cache */ { @@ -437,24 +463,6 @@ start_voice(struct snd_emux_voice *vp) } #endif - /* Q & current address (Q 4bit value, MSB) */ - addr = vp->reg.start; - temp = vp->reg.parm.filterQ; - temp = (temp<<28) | addr; - if (vp->apitch < 0xe400) - temp |= CCCA_INTERPROM_0; - else { - unsigned int shift = (vp->apitch - 0xe000) >> 10; - temp |= shift << 25; - } - if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) - temp |= CCCA_8BITSELECT; - snd_emu10k1_ptr_write(hw, CCCA, ch, temp); - - /* reset volume */ - temp = (unsigned int)vp->vtarget << 16; - snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget); - snd_emu10k1_ptr_write(hw, CVCF, ch, temp | CVCF_CURRENTFILTER_MASK); return 0; } @@ -464,7 +472,7 @@ start_voice(struct snd_emux_voice *vp) static void trigger_voice(struct snd_emux_voice *vp) { - unsigned int temp, ptarget; + unsigned int ptarget; struct snd_emu10k1 *hw; struct snd_emu10k1_memblk *emem; @@ -479,24 +487,25 @@ trigger_voice(struct snd_emux_voice *vp) #else ptarget = IP_TO_CP(vp->apitch); #endif - /* set pitch target and pan (volume) */ - temp = ptarget | (vp->apan << 8) | vp->aaux; - snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + /* set pitch target and pan (volume) */ + PTRX, ptarget | (vp->apan << 8) | vp->aaux, + + /* current pitch and fractional address */ + CPF, ptarget, - /* pitch target */ - snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget); + /* enable envelope engine */ + DCYSUSV, vp->reg.parm.voldcysus | DCYSUSV_CHANNELENABLE_MASK, - /* trigger voice */ - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK); + REGLIST_END); } #define MOD_SENSE 18 -/* set lfo1 modulation height and cutoff */ -static void -set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) +/* calculate lfo1 modulation height and cutoff register */ +static u32 +make_fmmod(struct snd_emux_voice *vp) { - unsigned short fmmod; short pitch; unsigned char cutoff; int modulation; @@ -506,15 +515,13 @@ set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; pitch += (MOD_SENSE * modulation) / 1200; LIMITVALUE(pitch, -128, 127); - fmmod = ((unsigned char)pitch<<8) | cutoff; - snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod); + return ((unsigned char)pitch << 8) | cutoff; } -/* set lfo2 pitch & frequency */ -static void -set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) +/* calculate set lfo2 pitch & frequency register */ +static u32 +make_fm2frq2(struct snd_emux_voice *vp) { - unsigned short fm2frq2; short pitch; unsigned char freq; int modulation; @@ -524,13 +531,5 @@ set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; pitch += (MOD_SENSE * modulation) / 1200; LIMITVALUE(pitch, -128, 127); - fm2frq2 = ((unsigned char)pitch<<8) | freq; - snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2); -} - -/* set filterQ */ -static void -set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) -{ - snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ); + return ((unsigned char)pitch << 8) | freq; } diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index da7c988b5c97..65207ef689cb 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -57,44 +57,49 @@ MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) { - snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(emu, VTFT, ch, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, ch, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, PTRX, ch, 0); - snd_emu10k1_ptr_write(emu, CPF, ch, 0); - snd_emu10k1_ptr_write(emu, CCR, ch, 0); - - snd_emu10k1_ptr_write(emu, PSST, ch, 0); - snd_emu10k1_ptr_write(emu, DSL, ch, 0x10); - snd_emu10k1_ptr_write(emu, CCCA, ch, 0); - snd_emu10k1_ptr_write(emu, Z1, ch, 0); - snd_emu10k1_ptr_write(emu, Z2, ch, 0); - snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000); - - // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero - snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); - snd_emu10k1_ptr_write(emu, IP, ch, 0); - snd_emu10k1_ptr_write(emu, IFATN, ch, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK); - snd_emu10k1_ptr_write(emu, PEFE, ch, 0); - snd_emu10k1_ptr_write(emu, FMMOD, ch, 0); - snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0); - snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0); - snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0); - snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + CCR, 0, + + PSST, 0, + DSL, 0x10, + CCCA, 0, + Z1, 0, + Z2, 0, + FXRT, 0x32100000, + + // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero + DCYSUSM, 0, + ATKHLDV, 0, + ATKHLDM, 0, + IP, 0, + IFATN, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK, + PEFE, 0, + FMMOD, 0, + TREMFRQ, 24, /* 1 Hz */ + FM2FRQ2, 24, /* 1 Hz */ + LFOVAL2, 0, + LFOVAL1, 0, + ENVVOL, 0, + ENVVAL, 0, + + REGLIST_END); /* Audigy extra stuffs */ if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_CSBA, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSDC, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSFE, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSHG, ch, 0); - snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100); - snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x07060504); - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + A_CSBA, 0, + A_CSDC, 0, + A_CSFE, 0, + A_CSHG, 0, + A_FXRT1, 0x03020100, + A_FXRT2, 0x07060504, + A_SENDAMOUNTS, 0, + REGLIST_END); } } @@ -148,22 +153,26 @@ static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir) outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG); - /* reset recording buffers */ - snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, MICBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, FXBA, 0, 0); - snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, ADCBA, 0, 0); - - /* disable channel interrupt */ outl(0, emu->port + INTE); - snd_emu10k1_ptr_write(emu, CLIEL, 0, 0); - snd_emu10k1_ptr_write(emu, CLIEH, 0, 0); - /* disable stop on loop end */ - snd_emu10k1_ptr_write(emu, SOLEL, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEH, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, ADCBS_BUFSIZE_NONE, + MICBA, 0, + FXBS, ADCBS_BUFSIZE_NONE, + FXBA, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + + /* disable stop on loop end */ + SOLEL, 0, + SOLEH, 0, + + REGLIST_END); if (emu->audigy) { /* set SPDIF bypass mode */ @@ -177,9 +186,11 @@ static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir) for (ch = 0; ch < NUM_G; ch++) snd_emu10k1_voice_init(emu, ch); - snd_emu10k1_ptr_write(emu, SPCS0, 0, emu->spdif_bits[0]); - snd_emu10k1_ptr_write(emu, SPCS1, 0, emu->spdif_bits[1]); - snd_emu10k1_ptr_write(emu, SPCS2, 0, emu->spdif_bits[2]); + snd_emu10k1_ptr_write_multiple(emu, 0, + SPCS0, emu->spdif_bits[0], + SPCS1, emu->spdif_bits[1], + SPCS2, emu->spdif_bits[2], + REGLIST_END); if (emu->card_capabilities->emu_model) { } else if (emu->card_capabilities->ca0151_chip) { /* audigy2 */ @@ -390,41 +401,48 @@ int snd_emu10k1_done(struct snd_emu10k1 *emu) outl(0, emu->port + INTE); /* - * Shutdown the chip + * Shutdown the voices */ - for (ch = 0; ch < NUM_G; ch++) - snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); for (ch = 0; ch < NUM_G; ch++) { - snd_emu10k1_ptr_write(emu, VTFT, ch, 0); - snd_emu10k1_ptr_write(emu, CVCF, ch, 0); - snd_emu10k1_ptr_write(emu, PTRX, ch, 0); - snd_emu10k1_ptr_write(emu, CPF, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, 0, + CVCF, 0, + PTRX, 0, + CPF, 0, + REGLIST_END); } - /* reset recording buffers */ - snd_emu10k1_ptr_write(emu, MICBS, 0, 0); - snd_emu10k1_ptr_write(emu, MICBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXBS, 0, 0); - snd_emu10k1_ptr_write(emu, FXBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXWC, 0, 0); - snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, ADCBA, 0, 0); - snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K); - snd_emu10k1_ptr_write(emu, TCB, 0, 0); + // stop the DSP if (emu->audigy) snd_emu10k1_ptr_write(emu, A_DBG, 0, A_DBG_SINGLE_STEP); else snd_emu10k1_ptr_write(emu, DBG, 0, EMU10K1_DBG_SINGLE_STEP); - /* disable channel interrupt */ - snd_emu10k1_ptr_write(emu, CLIEL, 0, 0); - snd_emu10k1_ptr_write(emu, CLIEH, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEL, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEH, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, 0, + MICBA, 0, + FXBS, 0, + FXBA, 0, + FXWC, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + TCBS, TCBS_BUFFSIZE_16K, + TCB, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + SOLEL, 0, + SOLEH, 0, + + PTB, 0, + + REGLIST_END); /* disable audio and lock cache */ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG); - snd_emu10k1_ptr_write(emu, PTB, 0, 0); return 0; } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 3a7f25f81504..183051e846f2 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1396,10 +1396,10 @@ static const struct snd_kcontrol_new snd_emu10k1_spdif_control = static void update_emu10k1_fxrt(struct snd_emu10k1 *emu, int voice, unsigned char *route) { if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXRT1, voice, - snd_emu10k1_compose_audigy_fxrt1(route)); - snd_emu10k1_ptr_write(emu, A_FXRT2, voice, - snd_emu10k1_compose_audigy_fxrt2(route)); + snd_emu10k1_ptr_write_multiple(emu, voice, + A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(route), + A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(route), + REGLIST_END); } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(route)); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9045359bb461..1ca16f0ddbed 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -268,47 +268,43 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_routing, &mix->send_routing[tmp][0], 8); memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - - if (stereo) { + if (emu->card_capabilities->emu_model) + pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ + else + pitch_target = emu10k1_calc_pitch_target(runtime->rate); + silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | + (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); + snd_emu10k1_ptr_write_multiple(emu, voice, // Not really necessary for the slave, but it doesn't hurt - snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); - } else { - snd_emu10k1_ptr_write(emu, CPF, voice, 0); - } - - /* setup routing */ + CPF, stereo ? CPF_STEREO_MASK : 0, + // Assumption that PT is already 0 so no harm overwriting + PTRX, (send_amount[0] << 8) | send_amount[1], + // Stereo slaves don't need to have the addresses set, but it doesn't hurt + DSL, end_addr | (send_amount[3] << 24), + PSST, start_addr | (send_amount[2] << 24), + CCCA, emu10k1_select_interprom(pitch_target) | + (w_16 ? 0 : CCCA_8BITSELECT), + // Clear filter delay memory + Z1, 0, + Z2, 0, + // Invalidate maps + MAPA, silent_page, + MAPB, silent_page, + // Disable filter (in conjunction with CCCA_RESONANCE == 0) + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + REGLIST_END); + // Setup routing if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXRT1, voice, - snd_emu10k1_compose_audigy_fxrt1(send_routing)); - snd_emu10k1_ptr_write(emu, A_FXRT2, voice, - snd_emu10k1_compose_audigy_fxrt2(send_routing)); - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, - snd_emu10k1_compose_audigy_sendamounts(send_amount)); - } else + snd_emu10k1_ptr_write_multiple(emu, voice, + A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(send_routing), + A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing), + A_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount), + REGLIST_END); + } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); - /* Assumption that PT is already 0 so no harm overwriting */ - snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); - // Stereo slaves don't need to have the addresses set, but it doesn't hurt - snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); - snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24)); - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_ptr_write(emu, CCCA, voice, - emu10k1_select_interprom(pitch_target) | - (w_16 ? 0 : CCCA_8BITSELECT)); - /* Clear filter delay memory */ - snd_emu10k1_ptr_write(emu, Z1, voice, 0); - snd_emu10k1_ptr_write(emu, Z2, voice, 0); - /* invalidate maps */ - silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page); - snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page); - // Disable filter (in conjunction with CCCA_RESONANCE == 0) - snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); + } spin_unlock_irqrestore(&emu->reg_lock, flags); } @@ -466,8 +462,10 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); } else snd_emu10k1_ptr_write(emu, FXWC, 0, 0); break; @@ -574,8 +572,10 @@ static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, unsigned int vattn) { - snd_emu10k1_ptr_write(emu, VTFT, evoice->number, vattn | VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, evoice->number, vattn | CVCF_CURRENTFILTER_MASK); + snd_emu10k1_ptr_write_multiple(emu, evoice->number, + VTFT, vattn | VTFT_FILTERTARGET_MASK, + CVCF, vattn | CVCF_CURRENTFILTER_MASK, + REGLIST_END); } static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, @@ -716,8 +716,10 @@ static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream, break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, epcm->capture_cr_val); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, epcm->capture_cr_val, + A_FXWC2, epcm->capture_cr_val2, + REGLIST_END); dev_dbg(emu->card->dev, "cr_val=0x%x, cr_val2=0x%x\n", epcm->capture_cr_val, @@ -744,8 +746,10 @@ static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream, break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); } else snd_emu10k1_ptr_write(emu, FXWC, 0, 0); break; @@ -1562,12 +1566,14 @@ static int snd_emu10k1_fx8010_playback_prepare(struct snd_pcm_substream *substre pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size); pcm->tram_shift = 0; - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_running, 0, 0); /* reset */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); /* reset */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size); - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0); /* reset ptr number */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size); - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size); + snd_emu10k1_ptr_write_multiple(emu, 0, + emu->gpr_base + pcm->gpr_running, 0, /* reset */ + emu->gpr_base + pcm->gpr_trigger, 0, /* reset */ + emu->gpr_base + pcm->gpr_size, runtime->buffer_size, + emu->gpr_base + pcm->gpr_ptr, 0, /* reset ptr number */ + emu->gpr_base + pcm->gpr_count, runtime->period_size, + emu->gpr_base + pcm->gpr_tmpcount, runtime->period_size, + REGLIST_END); for (i = 0; i < pcm->channels; i++) snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels)); return 0; diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 36fd6f7a0a2c..6419719c739c 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -94,6 +94,37 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i EXPORT_SYMBOL(snd_emu10k1_ptr_write); +void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...) +{ + va_list va; + u32 addr_mask; + unsigned long flags; + + if (snd_BUG_ON(!emu)) + return; + if (snd_BUG_ON(chn & ~PTR_CHANNELNUM_MASK)) + return; + addr_mask = ~((emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK) >> 16); + + va_start(va, chn); + spin_lock_irqsave(&emu->emu_lock, flags); + for (;;) { + u32 data; + u32 reg = va_arg(va, u32); + if (reg == REGLIST_END) + break; + data = va_arg(va, u32); + if (snd_BUG_ON(reg & addr_mask)) // Only raw registers supported here + continue; + outl((reg << 16) | chn, emu->port + PTR); + outl(data, emu->port + DATA); + } + spin_unlock_irqrestore(&emu->emu_lock, flags); + va_end(va); +} + +EXPORT_SYMBOL(snd_emu10k1_ptr_write_multiple); + unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn) -- cgit v1.2.3 From 816967d55f425a647137ef884d8e92f2baf541dc Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Thu, 18 May 2023 08:38:26 -0400 Subject: ALSA: emu10k1: set variables emu1010_routing_info and emu1010_pads_info storage-class-specifier to static smatch reports sound/pci/emu10k1/emumixer.c:519:39: warning: symbol 'emu1010_routing_info' was not declared. Should it be static? sound/pci/emu10k1/emumixer.c:859:36: warning: symbol 'emu1010_pads_info' was not declared. Should it be static? These variables are only used in their defining file, so it should be static Signed-off-by: Tom Rix Reviewed-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518123826.925752-1-trix@redhat.com Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 183051e846f2..47d5e6a88a89 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -516,7 +516,7 @@ struct snd_emu1010_routing_info { unsigned n_ins; }; -const struct snd_emu1010_routing_info emu1010_routing_info[] = { +static const struct snd_emu1010_routing_info emu1010_routing_info[] = { { /* rev1 1010 */ .src_regs = emu1010_src_regs, @@ -856,7 +856,7 @@ struct snd_emu1010_pads_info { unsigned n_adc_ctls, n_dac_ctls; }; -const struct snd_emu1010_pads_info emu1010_pads_info[] = { +static const struct snd_emu1010_pads_info emu1010_pads_info[] = { { /* rev1 1010 */ .adc_ctls = snd_emu1010_adc_pads, -- cgit v1.2.3 From df335e9a8bcb58be3b7388cff556f06eeb3d024f Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:38 +0200 Subject: ALSA: emu10k1: fix synthesizer sample playback position and caching Compensate for the cache delay, and actually populate the cache. Without these, the playback would start with garbage (which would be (mostly?) masqueraded by the attack phase). Unlike for the PCM voices, this doesn't try to compensate for the interpolator read-ahead, because it's pointless to be super-exact here. Note that this code is probably still broken for particularly short samples, because we ignore the loop-related parts of CCR. But I'm not going to reverse-engineer that now ... Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722308-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 06440b97b5d7..aab8d64fd708 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -253,7 +253,7 @@ lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw, /* check if sample is finished playing (non-looping only) */ if (bp != best + V_OFF && bp != best + V_FREE && (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { - val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch); + val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch) - 64; if (val >= vp->reg.loopstart) bp = best + V_OFF; } @@ -360,7 +360,7 @@ start_voice(struct snd_emux_voice *vp) map = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - addr = vp->reg.start; + addr = vp->reg.start + 64; temp = vp->reg.parm.filterQ; ccca = (temp << 28) | addr; if (vp->apitch < 0xe400) @@ -428,40 +428,14 @@ start_voice(struct snd_emux_voice *vp) /* Q & current address (Q 4bit value, MSB) */ CCCA, ccca, + /* cache */ + CCR, REG_VAL_PUT(CCR_CACHEINVALIDSIZE, 64), + /* reset volume */ VTFT, vtarget | vp->ftarget, CVCF, vtarget | CVCF_CURRENTFILTER_MASK, REGLIST_END); -#if 0 - /* cache */ - { - unsigned int val, sample; - val = 32; - if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) - sample = 0x80808080; - else { - sample = 0; - val *= 2; - } - - /* cache */ - snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16); - snd_emu10k1_ptr_write(hw, CDE, ch, sample); - snd_emu10k1_ptr_write(hw, CDF, ch, sample); - - /* invalidate maps */ - temp = ((unsigned int)hw->silent_page.addr << hw_address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(hw, MAPA, ch, temp); - snd_emu10k1_ptr_write(hw, MAPB, ch, temp); - - /* fill cache */ - val -= 4; - val <<= 25; - val |= 0x1c << 16; - snd_emu10k1_ptr_write(hw, CCR, ch, val); - } -#endif return 0; } -- cgit v1.2.3 From 5c2664cc09f94ba11c61908d5c7dac1c35d6dee8 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:39 +0200 Subject: ALSA: emu10k1: fix terminating synthesizer voices Make terminate_voice() actually do at all what it's supposed to do: instantly and completely shut down the note. The bogus behavior was mostly harmless, as usually the voice is freed right afterwards, which implicitly terminates it anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722308-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index aab8d64fd708..dcd7be2d281b 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -136,8 +136,13 @@ terminate_voice(struct snd_emux_voice *vp) if (snd_BUG_ON(!vp)) return; hw = vp->hw; - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, - DCYSUSV_PHASE1_MASK | DCYSUSV_DECAYTIME_MASK | DCYSUSV_CHANNELENABLE_MASK); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + REGLIST_END); if (vp->block) { struct snd_emu10k1_memblk *emem; emem = (struct snd_emu10k1_memblk *)vp->block; -- cgit v1.2.3 From 08e55ae996cbdbd76c3eb12fcad6cc79cba7ddbc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:38 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 3: pitch CPF_CURRENTPITCH starts swerving towards PTRX_PITCHTARGET as soon as that is set. In practice this means that CPF_FRACADDRESS may acquire a non-zero value before we manage to force CPF_CURRENTPITCH to the final value, which would prevent bit-for-bit reproduction. To avoid that this state persists, we now reset CPF_FRACADDRESS when setting CPF_CURRENTPITCH, and to (mostly) avoid that it progresses too far in the first place (possibly even reaching CCCA_CURRADDR), we write PTRX and CPF in one critical section (though NMIs, etc. still make this unreliable). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722279-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 1ca16f0ddbed..0b23ff8d9c3b 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -601,6 +601,17 @@ static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, snd_emu10k1_playback_commit_volume(emu, evoice, 0); } +static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, + u32 voice, u32 pitch_target) +{ + u32 ptrx = snd_emu10k1_ptr_read(emu, PTRX, voice); + u32 cpf = snd_emu10k1_ptr_read(emu, CPF, voice); + snd_emu10k1_ptr_write_multiple(emu, voice, + PTRX, (ptrx & ~PTRX_PITCHTARGET_MASK) | pitch_target, + CPF, (cpf & ~(CPF_CURRENTPITCH_MASK | CPF_FRACADDRESS_MASK)) | pitch_target, + REGLIST_END); +} + static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { @@ -616,8 +627,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); + snd_emu10k1_playback_commit_pitch(emu, voice, pitch_target << 16); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -626,8 +636,7 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, unsigned int voice; voice = evoice->number; - snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); + snd_emu10k1_playback_commit_pitch(emu, voice, 0); } static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, -- cgit v1.2.3 From fccd6f31a450d58109f64eda2dd9294e160fb0aa Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:39 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 4: send amounts On Audigy, the send amounts are merely targets, presumably to avoid sound distortion due to sudden changes, which the EMU8K docu explicitly warns about. However, that "soft-start" would prevent bit-for-bit reproduction, so we now force the current send amounts to their final values at PCM playback init. One might want to do that for the MIDI synthesizer as well, though it seems mostly pointless due to the attack phase each note has anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722279-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 ++ sound/pci/emu10k1/emupcm.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 9c5de1f45566..583fabef0b99 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -709,6 +709,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define ADCBS_BUFSIZE_57344 0x0000001e #define ADCBS_BUFSIZE_65536 0x0000001f +// On Audigy, the FX send amounts are not applied instantly, but determine +// targets towards which the following registers swerve gradually. #define A_CSBA 0x4c /* FX send B & A current amounts */ #define A_CSDC 0x4d /* FX send D & C current amounts */ #define A_CSFE 0x4e /* FX send F & E current amounts */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0b23ff8d9c3b..903a68a4d396 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -236,6 +236,18 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) return CCCA_INTERPROM_2; } +static u16 emu10k1_send_target_from_amount(u8 amount) +{ + static const u8 shifts[8] = { 4, 4, 5, 6, 7, 8, 9, 10 }; + static const u16 offsets[8] = { 0, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000 }; + u8 exp; + + if (amount == 0xff) + return 0xffff; + exp = amount >> 5; + return ((amount & 0x1f) << shifts[exp]) + offsets[exp]; +} + static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, @@ -301,6 +313,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing), A_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount), REGLIST_END); + for (int i = 0; i < 4; i++) { + u32 aml = emu10k1_send_target_from_amount(send_amount[2 * i]); + u32 amh = emu10k1_send_target_from_amount(send_amount[2 * i + 1]); + snd_emu10k1_ptr_write(emu, A_CSBA + i, voice, (amh << 16) | aml); + } } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); -- cgit v1.2.3 From f26a4cf087cbfc9dc71bb3812e8e11ccac0d4d61 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:41 +0200 Subject: ALSA: emu10k1: simplify freeing synth voices snd_emu10k1_voice_free() resets the hardware itself, so doing that in the calling function as well is redundant. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index dcd7be2d281b..6686ca8ce5fc 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -165,11 +165,6 @@ free_voice(struct snd_emux_voice *vp) /* Problem apparent on plug, unplug then plug */ /* on the Audigy 2 ZS Notebook. */ if (hw && (vp->ch >= 0)) { - snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00); - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK); - // snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0); - snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff); - snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff); snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]); vp->emu->num_voices--; vp->ch = -1; -- cgit v1.2.3 From 3eb5b1d0a11d1daf85243eb06f813cf83752e1d0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:42 +0200 Subject: ALSA: emu10k1: don't forget to reset reclaimed synth voices The subsequent allocation may still fail after freeing some voices, so we shouldn't leave them in their programmed state. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/voice.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index cbeb8443492c..a602df9117f6 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -98,6 +98,15 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, return 0; } +static void voice_free(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *pvoice) +{ + snd_emu10k1_voice_init(emu, pvoice->number); + pvoice->interrupt = NULL; + pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; + pvoice->epcm = NULL; +} + int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, struct snd_emu10k1_voice **rvoice) { @@ -118,12 +127,8 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, /* free a voice from synth */ if (emu->get_synth_voice) { result = emu->get_synth_voice(emu); - if (result >= 0) { - struct snd_emu10k1_voice *pvoice = &emu->voices[result]; - pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; - pvoice->epcm = NULL; - } + if (result >= 0) + voice_free(emu, &emu->voices[result]); } if (result < 0) break; @@ -143,10 +148,7 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, if (snd_BUG_ON(!pvoice)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; - pvoice->epcm = NULL; - snd_emu10k1_voice_init(emu, pvoice->number); + voice_free(emu, pvoice); spin_unlock_irqrestore(&emu->voice_lock, flags); return 0; } -- cgit v1.2.3 From b840f8d8fcb3df9e65bb6782a9072897b6ea117d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:43 +0200 Subject: ALSA: emu10k1: improve voice status display in /proc Eliminate the MIDI type, as there is no such thing - the MPU401 port doesn't have anything to do with voices. For clarity, differentiate between regular and extra voices. Don't atomize the enum into bits in the table display. Simplify/optimize the storage. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 13 ++++++------- sound/pci/emu10k1/emupcm.c | 2 +- sound/pci/emu10k1/emuproc.c | 16 ++++++++-------- sound/pci/emu10k1/voice.c | 20 +++----------------- 4 files changed, 18 insertions(+), 33 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 583fabef0b99..1fa7816c07fd 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1439,21 +1439,20 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM /* ------------------- STRUCTURES -------------------- */ enum { + EMU10K1_UNUSED, // This must be zero EMU10K1_EFX, + EMU10K1_EFX_IRQ, EMU10K1_PCM, + EMU10K1_PCM_IRQ, EMU10K1_SYNTH, - EMU10K1_MIDI + EMU10K1_NUM_TYPES }; struct snd_emu10k1; struct snd_emu10k1_voice { - int number; - unsigned int use: 1, - pcm: 1, - efx: 1, - synth: 1, - midi: 1; + unsigned char number; + unsigned char use; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 903a68a4d396..216b6cde326f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -117,7 +117,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic // period-sized loop as the interrupt source. Additionally, the interrupt // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, + epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, 1, &epcm->extra); if (err < 0) { diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 708aff6cf09a..c423a56ebf9e 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -367,17 +367,17 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, struct snd_emu10k1 *emu = entry->private_data; struct snd_emu10k1_voice *voice; int idx; - - snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n"); + static const char * const types[] = { + "Unused", "EFX", "EFX IRQ", "PCM", "PCM IRQ", "Synth" + }; + static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); + + snd_iprintf(buffer, "ch\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n", + snd_iprintf(buffer, "%i\t%s\n", idx, - voice->use, - voice->pcm, - voice->efx, - voice->synth, - voice->midi); + types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index a602df9117f6..ac89d09ed9bc 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -78,21 +78,7 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number); */ - voice->use = 1; - switch (type) { - case EMU10K1_PCM: - voice->pcm = 1; - break; - case EMU10K1_SYNTH: - voice->synth = 1; - break; - case EMU10K1_MIDI: - voice->midi = 1; - break; - case EMU10K1_EFX: - voice->efx = 1; - break; - } + voice->use = type; } *rvoice = &emu->voices[first_voice]; return 0; @@ -103,7 +89,7 @@ static void voice_free(struct snd_emu10k1 *emu, { snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; + pvoice->use = 0; pvoice->epcm = NULL; } @@ -121,7 +107,7 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, spin_lock_irqsave(&emu->voice_lock, flags); for (;;) { result = voice_alloc(emu, type, number, rvoice); - if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI) + if (result == 0 || type == EMU10K1_SYNTH) break; /* free a voice from synth */ -- cgit v1.2.3 From 82a9fa6e9e3c769f7edc62810c9718997cada53d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:44 +0200 Subject: ALSA: emu10k1: make freeing untouched playback voices cheap This allows us to drop the code that tries to preserve already allocated voices upon repeated hw_param callback invocations. Getting it right for multi-channel voices would otherwise get a bit hairy. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emu10k1_callback.c | 1 + sound/pci/emu10k1/emupcm.c | 13 ++----------- sound/pci/emu10k1/emuproc.c | 5 +++-- sound/pci/emu10k1/voice.c | 5 +++-- 5 files changed, 10 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 1fa7816c07fd..0ce84beb6441 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1453,6 +1453,7 @@ struct snd_emu10k1; struct snd_emu10k1_voice { unsigned char number; unsigned char use; + unsigned char dirty; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 6686ca8ce5fc..6d483bda46df 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -437,6 +437,7 @@ start_voice(struct snd_emux_voice *vp) REGLIST_END); + hw->voices[ch].dirty = 1; return 0; } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 216b6cde326f..324db1141479 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -80,17 +80,6 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic { int err, i; - if (epcm->voices[1] != NULL && voices < 2) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]); - epcm->voices[1] = NULL; - } - for (i = 0; i < voices; i++) { - if (epcm->voices[i] == NULL) - break; - } - if (i == voices) - return 0; /* already allocated */ - for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) { if (epcm->voices[i]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); @@ -323,6 +312,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_compose_send_routing(send_routing)); } + emu->voices[voice].dirty = 1; + spin_unlock_irqrestore(&emu->reg_lock, flags); } diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index c423a56ebf9e..abcec8a01760 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -372,11 +372,12 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, }; static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); - snd_iprintf(buffer, "ch\tuse\n"); + snd_iprintf(buffer, "ch\tdirty\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%s\n", + snd_iprintf(buffer, "%i\t%u\t%s\n", idx, + voice->dirty, types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index ac89d09ed9bc..25e78fc188bf 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -87,9 +87,10 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, static void voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice) { - snd_emu10k1_voice_init(emu, pvoice->number); + if (pvoice->dirty) + snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = 0; + pvoice->use = pvoice->dirty = 0; pvoice->epcm = NULL; } -- cgit v1.2.3 From 608f1b0dbddec6b2fd766c10bcce2b651995e936 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Thu, 18 May 2023 16:02:48 +0100 Subject: ASoC: cs35l56: Move DSP part string generation so that it is done only once Each time we go through dsp_work() it does a devm_kasprintf() to allocate memory to hold the part name string. It's not strictly a memory leak because devm will free it all if the driver is removed. But we keep allocating more and more memory to hold the same string. Move the allocation so that it is performed after the version and secured state information is gathered and handle allocation errors. Signed-off-by: Simon Trimmer Signed-off-by: Richard Fitzgerald Link: https://lore.kernel.org/r/Message-Id: <20230518150250.1121006-2-rf@opensource.cirrus.com> Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index 46762f7f1449..e4bf38873de5 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -837,12 +837,6 @@ static void cs35l56_dsp_work(struct work_struct *work) if (!cs35l56->init_done) return; - cs35l56->dsp.part = devm_kasprintf(cs35l56->dev, GFP_KERNEL, "cs35l56%s-%02x", - cs35l56->secured ? "s" : "", cs35l56->rev); - - if (!cs35l56->dsp.part) - return; - pm_runtime_get_sync(cs35l56->dev); /* @@ -1507,6 +1501,12 @@ int cs35l56_init(struct cs35l56_private *cs35l56) dev_info(cs35l56->dev, "Cirrus Logic CS35L56%s Rev %02X OTP%d\n", cs35l56->secured ? "s" : "", cs35l56->rev, otpid); + /* Populate the DSP information with the revision and security state */ + cs35l56->dsp.part = devm_kasprintf(cs35l56->dev, GFP_KERNEL, "cs35l56%s-%02x", + cs35l56->secured ? "s" : "", cs35l56->rev); + if (!cs35l56->dsp.part) + return -ENOMEM; + /* Wake source and *_BLOCKED interrupts default to unmasked, so mask them */ regmap_write(cs35l56->regmap, CS35L56_IRQ1_MASK_20, 0xffffffff); regmap_update_bits(cs35l56->regmap, CS35L56_IRQ1_MASK_1, -- cgit v1.2.3 From c9001a2754528fa5da20e8674b3afbd8c134cc91 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Thu, 18 May 2023 16:02:49 +0100 Subject: ASoC: cs35l56: sdw_write_no_pm() should be performed under a pm_runtime request SoundWire bus accesses must be performed under the guard of a pm_runtime request, in this case the write was being performed just after the request had been put() and so the bus could not be guaranteed to be available. Signed-off-by: Simon Trimmer Signed-off-by: Richard Fitzgerald Link: https://lore.kernel.org/r/Message-Id: <20230518150250.1121006-3-rf@opensource.cirrus.com> Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index e4bf38873de5..d1d304ad559b 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -903,15 +903,15 @@ static void cs35l56_dsp_work(struct work_struct *work) err_unlock: mutex_unlock(&cs35l56->irq_lock); err: - pm_runtime_mark_last_busy(cs35l56->dev); - pm_runtime_put_autosuspend(cs35l56->dev); - /* Re-enable SoundWire interrupts */ if (cs35l56->sdw_peripheral) { cs35l56->sdw_irq_no_unmask = false; sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); } + + pm_runtime_mark_last_busy(cs35l56->dev); + pm_runtime_put_autosuspend(cs35l56->dev); } static int cs35l56_component_probe(struct snd_soc_component *component) -- cgit v1.2.3 From 1a8edfcffa2803afc0ef3a6a48819230cdbda2c9 Mon Sep 17 00:00:00 2001 From: Simon Trimmer Date: Thu, 18 May 2023 16:02:50 +0100 Subject: ASoC: cs35l56: In secure mode skip SHUTDOWN and RESET around fw download If the device is in secure mode it's unnecessary to send a SHUTDOWN and SYSTEM_RESET around the firmware download. It could only be patching insecure tunings. A tuning patch doesn't need a SHUTDOWN and only needs a REINIT afterwards. This will reduce the overhead of exiting system suspend in secure mode. Signed-off-by: Simon Trimmer Signed-off-by: Richard Fitzgerald Link: https://lore.kernel.org/r/Message-Id: <20230518150250.1121006-4-rf@opensource.cirrus.com> Signed-off-by: Mark Brown --- include/sound/cs35l56.h | 1 + sound/soc/codecs/cs35l56.c | 47 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/include/sound/cs35l56.h b/include/sound/cs35l56.h index 002042b1c73c..1f9713d7ca76 100644 --- a/include/sound/cs35l56.h +++ b/include/sound/cs35l56.h @@ -223,6 +223,7 @@ #define CS35L56_MBOX_CMD_AUDIO_PLAY 0x0B000001 #define CS35L56_MBOX_CMD_AUDIO_PAUSE 0x0B000002 +#define CS35L56_MBOX_CMD_AUDIO_REINIT 0x0B000003 #define CS35L56_MBOX_CMD_HIBERNATE_NOW 0x02000001 #define CS35L56_MBOX_CMD_WAKEUP 0x02000002 #define CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE 0x02000003 diff --git a/sound/soc/codecs/cs35l56.c b/sound/soc/codecs/cs35l56.c index d1d304ad559b..5df8cb556772 100644 --- a/sound/soc/codecs/cs35l56.c +++ b/sound/soc/codecs/cs35l56.c @@ -825,19 +825,23 @@ static void cs35l56_system_reset(struct cs35l56_private *cs35l56) regcache_cache_only(cs35l56->regmap, false); } -static void cs35l56_dsp_work(struct work_struct *work) +static void cs35l56_secure_patch(struct cs35l56_private *cs35l56) { - struct cs35l56_private *cs35l56 = container_of(work, - struct cs35l56_private, - dsp_work); - unsigned int reg; - unsigned int val; - int ret = 0; + int ret; - if (!cs35l56->init_done) - return; + /* Use wm_adsp to load and apply the firmware patch and coefficient files */ + ret = wm_adsp_power_up(&cs35l56->dsp); + if (ret) + dev_dbg(cs35l56->dev, "%s: wm_adsp_power_up ret %d\n", __func__, ret); + else + cs35l56_mbox_send(cs35l56, CS35L56_MBOX_CMD_AUDIO_REINIT); +} - pm_runtime_get_sync(cs35l56->dev); +static void cs35l56_patch(struct cs35l56_private *cs35l56) +{ + unsigned int reg; + unsigned int val; + int ret; /* * Disable SoundWire interrupts to prevent race with IRQ work. @@ -909,6 +913,29 @@ err: sdw_write_no_pm(cs35l56->sdw_peripheral, CS35L56_SDW_GEN_INT_MASK_1, CS35L56_SDW_INT_MASK_CODEC_IRQ); } +} + +static void cs35l56_dsp_work(struct work_struct *work) +{ + struct cs35l56_private *cs35l56 = container_of(work, + struct cs35l56_private, + dsp_work); + + if (!cs35l56->init_done) + return; + + pm_runtime_get_sync(cs35l56->dev); + + /* + * When the device is running in secure mode the firmware files can + * only contain insecure tunings and therefore we do not need to + * shutdown the firmware to apply them and can use the lower cost + * reinit sequence instead. + */ + if (cs35l56->secured) + cs35l56_secure_patch(cs35l56); + else + cs35l56_patch(cs35l56); pm_runtime_mark_last_busy(cs35l56->dev); pm_runtime_put_autosuspend(cs35l56->dev); -- cgit v1.2.3 From bdb3b567b84e321c51786aba2a05ec23bb90bfdf Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 19 May 2023 20:41:22 +0200 Subject: ALSA: emu10k1: centralize freeing PCM voices This saves some code duplication; no functional change. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230519184122.3808185-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 324db1141479..9d6eb58e773f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -76,16 +76,22 @@ static void snd_emu10k1_pcm_efx_interrupt(struct snd_emu10k1 *emu, snd_pcm_period_elapsed(emu->pcm_capture_efx_substream); } -static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) { - int err, i; - - for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) { + for (unsigned i = 0; i < ARRAY_SIZE(epcm->voices); i++) { if (epcm->voices[i]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); epcm->voices[i] = NULL; } } +} + +static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +{ + int err, i; + + snd_emu10k1_pcm_free_voices(epcm); + err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, voices, @@ -115,15 +121,13 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic "failed extra: voices=%d, frame=%d\n", voices, frame); */ - for (i = 0; i < voices; i++) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); - epcm->voices[i] = NULL; - } + snd_emu10k1_pcm_free_voices(epcm); return err; } epcm->extra->epcm = epcm; epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; } + return 0; } @@ -359,7 +363,6 @@ static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream) struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm; - int i; if (runtime->private_data == NULL) return 0; @@ -368,12 +371,7 @@ static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream) snd_emu10k1_voice_free(epcm->emu, epcm->extra); epcm->extra = NULL; } - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - if (epcm->voices[i]) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); - epcm->voices[i] = NULL; - } - } + snd_emu10k1_pcm_free_voices(epcm); if (epcm->memblk) { snd_emu10k1_free_pages(emu, epcm->memblk); epcm->memblk = NULL; -- cgit v1.2.3 From b4fea2d3f25b5f3ad6b230f91e61151165f6d023 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:46 +0200 Subject: ALSA: emu10k1: make snd_emu10k1_voice_alloc() assign voices' epcm The voice allocator clearly knows about the field (it resets it), so it's more consistent (and leads to less duplicated code) to have the constructor take it as a parameter. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 ++- sound/pci/emu10k1/emu10k1_callback.c | 2 +- sound/pci/emu10k1/emupcm.c | 7 ++----- sound/pci/emu10k1/voice.c | 7 ++++--- 4 files changed, 9 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 0ce84beb6441..3cd1b7752c2e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1850,7 +1850,8 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, struct snd_emu10k1_voice **rvoice); +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice); int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); /* MIDI uart */ diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 6d483bda46df..2fdfed7f07c2 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port) if (vp->ch < 0) { /* allocate a voice */ struct snd_emu10k1_voice *hwvoice; - if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL) + if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, NULL, &hwvoice) < 0 || hwvoice == NULL) continue; vp->ch = hwvoice->number; emu->num_voices++; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9d6eb58e773f..0651e7795ecf 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -95,15 +95,13 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, voices, - &epcm->voices[0]); + epcm, &epcm->voices[0]); if (err < 0) return err; - epcm->voices[0]->epcm = epcm; if (voices > 1) { for (i = 1; i < voices; i++) { epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G]; - epcm->voices[i]->epcm = epcm; } } if (epcm->extra == NULL) { @@ -114,7 +112,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, 1, - &epcm->extra); + epcm, &epcm->extra); if (err < 0) { /* dev_dbg(emu->card->dev, "pcm_channel_alloc: " @@ -124,7 +122,6 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic snd_emu10k1_pcm_free_voices(epcm); return err; } - epcm->extra->epcm = epcm; epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index 25e78fc188bf..6c58e3bd14f7 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -32,7 +32,7 @@ */ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, - struct snd_emu10k1_voice **rvoice) + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { struct snd_emu10k1_voice *voice; int i, j, k, first_voice, last_voice, skip; @@ -79,6 +79,7 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, voice->number, idx-first_voice+1, number); */ voice->use = type; + voice->epcm = epcm; } *rvoice = &emu->voices[first_voice]; return 0; @@ -95,7 +96,7 @@ static void voice_free(struct snd_emu10k1 *emu, } int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, - struct snd_emu10k1_voice **rvoice) + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { unsigned long flags; int result; @@ -107,7 +108,7 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, spin_lock_irqsave(&emu->voice_lock, flags); for (;;) { - result = voice_alloc(emu, type, number, rvoice); + result = voice_alloc(emu, type, number, epcm, rvoice); if (result == 0 || type == EMU10K1_SYNTH) break; -- cgit v1.2.3 From a915d60426d4348a0b91f9870e299056fd604a32 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:47 +0200 Subject: ALSA: emu10k1: revamp playback voice allocator Instead of separate voices, we now allocate non-interleaved channels, which may in turn contain two interleaved voices each. The higher-level code keeps only one pointer per channel. The channels are not allocated in one block any more, as there is no reason to do that. As a consequence of that, and because it is cleaner regardless, we now let the allocator store these pointers at a specified location, rather than returning only the first one and having the calling code deduce the remaining ones. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +- sound/pci/emu10k1/emu10k1_callback.c | 2 +- sound/pci/emu10k1/emumixer.c | 24 ++++---- sound/pci/emu10k1/emupcm.c | 44 ++++++++------- sound/pci/emu10k1/emuproc.c | 5 +- sound/pci/emu10k1/voice.c | 106 ++++++++++++++++++----------------- 6 files changed, 95 insertions(+), 89 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 3cd1b7752c2e..0780f39f4bb6 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1454,6 +1454,7 @@ struct snd_emu10k1_voice { unsigned char number; unsigned char use; unsigned char dirty; + unsigned char last; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; @@ -1850,7 +1851,7 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice); int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 2fdfed7f07c2..ad0dea0c2be9 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port) if (vp->ch < 0) { /* allocate a voice */ struct snd_emu10k1_voice *hwvoice; - if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, NULL, &hwvoice) < 0 || hwvoice == NULL) + if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, 1, NULL, &hwvoice) < 0) continue; vp->ch = hwvoice->number; emu->num_voices++; diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 47d5e6a88a89..20a0b3afc8a5 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1467,13 +1467,13 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, &mix->send_routing[1][0]); - update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number, + update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number + 1, &mix->send_routing[2][0]); - } else if (mix->epcm->voices[0]) { + } else { update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, &mix->send_routing[0][0]); } @@ -1535,13 +1535,13 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, &mix->send_volume[1][0]); - update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number, + update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number + 1, &mix->send_volume[2][0]); - } else if (mix->epcm->voices[0]) { + } else { update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, &mix->send_volume[0][0]); } @@ -1601,11 +1601,11 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]); - snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]); - } else if (mix->epcm->voices[0]) { + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number + 1, mix->attn[2]); + } else { snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]); } } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0651e7795ecf..0036593cca7c 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -86,32 +86,26 @@ static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) } } -static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, + int type, int count, int channels) { - int err, i; + int err; snd_emu10k1_pcm_free_voices(epcm); err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, - voices, + type, count, channels, epcm, &epcm->voices[0]); - if (err < 0) return err; - if (voices > 1) { - for (i = 1; i < voices; i++) { - epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G]; - } - } + if (epcm->extra == NULL) { // The hardware supports only (half-)loop interrupts, so to support an // arbitrary number of periods per buffer, we use an extra voice with a // period-sized loop as the interrupt source. Additionally, the interrupt // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, - 1, + type + 1, 1, 1, epcm, &epcm->extra); if (err < 0) { /* @@ -325,9 +319,19 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; size_t alloc_size; + int type, channels, count; int err; - err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params)); + if (epcm->type == PLAYBACK_EMUVOICE) { + type = EMU10K1_PCM; + channels = 1; + count = params_channels(hw_params); + } else { + type = EMU10K1_EFX; + channels = params_channels(hw_params); + count = 1; + } + err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels); if (err < 0) return err; @@ -397,8 +401,8 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, &emu->pcm_mixer[substream->number]); - if (epcm->voices[1]) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], w_16, true, + if (stereo) + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[0] + 1, w_16, true, start_addr, end_addr, &emu->pcm_mixer[substream->number]); return 0; @@ -589,8 +593,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, unsigned int vattn; unsigned int tmp; - if (evoice == NULL) /* skip second voice for mono */ - return; tmp = stereo ? (master ? 1 : 2) : 0; vattn = mix->attn[tmp] << 16; snd_emu10k1_playback_commit_volume(emu, evoice, vattn); @@ -599,8 +601,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { - if (evoice == NULL) - return; snd_emu10k1_playback_commit_volume(emu, evoice, 0); } @@ -681,7 +681,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], stereo, false, mix); + if (stereo) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0] + 1, true, false, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -693,7 +694,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); - snd_emu10k1_playback_mute_voice(emu, epcm->voices[1]); + if (stereo) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[0] + 1); break; default: result = -EINVAL; diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index abcec8a01760..89ea3adff322 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -372,12 +372,13 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, }; static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); - snd_iprintf(buffer, "ch\tdirty\tuse\n"); + snd_iprintf(buffer, "ch\tdirty\tlast\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%u\t%s\n", + snd_iprintf(buffer, "%i\t%u\t%u\t%s\n", idx, voice->dirty, + voice->last, types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index 6c58e3bd14f7..6939498e26f0 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -23,11 +23,7 @@ * allocator uses a round robin scheme. The next free voice is tracked in * the card record and each allocation begins where the last left off. The * hardware requires stereo interleaved voices be aligned to an even/odd - * boundary. For multichannel voice allocation we ensure than the block of - * voices does not cross the 32 voice boundary. This simplifies the - * multichannel support and ensures we can use a single write to the - * (set|clear)_loop_stop registers. Otherwise (for example) the voices would - * get out of sync when pausing/resuming a stream. + * boundary. * --rlrevell */ @@ -35,54 +31,43 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { struct snd_emu10k1_voice *voice; - int i, j, k, first_voice, last_voice, skip; + int i, j, k, skip; - *rvoice = NULL; - first_voice = last_voice = 0; - for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) { + for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) { /* dev_dbg(emu->card->dev, "i %d j %d next free %d!\n", i, j, emu->next_free_voice); */ - i %= NUM_G; /* stereo voices must be even/odd */ - if ((number == 2) && (i % 2)) { - i++; + if ((number > 1) && (i % 2)) { + skip = 1; continue; } - - skip = 0; + for (k = 0; k < number; k++) { - voice = &emu->voices[(i+k) % NUM_G]; + voice = &emu->voices[i + k]; if (voice->use) { - skip = 1; - break; + skip = k + 1; + goto next; } } - if (!skip) { - /* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */ - first_voice = i; - last_voice = (i + number) % NUM_G; - emu->next_free_voice = last_voice; - break; + + for (k = 0; k < number; k++) { + voice = &emu->voices[i + k]; + voice->use = type; + voice->epcm = epcm; + /* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */ } + voice->last = 1; + + *rvoice = &emu->voices[i]; + emu->next_free_voice = (i + number) % NUM_G; + return 0; + + next: ; } - - if (first_voice == last_voice) - return -ENOMEM; - - for (i = 0; i < number; i++) { - voice = &emu->voices[(first_voice + i) % NUM_G]; - /* - dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", - voice->number, idx-first_voice+1, number); - */ - voice->use = type; - voice->epcm = epcm; - } - *rvoice = &emu->voices[first_voice]; - return 0; + return -ENOMEM; // -EBUSY would have been better } static void voice_free(struct snd_emu10k1 *emu, @@ -91,11 +76,11 @@ static void voice_free(struct snd_emu10k1 *emu, if (pvoice->dirty) snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = pvoice->dirty = 0; + pvoice->use = pvoice->dirty = pvoice->last = 0; pvoice->epcm = NULL; } -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { unsigned long flags; @@ -103,23 +88,36 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, if (snd_BUG_ON(!rvoice)) return -EINVAL; - if (snd_BUG_ON(!number)) + if (snd_BUG_ON(!count)) + return -EINVAL; + if (snd_BUG_ON(!channels)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - for (;;) { - result = voice_alloc(emu, type, number, epcm, rvoice); - if (result == 0 || type == EMU10K1_SYNTH) - break; - - /* free a voice from synth */ - if (emu->get_synth_voice) { + for (int got = 0; got < channels; ) { + result = voice_alloc(emu, type, count, epcm, &rvoice[got]); + if (result == 0) { + got++; + /* + dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", + rvoice[got - 1]->number, got, want); + */ + continue; + } + if (type != EMU10K1_SYNTH && emu->get_synth_voice) { + /* free a voice from synth */ result = emu->get_synth_voice(emu); - if (result >= 0) + if (result >= 0) { voice_free(emu, &emu->voices[result]); + continue; + } + } + for (int i = 0; i < got; i++) { + for (int j = 0; j < count; j++) + voice_free(emu, rvoice[i] + j); + rvoice[i] = NULL; } - if (result < 0) - break; + break; } spin_unlock_irqrestore(&emu->voice_lock, flags); @@ -132,11 +130,15 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice) { unsigned long flags; + int last; if (snd_BUG_ON(!pvoice)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - voice_free(emu, pvoice); + do { + last = pvoice->last; + voice_free(emu, pvoice++); + } while (!last); spin_unlock_irqrestore(&emu->voice_lock, flags); return 0; } -- cgit v1.2.3 From 4040fc51ca37b198bb43f716e1868fe7ff5d731c Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 19 May 2023 14:54:46 -0600 Subject: ALSA: mixart: Replace one-element arrays with simple object declarations One-element arrays are deprecated, and we are replacing them with flexible array members instead. However, in this case it seems those one-element arrays have never actually been used as fake flexible arrays. See this code that dates from Linux-2.6.12-rc2 initial git repository build (commit 1da177e4c3f4 ("Linux-2.6.12-rc2")): sound/pci/mixart/mixart_core.h: 215 struct mixart_stream_state_req 216 { 217 u32 delayed; 218 u64 scheduler; 219 u32 reserved4np[3]; 220 u32 stream_count; /* set to 1 for instance */ 221 struct mixart_flow_info stream_info; /* could be an array[stream_count] */ 222 } __attribute__((packed)); sound/pci/mixart/mixart.c: 388 389 memset(&stream_state_req, 0, sizeof(stream_state_req)); 390 stream_state_req.stream_count = 1; 391 stream_state_req.stream_info.stream_desc.uid_pipe = stream->pipe->group_uid; 392 stream_state_req.stream_info.stream_desc.stream_idx = stream->substream->number; 393 So, taking the code above as example, replace multiple one-element arrays with simple object declarations, and refactor the rest of the code, accordingly. This helps with the ongoing efforts to tighten the FORTIFY_SOURCE routines on memcpy() and help us make progress towards globally enabling -fstrict-flex-arrays=3 [1]. This results in no differences in binary output. Link: https://github.com/KSPP/linux/issues/79 Link: https://github.com/KSPP/linux/issues/296 Link: https://gcc.gnu.org/pipermail/gcc-patches/2022-October/602902.html [1] Signed-off-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/ZGfiFjcL8+r3mayq@work Signed-off-by: Takashi Iwai --- sound/pci/mixart/mixart.c | 8 ++++---- sound/pci/mixart/mixart_core.h | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index 1b078b789604..7ceaf6a7a77e 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -98,7 +98,7 @@ static int mixart_set_pipe_state(struct mixart_mgr *mgr, memset(&group_state, 0, sizeof(group_state)); group_state.pipe_count = 1; - group_state.pipe_uid[0] = pipe->group_uid; + group_state.pipe_uid = pipe->group_uid; if(start) request.message_id = MSG_STREAM_START_STREAM_GRP_PACKET; @@ -185,7 +185,7 @@ static int mixart_set_clock(struct mixart_mgr *mgr, clock_properties.clock_mode = CM_STANDALONE; clock_properties.frequency = rate; clock_properties.nb_callers = 1; /* only one entry in uid_caller ! */ - clock_properties.uid_caller[0] = pipe->group_uid; + clock_properties.uid_caller = pipe->group_uid; dev_dbg(&mgr->pci->dev, "mixart_set_clock to %d kHz\n", rate); @@ -565,8 +565,8 @@ static int mixart_set_format(struct mixart_stream *stream, snd_pcm_format_t form stream_param.pipe_count = 1; /* set to 1 */ stream_param.stream_count = 1; /* set to 1 */ - stream_param.stream_desc[0].uid_pipe = stream->pipe->group_uid; - stream_param.stream_desc[0].stream_idx = stream->substream->number; + stream_param.stream_desc.uid_pipe = stream->pipe->group_uid; + stream_param.stream_desc.stream_idx = stream->substream->number; request.message_id = MSG_STREAM_SET_INPUT_STAGE_PARAM; request.uid = (struct mixart_uid){0,0}; diff --git a/sound/pci/mixart/mixart_core.h b/sound/pci/mixart/mixart_core.h index 2f0e29ed5d63..d39233e0e070 100644 --- a/sound/pci/mixart/mixart_core.h +++ b/sound/pci/mixart/mixart_core.h @@ -231,7 +231,7 @@ struct mixart_group_state_req u64 scheduler; u32 reserved4np[2]; u32 pipe_count; /* set to 1 for instance */ - struct mixart_uid pipe_uid[1]; /* could be an array[pipe_count] */ + struct mixart_uid pipe_uid; /* could be an array[pipe_count], in theory */ } __attribute__((packed)); struct mixart_group_state_resp @@ -314,7 +314,7 @@ struct mixart_clock_properties u32 format; u32 board_mask; u32 nb_callers; /* set to 1 (see below) */ - struct mixart_uid uid_caller[1]; + struct mixart_uid uid_caller; } __attribute__((packed)); struct mixart_clock_properties_resp @@ -401,8 +401,7 @@ struct mixart_stream_param_desc u32 reserved4np[3]; u32 pipe_count; /* set to 1 (array size !) */ u32 stream_count; /* set to 1 (array size !) */ - struct mixart_txx_stream_desc stream_desc[1]; /* only one stream per command, but this could be an array */ - + struct mixart_txx_stream_desc stream_desc; /* only one stream per command, but this could be an array, in theory */ } __attribute__((packed)); -- cgit v1.2.3 From d474809e9284848b6cb57a885f3252b86a0b485f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 May 2023 13:13:09 +0300 Subject: ASoC: SOF: ipc4-loader: Drop unused bss_size from struct sof_ipc4_fw_module The bss_size is only set, but not used by the code, remove it. Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230522101313.12519-2-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-loader.c | 6 +----- sound/soc/sof/ipc4-priv.h | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index 1321acc402fd..bb215d33d455 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -112,16 +112,12 @@ static ssize_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev, return -EINVAL; } - /* a module's config is always the same size */ - fw_module->bss_size = fm_config[fm_entry->cfg_offset].is_bytes; dev_dbg(sdev->dev, "module %s: UUID %pUL cfg_count: %u, bss_size: %#x\n", fm_entry->name, &fm_entry->uuid, fm_entry->cfg_count, - fw_module->bss_size); + fm_config[fm_entry->cfg_offset].is_bytes); } else { - fw_module->bss_size = 0; - dev_dbg(sdev->dev, "module %s: UUID %pUL\n", fm_entry->name, &fm_entry->uuid); } diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index f461b8c70df3..546e52746276 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -29,13 +29,11 @@ enum sof_ipc4_mtrace_type { * struct sof_ipc4_fw_module - IPC4 module info * @sof_man4_module: Module info * @m_ida: Module instance identifier - * @bss_size: Module object size * @private: Module private data */ struct sof_ipc4_fw_module { struct sof_man4_module man4_module_entry; struct ida m_ida; - u32 bss_size; void *private; }; -- cgit v1.2.3 From fe04f300035d497a066172a9a9331439cc8300f6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 May 2023 13:13:10 +0300 Subject: ASoC: SOF: ipc4-loader: Save a pointer to fm_config in sof_ipc4_fw_module Save a pointer to the firmware module configuration area in sof_ipc4_fw_module struct for later use. Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230522101313.12519-3-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-loader.c | 1 + sound/soc/sof/ipc4-priv.h | 2 ++ 2 files changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index bb215d33d455..3860d3455960 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -112,6 +112,7 @@ static ssize_t sof_ipc4_fw_parse_ext_man(struct snd_sof_dev *sdev, return -EINVAL; } + fw_module->fw_mod_cfg = &fm_config[fm_entry->cfg_offset]; dev_dbg(sdev->dev, "module %s: UUID %pUL cfg_count: %u, bss_size: %#x\n", diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index 546e52746276..4b3495dc455d 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -28,11 +28,13 @@ enum sof_ipc4_mtrace_type { /** * struct sof_ipc4_fw_module - IPC4 module info * @sof_man4_module: Module info + * @fw_mod_cfg: Pointer to the module config start of the module * @m_ida: Module instance identifier * @private: Module private data */ struct sof_ipc4_fw_module { struct sof_man4_module man4_module_entry; + const struct sof_man4_module_config *fw_mod_cfg; struct ida m_ida; void *private; }; -- cgit v1.2.3 From 19c745d1fd1a61a04a0b44623c70c4e71b6f274a Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 May 2023 13:13:11 +0300 Subject: ASoC: SOF: ipc4-topology: Rename sof_ipc4_update_pipeline_mem_usage() to be generic Rename sof_ipc4_update_pipeline_mem_usage() to sof_ipc4_update_resource_usage() in order to be re-usable for generic resource storage, calculation of a module, like CPC adjustment. Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230522101313.12519-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 6b8e9edd28dd..0edcb44596d4 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -936,8 +936,8 @@ static void sof_ipc4_widget_free_comp_process(struct snd_sof_widget *swidget) } static void -sof_ipc4_update_pipeline_mem_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, - struct sof_ipc4_base_module_cfg *base_config) +sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget *swidget, + struct sof_ipc4_base_module_cfg *base_config) { struct sof_ipc4_fw_module *fw_module = swidget->module_info; struct snd_sof_widget *pipe_widget; @@ -1711,7 +1711,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, *data, copier_data->gtw_cfg.config_length * 4); /* update pipeline memory usage */ - sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &copier_data->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &copier_data->base_config); return 0; } @@ -1748,7 +1748,7 @@ static int sof_ipc4_prepare_gain_module(struct snd_sof_widget *swidget, } /* update pipeline memory usage */ - sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &gain->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &gain->base_config); return 0; } @@ -1785,7 +1785,7 @@ static int sof_ipc4_prepare_mixer_module(struct snd_sof_widget *swidget, } /* update pipeline memory usage */ - sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &mixer->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &mixer->base_config); return 0; } @@ -1822,7 +1822,7 @@ static int sof_ipc4_prepare_src_module(struct snd_sof_widget *swidget, } /* update pipeline memory usage */ - sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &src->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &src->base_config); /* update pipeline_params for sink widgets */ rate = hw_param_interval(pipeline_params, SNDRV_PCM_HW_PARAM_RATE); @@ -1959,7 +1959,7 @@ static int sof_ipc4_prepare_process_module(struct snd_sof_widget *swidget, } /* update pipeline memory usage */ - sof_ipc4_update_pipeline_mem_usage(sdev, swidget, &process->base_config); + sof_ipc4_update_resource_usage(sdev, swidget, &process->base_config); /* ipc_config_data is composed of the base_config followed by an optional extension */ memcpy(cfg, &process->base_config, sizeof(struct sof_ipc4_base_module_cfg)); -- cgit v1.2.3 From 9caa90180512581821d7498132f952ebd4ba05ad Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 May 2023 13:13:12 +0300 Subject: ASoC: SOF: ipc4-topology: Do not use the CPC value from topology Stop parsing the CPC value from topology to module_base_cfg. The CPC value is only set for few modules in topology which makes the CPC handling inconsistent. Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230522101313.12519-5-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 0edcb44596d4..910ca98bb205 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -39,8 +39,6 @@ static const struct sof_topology_token pipeline_tokens[] = { }; static const struct sof_topology_token ipc4_comp_tokens[] = { - {SOF_TKN_COMP_CPC, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, - offsetof(struct sof_ipc4_base_module_cfg, cpc)}, {SOF_TKN_COMP_IS_PAGES, SND_SOC_TPLG_TUPLE_TYPE_WORD, get_token_u32, offsetof(struct sof_ipc4_base_module_cfg, is_pages)}, }; @@ -235,7 +233,7 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, "Number of input audio formats: %d. Number of output audio formats: %d\n", available_fmt->num_input_formats, available_fmt->num_output_formats); - /* set cpc and is_pages in the module's base_config */ + /* set is_pages in the module's base_config */ ret = sof_update_ipc_object(scomp, module_base_cfg, SOF_COMP_TOKENS, swidget->tuples, swidget->num_tuples, sizeof(*module_base_cfg), 1); if (ret) { @@ -244,8 +242,8 @@ static int sof_ipc4_get_audio_fmt(struct snd_soc_component *scomp, return ret; } - dev_dbg(scomp->dev, "widget %s cpc: %d is_pages: %d\n", - swidget->widget->name, module_base_cfg->cpc, module_base_cfg->is_pages); + dev_dbg(scomp->dev, "widget %s: is_pages: %d\n", swidget->widget->name, + module_base_cfg->is_pages); if (available_fmt->num_input_formats) { in_format = kcalloc(available_fmt->num_input_formats, @@ -723,9 +721,9 @@ static int sof_ipc4_widget_setup_comp_pga(struct snd_sof_widget *swidget) } dev_dbg(scomp->dev, - "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x, cpc %d\n", + "pga widget %s: ramp type: %d, ramp duration %d, initial gain value: %#x\n", swidget->widget->name, gain->data.curve_type, gain->data.curve_duration_l, - gain->data.init_val, gain->base_config.cpc); + gain->data.init_val); ret = sof_ipc4_widget_setup_msg(swidget, &gain->msg); if (ret) -- cgit v1.2.3 From d8a2c987934959dd1f27de75401625650cd25e47 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 22 May 2023 13:13:13 +0300 Subject: ASoC: SOF: ipc4-loader/topology: Query the CPC value from manifest The manifest's firmware module configuration section contains the measured CPC values along with a matching IBS/OBS values. The CPC can be looked up by looking for a matching IBS/OBS entry. In case of multiple matches we will use the highest CPC value. If there is no mod_cfg or no CPC value (all 0) or no match was found then print warning message and use 0 as CPC value. Signed-off-by: Peter Ujfalusi Reviewed-by: Kai Vehmanen Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230522101313.12519-6-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-loader.c | 65 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/sof/ipc4-priv.h | 6 ++++ sound/soc/sof/ipc4-topology.c | 7 +++++ 3 files changed, 78 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-loader.c b/sound/soc/sof/ipc4-loader.c index 3860d3455960..eaa04762eb11 100644 --- a/sound/soc/sof/ipc4-loader.c +++ b/sound/soc/sof/ipc4-loader.c @@ -423,6 +423,71 @@ int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev) return ret; } +/** + * sof_ipc4_update_cpc_from_manifest - Update the cpc in base config from manifest + * @sdev: SOF device + * @fw_module: pointer struct sof_ipc4_fw_module to parse + * @basecfg: Pointer to the base_config to update + */ +void sof_ipc4_update_cpc_from_manifest(struct snd_sof_dev *sdev, + struct sof_ipc4_fw_module *fw_module, + struct sof_ipc4_base_module_cfg *basecfg) +{ + const struct sof_man4_module_config *fw_mod_cfg; + u32 cpc_pick = 0; + u32 max_cpc = 0; + const char *msg; + int i; + + if (!fw_module->fw_mod_cfg) { + msg = "No mod_cfg available for CPC lookup in the firmware file's manifest"; + goto no_cpc; + } + + /* + * Find the best matching (highest) CPC value based on the module's + * IBS/OBS configuration inferred from the audio format selection. + * + * The CPC value in each module config entry has been measured and + * recorded as a IBS/OBS/CPC triplet and stored in the firmware file's + * manifest + */ + fw_mod_cfg = fw_module->fw_mod_cfg; + for (i = 0; i < fw_module->man4_module_entry.cfg_count; i++) { + if (basecfg->obs == fw_mod_cfg[i].obs && + basecfg->ibs == fw_mod_cfg[i].ibs && + cpc_pick < fw_mod_cfg[i].cpc) + cpc_pick = fw_mod_cfg[i].cpc; + + if (max_cpc < fw_mod_cfg[i].cpc) + max_cpc = fw_mod_cfg[i].cpc; + } + + basecfg->cpc = cpc_pick; + + /* We have a matching configuration for CPC */ + if (basecfg->cpc) + return; + + /* + * No matching IBS/OBS found, the firmware manifest is missing + * information in the module's module configuration table. + */ + if (!max_cpc) + msg = "No CPC value available in the firmware file's manifest"; + else if (!cpc_pick) + msg = "No CPC match in the firmware file's manifest"; + +no_cpc: + dev_warn(sdev->dev, "%s (UUID: %pUL): %s (ibs/obs: %u/%u)\n", + fw_module->man4_module_entry.name, + &fw_module->man4_module_entry.uuid, msg, basecfg->ibs, + basecfg->obs); + dev_warn_once(sdev->dev, "Please try to update the firmware.\n"); + dev_warn_once(sdev->dev, "If the issue persists, file a bug at\n"); + dev_warn_once(sdev->dev, "https://github.com/thesofproject/sof/issues/\n"); +} + const struct sof_ipc_fw_loader_ops ipc4_loader_ops = { .validate = sof_ipc4_validate_firmware, .parse_ext_manifest = sof_ipc4_fw_parse_basefw_ext_man, diff --git a/sound/soc/sof/ipc4-priv.h b/sound/soc/sof/ipc4-priv.h index 4b3495dc455d..a5d0b2eae464 100644 --- a/sound/soc/sof/ipc4-priv.h +++ b/sound/soc/sof/ipc4-priv.h @@ -114,4 +114,10 @@ int sof_ipc4_query_fw_configuration(struct snd_sof_dev *sdev); int sof_ipc4_reload_fw_libraries(struct snd_sof_dev *sdev); struct sof_ipc4_fw_module *sof_ipc4_find_module_by_uuid(struct snd_sof_dev *sdev, const guid_t *uuid); + +struct sof_ipc4_base_module_cfg; +void sof_ipc4_update_cpc_from_manifest(struct snd_sof_dev *sdev, + struct sof_ipc4_fw_module *fw_module, + struct sof_ipc4_base_module_cfg *basecfg); + #endif diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 910ca98bb205..681239dd54ee 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -966,6 +966,13 @@ sof_ipc4_update_resource_usage(struct snd_sof_dev *sdev, struct snd_sof_widget * pipe_widget = swidget->spipe->pipe_widget; pipeline = pipe_widget->private; pipeline->mem_usage += total; + + /* Update base_config->cpc from the module manifest */ + sof_ipc4_update_cpc_from_manifest(sdev, fw_module, base_config); + + dev_dbg(sdev->dev, "%s: ibs / obs / cpc: %u / %u / %u\n", + swidget->widget->name, base_config->ibs, base_config->obs, + base_config->cpc); } static int sof_ipc4_widget_assign_instance_id(struct snd_sof_dev *sdev, -- cgit v1.2.3 From ec5dffcd428f54c117158c7b2cd79a1e14aa5b70 Mon Sep 17 00:00:00 2001 From: Amadeusz Sławiński Date: Fri, 19 May 2023 21:56:07 +0200 Subject: ASoC: topology: Log control load errors in soc_tplg_control_load() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify code by logging any errors in function that does the actual work instead of doing so in its callers. Signed-off-by: Amadeusz Sławiński Reviewed-by: Cezary Rojewski Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230519195611.4068853-2-amadeuszx.slawinski@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 47ab5cf99497..242abbd875fa 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -585,11 +585,15 @@ EXPORT_SYMBOL_GPL(snd_soc_tplg_widget_bind_event); static int soc_tplg_control_load(struct soc_tplg *tplg, struct snd_kcontrol_new *k, struct snd_soc_tplg_ctl_hdr *hdr) { + int ret = 0; + if (tplg->ops && tplg->ops->control_load) - return tplg->ops->control_load(tplg->comp, tplg->index, k, - hdr); + ret = tplg->ops->control_load(tplg->comp, tplg->index, k, hdr); - return 0; + if (ret) + dev_err(tplg->dev, "ASoC: failed to init %s\n", hdr->name); + + return ret; } @@ -691,10 +695,8 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, size_t size) /* pass control to driver for optional further init */ ret = soc_tplg_control_load(tplg, &kc, &be->hdr); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", be->hdr.name); + if (ret < 0) goto err; - } /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &sbe->dobj.control.kcontrol); @@ -776,10 +778,8 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, size_t size) /* pass control to driver for optional further init */ ret = soc_tplg_control_load(tplg, &kc, &mc->hdr); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", mc->hdr.name); + if (ret < 0) goto err; - } /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &sm->dobj.control.kcontrol); @@ -945,10 +945,8 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, size_t size) /* pass control to driver for optional further init */ ret = soc_tplg_control_load(tplg, &kc, &ec->hdr); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", ec->hdr.name); + if (ret < 0) goto err; - } /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &se->dobj.control.kcontrol); @@ -1162,11 +1160,8 @@ static int soc_tplg_dapm_widget_dmixer_create(struct soc_tplg *tplg, struct snd_ /* pass control to driver for optional further init */ err = soc_tplg_control_load(tplg, kc, &mc->hdr); - if (err < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", - mc->hdr.name); + if (err < 0) return err; - } return 0; } @@ -1246,11 +1241,8 @@ static int soc_tplg_dapm_widget_denum_create(struct soc_tplg *tplg, struct snd_k /* pass control to driver for optional further init */ err = soc_tplg_control_load(tplg, kc, &ec->hdr); - if (err < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", - ec->hdr.name); + if (err < 0) return err; - } return 0; } @@ -1298,11 +1290,8 @@ static int soc_tplg_dapm_widget_dbytes_create(struct soc_tplg *tplg, struct snd_ /* pass control to driver for optional further init */ err = soc_tplg_control_load(tplg, kc, &be->hdr); - if (err < 0) { - dev_err(tplg->dev, "ASoC: failed to init %s\n", - be->hdr.name); + if (err < 0) return err; - } return 0; } -- cgit v1.2.3 From 2316c11fa97779d06bfd7990f45b13a7b6ec1dae Mon Sep 17 00:00:00 2001 From: Amadeusz Sławiński Date: Fri, 19 May 2023 21:56:08 +0200 Subject: ASoC: topology: Remove redundant logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_tplg_add_kcontrol() logs all the failures in detail already. Signed-off-by: Amadeusz Sławiński Reviewed-by: Cezary Rojewski Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230519195611.4068853-3-amadeuszx.slawinski@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 242abbd875fa..b5c47b3c63ab 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -700,10 +700,8 @@ static int soc_tplg_dbytes_create(struct soc_tplg *tplg, size_t size) /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &sbe->dobj.control.kcontrol); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: failed to add %s\n", be->hdr.name); + if (ret < 0) goto err; - } list_add(&sbe->dobj.list, &tplg->comp->dobj_list); @@ -783,10 +781,8 @@ static int soc_tplg_dmixer_create(struct soc_tplg *tplg, size_t size) /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &sm->dobj.control.kcontrol); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: failed to add %s\n", mc->hdr.name); + if (ret < 0) goto err; - } list_add(&sm->dobj.list, &tplg->comp->dobj_list); @@ -950,10 +946,8 @@ static int soc_tplg_denum_create(struct soc_tplg *tplg, size_t size) /* register control here */ ret = soc_tplg_add_kcontrol(tplg, &kc, &se->dobj.control.kcontrol); - if (ret < 0) { - dev_err(tplg->dev, "ASoC: could not add kcontrol %s\n", ec->hdr.name); + if (ret < 0) goto err; - } list_add(&se->dobj.list, &tplg->comp->dobj_list); -- cgit v1.2.3 From 5308540278d776e10519db144cb0cf3b3dd7ffbf Mon Sep 17 00:00:00 2001 From: Amadeusz Sławiński Date: Fri, 19 May 2023 21:56:09 +0200 Subject: ASoC: topology: Do not split message string on multiple lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kernel coding guidelines recommend to not split string unnecessarily. While at it adapt the other print present in the function to 100 characters line limit. Signed-off-by: Amadeusz Sławiński Reviewed-by: Cezary Rojewski Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230519195611.4068853-4-amadeuszx.slawinski@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index b5c47b3c63ab..0249e915eafe 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1513,15 +1513,13 @@ static int soc_tplg_dapm_complete(struct soc_tplg *tplg) * If so, just return success. */ if (!snd_soc_card_is_instantiated(card)) { - dev_warn(tplg->dev, "ASoC: Parent card not yet available," - " widget card binding deferred\n"); + dev_warn(tplg->dev, "ASoC: Parent card not yet available, widget card binding deferred\n"); return 0; } ret = snd_soc_dapm_new_widgets(card); if (ret < 0) - dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", - ret); + dev_err(tplg->dev, "ASoC: failed to create new widgets %d\n", ret); return ret; } -- cgit v1.2.3 From db756c5c35dfcf820c2b0d7eec526e8dfe79a96d Mon Sep 17 00:00:00 2001 From: Amadeusz Sławiński Date: Fri, 19 May 2023 21:56:10 +0200 Subject: ASoC: topology: Remove redundant log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_tplg_dapm_complete() logs all the failures in detail already. Signed-off-by: Amadeusz Sławiński Reviewed-by: Cezary Rojewski Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230519195611.4068853-5-amadeuszx.slawinski@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 0249e915eafe..9bb4c6d2a2c9 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -2510,9 +2510,6 @@ static int soc_tplg_process_headers(struct soc_tplg *tplg) /* signal DAPM we are complete */ ret = soc_tplg_dapm_complete(tplg); - if (ret < 0) - dev_err(tplg->dev, - "ASoC: failed to initialise DAPM from Firmware\n"); return ret; } -- cgit v1.2.3 From f9d1fe7e81b87378e7bb4a0be9c6fb29bbaa73c0 Mon Sep 17 00:00:00 2001 From: Amadeusz Sławiński Date: Fri, 19 May 2023 21:56:11 +0200 Subject: ASoC: topology: Remove redundant log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_tplg_valid_header() logs all the failures in detail already. Signed-off-by: Amadeusz Sławiński Reviewed-by: Cezary Rojewski Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230519195611.4068853-6-amadeuszx.slawinski@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 9bb4c6d2a2c9..20fd46a41cbb 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -2486,11 +2486,8 @@ static int soc_tplg_process_headers(struct soc_tplg *tplg) /* make sure header is valid before loading */ ret = soc_tplg_valid_header(tplg, hdr); - if (ret < 0) { - dev_err(tplg->dev, - "ASoC: topology: invalid header: %d\n", ret); + if (ret < 0) return ret; - } /* load the header object */ ret = soc_tplg_load_header(tplg, hdr); -- cgit v1.2.3 From ef44ba21995e80e19e7056593067cb4bfaad0bde Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 16:48:19 +0200 Subject: ASoC: adau1761: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/ab0fe7e7ecf965df84b9516ba65428af9b3805c1.1684594081.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/codecs/adau17x1.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/adau17x1.c b/sound/soc/codecs/adau17x1.c index 634d4dbca5ec..f2932713b4de 100644 --- a/sound/soc/codecs/adau17x1.c +++ b/sound/soc/codecs/adau17x1.c @@ -1059,13 +1059,12 @@ int adau17x1_probe(struct device *dev, struct regmap *regmap, if (!adau) return -ENOMEM; - adau->mclk = devm_clk_get(dev, "mclk"); - if (IS_ERR(adau->mclk)) { - if (PTR_ERR(adau->mclk) != -ENOENT) - return PTR_ERR(adau->mclk); - /* Clock is optional (for the driver) */ - adau->mclk = NULL; - } else if (adau->mclk) { + /* Clock is optional (for the driver) */ + adau->mclk = devm_clk_get_optional(dev, "mclk"); + if (IS_ERR(adau->mclk)) + return PTR_ERR(adau->mclk); + + if (adau->mclk) { adau->clk_src = ADAU17X1_CLK_SRC_PLL_AUTO; /* -- cgit v1.2.3 From 8c03fd5fbd3e5a534675dffd5647166e919e1fc2 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 12:21:58 +0200 Subject: ASoC: atmel: sam9g20_wm8731: Remove the unneeded include This driver does not use i2c, so there is no point in including Remove it. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/9b39a59f5829d200d7d1fac4e993dbf8ce05836d.1684578051.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/atmel/sam9g20_wm8731.c | 1 - 1 file changed, 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index baf38964b491..0405e9e49140 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3 From c0998e0142af8037e4a0ee84dd01cd20cbe0c76e Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 16:50:54 +0200 Subject: ASoC: cs42l51: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/3debf3bb7ea504ee9ca2d8eb0f948a426681cbdd.1684594240.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l51.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs42l51.c b/sound/soc/codecs/cs42l51.c index e88d9ff95cdf..a67cd3ee84e0 100644 --- a/sound/soc/codecs/cs42l51.c +++ b/sound/soc/codecs/cs42l51.c @@ -724,12 +724,9 @@ int cs42l51_probe(struct device *dev, struct regmap *regmap) dev_set_drvdata(dev, cs42l51); cs42l51->regmap = regmap; - cs42l51->mclk_handle = devm_clk_get(dev, "MCLK"); - if (IS_ERR(cs42l51->mclk_handle)) { - if (PTR_ERR(cs42l51->mclk_handle) != -ENOENT) - return PTR_ERR(cs42l51->mclk_handle); - cs42l51->mclk_handle = NULL; - } + cs42l51->mclk_handle = devm_clk_get_optional(dev, "MCLK"); + if (IS_ERR(cs42l51->mclk_handle)) + return PTR_ERR(cs42l51->mclk_handle); for (i = 0; i < ARRAY_SIZE(cs42l51->supplies); i++) cs42l51->supplies[i].supply = cs42l51_supply_names[i]; -- cgit v1.2.3 From f364eb563164f52dcc42ea265a66510c6f15f829 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 16:56:00 +0200 Subject: ASoC: rt5659: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/5b44b2fddd8973e949e4ae2132971b147cfd1ec1.1684594544.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/codecs/rt5659.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5659.c b/sound/soc/codecs/rt5659.c index 22bb57029bc0..df6f0d769bbd 100644 --- a/sound/soc/codecs/rt5659.c +++ b/sound/soc/codecs/rt5659.c @@ -4141,13 +4141,9 @@ static int rt5659_i2c_probe(struct i2c_client *i2c) regmap_write(rt5659->regmap, RT5659_RESET, 0); /* Check if MCLK provided */ - rt5659->mclk = devm_clk_get(&i2c->dev, "mclk"); - if (IS_ERR(rt5659->mclk)) { - if (PTR_ERR(rt5659->mclk) != -ENOENT) - return PTR_ERR(rt5659->mclk); - /* Otherwise mark the mclk pointer to NULL */ - rt5659->mclk = NULL; - } + rt5659->mclk = devm_clk_get_optional(&i2c->dev, "mclk"); + if (IS_ERR(rt5659->mclk)) + return PTR_ERR(rt5659->mclk); rt5659_calibrate(rt5659); -- cgit v1.2.3 From 374628fb668e50b42fe81f2a63af616182415bcd Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 17:00:50 +0200 Subject: ASoC: stm32: sai: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/f7987f18dadf77bfa09969fd4c82d5a0f4e4e3b7.1684594838.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/stm/stm32_sai_sub.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/stm/stm32_sai_sub.c b/sound/soc/stm/stm32_sai_sub.c index f6695dee353b..271ec5b3378d 100644 --- a/sound/soc/stm/stm32_sai_sub.c +++ b/sound/soc/stm/stm32_sai_sub.c @@ -1485,12 +1485,9 @@ static int stm32_sai_sub_parse_of(struct platform_device *pdev, if (ret < 0) return ret; } else { - sai->sai_mclk = devm_clk_get(&pdev->dev, "MCLK"); - if (IS_ERR(sai->sai_mclk)) { - if (PTR_ERR(sai->sai_mclk) != -ENOENT) - return PTR_ERR(sai->sai_mclk); - sai->sai_mclk = NULL; - } + sai->sai_mclk = devm_clk_get_optional(&pdev->dev, "MCLK"); + if (IS_ERR(sai->sai_mclk)) + return PTR_ERR(sai->sai_mclk); } return 0; -- cgit v1.2.3 From 0b855cbbd769940fcaf49b2371a05235d8499d5d Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 16:54:06 +0200 Subject: ASoC: cs53l30: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/3219effee5c7f190530bdb1ef8ec35cb142e3611.1684594433.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/codecs/cs53l30.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index 51ca66e7b3ea..21962b828ab1 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -990,14 +990,10 @@ static int cs53l30_i2c_probe(struct i2c_client *client) } /* Check if MCLK provided */ - cs53l30->mclk = devm_clk_get(dev, "mclk"); + cs53l30->mclk = devm_clk_get_optional(dev, "mclk"); if (IS_ERR(cs53l30->mclk)) { - if (PTR_ERR(cs53l30->mclk) != -ENOENT) { - ret = PTR_ERR(cs53l30->mclk); - goto error; - } - /* Otherwise mark the mclk pointer to NULL */ - cs53l30->mclk = NULL; + ret = PTR_ERR(cs53l30->mclk); + goto error; } /* Fetch the MUTE control */ -- cgit v1.2.3 From 17cf9faeba463d24e7c497ff8137a8c8414644dc Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 20 May 2023 16:58:24 +0200 Subject: ASoC: rt5682s: Use the devm_clk_get_optional() helper Use devm_clk_get_optional() instead of hand writing it. This saves some LoC and improves the semantic. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/f538c24ad7b1926478347a03b5b7f0432e195e3b.1684594691.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682s.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index 81163b026b9e..a01de405c22c 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -2848,14 +2848,9 @@ static int rt5682s_dai_probe_clks(struct snd_soc_component *component) int ret; /* Check if MCLK provided */ - rt5682s->mclk = devm_clk_get(component->dev, "mclk"); - if (IS_ERR(rt5682s->mclk)) { - if (PTR_ERR(rt5682s->mclk) != -ENOENT) { - ret = PTR_ERR(rt5682s->mclk); - return ret; - } - rt5682s->mclk = NULL; - } + rt5682s->mclk = devm_clk_get_optional(component->dev, "mclk"); + if (IS_ERR(rt5682s->mclk)) + return PTR_ERR(rt5682s->mclk); /* Register CCF DAI clock control */ ret = rt5682s_register_dai_clks(component); -- cgit v1.2.3 From 36a52ae64ba85f075998921ae1881c01f29ddf31 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Mon, 22 May 2023 12:50:37 +0200 Subject: ALSA: add HAS_IOPORT dependencies In a future patch HAS_IOPORT=n will result in inb()/outb() and friends not being declared. We thus need to add HAS_IOPORT as dependency for those drivers using them. Co-developed-by: Arnd Bergmann Signed-off-by: Arnd Bergmann Signed-off-by: Niklas Schnelle Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230522105049.1467313-33-schnelle@linux.ibm.com Signed-off-by: Takashi Iwai --- sound/drivers/Kconfig | 3 +++ sound/isa/Kconfig | 1 + sound/pci/Kconfig | 45 ++++++++++++++++++++++++++++++++++----------- sound/pcmcia/Kconfig | 1 + 4 files changed, 39 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index be3009746f3a..864991d8776d 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -128,6 +128,7 @@ config SND_VIRMIDI config SND_MTPAV tristate "MOTU MidiTimePiece AV multiport MIDI" + depends on HAS_IOPORT select SND_RAWMIDI help To use a MOTU MidiTimePiece AV multiport MIDI adapter @@ -152,6 +153,7 @@ config SND_MTS64 config SND_SERIAL_U16550 tristate "UART16550 serial MIDI driver" + depends on HAS_IOPORT select SND_RAWMIDI help To include support for MIDI serial port interfaces, say Y here @@ -185,6 +187,7 @@ config SND_SERIAL_GENERIC config SND_MPU401 tristate "Generic MPU-401 UART driver" + depends on HAS_IOPORT select SND_MPU401_UART help Say Y here to include support for MIDI ports compatible with diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 6ffa48dd5983..f8159179e38d 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -23,6 +23,7 @@ menuconfig SND_ISA bool "ISA sound devices" depends on ISA || COMPILE_TEST depends on ISA_DMA_API + depends on HAS_IOPORT default y help Support for sound devices connected via the ISA bus. diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 861958451ef5..787868c9e91b 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -26,7 +26,7 @@ config SND_ALS300 select SND_PCM select SND_AC97_CODEC select SND_OPL3_LIB - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+ @@ -36,6 +36,7 @@ config SND_ALS300 config SND_ALS4000 tristate "Avance Logic ALS4000" depends on ISA_DMA_API + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_PCM @@ -51,7 +52,7 @@ config SND_ALI5451 tristate "ALi M5451 PCI Audio Controller" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for the integrated AC97 sound device on motherboards using the ALi M5451 Audio Controller @@ -96,6 +97,7 @@ config SND_ATIIXP_MODEM config SND_AU8810 tristate "Aureal Advantage" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -110,6 +112,7 @@ config SND_AU8810 config SND_AU8820 tristate "Aureal Vortex" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -123,6 +126,7 @@ config SND_AU8820 config SND_AU8830 tristate "Aureal Vortex 2" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -157,7 +161,7 @@ config SND_AZT3328 select SND_RAWMIDI select SND_AC97_CODEC select SND_TIMER - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for Aztech AZF3328 (PCI168) soundcards. @@ -193,6 +197,7 @@ config SND_BT87X_OVERCLOCK config SND_CA0106 tristate "SB Audigy LS / Live 24bit" + depends on HAS_IOPORT select SND_AC97_CODEC select SND_RAWMIDI select SND_VMASTER @@ -205,6 +210,7 @@ config SND_CA0106 config SND_CMIPCI tristate "C-Media 8338, 8738, 8768, 8770" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_PCM @@ -221,6 +227,7 @@ config SND_OXYGEN_LIB config SND_OXYGEN tristate "C-Media 8786, 8787, 8788 (Oxygen)" + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -246,6 +253,7 @@ config SND_OXYGEN config SND_CS4281 tristate "Cirrus Logic (Sound Fusion) CS4281" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_RAWMIDI select SND_AC97_CODEC @@ -257,6 +265,7 @@ config SND_CS4281 config SND_CS46XX tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC select FW_LOADER @@ -290,6 +299,7 @@ config SND_CS5530 config SND_CS5535AUDIO tristate "CS5535/CS5536 Audio" depends on X86_32 || MIPS || COMPILE_TEST + depends on HAS_IOPORT select SND_PCM select SND_AC97_CODEC help @@ -307,6 +317,7 @@ config SND_CS5535AUDIO config SND_CTXFI tristate "Creative Sound Blaster X-Fi" + depends on HAS_IOPORT select SND_PCM help If you want to use soundcards based on Creative Sound Blastr X-Fi @@ -468,7 +479,7 @@ config SND_EMU10K1 select SND_AC97_CODEC select SND_TIMER select SND_SEQ_DEVICE if SND_SEQUENCER != n - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y to include support for Sound Blaster PCI 512, Live!, Audigy and E-MU APS/0404/1010/1212/1616/1820 soundcards. @@ -491,7 +502,7 @@ config SND_EMU10K1X tristate "Emu10k1X (Dell OEM Version)" select SND_AC97_CODEC select SND_RAWMIDI - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for the Dell OEM version of the Sound Blaster Live!. @@ -501,6 +512,7 @@ config SND_EMU10K1X config SND_ENS1370 tristate "(Creative) Ensoniq AudioPCI 1370" + depends on HAS_IOPORT select SND_RAWMIDI select SND_PCM help @@ -511,6 +523,7 @@ config SND_ENS1370 config SND_ENS1371 tristate "(Creative) Ensoniq AudioPCI 1371/1373" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC help @@ -525,7 +538,7 @@ config SND_ES1938 select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Solo-1 (ES1938, ES1946, ES1969) chips. @@ -537,7 +550,7 @@ config SND_ES1968 tristate "ESS ES1968/1978 (Maestro-1/2/2E)" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Maestro 1/2/2E chips. @@ -569,6 +582,7 @@ config SND_ES1968_RADIO config SND_FM801 tristate "ForteMedia FM801" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC @@ -624,7 +638,7 @@ config SND_ICE1712 select SND_MPU401_UART select SND_AC97_CODEC select BITREVERSE - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on the ICE1712 (Envy24) chip. @@ -640,6 +654,7 @@ config SND_ICE1712 config SND_ICE1724 tristate "ICE/VT1724/1720 (Envy24HT/PT)" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC select SND_VMASTER @@ -712,7 +727,7 @@ config SND_LX6464ES config SND_MAESTRO3 tristate "ESS Allegro/Maestro3" select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Maestro 3 (Allegro) chips. @@ -753,6 +768,7 @@ config SND_NM256 config SND_PCXHR tristate "Digigram PCXHR" + depends on HAS_IOPORT select FW_LOADER select SND_PCM select SND_HWDEP @@ -764,6 +780,7 @@ config SND_PCXHR config SND_RIPTIDE tristate "Conexant Riptide" + depends on HAS_IOPORT select FW_LOADER select SND_OPL3_LIB select SND_MPU401_UART @@ -808,6 +825,7 @@ config SND_RME9652 config SND_SE6X tristate "Studio Evolution SE6X" depends on SND_OXYGEN=n && SND_VIRTUOSO=n # PCI ID conflict + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -830,7 +848,7 @@ config SND_SONICVIBES select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on the S3 SonicVibes chip. @@ -842,7 +860,7 @@ config SND_TRIDENT tristate "Trident 4D-Wave DX/NX; SiS 7018" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on Trident 4D-Wave DX/NX or SiS 7018 chips. @@ -852,6 +870,7 @@ config SND_TRIDENT config SND_VIA82XX tristate "VIA 82C686A/B, 8233/8235 AC97 Controller" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -863,6 +882,7 @@ config SND_VIA82XX config SND_VIA82XX_MODEM tristate "VIA 82C686A/B, 8233 based Modems" + depends on HAS_IOPORT select SND_AC97_CODEC help Say Y here to include support for the integrated MC97 modem on @@ -873,6 +893,7 @@ config SND_VIA82XX_MODEM config SND_VIRTUOSO tristate "Asus Virtuoso 66/100/200 (Xonar)" + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -889,6 +910,7 @@ config SND_VIRTUOSO config SND_VX222 tristate "Digigram VX222" + depends on HAS_IOPORT select SND_VX_LIB help Say Y here to include support for Digigram VX222 soundcards. @@ -898,6 +920,7 @@ config SND_VX222 config SND_YMFPCI tristate "Yamaha YMF724/740/744/754" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC diff --git a/sound/pcmcia/Kconfig b/sound/pcmcia/Kconfig index 10291c43cb18..2e3dfc1ff540 100644 --- a/sound/pcmcia/Kconfig +++ b/sound/pcmcia/Kconfig @@ -4,6 +4,7 @@ menuconfig SND_PCMCIA bool "PCMCIA sound devices" depends on PCMCIA + depends on HAS_IOPORT default y help Support for sound devices connected via the PCMCIA bus. -- cgit v1.2.3 From 1d4a84632b90d88316986b05bcdfe715399a33db Mon Sep 17 00:00:00 2001 From: Venkata Prasad Potturu Date: Tue, 23 May 2023 12:50:01 +0530 Subject: ASoC: SOF: amd: Add pci revision id check Add pci revision id check for renoir and rembrandt platforms. Signed-off-by: Venkata Prasad Potturu Link: https://lore.kernel.org/r/20230523072009.2379198-1-venkataprasad.potturu@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.h | 3 +++ sound/soc/sof/amd/pci-rmb.c | 3 +++ sound/soc/sof/amd/pci-rn.c | 3 +++ 3 files changed, 9 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 1c535cc6c3a9..dc624f727aa3 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -55,6 +55,9 @@ #define ACP_DSP_TO_HOST_IRQ 0x04 +#define ACP_RN_PCI_ID 0x01 +#define ACP_RMB_PCI_ID 0x6F + #define HOST_BRIDGE_CZN 0x1630 #define HOST_BRIDGE_RMB 0x14B5 #define ACP_SHA_STAT 0x8000 diff --git a/sound/soc/sof/amd/pci-rmb.c b/sound/soc/sof/amd/pci-rmb.c index eaf70ea6e556..58b3092425f1 100644 --- a/sound/soc/sof/amd/pci-rmb.c +++ b/sound/soc/sof/amd/pci-rmb.c @@ -65,6 +65,9 @@ static int acp_pci_rmb_probe(struct pci_dev *pci, const struct pci_device_id *pc { unsigned int flag; + if (pci->revision != ACP_RMB_PCI_ID) + return -ENODEV; + flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; diff --git a/sound/soc/sof/amd/pci-rn.c b/sound/soc/sof/amd/pci-rn.c index 4809cb644152..7409e21ce5aa 100644 --- a/sound/soc/sof/amd/pci-rn.c +++ b/sound/soc/sof/amd/pci-rn.c @@ -65,6 +65,9 @@ static int acp_pci_rn_probe(struct pci_dev *pci, const struct pci_device_id *pci { unsigned int flag; + if (pci->revision != ACP_RN_PCI_ID) + return -ENODEV; + flag = snd_amd_acp_find_config(pci); if (flag != FLAG_AMD_SOF && flag != FLAG_AMD_SOF_ONLY_DMIC) return -ENODEV; -- cgit v1.2.3 From f9d790c578d4e0f715213cc7f2f6f2b0d2d91988 Mon Sep 17 00:00:00 2001 From: David Lin Date: Tue, 23 May 2023 16:33:04 +0800 Subject: ASoC: nau8825: Add pre-charge actions for input Adding pre-charge actions to make FEPGA power stable faster. It improve the recording quality at the beginning. Thus, it is also meaningfully to decrease the final adc delay time. Signed-off-by: David Lin Link: https://lore.kernel.org/r/20230523083303.98436-1-CTLIN0@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 30 ++++++++++++++++++++++++++++-- sound/soc/codecs/nau8825.h | 7 +++++++ 2 files changed, 35 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 775c8e0cb09e..cc3e18207c42 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -911,6 +911,32 @@ static bool nau8825_volatile_reg(struct device *dev, unsigned int reg) } } +static int nau8825_fepga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct nau8825 *nau8825 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_update_bits(nau8825->regmap, NAU8825_REG_FEPGA, + NAU8825_ACDC_CTRL_MASK, + NAU8825_ACDC_VREF_MICP | NAU8825_ACDC_VREF_MICN); + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_DISCHRG_EN, NAU8825_DISCHRG_EN); + msleep(40); + regmap_update_bits(nau8825->regmap, NAU8825_REG_BOOST, + NAU8825_DISCHRG_EN, 0); + regmap_update_bits(nau8825->regmap, NAU8825_REG_FEPGA, + NAU8825_ACDC_CTRL_MASK, 0); + break; + default: + break; + } + + return 0; +} + static int nau8825_adc_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -1127,8 +1153,8 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { SND_SOC_DAPM_INPUT("MIC"), SND_SOC_DAPM_MICBIAS("MICBIAS", NAU8825_REG_MIC_BIAS, 8, 0), - SND_SOC_DAPM_PGA("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, - NULL, 0), + SND_SOC_DAPM_PGA_E("Frontend PGA", NAU8825_REG_POWER_UP_CONTROL, 14, 0, + NULL, 0, nau8825_fepga_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_ADC_E("ADC", NULL, SND_SOC_NOPM, 0, 0, nau8825_adc_event, SND_SOC_DAPM_POST_PMU | diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h index 44b62bc3880f..38ce052aed50 100644 --- a/sound/soc/codecs/nau8825.h +++ b/sound/soc/codecs/nau8825.h @@ -442,10 +442,17 @@ /* BOOST (0x76) */ #define NAU8825_PRECHARGE_DIS (1 << 13) #define NAU8825_GLOBAL_BIAS_EN (1 << 12) +#define NAU8825_DISCHRG_EN (1 << 11) #define NAU8825_HP_BOOST_DIS (1 << 9) #define NAU8825_HP_BOOST_G_DIS (1 << 8) #define NAU8825_SHORT_SHUTDOWN_EN (1 << 6) +/* FEPGA (0x77) */ +#define NAU8825_ACDC_CTRL_SFT 14 +#define NAU8825_ACDC_CTRL_MASK (0x3 << NAU8825_ACDC_CTRL_SFT) +#define NAU8825_ACDC_VREF_MICP (0x1 << NAU8825_ACDC_CTRL_SFT) +#define NAU8825_ACDC_VREF_MICN (0x2 << NAU8825_ACDC_CTRL_SFT) + /* POWER_UP_CONTROL (0x7f) */ #define NAU8825_POWERUP_INTEGR_R (1 << 5) #define NAU8825_POWERUP_INTEGR_L (1 << 4) -- cgit v1.2.3 From 09b62892ddeeb38c11979979e3c65a14dba5fdc6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:22 +0200 Subject: ALSA: rawmidi: Pass rawmidi directly to snd_rawmidi_kernel_open() snd_rawmidi_kernel_open() is used only internally from ALSA sequencer, so far, and parsing the card / device matching table at each open is redundant, as each sequencer client already gets the rawmidi object beforehand. This patch optimizes the path by passing the rawmidi object directly at snd_rawmidi_kernel_open(). This is also a preparation for the upcoming UMP rawmidi I/O support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 2 +- sound/core/rawmidi.c | 17 ++++------------- sound/core/seq/seq_midi.c | 8 ++++---- 3 files changed, 9 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index e1f59b2940af..52b1cbfb2526 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -161,7 +161,7 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); /* main midi functions */ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); -int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice, +int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile); int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 7147fda66d93..589b75087d27 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -406,24 +406,15 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, } /* called from sound/core/seq/seq_midi.c */ -int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice, +int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile) { - struct snd_rawmidi *rmidi; - int err = 0; + int err; if (snd_BUG_ON(!rfile)) return -EINVAL; - - mutex_lock(®ister_mutex); - rmidi = snd_rawmidi_search(card, device); - if (!rmidi) - err = -ENODEV; - else if (!try_module_get(rmidi->card->module)) - err = -ENXIO; - mutex_unlock(®ister_mutex); - if (err < 0) - return err; + if (!try_module_get(rmidi->card->module)) + return -ENXIO; mutex_lock(&rmidi->open_mutex); err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 4589aac09154..2b5fff80de58 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -38,6 +38,7 @@ MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); /* data for this midi synth driver */ struct seq_midisynth { struct snd_card *card; + struct snd_rawmidi *rmidi; int device; int subdevice; struct snd_rawmidi_file input_rfile; @@ -168,8 +169,7 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe struct snd_rawmidi_params params; /* open midi port */ - err = snd_rawmidi_kernel_open(msynth->card, msynth->device, - msynth->subdevice, + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile); if (err < 0) { @@ -212,8 +212,7 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info struct snd_rawmidi_params params; /* open midi port */ - err = snd_rawmidi_kernel_open(msynth->card, msynth->device, - msynth->subdevice, + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile); if (err < 0) { @@ -328,6 +327,7 @@ snd_seq_midisynth_probe(struct device *_dev) for (p = 0; p < ports; p++) { ms = &msynth[p]; + ms->rmidi = rmidi; if (snd_seq_midisynth_new(ms, card, device, p) < 0) goto __nomem; -- cgit v1.2.3 From fb3bd1215909866d6105224abe1566fd52695859 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:23 +0200 Subject: ALSA: rawmidi: Add ioctl callback to snd_rawmidi_global_ops A new callback, ioctl, is added to snd_rawmidi_global_ops for allowing the driver to deal with the own ioctls. This is another preparation patch for the upcoming UMP support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 2 ++ sound/core/rawmidi.c | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 52b1cbfb2526..84413cfcdcb5 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -47,6 +47,8 @@ struct snd_rawmidi_global_ops { int (*dev_unregister) (struct snd_rawmidi * rmidi); void (*get_port_info)(struct snd_rawmidi *rmidi, int number, struct snd_seq_port_info *info); + long (*ioctl)(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp); }; struct snd_rawmidi_runtime { diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 589b75087d27..ab28cfc1fac8 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -893,6 +893,7 @@ static int snd_rawmidi_ioctl_status64(struct snd_rawmidi_file *rfile, static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_rawmidi_file *rfile; + struct snd_rawmidi *rmidi; void __user *argp = (void __user *)arg; rfile = file->private_data; @@ -984,8 +985,10 @@ static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long } } default: - rmidi_dbg(rfile->rmidi, - "rawmidi: unknown command = 0x%x\n", cmd); + rmidi = rfile->rmidi; + if (rmidi->ops && rmidi->ops->ioctl) + return rmidi->ops->ioctl(rmidi, cmd, argp); + rmidi_dbg(rmidi, "rawmidi: unknown command = 0x%x\n", cmd); } return -ENOTTY; } -- cgit v1.2.3 From e3a8a5b726bdd903de52bee6ba7c935c09d07ee8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:24 +0200 Subject: ALSA: rawmidi: UMP support This patch adds the support helpers for UMP (Universal MIDI Packet) in ALSA core. The basic design is that a rawmidi instance is assigned to each UMP Endpoint. A UMP Endpoint provides a UMP stream, typically bidirectional (but can be also uni-directional, too), which may hold up to 16 UMP Groups, where each UMP (input/output) Group corresponds to the traditional MIDI I/O Endpoint. Additionally, the ALSA UMP abstraction provides the multiple UMP Blocks that can be assigned to each UMP Endpoint. A UMP Block is a metadata to hold the UMP Group clusters, and can represent the functions assigned to each UMP Group. A typical implementation of UMP Block is the Group Terminal Blocks of USB MIDI 2.0 specification. For distinguishing from the legacy byte-stream MIDI device, a new device "umpC*D*" will be created, instead of the standard (MIDI 1.0) devices "midiC*D*". The UMP instance can be identified by the new rawmidi info bit SNDRV_RAWMIDI_INFO_UMP, too. A UMP rawmidi device reads/writes only in 4-bytes words alignment, stored in CPU native endianness. The transmit and receive functions take care of the input/out data alignment, and may return zero or aligned size, and the params ioctl may return -EINVAL when the given input/output buffer size isn't aligned. A few new UMP-specific ioctls are added for obtaining the new UMP endpoint and block information. As of this commit, no ALSA sequencer instance is attached to UMP devices yet. They will be supported by later patches. Along with those changes, the protocol version for rawmidi is bumped to 2.0.3. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 8 ++ include/sound/ump.h | 118 +++++++++++++++++++++++ include/uapi/sound/asound.h | 57 ++++++++++- sound/core/Kconfig | 4 + sound/core/Makefile | 2 + sound/core/rawmidi.c | 139 ++++++++++++++++++-------- sound/core/rawmidi_compat.c | 4 + sound/core/ump.c | 230 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 521 insertions(+), 41 deletions(-) create mode 100644 include/sound/ump.h create mode 100644 sound/core/ump.c (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 84413cfcdcb5..b8a230a7583b 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -63,6 +63,7 @@ struct snd_rawmidi_runtime { size_t avail_min; /* min avail for wakeup */ size_t avail; /* max used buffer for wakeup */ size_t xruns; /* over/underruns counter */ + size_t align; /* alignment (0 = byte stream, 3 = UMP) */ int buffer_ref; /* buffer reference count */ /* misc */ wait_queue_head_t sleep; @@ -148,6 +149,13 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device, void snd_rawmidi_set_ops(struct snd_rawmidi *rmidi, int stream, const struct snd_rawmidi_ops *ops); +/* internal */ +int snd_rawmidi_init(struct snd_rawmidi *rmidi, + struct snd_card *card, char *id, int device, + int output_count, int input_count, + unsigned int info_flags); +int snd_rawmidi_free(struct snd_rawmidi *rmidi); + /* callbacks */ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, diff --git a/include/sound/ump.h b/include/sound/ump.h new file mode 100644 index 000000000000..8a3ac97cd1d3 --- /dev/null +++ b/include/sound/ump.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Universal MIDI Packet (UMP) Support + */ +#ifndef __SOUND_UMP_H +#define __SOUND_UMP_H + +#include + +struct snd_ump_endpoint; +struct snd_ump_block; + +struct snd_ump_endpoint { + struct snd_rawmidi core; /* raw UMP access */ + + struct snd_ump_endpoint_info info; + + void *private_data; + void (*private_free)(struct snd_ump_endpoint *ump); + + struct list_head block_list; /* list of snd_ump_block objects */ +}; + +struct snd_ump_block { + struct snd_ump_block_info info; + struct snd_ump_endpoint *ump; + + void *private_data; + void (*private_free)(struct snd_ump_block *blk); + + struct list_head list; +}; + +#define rawmidi_to_ump(rmidi) container_of(rmidi, struct snd_ump_endpoint, core) + +int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, + int output, int input, + struct snd_ump_endpoint **ump_ret); +int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, + unsigned int direction, unsigned int first_group, + unsigned int num_groups, struct snd_ump_block **blk_ret); + +/* + * Some definitions for UMP + */ + +/* MIDI 2.0 Message Type */ +enum { + UMP_MSG_TYPE_UTILITY = 0x00, + UMP_MSG_TYPE_SYSTEM = 0x01, + UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE = 0x02, + UMP_MSG_TYPE_DATA = 0x03, + UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04, + UMP_MSG_TYPE_EXTENDED_DATA = 0x05, +}; + +/* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */ +enum { + UMP_SYSEX_STATUS_SINGLE = 0, + UMP_SYSEX_STATUS_START = 1, + UMP_SYSEX_STATUS_CONTINUE = 2, + UMP_SYSEX_STATUS_END = 3, +}; + +/* + * Helpers for retrieving / filling bits from UMP + */ +/* get the message type (4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_type(u32 data) +{ + return data >> 28; +} + +/* get the group number (0-based, 4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_group(u32 data) +{ + return (data >> 24) & 0x0f; +} + +/* get the MIDI status code (4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_status_code(u32 data) +{ + return (data >> 20) & 0x0f; +} + +/* get the MIDI channel number (0-based, 4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_channel(u32 data) +{ + return (data >> 16) & 0x0f; +} + +/* get the MIDI status + channel combo byte (8bit) from a UMP packet (header) */ +static inline unsigned char ump_message_status_channel(u32 data) +{ + return (data >> 16) & 0xff; +} + +/* compose a UMP packet (header) from type, group and status values */ +static inline u32 ump_compose(unsigned char type, unsigned char group, + unsigned char status, unsigned char channel) +{ + return ((u32)type << 28) | ((u32)group << 24) | ((u32)status << 20) | + ((u32)channel << 16); +} + +/* get SysEx message status (for both 7 and 8bits) from a UMP packet (header) */ +static inline unsigned char ump_sysex_message_status(u32 data) +{ + return (data >> 20) & 0xf; +} + +/* get SysEx message length (for both 7 and 8bits) from a UMP packet (header) */ +static inline unsigned char ump_sysex_message_length(u32 data) +{ + return (data >> 16) & 0xf; +} + +#endif /* __SOUND_UMP_H */ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 0aa955aa8246..b001df4b335e 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -708,7 +708,7 @@ enum { * Raw MIDI section - /dev/snd/midi?? */ -#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2) +#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3) enum { SNDRV_RAWMIDI_STREAM_OUTPUT = 0, @@ -719,6 +719,7 @@ enum { #define SNDRV_RAWMIDI_INFO_OUTPUT 0x00000001 #define SNDRV_RAWMIDI_INFO_INPUT 0x00000002 #define SNDRV_RAWMIDI_INFO_DUPLEX 0x00000004 +#define SNDRV_RAWMIDI_INFO_UMP 0x00000008 struct snd_rawmidi_info { unsigned int device; /* RO/WR (control): device number */ @@ -779,6 +780,57 @@ struct snd_rawmidi_status { }; #endif +/* UMP EP Protocol / JRTS capability bits */ +#define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300 +#define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */ +#define SNDRV_UMP_EP_INFO_PROTO_MIDI2 0x0200 /* MIDI 2.0 */ +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_MASK 0x0003 +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_TX 0x0001 /* JRTS Transmit */ +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_RX 0x0002 /* JRTS Receive */ + +/* UMP Endpoint information */ +struct snd_ump_endpoint_info { + int card; /* card number */ + int device; /* device number */ + unsigned int flags; /* additional info */ + unsigned int protocol_caps; /* protocol capabilities */ + unsigned int protocol; /* current protocol */ + unsigned int num_blocks; /* # of function blocks */ + unsigned short version; /* UMP major/minor version */ + unsigned short padding[7]; + unsigned char name[128]; /* endpoint name string */ + unsigned char product_id[128]; /* unique product id string */ + unsigned char reserved[32]; +} __packed; + +/* UMP direction */ +#define SNDRV_UMP_DIR_INPUT 0x01 +#define SNDRV_UMP_DIR_OUTPUT 0x02 +#define SNDRV_UMP_DIR_BIDIRECTION 0x03 + +/* UMP block info flags */ +#define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */ +#define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */ + +/* UMP groups and blocks */ +#define SNDRV_UMP_MAX_GROUPS 16 +#define SNDRV_UMP_MAX_BLOCKS 32 + +/* UMP Block information */ +struct snd_ump_block_info { + int card; /* card number */ + int device; /* device number */ + unsigned char block_id; /* block ID (R/W) */ + unsigned char direction; /* UMP direction */ + unsigned char active; /* Activeness */ + unsigned char first_group; /* first group ID */ + unsigned char num_groups; /* number of groups */ + unsigned char padding[3]; + unsigned int flags; /* various info flags */ + unsigned char name[128]; /* block name string */ + unsigned char reserved[32]; +} __packed; + #define SNDRV_RAWMIDI_IOCTL_PVERSION _IOR('W', 0x00, int) #define SNDRV_RAWMIDI_IOCTL_INFO _IOR('W', 0x01, struct snd_rawmidi_info) #define SNDRV_RAWMIDI_IOCTL_USER_PVERSION _IOW('W', 0x02, int) @@ -786,6 +838,9 @@ struct snd_rawmidi_status { #define SNDRV_RAWMIDI_IOCTL_STATUS _IOWR('W', 0x20, struct snd_rawmidi_status) #define SNDRV_RAWMIDI_IOCTL_DROP _IOW('W', 0x30, int) #define SNDRV_RAWMIDI_IOCTL_DRAIN _IOW('W', 0x31, int) +/* Additional ioctls for UMP rawmidi devices */ +#define SNDRV_UMP_IOCTL_ENDPOINT_INFO _IOR('W', 0x40, struct snd_ump_endpoint_info) +#define SNDRV_UMP_IOCTL_BLOCK_INFO _IOR('W', 0x41, struct snd_ump_block_info) /* * Timer section - /dev/snd/timer diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 12990d9a4dff..eb1c6c930de9 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -26,6 +26,10 @@ config SND_RAWMIDI tristate select SND_SEQ_DEVICE if SND_SEQUENCER != n +config SND_UMP + tristate + select SND_RAWMIDI + config SND_COMPRESS_OFFLOAD tristate diff --git a/sound/core/Makefile b/sound/core/Makefile index 2762f03d9b7b..562a05edbc50 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -28,6 +28,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o snd-ctl-led-objs := control_led.o snd-rawmidi-objs := rawmidi.o +snd-ump-objs := ump.o snd-timer-objs := timer.o snd-hrtimer-objs := hrtimer.o snd-rtctimer-objs := rtctimer.o @@ -45,6 +46,7 @@ obj-$(CONFIG_SND_PCM) += snd-pcm.o obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o obj-$(CONFIG_SND_SEQ_DEVICE) += snd-seq-device.o obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o +obj-$(CONFIG_SND_UMP) += snd-ump.o obj-$(CONFIG_SND_OSSEMUL) += oss/ obj-$(CONFIG_SND_SEQUENCER) += seq/ diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index ab28cfc1fac8..6360e2239a63 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -21,6 +21,7 @@ #include #include #include +#include MODULE_AUTHOR("Jaroslav Kysela "); MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA."); @@ -35,7 +36,6 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ -static int snd_rawmidi_free(struct snd_rawmidi *rmidi); static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -73,6 +73,9 @@ struct snd_rawmidi_status64 { #define SNDRV_RAWMIDI_IOCTL_STATUS64 _IOWR('W', 0x20, struct snd_rawmidi_status64) +#define rawmidi_is_ump(rmidi) \ + (IS_ENABLED(CONFIG_SND_UMP) && ((rmidi)->info_flags & SNDRV_RAWMIDI_INFO_UMP)) + static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device) { struct snd_rawmidi *rawmidi; @@ -181,9 +184,23 @@ static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) } runtime->appl_ptr = runtime->hw_ptr = 0; substream->runtime = runtime; + if (rawmidi_is_ump(substream->rmidi)) + runtime->align = 3; return 0; } +/* get the current alignment (either 0 or 3) */ +static inline int get_align(struct snd_rawmidi_runtime *runtime) +{ + if (IS_ENABLED(CONFIG_SND_UMP)) + return runtime->align; + else + return 0; +} + +/* get the trimmed size with the current alignment */ +#define get_aligned_size(runtime, size) ((size) & ~get_align(runtime)) + static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) { struct snd_rawmidi_runtime *runtime = substream->runtime; @@ -721,6 +738,8 @@ static int resize_runtime_buffer(struct snd_rawmidi_substream *substream, return -EINVAL; if (params->avail_min < 1 || params->avail_min > params->buffer_size) return -EINVAL; + if (params->buffer_size & get_align(runtime)) + return -EINVAL; if (params->buffer_size != runtime->buffer_size) { newbuf = kvzalloc(params->buffer_size, GFP_KERNEL); if (!newbuf) @@ -1046,12 +1065,13 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec }; int orig_count = src_count; int frame_size = sizeof(struct snd_rawmidi_framing_tstamp); + int align = get_align(runtime); BUILD_BUG_ON(frame_size != 0x20); if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0)) return -EINVAL; - while (src_count > 0) { + while (src_count > align) { if ((int)(runtime->buffer_size - runtime->avail) < frame_size) { runtime->xruns += src_count; break; @@ -1059,7 +1079,9 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH) frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH; else { - frame.length = src_count; + frame.length = get_aligned_size(runtime, src_count); + if (!frame.length) + break; memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH); } memcpy(frame.data, buffer, frame.length); @@ -1123,6 +1145,10 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, goto unlock; } + count = get_aligned_size(runtime, count); + if (!count) + goto unlock; + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { result = receive_with_tstamp_framing(substream, buffer, count, &ts64); } else if (count == 1) { /* special case, faster code */ @@ -1142,6 +1168,9 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, count1 = count; if (count1 > (int)(runtime->buffer_size - runtime->avail)) count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto unlock; memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1); runtime->hw_ptr += count1; runtime->hw_ptr %= runtime->buffer_size; @@ -1342,12 +1371,18 @@ static int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, count1 = count; if (count1 > (int)(runtime->buffer_size - runtime->avail)) count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto __skip; memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1); count -= count1; result += count1; if (count > 0) { if (count > (int)(runtime->buffer_size - runtime->avail - count1)) count = runtime->buffer_size - runtime->avail - count1; + count = get_aligned_size(runtime, count); + if (!count) + goto __skip; memcpy(buffer + count1, runtime->buffer, count); result += count; } @@ -1404,6 +1439,7 @@ static int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, return -EINVAL; } snd_BUG_ON(runtime->avail + count > runtime->buffer_size); + count = get_aligned_size(runtime, count); runtime->hw_ptr += count; runtime->hw_ptr %= runtime->buffer_size; runtime->avail += count; @@ -1690,6 +1726,9 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, rmidi = entry->private_data; snd_iprintf(buffer, "%s\n\n", rmidi->name); + if (IS_ENABLED(CONFIG_SND_UMP)) + snd_iprintf(buffer, "Type: %s\n", + rawmidi_is_ump(rmidi) ? "UMP" : "Legacy"); mutex_lock(&rmidi->open_mutex); if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { list_for_each_entry(substream, @@ -1800,25 +1839,12 @@ static void release_rawmidi_device(struct device *dev) kfree(container_of(dev, struct snd_rawmidi, dev)); } -/** - * snd_rawmidi_new - create a rawmidi instance - * @card: the card instance - * @id: the id string - * @device: the device index - * @output_count: the number of output streams - * @input_count: the number of input streams - * @rrawmidi: the pointer to store the new rawmidi instance - * - * Creates a new rawmidi instance. - * Use snd_rawmidi_set_ops() to set the operators to the new instance. - * - * Return: Zero if successful, or a negative error code on failure. - */ -int snd_rawmidi_new(struct snd_card *card, char *id, int device, - int output_count, int input_count, - struct snd_rawmidi **rrawmidi) +/* used for both rawmidi and ump */ +int snd_rawmidi_init(struct snd_rawmidi *rmidi, + struct snd_card *card, char *id, int device, + int output_count, int input_count, + unsigned int info_flags) { - struct snd_rawmidi *rmidi; int err; static const struct snd_device_ops ops = { .dev_free = snd_rawmidi_dev_free, @@ -1826,50 +1852,78 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device, .dev_disconnect = snd_rawmidi_dev_disconnect, }; - if (snd_BUG_ON(!card)) - return -ENXIO; - if (rrawmidi) - *rrawmidi = NULL; - rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); - if (!rmidi) - return -ENOMEM; rmidi->card = card; rmidi->device = device; mutex_init(&rmidi->open_mutex); init_waitqueue_head(&rmidi->open_wait); INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams); INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams); + rmidi->info_flags = info_flags; if (id != NULL) strscpy(rmidi->id, id, sizeof(rmidi->id)); snd_device_initialize(&rmidi->dev, card); rmidi->dev.release = release_rawmidi_device; - dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device); + if (rawmidi_is_ump(rmidi)) + dev_set_name(&rmidi->dev, "umpC%iD%i", card->number, device); + else + dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device); err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], SNDRV_RAWMIDI_STREAM_INPUT, input_count); if (err < 0) - goto error; + return err; err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], SNDRV_RAWMIDI_STREAM_OUTPUT, output_count); if (err < 0) - goto error; + return err; err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops); if (err < 0) - goto error; + return err; + return 0; +} +EXPORT_SYMBOL_GPL(snd_rawmidi_init); + +/** + * snd_rawmidi_new - create a rawmidi instance + * @card: the card instance + * @id: the id string + * @device: the device index + * @output_count: the number of output streams + * @input_count: the number of input streams + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new rawmidi instance. + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_rawmidi_new(struct snd_card *card, char *id, int device, + int output_count, int input_count, + struct snd_rawmidi **rrawmidi) +{ + struct snd_rawmidi *rmidi; + int err; + if (rrawmidi) + *rrawmidi = NULL; + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + err = snd_rawmidi_init(rmidi, card, id, device, + output_count, input_count, 0); + if (err < 0) { + snd_rawmidi_free(rmidi); + return err; + } if (rrawmidi) *rrawmidi = rmidi; return 0; - - error: - snd_rawmidi_free(rmidi); - return err; } EXPORT_SYMBOL(snd_rawmidi_new); @@ -1884,7 +1938,8 @@ static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream) } } -static int snd_rawmidi_free(struct snd_rawmidi *rmidi) +/* called from ump.c, too */ +int snd_rawmidi_free(struct snd_rawmidi *rmidi) { if (!rmidi) return 0; @@ -1901,6 +1956,7 @@ static int snd_rawmidi_free(struct snd_rawmidi *rmidi) put_device(&rmidi->dev); return 0; } +EXPORT_SYMBOL_GPL(snd_rawmidi_free); static int snd_rawmidi_dev_free(struct snd_device *device) { @@ -1951,7 +2007,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) } #ifdef CONFIG_SND_OSSEMUL rmidi->ossreg = 0; - if ((int)rmidi->device == midi_map[rmidi->card->number]) { + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == midi_map[rmidi->card->number]) { if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0, &snd_rawmidi_f_ops, rmidi) < 0) { @@ -1965,7 +2022,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) #endif } } - if ((int)rmidi->device == amidi_map[rmidi->card->number]) { + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == amidi_map[rmidi->card->number]) { if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1, &snd_rawmidi_f_ops, rmidi) < 0) { @@ -1989,7 +2047,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) } rmidi->proc_entry = entry; #if IS_ENABLED(CONFIG_SND_SEQUENCER) - if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */ + /* no own registration mechanism? */ + if (!rmidi->ops || !rmidi->ops->dev_register) { if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) { rmidi->seq_dev->private_data = rmidi; rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free; diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c index 68a93443583c..b81b30d82f88 100644 --- a/sound/core/rawmidi_compat.c +++ b/sound/core/rawmidi_compat.c @@ -111,6 +111,10 @@ static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsign case SNDRV_RAWMIDI_IOCTL_INFO: case SNDRV_RAWMIDI_IOCTL_DROP: case SNDRV_RAWMIDI_IOCTL_DRAIN: +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + case SNDRV_UMP_IOCTL_BLOCK_INFO: +#endif return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp); case SNDRV_RAWMIDI_IOCTL_PARAMS32: return snd_rawmidi_ioctl_params_compat(rfile, argp); diff --git a/sound/core/ump.c b/sound/core/ump.c new file mode 100644 index 000000000000..ee57ba1ee989 --- /dev/null +++ b/sound/core/ump.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP) support + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) +#define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) +#define ump_info(ump, fmt, args...) dev_info(&(ump)->core.dev, fmt, ##args) +#define ump_dbg(ump, fmt, args...) dev_dbg(&(ump)->core.dev, fmt, ##args) + +static int snd_ump_dev_register(struct snd_rawmidi *rmidi); +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp); + +static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { + .dev_register = snd_ump_dev_register, + .dev_unregister = snd_ump_dev_unregister, + .ioctl = snd_ump_ioctl, +}; + +static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + while (!list_empty(&ump->block_list)) { + fb = list_first_entry(&ump->block_list, struct snd_ump_block, + list); + list_del(&fb->list); + if (fb->private_free) + fb->private_free(fb); + kfree(fb); + } + + if (ump->private_free) + ump->private_free(ump); +} + +/** + * snd_ump_endpoint_new - create a UMP Endpoint object + * @card: the card instance + * @id: the id string for rawmidi + * @device: the device index for rawmidi + * @output: 1 for enabling output + * @input: 1 for enabling input + * @ump_ret: the pointer to store the new UMP instance + * + * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi + * instance with one input and/or one output rawmidi stream (either uni- + * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks + * that consist of one or multiple UMP Groups. + * + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself + * depending on the given @output and @input. + * + * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device + * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is + * created. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, + int output, int input, + struct snd_ump_endpoint **ump_ret) +{ + unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP; + struct snd_ump_endpoint *ump; + int err; + + if (input) + info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + if (output) + info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + if (input && output) + info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + ump = kzalloc(sizeof(*ump), GFP_KERNEL); + if (!ump) + return -ENOMEM; + INIT_LIST_HEAD(&ump->block_list); + err = snd_rawmidi_init(&ump->core, card, id, device, + output, input, info_flags); + if (err < 0) { + snd_rawmidi_free(&ump->core); + return err; + } + + ump->info.card = card->number; + ump->info.device = device; + + ump->core.private_free = snd_ump_endpoint_free; + ump->core.ops = &snd_ump_rawmidi_ops; + + ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); + *ump_ret = ump; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); + +/* + * Device register / unregister hooks; + * do nothing, placeholders for avoiding the default rawmidi handling + */ +static int snd_ump_dev_register(struct snd_rawmidi *rmidi) +{ + return 0; +} + +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi) +{ + return 0; +} + +static struct snd_ump_block * +snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) +{ + struct snd_ump_block *fb; + + list_for_each_entry(fb, &ump->block_list, list) { + if (fb->info.block_id == id) + return fb; + } + return NULL; +} + +/** + * snd_ump_block_new - Create a UMP block + * @ump: UMP object + * @blk: block ID number to create + * @direction: direction (in/out/bidirection) + * @first_group: the first group ID (0-based) + * @num_groups: the number of groups in this block + * @blk_ret: the pointer to store the resultant block object + */ +int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, + unsigned int direction, unsigned int first_group, + unsigned int num_groups, struct snd_ump_block **blk_ret) +{ + struct snd_ump_block *fb, *p; + + if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS) + return -EINVAL; + + if (snd_ump_get_block(ump, blk)) + return -EBUSY; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return -ENOMEM; + + fb->ump = ump; + fb->info.card = ump->info.card; + fb->info.device = ump->info.device; + fb->info.block_id = blk; + if (blk >= ump->info.num_blocks) + ump->info.num_blocks = blk + 1; + fb->info.direction = direction; + fb->info.active = 1; + fb->info.first_group = first_group; + fb->info.num_groups = num_groups; + /* fill the default name, may be overwritten to a better name */ + snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d", + first_group + 1, first_group + num_groups); + + /* put the entry in the ordered list */ + list_for_each_entry(p, &ump->block_list, list) { + if (p->info.block_id > blk) { + list_add_tail(&fb->list, &p->list); + goto added; + } + } + list_add_tail(&fb->list, &ump->block_list); + + added: + ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name); + *blk_ret = fb; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_block_new); + +static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump, + struct snd_ump_block_info __user *argp) +{ + struct snd_ump_block *fb; + unsigned char id; + + if (get_user(id, &argp->block_id)) + return -EFAULT; + fb = snd_ump_get_block(ump, id); + if (!fb) + return -ENOENT; + if (copy_to_user(argp, &fb->info, sizeof(fb->info))) + return -EFAULT; + return 0; +} + +/* + * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl() + */ +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + + switch (cmd) { + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + if (copy_to_user(argp, &ump->info, sizeof(ump->info))) + return -EFAULT; + return 0; + case SNDRV_UMP_IOCTL_BLOCK_INFO: + return snd_ump_ioctl_block(ump, argp); + default: + ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd); + return -ENOTTY; + } +} + +MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 127ae6f6dad2edb2201e27b7e6fa72994b537fad Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:25 +0200 Subject: ALSA: rawmidi: Skip UMP devices at SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE Applications may look for rawmidi devices with the ioctl SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE. Returning a UMP device from this ioctl may confuse the existing applications that support only the legacy rawmidi. This patch changes the code to skip the UMP devices from the lookup for avoiding the confusion, and introduces a new ioctl to look for the UMP devices instead. Along with this change, bump the CTL protocol version to 2.0.9. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 ++- sound/core/rawmidi.c | 57 ++++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index b001df4b335e..1e4a21036109 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1016,7 +1016,7 @@ struct snd_timer_tread { * * ****************************************************************************/ -#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 8) +#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) struct snd_ctl_card_info { int card; /* card number */ @@ -1177,6 +1177,7 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE _IOWR('U', 0x40, int) #define SNDRV_CTL_IOCTL_RAWMIDI_INFO _IOWR('U', 0x41, struct snd_rawmidi_info) #define SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE _IOW('U', 0x42, int) +#define SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE _IOWR('U', 0x43, int) #define SNDRV_CTL_IOCTL_POWER _IOWR('U', 0xd0, int) #define SNDRV_CTL_IOCTL_POWER_STATE _IOR('U', 0xd1, int) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 6360e2239a63..9936ed282b85 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1012,6 +1012,37 @@ static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long return -ENOTTY; } +/* ioctl to find the next device; either legacy or UMP depending on @find_ump */ +static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp, + bool find_ump) + +{ + struct snd_rawmidi *rmidi; + int device; + bool is_ump; + + if (get_user(device, argp)) + return -EFAULT; + if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ + device = SNDRV_RAWMIDI_DEVICES - 1; + mutex_lock(®ister_mutex); + device = device < 0 ? 0 : device + 1; + for (; device < SNDRV_RAWMIDI_DEVICES; device++) { + rmidi = snd_rawmidi_search(card, device); + if (!rmidi) + continue; + is_ump = rawmidi_is_ump(rmidi); + if (find_ump == is_ump) + break; + } + if (device == SNDRV_RAWMIDI_DEVICES) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, argp)) + return -EFAULT; + return 0; +} + static int snd_rawmidi_control_ioctl(struct snd_card *card, struct snd_ctl_file *control, unsigned int cmd, @@ -1021,27 +1052,11 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, switch (cmd) { case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: - { - int device; - - if (get_user(device, (int __user *)argp)) - return -EFAULT; - if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ - device = SNDRV_RAWMIDI_DEVICES - 1; - mutex_lock(®ister_mutex); - device = device < 0 ? 0 : device + 1; - while (device < SNDRV_RAWMIDI_DEVICES) { - if (snd_rawmidi_search(card, device)) - break; - device++; - } - if (device == SNDRV_RAWMIDI_DEVICES) - device = -1; - mutex_unlock(®ister_mutex); - if (put_user(device, (int __user *)argp)) - return -EFAULT; - return 0; - } + return snd_rawmidi_next_device(card, argp, false); +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE: + return snd_rawmidi_next_device(card, argp, true); +#endif case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: { int val; -- cgit v1.2.3 From 30fc139260d46e9bdc06e46eec91e9ff61eb387e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:26 +0200 Subject: ALSA: ump: Add ioctls to inquiry UMP EP and Block info via control API It'd be convenient to have ioctls to inquiry the UMP Endpoint and UMP Block information directly via the control API without opening the rawmidi interface, just like SNDRV_CTL_IOCTL_RAWMIDI_INFO. This patch extends the rawmidi ioctl handler to support those; new ioctls, SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO and SNDRV_CTL_IOCTL_UMP_BLOCK_INFO, return the snd_ump_endpoint and snd_ump_block data that is specified by the device field, respectively. Suggested-by: Jaroslav Kysela Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 2 ++ sound/core/rawmidi.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 1e4a21036109..5c5f41dd4001 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1178,6 +1178,8 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_RAWMIDI_INFO _IOWR('U', 0x41, struct snd_rawmidi_info) #define SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE _IOW('U', 0x42, int) #define SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE _IOWR('U', 0x43, int) +#define SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO _IOWR('U', 0x44, struct snd_ump_endpoint_info) +#define SNDRV_CTL_IOCTL_UMP_BLOCK_INFO _IOWR('U', 0x45, struct snd_ump_block_info) #define SNDRV_CTL_IOCTL_POWER _IOWR('U', 0xd0, int) #define SNDRV_CTL_IOCTL_POWER_STATE _IOR('U', 0xd1, int) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 9936ed282b85..ffb5b58105f4 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1043,6 +1043,28 @@ static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp, return 0; } +#if IS_ENABLED(CONFIG_SND_UMP) +/* inquiry of UMP endpoint and block info via control API */ +static int snd_rawmidi_call_ump_ioctl(struct snd_card *card, int cmd, + void __user *argp) +{ + struct snd_ump_endpoint_info __user *info = argp; + struct snd_rawmidi *rmidi; + int device, ret; + + if (get_user(device, &info->device)) + return -EFAULT; + mutex_lock(®ister_mutex); + rmidi = snd_rawmidi_search(card, device); + if (rmidi && rmidi->ops && rmidi->ops->ioctl) + ret = rmidi->ops->ioctl(rmidi, cmd, argp); + else + ret = -ENXIO; + mutex_unlock(®ister_mutex); + return ret; +} +#endif + static int snd_rawmidi_control_ioctl(struct snd_card *card, struct snd_ctl_file *control, unsigned int cmd, @@ -1056,6 +1078,10 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, #if IS_ENABLED(CONFIG_SND_UMP) case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE: return snd_rawmidi_next_device(card, argp, true); + case SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_ENDPOINT_INFO, argp); + case SNDRV_CTL_IOCTL_UMP_BLOCK_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_BLOCK_INFO, argp); #endif case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: { -- cgit v1.2.3 From fa030f666d2431be5310c0c0fef254e2e205d4cc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:27 +0200 Subject: ALSA: ump: Additional proc output UMP devices may have more interesting information than the traditional rawmidi. Extend the rawmidi_global_ops to allow the optional proc info output and show some more bits in the proc file for UMP. Note that the "Groups" field shows the first and the last UMP Groups, and both numbers are 1-based (i.e. the first group is 1). Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-7-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 3 +++ sound/core/rawmidi.c | 2 ++ sound/core/ump.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index b8a230a7583b..b0197b1d1fe4 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -18,6 +18,7 @@ #if IS_ENABLED(CONFIG_SND_SEQUENCER) #include #endif +#include /* * Raw MIDI interface @@ -49,6 +50,8 @@ struct snd_rawmidi_global_ops { struct snd_seq_port_info *info); long (*ioctl)(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); + void (*proc_read)(struct snd_info_entry *entry, + struct snd_info_buffer *buf); }; struct snd_rawmidi_runtime { diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index ffb5b58105f4..2d3cec908154 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1770,6 +1770,8 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, if (IS_ENABLED(CONFIG_SND_UMP)) snd_iprintf(buffer, "Type: %s\n", rawmidi_is_ump(rmidi) ? "UMP" : "Legacy"); + if (rmidi->ops->proc_read) + rmidi->ops->proc_read(entry, buffer); mutex_lock(&rmidi->open_mutex); if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { list_for_each_entry(substream, diff --git a/sound/core/ump.c b/sound/core/ump.c index ee57ba1ee989..651cd3752719 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -21,11 +21,14 @@ static int snd_ump_dev_register(struct snd_rawmidi *rmidi); static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer); static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, .dev_unregister = snd_ump_dev_unregister, .ioctl = snd_ump_ioctl, + .proc_read = snd_ump_proc_read, }; static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) @@ -226,5 +229,51 @@ static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, } } +static const char *ump_direction_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_DIR_INPUT: + return "input"; + case SNDRV_UMP_DIR_OUTPUT: + return "output"; + case SNDRV_UMP_DIR_BIDIRECTION: + return "bidirection"; + default: + return "unknown"; + } +} + +/* Additional proc file output */ +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_rawmidi *rmidi = entry->private_data; + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + snd_iprintf(buffer, "EP Name: %s\n", ump->info.name); + snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id); + snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); + snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); + snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); + snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); + + list_for_each_entry(fb, &ump->block_list, list) { + snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id, + fb->info.name); + snd_iprintf(buffer, " Direction: %s\n", + ump_direction_string(fb->info.direction)); + snd_iprintf(buffer, " Active: %s\n", + fb->info.active ? "Yes" : "No"); + snd_iprintf(buffer, " Groups: %d-%d\n", + fb->info.first_group + 1, + fb->info.first_group + fb->info.num_groups); + snd_iprintf(buffer, " Is MIDI1: %s%s\n", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); + snd_iprintf(buffer, "\n"); + } +} + MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From bb1bf4fa5953418c131796ee745c59899d34a149 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:28 +0200 Subject: ALSA: usb-audio: Manage number of rawmidis globally We're going to create rawmidi objects for MIDI 2.0 in a different code from the current code for USB-MIDI 1.0. As a preliminary work, this patch adds the number of rawmidi objects to keep globally in a USB-audio card instance, so that it can be referred from both MIDI 1.0 and 2.0 code. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-8-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/card.c | 5 +++-- sound/usb/midi.c | 7 ++++++- sound/usb/midi.h | 5 +++-- sound/usb/quirks.c | 5 +++-- sound/usb/usbaudio.h | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index f6e99ced8068..bd051e634516 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -179,8 +179,9 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { int err = __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL, - chip->usb_id); + &chip->midi_list, NULL, + chip->usb_id, + &chip->num_rawmidis); if (err < 0) { dev_err(&dev->dev, "%u:%d: cannot create sequencer device\n", diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 2839f6b6f09b..6b0993258e03 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -2461,7 +2461,8 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id) + unsigned int usb_id, + unsigned int *num_rawmidis) { struct snd_usb_midi *umidi; struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; @@ -2476,6 +2477,8 @@ int __snd_usbmidi_create(struct snd_card *card, umidi->iface = iface; umidi->quirk = quirk; umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; + if (num_rawmidis) + umidi->next_midi_device = *num_rawmidis; spin_lock_init(&umidi->disc_lock); init_rwsem(&umidi->disc_rwsem); mutex_init(&umidi->mutex); @@ -2595,6 +2598,8 @@ int __snd_usbmidi_create(struct snd_card *card, usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); + if (num_rawmidis) + *num_rawmidis = umidi->next_midi_device; return 0; free_midi: diff --git a/sound/usb/midi.h b/sound/usb/midi.h index 3f153195c841..2100f1486b03 100644 --- a/sound/usb/midi.h +++ b/sound/usb/midi.h @@ -46,14 +46,15 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id); + unsigned int usb_id, + unsigned int *num_rawmidis); static inline int snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk) { - return __snd_usbmidi_create(card, iface, midi_list, quirk, 0); + return __snd_usbmidi_create(card, iface, midi_list, quirk, 0, NULL); } void snd_usbmidi_input_stop(struct list_head *p); diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 3ecd1ba7fd4b..1cabe4cc019f 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -436,8 +436,9 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, chip->usb_id == USB_ID(0x0582, 0x002b) ? &ua700_quirk : &uaxx_quirk; return __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, quirk, - chip->usb_id); + &chip->midi_list, quirk, + chip->usb_id, + &chip->num_rawmidis); } if (altsd->bNumEndpoints != 1) diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 38a85b2c9a73..b1fa0a377866 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -49,6 +49,7 @@ struct snd_usb_audio { struct list_head clock_ref_list; /* list of clock refcounts */ int pcm_devs; + unsigned int num_rawmidis; /* number of created rawmidi devices */ struct list_head midi_list; /* list of midi interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ -- cgit v1.2.3 From ff49d1df79aef7580fe3ac99d17c3f886655d080 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:30 +0200 Subject: ALSA: usb-audio: USB MIDI 2.0 UMP support This patch provides a basic support for USB MIDI 2.0. As of this patch, the driver creates a UMP device per MIDI I/O endpoints, which serves as a dumb terminal to read/write UMP streams. A new Kconfig CONFIG_SND_USB_AUDIO_MIDI_V2 manages whether to enable or disable the MIDI 2.0 support. Also, the driver provides a new module option, midi2_enable, to allow disabling the MIDI 2.0 at runtime, too. When MIDI 2.0 support is disabled, the driver tries to fall back to the already existing MIDI 1.0 device (each MIDI 2.0 device is supposed to provide the MIDI 1.0 interface at the altset 0). For now, the driver doesn't manage any MIDI-CI or other protocol setups by itself, but relies on the default protocol given via the group terminal block descriptors. The MIDI 1.0 messages on MIDI 2.0 device will be automatically converted in ALSA sequencer in a later patch. As of this commit, the driver accepts merely the rawmidi UMP accesses. The driver builds up the topology in the following way: - Create an object for each MIDI endpoint belonging to the USB interface - Find MIDI EP "pairs" that share the same GTB; note that MIDI EP is unidirectional, while UMP is (normally) bidirectional, so two MIDI EPs can form a single UMP EP - A UMP endpoint object is created for each I/O pair - For remaining "solo" MIDI EPs, create unidirectional UMP EPs - Finally, parse GTBs and fill the protocol bits on each UMP So the driver may support multiple UMP Endpoints in theory, although most devices are supposed to have a single UMP EP that can contain up to 16 groups -- which should be large enough. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-10-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/Kconfig | 11 + sound/usb/Makefile | 1 + sound/usb/card.c | 13 +- sound/usb/midi2.c | 1052 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/midi2.h | 33 ++ sound/usb/quirks.c | 3 +- sound/usb/usbaudio.h | 1 + 7 files changed, 1109 insertions(+), 5 deletions(-) create mode 100644 sound/usb/midi2.c create mode 100644 sound/usb/midi2.h (limited to 'sound') diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 059242f15d75..4a9569a3a39a 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -15,6 +15,7 @@ config SND_USB_AUDIO select SND_HWDEP select SND_RAWMIDI select SND_PCM + select SND_UMP if SND_USB_AUDIO_MIDI_V2 select BITREVERSE select SND_USB_AUDIO_USE_MEDIA_CONTROLLER if MEDIA_CONTROLLER && (MEDIA_SUPPORT=y || MEDIA_SUPPORT=SND_USB_AUDIO) help @@ -24,6 +25,16 @@ config SND_USB_AUDIO To compile this driver as a module, choose M here: the module will be called snd-usb-audio. +config SND_USB_AUDIO_MIDI_V2 + bool "MIDI 2.0 support by USB Audio driver" + depends on SND_USB_AUDIO + help + Say Y here to include the support for MIDI 2.0 by USB Audio driver. + When the config is set, the driver tries to probe MIDI 2.0 interface + at first, then falls back to MIDI 1.0 interface as default. + The MIDI 2.0 support can be disabled dynamically via midi2_enable + module option, too. + config SND_USB_AUDIO_USE_MEDIA_CONTROLLER bool diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 9ccb21a4ff8a..db5ff76d0e61 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -22,6 +22,7 @@ snd-usb-audio-objs := card.o \ stream.o \ validate.o +snd-usb-audio-$(CONFIG_SND_USB_AUDIO_MIDI_V2) += midi2.o snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o snd-usbmidi-lib-objs := midi.o diff --git a/sound/usb/card.c b/sound/usb/card.c index bd051e634516..1b2edc0fd2e9 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -44,6 +44,7 @@ #include "usbaudio.h" #include "card.h" #include "midi.h" +#include "midi2.h" #include "mixer.h" #include "proc.h" #include "quirks.h" @@ -178,10 +179,8 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { - int err = __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL, - chip->usb_id, - &chip->num_rawmidis); + int err = snd_usb_midi_v2_create(chip, iface, NULL, + chip->usb_id); if (err < 0) { dev_err(&dev->dev, "%u:%d: cannot create sequencer device\n", @@ -486,6 +485,7 @@ static void snd_usb_audio_free(struct snd_card *card) struct snd_usb_audio *chip = card->private_data; snd_usb_endpoint_free_all(chip); + snd_usb_midi_v2_free_all(chip); mutex_destroy(&chip->mutex); if (!atomic_read(&chip->shutdown)) @@ -645,6 +645,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, INIT_LIST_HEAD(&chip->iface_ref_list); INIT_LIST_HEAD(&chip->clock_ref_list); INIT_LIST_HEAD(&chip->midi_list); + INIT_LIST_HEAD(&chip->midi_v2_list); INIT_LIST_HEAD(&chip->mixer_list); if (quirk_flags[idx]) @@ -969,6 +970,7 @@ static void usb_audio_disconnect(struct usb_interface *intf) list_for_each(p, &chip->midi_list) { snd_usbmidi_disconnect(p); } + snd_usb_midi_v2_disconnect_all(chip); /* * Nice to check quirk && quirk->shares_media_device and * then call the snd_media_device_delete(). Don't have @@ -1080,6 +1082,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) snd_usbmidi_suspend(p); list_for_each_entry(mixer, &chip->mixer_list, list) snd_usb_mixer_suspend(mixer); + snd_usb_midi_v2_suspend_all(chip); } if (!PMSG_IS_AUTO(message) && !chip->system_suspend) { @@ -1125,6 +1128,8 @@ static int usb_audio_resume(struct usb_interface *intf) snd_usbmidi_resume(p); } + snd_usb_midi_v2_resume_all(chip); + out: if (chip->num_suspended_intf == chip->system_suspend) { snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c new file mode 100644 index 000000000000..7b4cfbaf2ec0 --- /dev/null +++ b/sound/usb/midi2.c @@ -0,0 +1,1052 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIDI 2.0 support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbaudio.h" +#include "midi.h" +#include "midi2.h" +#include "helper.h" + +static bool midi2_enable = true; +module_param(midi2_enable, bool, 0444); +MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); + +/* stream direction; just shorter names */ +enum { + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT +}; + +#define NUM_URBS 8 + +struct snd_usb_midi2_urb; +struct snd_usb_midi2_endpoint; +struct snd_usb_midi2_ump; +struct snd_usb_midi2_interface; + +/* URB context */ +struct snd_usb_midi2_urb { + struct urb *urb; + struct snd_usb_midi2_endpoint *ep; + unsigned int index; /* array index */ +}; + +/* A USB MIDI input/output endpoint */ +struct snd_usb_midi2_endpoint { + struct usb_device *dev; + const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */ + struct snd_usb_midi2_endpoint *pair; /* bidirectional pair EP */ + struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP */ + int direction; /* direction (STR_IN/OUT) */ + unsigned int endpoint; /* EP number */ + unsigned int pipe; /* URB pipe */ + unsigned int packets; /* packet buffer size in bytes */ + unsigned int interval; /* interval for INT EP */ + wait_queue_head_t wait; /* URB waiter */ + spinlock_t lock; /* URB locking */ + struct snd_rawmidi_substream *substream; /* NULL when closed */ + unsigned int num_urbs; /* number of allocated URBs */ + unsigned long urb_free; /* bitmap for free URBs */ + unsigned long urb_free_mask; /* bitmask for free URBs */ + atomic_t running; /* running status */ + atomic_t suspended; /* saved running status for suspend */ + bool disconnected; /* shadow of umidi->disconnected */ + struct list_head list; /* list to umidi->ep_list */ + struct snd_usb_midi2_urb urbs[NUM_URBS]; +}; + +/* A UMP endpoint - one or two USB MIDI endpoints are assigned */ +struct snd_usb_midi2_ump { + struct usb_device *dev; + struct snd_usb_midi2_interface *umidi; /* reference to MIDI iface */ + struct snd_ump_endpoint *ump; /* assigned UMP EP object */ + struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ + int index; /* rawmidi device index */ + unsigned char usb_block_id; /* USB GTB id used for finding a pair */ + struct list_head list; /* list to umidi->rawmidi_list */ +}; + +/* top-level instance per USB MIDI interface */ +struct snd_usb_midi2_interface { + struct snd_usb_audio *chip; /* assigned USB-audio card */ + struct usb_interface *iface; /* assigned USB interface */ + struct usb_host_interface *hostif; + const char *blk_descs; /* group terminal block descriptors */ + unsigned int blk_desc_size; /* size of GTB descriptors */ + bool disconnected; + struct list_head ep_list; /* list of endpoints */ + struct list_head rawmidi_list; /* list of UMP rawmidis */ + struct list_head list; /* list to chip->midi_v2_list */ +}; + +/* submit URBs as much as possible; used for both input and output */ +static void do_submit_urbs_locked(struct snd_usb_midi2_endpoint *ep, + int (*prepare)(struct snd_usb_midi2_endpoint *, + struct urb *)) +{ + struct snd_usb_midi2_urb *ctx; + int index, err = 0; + + if (ep->disconnected) + return; + + while (ep->urb_free) { + index = find_first_bit(&ep->urb_free, ep->num_urbs); + if (index >= ep->num_urbs) + return; + ctx = &ep->urbs[index]; + err = prepare(ep, ctx->urb); + if (err < 0) + return; + if (!ctx->urb->transfer_buffer_length) + return; + ctx->urb->dev = ep->dev; + err = usb_submit_urb(ctx->urb, GFP_ATOMIC); + if (err < 0) { + dev_dbg(&ep->dev->dev, + "usb_submit_urb error %d\n", err); + return; + } + clear_bit(index, &ep->urb_free); + } +} + +/* prepare for output submission: copy from rawmidi buffer to urb packet */ +static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + int count; + + if (ep->substream) + count = snd_rawmidi_transmit(ep->substream, + urb->transfer_buffer, + ep->packets); + else + count = -ENODEV; + if (count < 0) { + dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count); + return count; + } + cpu_to_le32_array((u32 *)urb->transfer_buffer, count >> 2); + urb->transfer_buffer_length = count; + return 0; +} + +static void submit_output_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_output_urb); +} + +/* URB completion for output; re-filling and re-submit */ +static void output_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + set_bit(ctx->index, &ep->urb_free); + if (urb->status >= 0 && atomic_read(&ep->running)) + submit_output_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* prepare for input submission: just set the buffer length */ +static int prepare_input_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + urb->transfer_buffer_length = ep->packets; + return 0; +} + +static void submit_input_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_input_urb); +} + +/* URB completion for input; copy into rawmidi buffer and resubmit */ +static void input_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + unsigned long flags; + int len; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->disconnected || urb->status < 0) + goto dequeue; + len = urb->actual_length; + len &= ~3; /* align UMP */ + if (len > ep->packets) + len = ep->packets; + if (len > 0 && ep->substream) { + le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2); + snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len); + } + dequeue: + set_bit(ctx->index, &ep->urb_free); + submit_input_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* URB submission helper; for both direction */ +static void submit_io_urbs(struct snd_usb_midi2_endpoint *ep) +{ + unsigned long flags; + + if (!ep) + return; + spin_lock_irqsave(&ep->lock, flags); + if (ep->direction == STR_IN) + submit_input_urbs_locked(ep); + else + submit_output_urbs_locked(ep); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* kill URBs for close, suspend and disconnect */ +static void kill_midi_urbs(struct snd_usb_midi2_endpoint *ep, bool suspending) +{ + int i; + + if (!ep) + return; + if (suspending) + ep->suspended = ep->running; + atomic_set(&ep->running, 0); + for (i = 0; i < ep->num_urbs; i++) { + if (!ep->urbs[i].urb) + break; + usb_kill_urb(ep->urbs[i].urb); + } +} + +/* wait until all URBs get freed */ +static void drain_urb_queue(struct snd_usb_midi2_endpoint *ep) +{ + if (!ep) + return; + spin_lock_irq(&ep->lock); + atomic_set(&ep->running, 0); + wait_event_lock_irq_timeout(ep->wait, + ep->disconnected || + ep->urb_free == ep->urb_free_mask, + ep->lock, msecs_to_jiffies(500)); + spin_unlock_irq(&ep->lock); +} + +/* release URBs for an EP */ +static void free_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + int i; + + if (!ep) + return; + for (i = 0; i < ep->num_urbs; ++i) { + ctx = &ep->urbs[i]; + if (!ctx->urb) + break; + usb_free_coherent(ep->dev, ep->packets, + ctx->urb->transfer_buffer, + ctx->urb->transfer_dma); + usb_free_urb(ctx->urb); + ctx->urb = NULL; + } + ep->num_urbs = 0; +} + +/* allocate URBs for an EP */ +static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + void (*comp)(struct urb *urb); + void *buffer; + int i, err; + int endpoint, len; + + endpoint = ep->endpoint; + len = ep->packets; + if (ep->direction == STR_IN) + comp = input_urb_complete; + else + comp = output_urb_complete; + + ep->num_urbs = 0; + ep->urb_free = ep->urb_free_mask = 0; + for (i = 0; i < NUM_URBS; i++) { + ctx = &ep->urbs[i]; + ctx->index = i; + ctx->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ctx->urb) { + dev_err(&ep->dev->dev, "URB alloc failed\n"); + return -ENOMEM; + } + ctx->ep = ep; + buffer = usb_alloc_coherent(ep->dev, len, GFP_KERNEL, + &ctx->urb->transfer_dma); + if (!buffer) { + dev_err(&ep->dev->dev, + "URB buffer alloc failed (size %d)\n", len); + return -ENOMEM; + } + if (ep->interval) + usb_fill_int_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx, ep->interval); + else + usb_fill_bulk_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx); + err = usb_urb_ep_type_check(ctx->urb); + if (err < 0) { + dev_err(&ep->dev->dev, "invalid MIDI EP %x\n", + endpoint); + return err; + } + ctx->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + ep->num_urbs++; + } + ep->urb_free = ep->urb_free_mask = GENMASK(ep->num_urbs - 1, 0); + return 0; +} + +static struct snd_usb_midi2_endpoint * +substream_to_endpoint(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + struct snd_usb_midi2_ump *rmidi = ump->private_data; + + return rmidi->eps[substream->stream]; +} + +/* rawmidi open callback */ +static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + int err = 0; + + if (!ep || !ep->endpoint) + return -ENODEV; + if (ep->disconnected) + return -EIO; + if (ep->substream) + return -EBUSY; + if (ep->direction == STR_OUT) { + err = alloc_midi_urbs(ep); + if (err) + return err; + } + spin_lock_irq(&ep->lock); + ep->substream = substream; + spin_unlock_irq(&ep->lock); + return 0; +} + +/* rawmidi close callback */ +static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + spin_lock_irq(&ep->lock); + ep->substream = NULL; + spin_unlock_irq(&ep->lock); + if (ep->direction == STR_OUT) { + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + free_midi_urbs(ep); + } + return 0; +} + +/* rawmidi trigger callback */ +static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + atomic_set(&ep->running, up); + if (up && ep->direction == STR_OUT && !ep->disconnected) + submit_io_urbs(ep); +} + +/* rawmidi drain callback */ +static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + drain_urb_queue(ep); +} + +/* allocate and start all input streams */ +static int start_input_streams(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int err; + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) { + err = alloc_midi_urbs(ep); + if (err < 0) + goto error; + } + } + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + submit_io_urbs(ep); + } + + return 0; + + error: + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + free_midi_urbs(ep); + } + + return err; +} + +static const struct snd_rawmidi_ops output_ops = { + .open = snd_usb_midi_v2_open, + .close = snd_usb_midi_v2_close, + .trigger = snd_usb_midi_v2_trigger, + .drain = snd_usb_midi_v2_drain, +}; + +static const struct snd_rawmidi_ops input_ops = { + .open = snd_usb_midi_v2_open, + .close = snd_usb_midi_v2_close, + .trigger = snd_usb_midi_v2_trigger, +}; + +/* create a USB MIDI 2.0 endpoint object */ +static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, + struct usb_host_endpoint *hostep, + const struct usb_ms20_endpoint_descriptor *ms_ep) +{ + struct snd_usb_midi2_endpoint *ep; + int endpoint, dir; + + usb_audio_dbg(umidi->chip, "Creating an EP 0x%02x, #GTB=%d\n", + hostep->desc.bEndpointAddress, + ms_ep->bNumGrpTrmBlock); + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + spin_lock_init(&ep->lock); + init_waitqueue_head(&ep->wait); + ep->dev = umidi->chip->dev; + endpoint = hostep->desc.bEndpointAddress; + dir = (endpoint & USB_DIR_IN) ? STR_IN : STR_OUT; + + ep->endpoint = endpoint; + ep->direction = dir; + ep->ms_ep = ms_ep; + if (usb_endpoint_xfer_int(&hostep->desc)) + ep->interval = hostep->desc.bInterval; + else + ep->interval = 0; + if (dir == STR_IN) { + if (ep->interval) + ep->pipe = usb_rcvintpipe(ep->dev, endpoint); + else + ep->pipe = usb_rcvbulkpipe(ep->dev, endpoint); + } else { + if (ep->interval) + ep->pipe = usb_sndintpipe(ep->dev, endpoint); + else + ep->pipe = usb_sndbulkpipe(ep->dev, endpoint); + } + ep->packets = usb_maxpacket(ep->dev, ep->pipe); + list_add_tail(&ep->list, &umidi->ep_list); + + return 0; +} + +/* destructor for endpoint; from snd_usb_midi_v2_free() */ +static void free_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + list_del(&ep->list); + free_midi_urbs(ep); + kfree(ep); +} + +/* call all endpoint destructors */ +static void free_all_midi2_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + + while (!list_empty(&umidi->ep_list)) { + ep = list_first_entry(&umidi->ep_list, + struct snd_usb_midi2_endpoint, list); + free_midi2_endpoint(ep); + } +} + +/* find a MIDI STREAMING descriptor with a given subtype */ +static void *find_usb_ms_endpoint_descriptor(struct usb_host_endpoint *hostep, + unsigned char subtype) +{ + unsigned char *extra = hostep->extra; + int extralen = hostep->extralen; + + while (extralen > 3) { + struct usb_ms_endpoint_descriptor *ms_ep = + (struct usb_ms_endpoint_descriptor *)extra; + + if (ms_ep->bLength > 3 && + ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT && + ms_ep->bDescriptorSubtype == subtype) + return ms_ep; + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return NULL; +} + +/* get the full group terminal block descriptors and return the size */ +static int get_group_terminal_block_descs(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_device *dev = umidi->chip->dev; + struct usb_ms20_gr_trm_block_header_descriptor header = { 0 }; + unsigned char *data; + int err, size; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, + &header, sizeof(header)); + if (err < 0) + return err; + size = __le16_to_cpu(header.wTotalLength); + if (!size) { + dev_err(&dev->dev, "Failed to get GTB descriptors for %d:%d\n", + hostif->desc.bInterfaceNumber, hostif->desc.bAlternateSetting); + return -EINVAL; + } + + data = kzalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, data, size); + if (err < 0) { + kfree(data); + return err; + } + + umidi->blk_descs = data; + umidi->blk_desc_size = size; + return 0; +} + +/* find the corresponding group terminal block descriptor */ +static const struct usb_ms20_gr_trm_block_descriptor * +find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id) +{ + const unsigned char *data = umidi->blk_descs; + int size = umidi->blk_desc_size; + const struct usb_ms20_gr_trm_block_descriptor *desc; + + size -= sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + data += sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + while (size > 0 && *data && *data <= size) { + desc = (const struct usb_ms20_gr_trm_block_descriptor *)data; + if (desc->bLength >= sizeof(*desc) && + desc->bDescriptorType == USB_DT_CS_GR_TRM_BLOCK && + desc->bDescriptorSubtype == USB_MS_GR_TRM_BLOCK && + desc->bGrpTrmBlkID == id) + return desc; + size -= *data; + data += *data; + } + + return NULL; +} + +/* fill up the information from GTB */ +static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, + const struct usb_ms20_gr_trm_block_descriptor *desc) +{ + struct snd_usb_audio *chip = rmidi->umidi->chip; + struct snd_ump_endpoint *ump = rmidi->ump; + + usb_audio_dbg(chip, + "GTB id %d: groups = %d / %d, type = %d\n", + desc->bGrpTrmBlkID, desc->nGroupTrm, desc->nNumGroupTrm, + desc->bGrpTrmBlkType); + + /* set default protocol */ + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64: + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; + break; + case USB_MS_MIDI_PROTO_2_0: + case USB_MS_MIDI_PROTO_2_0_JRTS: + ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; + break; + } + + ump->info.protocol_caps = ump->info.protocol; + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + case USB_MS_MIDI_PROTO_2_0_JRTS: + ump->info.protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | + SNDRV_UMP_EP_INFO_PROTO_JRTS_RX; + break; + } + + return 0; +} + +/* allocate and parse for each assigned group terminal block */ +static int parse_group_terminal_blocks(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + int err; + + err = get_group_terminal_block_descs(umidi); + if (err < 0) + return err; + if (!umidi->blk_descs) + return 0; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + desc = find_group_terminal_block(umidi, rmidi->usb_block_id); + if (!desc) + continue; + err = parse_group_terminal_block(rmidi, desc); + if (err < 0) + return err; + } + + return 0; +} + +/* parse endpoints included in the given interface and create objects */ +static int parse_midi_2_0_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_host_endpoint *hostep; + struct usb_ms20_endpoint_descriptor *ms_ep; + int i, err; + + for (i = 0; i < hostif->desc.bNumEndpoints; i++) { + hostep = &hostif->endpoint[i]; + if (!usb_endpoint_xfer_bulk(&hostep->desc) && + !usb_endpoint_xfer_int(&hostep->desc)) + continue; + ms_ep = find_usb_ms_endpoint_descriptor(hostep, USB_MS_GENERAL_2_0); + if (!ms_ep) + continue; + if (ms_ep->bLength <= sizeof(*ms_ep)) + continue; + if (!ms_ep->bNumGrpTrmBlock) + continue; + if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumGrpTrmBlock) + continue; + err = create_midi2_endpoint(umidi, hostep, ms_ep); + if (err < 0) + return err; + } + return 0; +} + +static void free_all_midi2_umps(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + + while (!list_empty(&umidi->rawmidi_list)) { + rmidi = list_first_entry(&umidi->rawmidi_list, + struct snd_usb_midi2_ump, list); + list_del(&rmidi->list); + kfree(rmidi); + } +} + +static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep_in, + struct snd_usb_midi2_endpoint *ep_out, + int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; + int input, output; + char idstr[16]; + int err; + + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + INIT_LIST_HEAD(&rmidi->list); + rmidi->dev = umidi->chip->dev; + rmidi->umidi = umidi; + rmidi->usb_block_id = blk_id; + + rmidi->index = umidi->chip->num_rawmidis; + snprintf(idstr, sizeof(idstr), "UMP %d", rmidi->index); + input = ep_in ? 1 : 0; + output = ep_out ? 1 : 0; + err = snd_ump_endpoint_new(umidi->chip->card, idstr, rmidi->index, + output, input, &ump); + if (err < 0) { + usb_audio_dbg(umidi->chip, "Failed to create a UMP object\n"); + kfree(rmidi); + return err; + } + + rmidi->ump = ump; + umidi->chip->num_rawmidis++; + + ump->private_data = rmidi; + + if (input) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, + &input_ops); + if (output) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, + &output_ops); + + rmidi->eps[STR_IN] = ep_in; + rmidi->eps[STR_OUT] = ep_out; + if (ep_in) { + ep_in->pair = ep_out; + ep_in->rmidi = rmidi; + } + if (ep_out) { + ep_out->pair = ep_in; + ep_out->rmidi = rmidi; + } + + list_add_tail(&rmidi->list, &umidi->rawmidi_list); + return 0; +} + +/* find the UMP EP with the given USB block id */ +static struct snd_usb_midi2_ump * +find_midi2_ump(struct snd_usb_midi2_interface *umidi, int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (rmidi->usb_block_id == blk_id) + return rmidi; + } + return NULL; +} + +/* look for the matching output endpoint and create UMP object if found */ +static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep, + int blk_id) +{ + struct snd_usb_midi2_endpoint *pair_ep; + int blk; + + usb_audio_dbg(umidi->chip, "Looking for a pair for EP-in 0x%02x\n", + ep->endpoint); + list_for_each_entry(pair_ep, &umidi->ep_list, list) { + if (pair_ep->direction != STR_OUT) + continue; + if (pair_ep->pair) + continue; /* already paired */ + for (blk = 0; blk < pair_ep->ms_ep->bNumGrpTrmBlock; blk++) { + if (pair_ep->ms_ep->baAssoGrpTrmBlkID[blk] == blk_id) { + usb_audio_dbg(umidi->chip, + "Found a match with EP-out 0x%02x blk %d\n", + pair_ep->endpoint, blk); + return create_midi2_ump(umidi, ep, pair_ep, blk_id); + } + } + } + return 0; +} + +static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) +{ + free_all_midi2_endpoints(umidi); + free_all_midi2_umps(umidi); + list_del(&umidi->list); + kfree(umidi->blk_descs); + kfree(umidi); +} + +/* parse the interface for MIDI 2.0 */ +static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int blk, id, err; + + /* First, create an object for each USB MIDI Endpoint */ + err = parse_midi_2_0_endpoints(umidi); + if (err < 0) + return err; + if (list_empty(&umidi->ep_list)) { + usb_audio_warn(umidi->chip, "No MIDI endpoints found\n"); + return -ENODEV; + } + + /* + * Next, look for EP I/O pairs that are found in group terminal blocks + * A UMP object is created for each EP I/O pair as bidirecitonal + * UMP EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + /* only input in this loop; output is matched in find_midi_ump() */ + if (ep->direction != STR_IN) + continue; + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + err = find_matching_ep_partner(umidi, ep, id); + if (err < 0) + return err; + } + } + + /* + * For the remaining EPs, treat as singles, create a UMP object with + * unidirectional EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->rmidi) + continue; /* already paired */ + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + if (find_midi2_ump(umidi, id)) + continue; + usb_audio_dbg(umidi->chip, + "Creating a unidirection UMP for EP=0x%02x, blk=%d\n", + ep->endpoint, id); + if (ep->direction == STR_IN) + err = create_midi2_ump(umidi, ep, NULL, id); + else + err = create_midi2_ump(umidi, NULL, ep, id); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* is the given interface for MIDI 2.0? */ +static bool is_midi2_altset(struct usb_host_interface *hostif) +{ + struct usb_ms_header_descriptor *ms_header = + (struct usb_ms_header_descriptor *)hostif->extra; + + if (hostif->extralen < 7 || + ms_header->bLength < 7 || + ms_header->bDescriptorType != USB_DT_CS_INTERFACE || + ms_header->bDescriptorSubtype != UAC_HEADER) + return false; + + return le16_to_cpu(ms_header->bcdMSC) == USB_MS_REV_MIDI_2_0; +} + +/* change the altsetting */ +static int set_altset(struct snd_usb_midi2_interface *umidi) +{ + usb_audio_dbg(umidi->chip, "Setting host iface %d:%d\n", + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); + return usb_set_interface(umidi->chip->dev, + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); +} + +/* fill the fallback name string for each rawmidi instance */ +static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!*rmidi->ump->core.name) + sprintf(rmidi->ump->core.name, "USB MIDI %d", + rmidi->index); + } +} + +/* create MIDI interface; fallback to MIDI 1.0 if needed */ +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + struct snd_usb_midi2_interface *umidi; + struct usb_host_interface *hostif; + int err; + + usb_audio_dbg(chip, "Parsing interface %d...\n", + iface->altsetting[0].desc.bInterfaceNumber); + + /* fallback to MIDI 1.0? */ + if (!midi2_enable) { + usb_audio_info(chip, "Falling back to MIDI 1.0 by module option\n"); + goto fallback_to_midi1; + } + if ((quirk && quirk->type != QUIRK_MIDI_STANDARD_INTERFACE) || + iface->num_altsetting < 2) { + usb_audio_info(chip, "Quirk or no altest; falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + hostif = &iface->altsetting[1]; + if (!is_midi2_altset(hostif)) { + usb_audio_info(chip, "No MIDI 2.0 at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + if (!hostif->desc.bNumEndpoints) { + usb_audio_info(chip, "No endpoint at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + + usb_audio_dbg(chip, "Creating a MIDI 2.0 instance for %d:%d\n", + hostif->desc.bInterfaceNumber, + hostif->desc.bAlternateSetting); + + umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); + if (!umidi) + return -ENOMEM; + umidi->chip = chip; + umidi->iface = iface; + umidi->hostif = hostif; + INIT_LIST_HEAD(&umidi->rawmidi_list); + INIT_LIST_HEAD(&umidi->ep_list); + + list_add_tail(&umidi->list, &chip->midi_v2_list); + + err = set_altset(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to set altset\n"); + goto error; + } + + /* assume only altset 1 corresponding to MIDI 2.0 interface */ + err = parse_midi_2_0(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse MIDI 2.0 interface\n"); + goto error; + } + + /* parse USB group terminal blocks */ + err = parse_group_terminal_blocks(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse GTB\n"); + goto error; + } + + err = start_input_streams(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to start input streams\n"); + goto error; + } + + set_fallback_rawmidi_names(umidi); + return 0; + + error: + snd_usb_midi_v2_free(umidi); + return err; + + fallback_to_midi1: + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static void suspend_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + kill_midi_urbs(ep, true); + drain_urb_queue(ep); +} + +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + list_for_each_entry(ep, &umidi->ep_list, list) + suspend_midi2_endpoint(ep); + } +} + +static void resume_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + ep->running = ep->suspended; + if (ep->direction == STR_IN) + submit_io_urbs(ep); + /* FIXME: does it all? */ +} + +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + set_altset(umidi); + list_for_each_entry(ep, &umidi->ep_list, list) + resume_midi2_endpoint(ep); + } +} + +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + umidi->disconnected = 1; + list_for_each_entry(ep, &umidi->ep_list, list) { + ep->disconnected = 1; + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + } + } +} + +/* release the MIDI instance */ +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi, *next; + + list_for_each_entry_safe(umidi, next, &chip->midi_v2_list, list) + snd_usb_midi_v2_free(umidi); +} diff --git a/sound/usb/midi2.h b/sound/usb/midi2.h new file mode 100644 index 000000000000..94a65fcbd58b --- /dev/null +++ b/sound/usb/midi2.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __USB_AUDIO_MIDI2_H +#define __USB_AUDIO_MIDI2_H + +#include "midi.h" + +#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2) +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip); +#else /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ +/* fallback to MIDI 1.0 creation */ +static inline int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static inline void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) {} +#endif /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ + +#endif /* __USB_AUDIO_MIDI2_H */ diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 1cabe4cc019f..53e079e91580 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -19,6 +19,7 @@ #include "mixer.h" #include "mixer_quirks.h" #include "midi.h" +#include "midi2.h" #include "quirks.h" #include "helper.h" #include "endpoint.h" @@ -80,7 +81,7 @@ static int create_any_midi_quirk(struct snd_usb_audio *chip, struct usb_driver *driver, const struct snd_usb_audio_quirk *quirk) { - return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); + return snd_usb_midi_v2_create(chip, intf, quirk, 0); } /* diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index b1fa0a377866..43d4029edab4 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -51,6 +51,7 @@ struct snd_usb_audio { unsigned int num_rawmidis; /* number of created rawmidi devices */ struct list_head midi_list; /* list of midi interfaces */ + struct list_head midi_v2_list; /* list of MIDI 2 interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ -- cgit v1.2.3 From 06cf3bf09d83944382b36c7617277619f25f49e4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:31 +0200 Subject: ALSA: usb-audio: Get UMP EP name string from USB interface USB descriptor may provide a nicer name for USB interface, and we may take it as the UMP Endpoint name. The UMP EP name is copied as the rawmidi name, too. Also, fill the UMP block product_id field from the iSerialNumber string of the USB device descriptor as a recommended unique id, too. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-11-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 7b4cfbaf2ec0..2ac3f96216bc 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -892,15 +892,39 @@ static int set_altset(struct snd_usb_midi2_interface *umidi) umidi->hostif->desc.bAlternateSetting); } +/* fill UMP Endpoint name string from USB descriptor */ +static void fill_ump_ep_name(struct snd_ump_endpoint *ump, + struct usb_device *dev, int id) +{ + usb_string(dev, id, ump->info.name, sizeof(ump->info.name)); +} + /* fill the fallback name string for each rawmidi instance */ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) { + struct usb_device *dev = umidi->chip->dev; struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { - if (!*rmidi->ump->core.name) - sprintf(rmidi->ump->core.name, "USB MIDI %d", - rmidi->index); + ump = rmidi->ump; + /* fill UMP EP name from USB descriptors */ + if (!*ump->info.name && umidi->hostif->desc.iInterface) + fill_ump_ep_name(ump, dev, umidi->hostif->desc.iInterface); + else if (!*ump->info.name && dev->descriptor.iProduct) + fill_ump_ep_name(ump, dev, dev->descriptor.iProduct); + /* fill fallback name */ + if (!*ump->info.name) + sprintf(ump->info.name, "USB MIDI %d", rmidi->index); + /* copy as rawmidi name if not set */ + if (!*ump->core.name) + strscpy(ump->core.name, ump->info.name, + sizeof(ump->core.name)); + /* use serial number string as unique UMP product id */ + if (!*ump->info.product_id && dev->descriptor.iSerialNumber) + usb_string(dev, dev->descriptor.iSerialNumber, + ump->info.product_id, + sizeof(ump->info.product_id)); } } -- cgit v1.2.3 From 51701400a94e999c3109c84f88273a9face51c4b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:32 +0200 Subject: ALSA: usb-audio: Trim superfluous "MIDI" suffix from UMP EP name A single USB audio device may have multiple interfaces for different purposes (e.g. audio, MIDI and HID), where the iInterface descriptor of each interface may contain an own suffix, e.g. "MIDI" for a MIDI interface. as such a suffix is superfluous as a rawmidi and UMP Endpoint name, this patch trims the superfluous "MIDI" suffix from the name string. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-12-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 2ac3f96216bc..790e4cd5d35c 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -896,7 +896,14 @@ static int set_altset(struct snd_usb_midi2_interface *umidi) static void fill_ump_ep_name(struct snd_ump_endpoint *ump, struct usb_device *dev, int id) { + int len; + usb_string(dev, id, ump->info.name, sizeof(ump->info.name)); + + /* trim superfluous "MIDI" suffix */ + len = strlen(ump->info.name); + if (len > 5 && !strcmp(ump->info.name + len - 5, " MIDI")) + ump->info.name[len - 5] = 0; } /* fill the fallback name string for each rawmidi instance */ -- cgit v1.2.3 From d9c99876868c861afd0e9ce2cea407bbc446b3c9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:33 +0200 Subject: ALSA: usb-audio: Create UMP blocks from USB MIDI GTBs USB MIDI spec defines the Group Terminal Blocks (GTB) that associate multiple UMP Groups. Those correspond to snd_ump_block entities in ALSA UMP abstraction, and now we create those UMP Block objects for each UMP Endpoint from the parsed GTB information. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-13-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 790e4cd5d35c..5ffee06ac746 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -599,14 +599,8 @@ find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id) static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, const struct usb_ms20_gr_trm_block_descriptor *desc) { - struct snd_usb_audio *chip = rmidi->umidi->chip; struct snd_ump_endpoint *ump = rmidi->ump; - usb_audio_dbg(chip, - "GTB id %d: groups = %d / %d, type = %d\n", - desc->bGrpTrmBlkID, desc->nGroupTrm, desc->nNumGroupTrm, - desc->bGrpTrmBlkType); - /* set default protocol */ switch (desc->bMIDIProtocol) { case USB_MS_MIDI_PROTO_1_0_64: @@ -798,6 +792,94 @@ static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, return 0; } +/* create a UMP block from a GTB entry */ +static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) +{ + struct snd_usb_midi2_interface *umidi = rmidi->umidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + struct snd_ump_block *fb; + int type, err; + + desc = find_group_terminal_block(umidi, blk); + if (!desc) + return 0; + + usb_audio_dbg(umidi->chip, + "GTB %d: type=%d, group=%d/%d, protocol=%d, in bw=%d, out bw=%d\n", + blk, desc->bGrpTrmBlkType, desc->nGroupTrm, + desc->nNumGroupTrm, desc->bMIDIProtocol, + __le16_to_cpu(desc->wMaxInputBandwidth), + __le16_to_cpu(desc->wMaxOutputBandwidth)); + + /* assign the direction */ + switch (desc->bGrpTrmBlkType) { + case USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL: + type = SNDRV_UMP_DIR_BIDIRECTION; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY: + type = SNDRV_UMP_DIR_INPUT; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY: + type = SNDRV_UMP_DIR_OUTPUT; + break; + default: + usb_audio_dbg(umidi->chip, "Unsupported GTB type %d\n", + desc->bGrpTrmBlkType); + return 0; /* unsupported */ + } + + /* guess work: set blk-1 as the (0-based) block ID */ + err = snd_ump_block_new(rmidi->ump, blk - 1, type, + desc->nGroupTrm, desc->nNumGroupTrm, + &fb); + if (err == -EBUSY) + return 0; /* already present */ + else if (err) + return err; + + if (desc->iBlockItem) + usb_string(rmidi->dev, desc->iBlockItem, + fb->info.name, sizeof(fb->info.name)); + + if (__le16_to_cpu(desc->wMaxInputBandwidth) == 1 || + __le16_to_cpu(desc->wMaxOutputBandwidth) == 1) + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 | + SNDRV_UMP_BLOCK_IS_LOWSPEED; + + usb_audio_dbg(umidi->chip, + "Created a UMP block %d from GTB, name=%s\n", + blk, fb->info.name); + return 0; +} + +/* Create UMP blocks for each UMP EP */ +static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int i, blk, err, dir; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump) + continue; + /* Blocks have been already created? */ + if (rmidi->ump->info.num_blocks) + continue; + /* loop over GTBs */ + for (dir = 0; dir < 2; dir++) { + if (!rmidi->eps[dir]) + continue; + for (i = 0; i < rmidi->eps[dir]->ms_ep->bNumGrpTrmBlock; i++) { + blk = rmidi->eps[dir]->ms_ep->baAssoGrpTrmBlkID[i]; + err = create_gtb_block(rmidi, dir, blk); + if (err < 0) + return err; + } + } + } + + return 0; +} + static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) { free_all_midi2_endpoints(umidi); @@ -1009,6 +1091,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } + err = create_blocks_from_gtb(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to create GTB blocks\n"); + goto error; + } + set_fallback_rawmidi_names(umidi); return 0; -- cgit v1.2.3 From 6b41e64a5d17ec01380bc7ad10afd90e63beca19 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:34 +0200 Subject: ALSA: ump: Redirect rawmidi substream access via own helpers This is a code refactoring for abstracting the rawmidi access to the UMP's own helpers. It's a preliminary work for the later code refactoring of the UMP layer. Until now, we access to the rawmidi substream directly from the driver via rawmidi access helpers, but after this change, the driver is supposed to access via the newly introduced snd_ump_ops and receive/transmit via snd_ump_receive() and snd_ump_transmit() helpers. As of this commit, those are merely wrappers for the rawmidi substream, and no much function change is seen here. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-14-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 14 +++++++ sound/core/ump.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/midi2.c | 71 ++++++++++++--------------------- 3 files changed, 149 insertions(+), 47 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 8a3ac97cd1d3..6f786b462f16 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -9,18 +9,30 @@ struct snd_ump_endpoint; struct snd_ump_block; +struct snd_ump_ops; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ struct snd_ump_endpoint_info info; + const struct snd_ump_ops *ops; /* UMP ops set by the driver */ + struct snd_rawmidi_substream *substreams[2]; /* opened substreams */ + void *private_data; void (*private_free)(struct snd_ump_endpoint *ump); struct list_head block_list; /* list of snd_ump_block objects */ }; +/* ops filled by UMP drivers */ +struct snd_ump_ops { + int (*open)(struct snd_ump_endpoint *ump, int dir); + void (*close)(struct snd_ump_endpoint *ump, int dir); + void (*trigger)(struct snd_ump_endpoint *ump, int dir, int up); + void (*drain)(struct snd_ump_endpoint *ump, int dir); +}; + struct snd_ump_block { struct snd_ump_block_info info; struct snd_ump_endpoint *ump; @@ -39,6 +51,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, unsigned int direction, unsigned int first_group, unsigned int num_groups, struct snd_ump_block **blk_ret); +int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count); +int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count); /* * Some definitions for UMP diff --git a/sound/core/ump.c b/sound/core/ump.c index 651cd3752719..46ec297a786c 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -23,6 +23,11 @@ static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); static void snd_ump_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream); +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream); +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up); +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, @@ -31,6 +36,19 @@ static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .proc_read = snd_ump_proc_read, }; +static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, + .drain = snd_ump_rawmidi_drain, +}; + static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) { struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); @@ -104,6 +122,12 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, ump->core.private_free = snd_ump_endpoint_free; ump->core.ops = &snd_ump_rawmidi_ops; + if (input) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_rawmidi_input_ops); + if (output) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_rawmidi_output_ops); ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); *ump_ret = ump; @@ -137,6 +161,93 @@ snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) return NULL; } +/* + * rawmidi ops for UMP endpoint + */ +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + int err; + + if (ump->substreams[dir]) + return -EBUSY; + err = ump->ops->open(ump, dir); + if (err < 0) + return err; + ump->substreams[dir] = substream; + return 0; +} + +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->substreams[dir] = NULL; + ump->ops->close(ump, dir); + return 0; +} + +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +/** + * snd_ump_receive - transfer UMP packets from the device + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to submit the received UMP packets from the device + * to user-space. It's essentially a wrapper of rawmidi_receive(). + * The data to receive is in CPU-native endianness. + */ +int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream = + ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + + if (!substream) + return 0; + return snd_rawmidi_receive(substream, (const char *)buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_receive); + +/** + * snd_ump_transmit - transmit UMP packets + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to obtain the UMP packets from user-space to the + * device. It's essentially a wrapper of rawmidi_transmit(). + * The data to transmit is in CPU-native endianness. + */ +int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream = + ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + if (!substream) + return -ENODEV; + return snd_rawmidi_transmit(substream, (char *)buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_transmit); + /** * snd_ump_block_new - Create a UMP block * @ump: UMP object diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 5ffee06ac746..7e849b2384ee 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -52,7 +52,8 @@ struct snd_usb_midi2_endpoint { struct usb_device *dev; const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */ struct snd_usb_midi2_endpoint *pair; /* bidirectional pair EP */ - struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP */ + struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP pair */ + struct snd_ump_endpoint *ump; /* assigned UMP EP */ int direction; /* direction (STR_IN/OUT) */ unsigned int endpoint; /* EP number */ unsigned int pipe; /* URB pipe */ @@ -133,12 +134,8 @@ static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep, { int count; - if (ep->substream) - count = snd_rawmidi_transmit(ep->substream, - urb->transfer_buffer, - ep->packets); - else - count = -ENODEV; + count = snd_ump_transmit(ep->ump, urb->transfer_buffer, + ep->packets); if (count < 0) { dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count); return count; @@ -197,9 +194,9 @@ static void input_urb_complete(struct urb *urb) len &= ~3; /* align UMP */ if (len > ep->packets) len = ep->packets; - if (len > 0 && ep->substream) { + if (len > 0) { le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2); - snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len); + snd_ump_receive(ep->ump, (u32 *)urb->transfer_buffer, len); } dequeue: set_bit(ctx->index, &ep->urb_free); @@ -330,68 +327,58 @@ static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep) } static struct snd_usb_midi2_endpoint * -substream_to_endpoint(struct snd_rawmidi_substream *substream) +ump_to_endpoint(struct snd_ump_endpoint *ump, int dir) { - struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); struct snd_usb_midi2_ump *rmidi = ump->private_data; - return rmidi->eps[substream->stream]; + return rmidi->eps[dir]; } -/* rawmidi open callback */ -static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream) +/* ump open callback */ +static int snd_usb_midi_v2_open(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); int err = 0; if (!ep || !ep->endpoint) return -ENODEV; if (ep->disconnected) return -EIO; - if (ep->substream) - return -EBUSY; if (ep->direction == STR_OUT) { err = alloc_midi_urbs(ep); if (err) return err; } - spin_lock_irq(&ep->lock); - ep->substream = substream; - spin_unlock_irq(&ep->lock); return 0; } -/* rawmidi close callback */ -static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream) +/* ump close callback */ +static void snd_usb_midi_v2_close(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); - spin_lock_irq(&ep->lock); - ep->substream = NULL; - spin_unlock_irq(&ep->lock); if (ep->direction == STR_OUT) { kill_midi_urbs(ep, false); drain_urb_queue(ep); free_midi_urbs(ep); } - return 0; } -/* rawmidi trigger callback */ -static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream, +/* ump trigger callback */ +static void snd_usb_midi_v2_trigger(struct snd_ump_endpoint *ump, int dir, int up) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); atomic_set(&ep->running, up); if (up && ep->direction == STR_OUT && !ep->disconnected) submit_io_urbs(ep); } -/* rawmidi drain callback */ -static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream) +/* ump drain callback */ +static void snd_usb_midi_v2_drain(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); drain_urb_queue(ep); } @@ -426,19 +413,13 @@ static int start_input_streams(struct snd_usb_midi2_interface *umidi) return err; } -static const struct snd_rawmidi_ops output_ops = { +static const struct snd_ump_ops snd_usb_midi_v2_ump_ops = { .open = snd_usb_midi_v2_open, .close = snd_usb_midi_v2_close, .trigger = snd_usb_midi_v2_trigger, .drain = snd_usb_midi_v2_drain, }; -static const struct snd_rawmidi_ops input_ops = { - .open = snd_usb_midi_v2_open, - .close = snd_usb_midi_v2_close, - .trigger = snd_usb_midi_v2_trigger, -}; - /* create a USB MIDI 2.0 endpoint object */ static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, struct usb_host_endpoint *hostep, @@ -729,23 +710,19 @@ static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, umidi->chip->num_rawmidis++; ump->private_data = rmidi; - - if (input) - snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, - &input_ops); - if (output) - snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, - &output_ops); + ump->ops = &snd_usb_midi_v2_ump_ops; rmidi->eps[STR_IN] = ep_in; rmidi->eps[STR_OUT] = ep_out; if (ep_in) { ep_in->pair = ep_out; ep_in->rmidi = rmidi; + ep_in->ump = ump; } if (ep_out) { ep_out->pair = ep_in; ep_out->rmidi = rmidi; + ep_out->ump = ump; } list_add_tail(&rmidi->list, &umidi->rawmidi_list); -- cgit v1.2.3 From 0b5288f5fe63eab687c14e5940b9e0d532b129f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:35 +0200 Subject: ALSA: ump: Add legacy raw MIDI support This patch extends the UMP core code to support the legacy MIDI 1.0 rawmidi devices. When the new kconfig CONFIG_SND_UMP_LEGACY_RAWMIDI is set, the UMP core allows to attach an additional rawmidi device for each UMP Endpoint. The rawmidi device contains 16 substreams where each substream corresponds to a UMP Group belonging to the EP. The device reads/writes the legacy MIDI 1.0 byte streams and translates from/to UMP packets. The legacy rawmidi devices are exclusive with the UMP rawmidi devices, hence both of them can't be opened at the same time unless the UMP rawmidi is opened in APPEND mode. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-15-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 30 +++ include/sound/ump_msg.h | 540 +++++++++++++++++++++++++++++++++++++++++++++++ sound/core/Kconfig | 9 + sound/core/Makefile | 1 + sound/core/ump.c | 258 +++++++++++++++++++++- sound/core/ump_convert.c | 520 +++++++++++++++++++++++++++++++++++++++++++++ sound/core/ump_convert.h | 43 ++++ 7 files changed, 1398 insertions(+), 3 deletions(-) create mode 100644 include/sound/ump_msg.h create mode 100644 sound/core/ump_convert.c create mode 100644 sound/core/ump_convert.h (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 6f786b462f16..45f4c9b673b5 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -10,6 +10,7 @@ struct snd_ump_endpoint; struct snd_ump_block; struct snd_ump_ops; +struct ump_cvt_to_ump; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ @@ -23,6 +24,24 @@ struct snd_ump_endpoint { void (*private_free)(struct snd_ump_endpoint *ump); struct list_head block_list; /* list of snd_ump_block objects */ + + /* intermediate buffer for UMP input */ + u32 input_buf[4]; + int input_buf_head; + int input_pending; + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct mutex open_mutex; + + spinlock_t legacy_locks[2]; + struct snd_rawmidi *legacy_rmidi; + struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS]; + + /* for legacy output; need to open the actual substream unlike input */ + int legacy_out_opens; + struct snd_rawmidi_file legacy_out_rfile; + struct ump_cvt_to_ump *out_cvts; +#endif }; /* ops filled by UMP drivers */ @@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count); int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device); +#else +static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + return 0; +} +#endif + /* * Some definitions for UMP */ diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h new file mode 100644 index 000000000000..c76c39944a5f --- /dev/null +++ b/include/sound/ump_msg.h @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP): Message Definitions + */ +#ifndef __SOUND_UMP_MSG_H +#define __SOUND_UMP_MSG_H + +/* MIDI 1.0 / 2.0 Status Code (4bit) */ +enum { + UMP_MSG_STATUS_PER_NOTE_RCC = 0x0, + UMP_MSG_STATUS_PER_NOTE_ACC = 0x1, + UMP_MSG_STATUS_RPN = 0x2, + UMP_MSG_STATUS_NRPN = 0x3, + UMP_MSG_STATUS_RELATIVE_RPN = 0x4, + UMP_MSG_STATUS_RELATIVE_NRPN = 0x5, + UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6, + UMP_MSG_STATUS_NOTE_OFF = 0x8, + UMP_MSG_STATUS_NOTE_ON = 0x9, + UMP_MSG_STATUS_POLY_PRESSURE = 0xa, + UMP_MSG_STATUS_CC = 0xb, + UMP_MSG_STATUS_PROGRAM = 0xc, + UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd, + UMP_MSG_STATUS_PITCH_BEND = 0xe, + UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf, +}; + +/* MIDI 1.0 Channel Control (7bit) */ +enum { + UMP_CC_BANK_SELECT = 0, + UMP_CC_MODULATION = 1, + UMP_CC_BREATH = 2, + UMP_CC_FOOT = 4, + UMP_CC_PORTAMENTO_TIME = 5, + UMP_CC_DATA = 6, + UMP_CC_VOLUME = 7, + UMP_CC_BALANCE = 8, + UMP_CC_PAN = 10, + UMP_CC_EXPRESSION = 11, + UMP_CC_EFFECT_CONTROL_1 = 12, + UMP_CC_EFFECT_CONTROL_2 = 13, + UMP_CC_GP_1 = 16, + UMP_CC_GP_2 = 17, + UMP_CC_GP_3 = 18, + UMP_CC_GP_4 = 19, + UMP_CC_BANK_SELECT_LSB = 32, + UMP_CC_MODULATION_LSB = 33, + UMP_CC_BREATH_LSB = 34, + UMP_CC_FOOT_LSB = 36, + UMP_CC_PORTAMENTO_TIME_LSB = 37, + UMP_CC_DATA_LSB = 38, + UMP_CC_VOLUME_LSB = 39, + UMP_CC_BALANCE_LSB = 40, + UMP_CC_PAN_LSB = 42, + UMP_CC_EXPRESSION_LSB = 43, + UMP_CC_EFFECT1_LSB = 44, + UMP_CC_EFFECT2_LSB = 45, + UMP_CC_GP_1_LSB = 48, + UMP_CC_GP_2_LSB = 49, + UMP_CC_GP_3_LSB = 50, + UMP_CC_GP_4_LSB = 51, + UMP_CC_SUSTAIN = 64, + UMP_CC_PORTAMENTO_SWITCH = 65, + UMP_CC_SOSTENUTO = 66, + UMP_CC_SOFT_PEDAL = 67, + UMP_CC_LEGATO = 68, + UMP_CC_HOLD_2 = 69, + UMP_CC_SOUND_CONTROLLER_1 = 70, + UMP_CC_SOUND_CONTROLLER_2 = 71, + UMP_CC_SOUND_CONTROLLER_3 = 72, + UMP_CC_SOUND_CONTROLLER_4 = 73, + UMP_CC_SOUND_CONTROLLER_5 = 74, + UMP_CC_SOUND_CONTROLLER_6 = 75, + UMP_CC_SOUND_CONTROLLER_7 = 76, + UMP_CC_SOUND_CONTROLLER_8 = 77, + UMP_CC_SOUND_CONTROLLER_9 = 78, + UMP_CC_SOUND_CONTROLLER_10 = 79, + UMP_CC_GP_5 = 80, + UMP_CC_GP_6 = 81, + UMP_CC_GP_7 = 82, + UMP_CC_GP_8 = 83, + UMP_CC_PORTAMENTO_CONTROL = 84, + UMP_CC_EFFECT_1 = 91, + UMP_CC_EFFECT_2 = 92, + UMP_CC_EFFECT_3 = 93, + UMP_CC_EFFECT_4 = 94, + UMP_CC_EFFECT_5 = 95, + UMP_CC_DATA_INC = 96, + UMP_CC_DATA_DEC = 97, + UMP_CC_NRPN_LSB = 98, + UMP_CC_NRPN_MSB = 99, + UMP_CC_RPN_LSB = 100, + UMP_CC_RPN_MSB = 101, + UMP_CC_ALL_SOUND_OFF = 120, + UMP_CC_RESET_ALL = 121, + UMP_CC_LOCAL_CONTROL = 122, + UMP_CC_ALL_NOTES_OFF = 123, + UMP_CC_OMNI_OFF = 124, + UMP_CC_OMNI_ON = 125, + UMP_CC_POLY_OFF = 126, + UMP_CC_POLY_ON = 127, +}; + +/* MIDI 1.0 / 2.0 System Messages (0xfx) */ +enum { + UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1, + UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2, + UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3, + UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6, + UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8, + UMP_SYSTEM_STATUS_START = 0xfa, + UMP_SYSTEM_STATUS_CONTINUE = 0xfb, + UMP_SYSTEM_STATUS_STOP = 0xfc, + UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe, + UMP_SYSTEM_STATUS_RESET = 0xff, +}; + +/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */ +enum { + UMP_MIDI1_MSG_REALTIME = 0xf0, /* mask */ + UMP_MIDI1_MSG_SYSEX_START = 0xf0, + UMP_MIDI1_MSG_SYSEX_END = 0xf7, +}; + +/* + * UMP Message Definitions + */ + +/* MIDI 1.0 Note Off / Note On (32bit) */ +struct snd_ump_midi1_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 velocity:8; +#else + u32 velocity:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Poly Pressure (32bit) */ +struct snd_ump_midi1_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 data:8; +#else + u32 data:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Control Change (32bit) */ +struct snd_ump_midi1_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 data:8; +#else + u32 data:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Program Change (32bit) */ +struct snd_ump_midi1_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 program:8; + u32 reserved:8; +#else +#endif + u32 reserved:8; + u32 program:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +} __packed; + +/* MIDI 1.0 Channel Pressure (32bit) */ +struct snd_ump_midi1_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data:8; + u32 reserved:8; +#else + u32 reserved:8; + u32 data:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Pitch Bend (32bit) */ +struct snd_ump_midi1_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data_lsb:8; + u32 data_msb:8; +#else + u32 data_msb:8; + u32 data_lsb:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* System Common and Real Time messages (32bit); no channel field */ +struct snd_ump_system_msg { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:8; + u32 parm1:8; + u32 parm2:8; +#else + u32 parm2:8; + u32 parm1:8; + u32 status:8; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 UMP CVM (32bit) */ +union snd_ump_midi1_msg { + struct snd_ump_midi1_msg_note note; + struct snd_ump_midi1_msg_paf paf; + struct snd_ump_midi1_msg_cc cc; + struct snd_ump_midi1_msg_program pg; + struct snd_ump_midi1_msg_caf caf; + struct snd_ump_midi1_msg_pitchbend pb; + struct snd_ump_system_msg system; + u32 raw; +}; + +/* MIDI 2.0 Note Off / Note On (64bit) */ +struct snd_ump_midi2_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 attribute_type:8; + /* 1 */ + u32 velocity:16; + u32 attribute_data:16; +#else + /* 0 */ + u32 attribute_type:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 attribute_data:16; + u32 velocity:16; +#endif +} __packed; + +/* MIDI 2.0 Poly Pressure (64bit) */ +struct snd_ump_midi2_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Controller (64bit) */ +struct snd_ump_midi2_msg_pernote_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Management (64bit) */ +struct snd_ump_midi2_msg_pernote_mgmt { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 flags:8; + /* 1 */ + u32 reserved; +#else + /* 0 */ + u32 flags:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 reserved; +#endif +} __packed; + +/* MIDI 2.0 Control Change (64bit) */ +struct snd_ump_midi2_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */ +struct snd_ump_midi2_msg_rpn { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 bank:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 bank:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Program Change (64bit) */ +struct snd_ump_midi2_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:15; + u32 bank_valid:1; + /* 1 */ + u32 program:8; + u32 reserved2:8; + u32 bank_msb:8; + u32 bank_lsb:8; +#else + /* 0 */ + u32 bank_valid:1; + u32 reserved:15; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 bank_lsb:8; + u32 bank_msb:8; + u32 reserved2:8; + u32 program:8; +#endif +} __packed; + +/* MIDI 2.0 Channel Pressure (64bit) */ +struct snd_ump_midi2_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pernote_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 UMP CVM (64bit) */ +union snd_ump_midi2_msg { + struct snd_ump_midi2_msg_note note; + struct snd_ump_midi2_msg_paf paf; + struct snd_ump_midi2_msg_pernote_cc pernote_cc; + struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt; + struct snd_ump_midi2_msg_cc cc; + struct snd_ump_midi2_msg_rpn rpn; + struct snd_ump_midi2_msg_program pg; + struct snd_ump_midi2_msg_caf caf; + struct snd_ump_midi2_msg_pitchbend pb; + struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb; + u32 raw[2]; +}; + +#endif /* __SOUND_UMP_MSG_H */ diff --git a/sound/core/Kconfig b/sound/core/Kconfig index eb1c6c930de9..e41818e59a15 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -30,6 +30,15 @@ config SND_UMP tristate select SND_RAWMIDI +config SND_UMP_LEGACY_RAWMIDI + bool "Legacy raw MIDI support for UMP streams" + depends on SND_UMP + help + This option enables the legacy raw MIDI support for UMP streams. + When this option is set, an additional rawmidi device for the + legacy MIDI 1.0 byte streams is created for each UMP Endpoint. + The device contains 16 substreams corresponding to UMP groups. + config SND_COMPRESS_OFFLOAD tristate diff --git a/sound/core/Makefile b/sound/core/Makefile index 562a05edbc50..a6b444ee2832 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -29,6 +29,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o snd-ctl-led-objs := control_led.o snd-rawmidi-objs := rawmidi.o snd-ump-objs := ump.o +snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o snd-timer-objs := timer.o snd-hrtimer-objs := hrtimer.o snd-rtctimer-objs := rtctimer.o diff --git a/sound/core/ump.c b/sound/core/ump.c index 46ec297a786c..cbe704b5d90d 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -11,6 +11,7 @@ #include #include #include +#include "ump_convert.h" #define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) #define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) @@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, int up); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count); +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words); +#else +static inline int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + return 0; +} +static inline void process_legacy_input(struct snd_ump_endpoint *ump, + const u32 *src, int words) +{ +} +#endif + static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, .dev_unregister = snd_ump_dev_unregister, @@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) if (ump->private_free) ump->private_free(ump); + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + snd_ump_convert_free(ump); +#endif } /** @@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, if (!ump) return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + mutex_init(&ump->open_mutex); + spin_lock_init(&ump->legacy_locks[0]); + spin_lock_init(&ump->legacy_locks[1]); +#endif err = snd_rawmidi_init(&ump->core, card, id, device, output, input, info_flags); if (err < 0) { @@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); } +/* number of 32bit words per message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* parse the UMP packet data; + * the data is copied onto ump->input_buf[]. + * When a full packet is completed, returns the number of words (from 1 to 4). + * OTOH, if the packet is incomplete, returns 0. + */ +static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +{ + int words; + + if (!ump->input_pending) + ump->input_pending = ump_packet_words[ump_message_type(val)]; + + ump->input_buf[ump->input_buf_head++] = val; + ump->input_pending--; + if (!ump->input_pending) { + words = ump->input_buf_head; + ump->input_buf_head = 0; + return words; + } + return 0; +} + /** * snd_ump_receive - transfer UMP packets from the device * @ump: the UMP endpoint @@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) */ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) { - struct snd_rawmidi_substream *substream = - ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + struct snd_rawmidi_substream *substream; + const u32 *p = buffer; + int n, words = count >> 2; + + while (words--) { + n = snd_ump_receive_ump_val(ump, *p++); + if (!n) + continue; + process_legacy_input(ump, ump->input_buf, n); + } + substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; if (!substream) return 0; return snd_rawmidi_receive(substream, (const char *)buffer, count); @@ -241,10 +304,15 @@ int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) { struct snd_rawmidi_substream *substream = ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + int err; if (!substream) return -ENODEV; - return snd_rawmidi_transmit(substream, (char *)buffer, count); + err = snd_rawmidi_transmit(substream, (char *)buffer, count); + /* received either data or an error? */ + if (err) + return err; + return process_legacy_output(ump, buffer, count); } EXPORT_SYMBOL_GPL(snd_ump_transmit); @@ -386,5 +454,189 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, } } +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +/* + * Legacy rawmidi support + */ +static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + int err; + + mutex_lock(&ump->open_mutex); + if (ump->legacy_substreams[dir][group]) { + err = -EBUSY; + goto unlock; + } + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &ump->legacy_out_rfile); + if (err < 0) + goto unlock; + } + ump->legacy_out_opens++; + snd_ump_reset_convert_to_ump(ump, group); + } + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = substream; + spin_unlock_irq(&ump->legacy_locks[dir]); + unlock: + mutex_unlock(&ump->open_mutex); + return 0; +} + +static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + + mutex_lock(&ump->open_mutex); + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = NULL; + spin_unlock_irq(&ump->legacy_locks[dir]); + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) + snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + } + mutex_unlock(&ump->open_mutex); + return 0; +} + +static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi) +{ + /* dummy, just for avoiding create superfluous seq clients */ + return 0; +} + +static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, + .drain = snd_ump_legacy_drain, +}; + +static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = { + .dev_register = snd_ump_legacy_dev_register, +}; + +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream; + struct ump_cvt_to_ump *ctx; + const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT; + unsigned char c; + int group, size = 0; + unsigned long flags; + + if (!ump->out_cvts || !ump->legacy_out_opens) + return 0; + + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) { + substream = ump->legacy_substreams[dir][group]; + if (!substream) + continue; + ctx = &ump->out_cvts[group]; + while (!ctx->ump_bytes && + snd_rawmidi_transmit(substream, &c, 1) > 0) + snd_ump_convert_to_ump(ump, group, c); + if (ctx->ump_bytes && ctx->ump_bytes <= count) { + size = ctx->ump_bytes; + memcpy(buffer, ctx->ump, size); + ctx->ump_bytes = 0; + break; + } + } + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); + return size; +} + +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words) +{ + struct snd_rawmidi_substream *substream; + unsigned char buf[16]; + unsigned char group; + unsigned long flags; + const int dir = SNDRV_RAWMIDI_STREAM_INPUT; + int size; + + size = snd_ump_convert_from_ump(ump, src, buf, &group); + if (size <= 0) + return; + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + substream = ump->legacy_substreams[dir][group]; + if (substream) + snd_rawmidi_receive(substream, buf, size); + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); +} + +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + struct snd_rawmidi *rmidi; + bool input, output; + int err; + + err = snd_ump_convert_init(ump); + if (err < 0) + return err; + + input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; + output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; + err = snd_rawmidi_new(ump->core.card, id, device, + output ? 16 : 0, input ? 16 : 0, + &rmidi); + if (err < 0) { + snd_ump_convert_free(ump); + return err; + } + + if (input) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_legacy_input_ops); + if (output) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_legacy_output_ops); + rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP; + rmidi->ops = &snd_ump_legacy_ops; + rmidi->private_data = ump; + ump->legacy_rmidi = rmidi; + ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi); +#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */ + MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); MODULE_LICENSE("GPL"); diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c new file mode 100644 index 000000000000..cb7c2f959a27 --- /dev/null +++ b/sound/core/ump_convert.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Helpers for UMP <-> MIDI 1.0 byte stream conversion + */ + +#include +#include +#include +#include +#include +#include "ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* + * UMP -> MIDI 1 byte stream conversion + */ +/* convert a UMP System message to MIDI 1.0 byte stream */ +static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + switch (ump_message_status_code(data)) { + case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: + case UMP_SYSTEM_STATUS_SONG_SELECT: + buf[1] = (data >> 8) & 0x7f; + return 1; + case UMP_SYSTEM_STATUS_SONG_POSITION: + buf[1] = (data >> 8) & 0x7f; + buf[2] = data & 0x7f; + return 3; + default: + return 1; + } +} + +/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + buf[1] = (data >> 8) & 0xff; + switch (ump_message_status_code(data)) { + case UMP_MSG_STATUS_PROGRAM: + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + return 2; + default: + buf[2] = data & 0xff; + return 3; + } +} + +/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2, + unsigned char *buf) +{ + unsigned char status = midi2->note.status; + unsigned char channel = midi2->note.channel; + u16 v; + + buf[0] = (status << 4) | channel; + switch (status) { + case UMP_MSG_STATUS_NOTE_OFF: + case UMP_MSG_STATUS_NOTE_ON: + buf[1] = midi2->note.note; + buf[2] = downscale_16_to_7bit(midi2->note.velocity); + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + buf[2] = 1; + return 3; + case UMP_MSG_STATUS_POLY_PRESSURE: + buf[1] = midi2->paf.note; + buf[2] = downscale_32_to_7bit(midi2->paf.data); + return 3; + case UMP_MSG_STATUS_CC: + buf[1] = midi2->cc.index; + buf[2] = downscale_32_to_7bit(midi2->cc.data); + return 3; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + buf[1] = downscale_32_to_7bit(midi2->caf.data); + return 2; + case UMP_MSG_STATUS_PROGRAM: + if (midi2->pg.bank_valid) { + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = UMP_CC_BANK_SELECT; + buf[2] = midi2->pg.bank_msb; + buf[3] = channel | (UMP_MSG_STATUS_CC << 4); + buf[4] = UMP_CC_BANK_SELECT_LSB; + buf[5] = midi2->pg.bank_lsb; + buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4); + buf[7] = midi2->pg.program; + return 8; + } + buf[1] = midi2->pg.program; + return 2; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + buf[1] = v & 0x7f; + buf[2] = v >> 7; + return 3; + case UMP_MSG_STATUS_RPN: + case UMP_MSG_STATUS_NRPN: + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + buf[2] = midi2->rpn.bank; + buf[3] = buf[0]; + buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + buf[5] = midi2->rpn.index; + buf[6] = buf[0]; + buf[7] = UMP_CC_DATA; + v = downscale_32_to_14bit(midi2->rpn.data); + buf[8] = v >> 7; + buf[9] = buf[0]; + buf[10] = UMP_CC_DATA_LSB; + buf[11] = v & 0x7f; + return 12; + default: + return 0; + } +} + +/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */ +static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) +{ + unsigned char status; + unsigned char bytes; + int size, offset; + + status = ump_sysex_message_status(*data); + if (status > UMP_SYSEX_STATUS_END) + return 0; // unsupported, skip + bytes = ump_sysex_message_length(*data); + if (bytes > 6) + return 0; // skip + + size = 0; + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + offset = 8; + for (; bytes; bytes--, size++) { + buf[size] = (*data >> offset) & 0x7f; + if (!offset) { + offset = 24; + data++; + } else { + offset -= 8; + } + } + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + return size; +} + +/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf; + * the target group is stored at @group_ret, + * returns the number of bytes of MIDI 1.0 stream + */ +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, + unsigned char *buf, + unsigned char *group_ret) +{ + *group_ret = ump_message_group(*data); + + switch (ump_message_type(*data)) { + case UMP_MSG_TYPE_SYSTEM: + return cvt_ump_system_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + return cvt_ump_midi1_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data, + buf); + case UMP_MSG_TYPE_DATA: + return cvt_ump_sysex7_to_legacy(data, buf); + } + + return 0; +} + +/* + * MIDI 1 byte stream -> UMP conversion + */ +/* convert MIDI 1.0 SysEx to a UMP packet */ +static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, bool finish) +{ + unsigned char status; + bool start = cvt->in_sysex == 1; + int i, offset; + + if (start && finish) + status = UMP_SYSEX_STATUS_SINGLE; + else if (start) + status = UMP_SYSEX_STATUS_START; + else if (finish) + status = UMP_SYSEX_STATUS_END; + else + status = UMP_SYSEX_STATUS_CONTINUE; + *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len); + offset = 8; + for (i = 0; i < cvt->len; i++) { + *data |= cvt->buf[i] << offset; + if (!offset) { + offset = 24; + data++; + } else + offset -= 8; + } + cvt->len = 0; + if (finish) + cvt->in_sysex = 0; + else + cvt->in_sysex++; + return 8; +} + +/* convert to a UMP System message */ +static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data) +{ + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]); + if (cvt->cmd_bytes > 1) + data[0] |= cvt->buf[1] << 8; + if (cvt->cmd_bytes > 2) + data[0] |= cvt->buf[2]; + return 4; +} + +static void fill_rpn(struct ump_cvt_to_ump_bank *cc, + union snd_ump_midi2_msg *midi2) +{ + if (cc->rpn_set) { + midi2->rpn.status = UMP_MSG_STATUS_RPN; + midi2->rpn.bank = cc->cc_rpn_msb; + midi2->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + midi2->rpn.status = UMP_MSG_STATUS_NRPN; + midi2->rpn.bank = cc->cc_nrpn_msb; + midi2->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert to a MIDI 1.0 Channel Voice message */ +static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, + struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, + unsigned char bytes) +{ + const unsigned char *buf = cvt->buf; + struct ump_cvt_to_ump_bank *cc; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data; + unsigned char status, channel; + + BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4); + BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); + + /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ + if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, + group, 0, buf[0]); + data[0] |= buf[1] << 8; + if (bytes > 2) + data[0] |= buf[2]; + return 4; + } + + status = *buf >> 4; + channel = *buf & 0x0f; + cc = &cvt->bank[channel]; + + /* special handling: treat note-on with 0 velocity as note-off */ + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + + /* initialize the packet */ + data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE, + group, status, channel); + data[1] = 0; + + switch (status) { + case UMP_MSG_STATUS_NOTE_ON: + if (!buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + fallthrough; + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = buf[1]; + midi2->note.velocity = upscale_7_to_16bit(buf[2]); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = buf[1]; + midi2->paf.data = upscale_7_to_32bit(buf[2]); + break; + case UMP_MSG_STATUS_CC: + switch (buf[1]) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = buf[2]; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = buf[2]; + if (cc->rpn_set || cc->nrpn_set) + fill_rpn(cc, midi2); + else + return 0; // skip + break; + default: + midi2->cc.index = buf[1]; + midi2->cc.data = upscale_7_to_32bit(buf[2]); + break; + } + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = buf[1]; + if (cc->bank_set) { + midi2->pg.bank_valid = 1; + midi2->pg.bank_msb = cc->cc_bank_msb; + midi2->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(buf[1]); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7)); + break; + default: + return 0; + } + + return 8; +} + +static int do_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c, u32 *data) +{ + /* bytes for 0x80-0xf0 */ + static unsigned char cmd_bytes[8] = { + 3, 3, 3, 3, 2, 2, 3, 0 + }; + /* bytes for 0xf0-0xff */ + static unsigned char system_bytes[16] = { + 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 + }; + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + unsigned char bytes; + + if (c == UMP_MIDI1_MSG_SYSEX_START) { + cvt->in_sysex = 1; + cvt->len = 0; + return 0; + } + if (c == UMP_MIDI1_MSG_SYSEX_END) { + if (!cvt->in_sysex) + return 0; /* skip */ + return cvt_legacy_sysex_to_ump(cvt, group, data, true); + } + + if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) { + bytes = system_bytes[c & 0x0f]; + if (!bytes) + return 0; /* skip */ + if (bytes == 1) { + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c); + return 4; + } + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (c & 0x80) { + bytes = cmd_bytes[(c >> 8) & 7]; + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (cvt->in_sysex) { + cvt->buf[cvt->len++] = c; + if (cvt->len == 6) + return cvt_legacy_sysex_to_ump(cvt, group, data, false); + return 0; + } + + if (!cvt->len) + return 0; + + cvt->buf[cvt->len++] = c; + if (cvt->len < cvt->cmd_bytes) + return 0; + cvt->len = 1; + if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) + return cvt_legacy_system_to_ump(cvt, group, data); + return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes); +} + +/* feed a MIDI 1.0 byte @c and convert to a UMP packet; + * the target group is @group, + * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes + */ +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c) +{ + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + + cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump); +} + +/* reset the converter context, called at each open */ +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group) +{ + memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts)); +} + +/* initialize converters */ +int snd_ump_convert_init(struct snd_ump_endpoint *ump) +{ + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; + return 0; +} + +/* release resources */ +void snd_ump_convert_free(struct snd_ump_endpoint *ump) +{ + kfree(ump->out_cvts); + ump->out_cvts = NULL; +} diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h new file mode 100644 index 000000000000..bbfe96084779 --- /dev/null +++ b/sound/core/ump_convert.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UMP_CONVERT_H +#define __UMP_CONVERT_H + +#include + +/* context for converting from legacy control messages to UMP packet */ +struct ump_cvt_to_ump_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +/* context for converting from MIDI1 byte stream to UMP packet */ +struct ump_cvt_to_ump { + /* MIDI1 intermediate buffer */ + unsigned char buf[4]; + int len; + int cmd_bytes; + + /* UMP output packet */ + u32 ump[4]; + int ump_bytes; + + /* various status */ + unsigned int in_sysex; + struct ump_cvt_to_ump_bank bank[16]; /* per channel */ +}; + +int snd_ump_convert_init(struct snd_ump_endpoint *ump); +void snd_ump_convert_free(struct snd_ump_endpoint *ump); +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, unsigned char *dst, + unsigned char *group_ret); +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c); +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group); +#endif /* __UMP_CONVERT_H */ -- cgit v1.2.3 From ec362b63c4b560006666998c582edc76a2f77910 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:36 +0200 Subject: ALSA: usb-audio: Enable the legacy raw MIDI support Attach the legacy rawmidi devices when enabled in Kconfig accordingly. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-16-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 7e849b2384ee..f3fba8b07cb3 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -857,6 +857,25 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) return 0; } +/* attach legacy rawmidis */ +static int attach_legacy_rawmidi(struct snd_usb_midi2_interface *umidi) +{ +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + err = snd_ump_attach_legacy_rawmidi(rmidi->ump, + "Legacy MIDI", + umidi->chip->num_rawmidis); + if (err < 0) + return err; + umidi->chip->num_rawmidis++; + } +#endif + return 0; +} + static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) { free_all_midi2_endpoints(umidi); @@ -922,7 +941,7 @@ static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi) } } - return 0; + return attach_legacy_rawmidi(umidi); } /* is the given interface for MIDI 2.0? */ @@ -991,6 +1010,12 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) usb_string(dev, dev->descriptor.iSerialNumber, ump->info.product_id, sizeof(ump->info.product_id)); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + if (ump->legacy_rmidi && !*ump->legacy_rmidi->name) + snprintf(ump->legacy_rmidi->name, + sizeof(ump->legacy_rmidi->name), + "%s (MIDI 1.0)", ump->info.name); +#endif } } -- cgit v1.2.3 From f4487c42aae596f02e0cb02a028d2a107ec1737d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:37 +0200 Subject: ALSA: usb-audio: Inform inconsistent protocols in GTBs When parsing Group Terminal Blocks, we overwrote the preferred protocol and the protocol capabilities silently from the last parsed GTB. This patch adds the information print indicating the unexpected overrides instead of silent action. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-17-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index f3fba8b07cb3..341783418a6a 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -581,6 +581,7 @@ static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, const struct usb_ms20_gr_trm_block_descriptor *desc) { struct snd_ump_endpoint *ump = rmidi->ump; + unsigned int protocol, protocol_caps; /* set default protocol */ switch (desc->bMIDIProtocol) { @@ -588,24 +589,40 @@ static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, case USB_MS_MIDI_PROTO_1_0_64_JRTS: case USB_MS_MIDI_PROTO_1_0_128: case USB_MS_MIDI_PROTO_1_0_128_JRTS: - ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; break; case USB_MS_MIDI_PROTO_2_0: case USB_MS_MIDI_PROTO_2_0_JRTS: - ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; break; + default: + return 0; } - ump->info.protocol_caps = ump->info.protocol; + if (ump->info.protocol && ump->info.protocol != protocol) + usb_audio_info(rmidi->umidi->chip, + "Overriding preferred MIDI protocol in GTB %d: %x -> %x\n", + rmidi->usb_block_id, ump->info.protocol, + protocol); + ump->info.protocol = protocol; + + protocol_caps = protocol; switch (desc->bMIDIProtocol) { case USB_MS_MIDI_PROTO_1_0_64_JRTS: case USB_MS_MIDI_PROTO_1_0_128_JRTS: case USB_MS_MIDI_PROTO_2_0_JRTS: - ump->info.protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | + protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | SNDRV_UMP_EP_INFO_PROTO_JRTS_RX; break; } + if (ump->info.protocol_caps && ump->info.protocol_caps != protocol_caps) + usb_audio_info(rmidi->umidi->chip, + "Overriding MIDI protocol caps in GTB %d: %x -> %x\n", + rmidi->usb_block_id, ump->info.protocol_caps, + protocol_caps); + ump->info.protocol_caps = protocol_caps; + return 0; } -- cgit v1.2.3 From f80e6d60d677be1d4dbbcdbf97379b8fbcf97ff0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:38 +0200 Subject: ALSA: seq: Clear padded bytes at expanding events There can be a small memory hole that may not be cleared at expanding an event with the variable length type. Make sure to clear it. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-18-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_memory.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index 47ef6bc30c0e..c8d26bce69ff 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -152,12 +152,16 @@ int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char return -EINVAL; if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) return -EFAULT; - return newlen; + } else { + err = snd_seq_dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf); + if (err < 0) + return err; } - err = snd_seq_dump_var_event(event, - in_kernel ? seq_copy_in_kernel : seq_copy_in_user, - &buf); - return err < 0 ? err : newlen; + if (len != newlen) + memset(buf + len, 0, newlen - len); + return newlen; } EXPORT_SYMBOL(snd_seq_expand_var_event); -- cgit v1.2.3 From ea46f79709b6262f12c8ca24f32bfe8d638152ee Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:39 +0200 Subject: ALSA: seq: Add snd_seq_expand_var_event_at() helper Create a new variant of snd_seq_expand_var_event() for expanding the data starting from the given byte offset. It'll be used by the new UMP sequencer code later. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-19-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/seq_kernel.h | 2 ++ sound/core/seq/seq_memory.c | 86 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/include/sound/seq_kernel.h b/include/sound/seq_kernel.h index 658911926f3a..527e7f8ad661 100644 --- a/include/sound/seq_kernel.h +++ b/include/sound/seq_kernel.h @@ -70,6 +70,8 @@ int snd_seq_kernel_client_ctl(int client, unsigned int cmd, void *arg); typedef int (*snd_seq_dump_func_t)(void *ptr, void *buf, int count); int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, int in_kernel, int size_aligned); +int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, + char *buf, int offset); int snd_seq_dump_var_event(const struct snd_seq_event *event, snd_seq_dump_func_t func, void *private_data); diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index c8d26bce69ff..a8d2db439f86 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -63,8 +63,9 @@ static int get_var_len(const struct snd_seq_event *event) return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; } -int snd_seq_dump_var_event(const struct snd_seq_event *event, - snd_seq_dump_func_t func, void *private_data) +static int dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data, + int offset, int maxlen) { int len, err; struct snd_seq_event_cell *cell; @@ -72,10 +73,16 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event, len = get_var_len(event); if (len <= 0) return len; + if (len <= offset) + return 0; + if (maxlen && len > offset + maxlen) + len = offset + maxlen; if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { char buf[32]; char __user *curptr = (char __force __user *)event->data.ext.ptr; + curptr += offset; + len -= offset; while (len > 0) { int size = sizeof(buf); if (len < size) @@ -91,20 +98,35 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event, return 0; } if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) - return func(private_data, event->data.ext.ptr, len); + return func(private_data, event->data.ext.ptr + offset, + len - offset); cell = (struct snd_seq_event_cell *)event->data.ext.ptr; for (; len > 0 && cell; cell = cell->next) { int size = sizeof(struct snd_seq_event); + char *curptr = (char *)&cell->event; + + if (offset >= size) { + offset -= size; + len -= size; + continue; + } if (len < size) size = len; - err = func(private_data, &cell->event, size); + err = func(private_data, curptr + offset, size - offset); if (err < 0) return err; + offset = 0; len -= size; } return 0; } + +int snd_seq_dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data) +{ + return dump_var_event(event, func, private_data, 0, 0); +} EXPORT_SYMBOL(snd_seq_dump_var_event); @@ -132,11 +154,27 @@ static int seq_copy_in_user(void *ptr, void *src, int size) return 0; } +static int expand_var_event(const struct snd_seq_event *event, + int offset, int size, char *buf, bool in_kernel) +{ + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, + (char __force __user *)event->data.ext.ptr + offset, + size)) + return -EFAULT; + return 0; + } + return dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf, offset, size); +} + int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, int in_kernel, int size_aligned) { - int len, newlen; - int err; + int len, newlen, err; len = get_var_len(event); if (len < 0) @@ -146,25 +184,35 @@ int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char newlen = roundup(len, size_aligned); if (count < newlen) return -EAGAIN; - - if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { - if (! in_kernel) - return -EINVAL; - if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) - return -EFAULT; - } else { - err = snd_seq_dump_var_event(event, - in_kernel ? seq_copy_in_kernel : seq_copy_in_user, - &buf); - if (err < 0) - return err; - } + err = expand_var_event(event, 0, len, buf, in_kernel); + if (err < 0) + return err; if (len != newlen) memset(buf + len, 0, newlen - len); return newlen; } EXPORT_SYMBOL(snd_seq_expand_var_event); +int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, + char *buf, int offset) +{ + int len, err; + + len = get_var_len(event); + if (len < 0) + return len; + if (len <= offset) + return 0; + len -= offset; + if (len > count) + len = count; + err = expand_var_event(event, offset, count, buf, true); + if (err < 0) + return err; + return len; +} +EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at); + /* * release this cell, free extended data if available */ -- cgit v1.2.3 From d0c8308fc58b3f02868388b294a94d501c816900 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:40 +0200 Subject: ALSA: seq: Treat snd_seq_client object directly in client drivers Introduce the new helpers, snd_seq_kernel_client_get() and _put() for kernel client drivers to treat the snd_seq_client more directly. This allows us to reduce the exported symbols and APIs at each time we need to access some field in future. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-20-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 15 +++++++++++++++ sound/core/seq/seq_clientmgr.h | 4 ++++ 2 files changed, 19 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 2d707afa1ef1..98e8032a32e2 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2390,6 +2390,21 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table } EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); +/* get a sequencer client object; for internal use from a kernel client */ +struct snd_seq_client *snd_seq_kernel_client_get(int id) +{ + return snd_seq_client_use_ptr(id); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_get); + +/* put a sequencer client object; for internal use from a kernel client */ +void snd_seq_kernel_client_put(struct snd_seq_client *cptr) +{ + if (cptr) + snd_seq_client_unlock(cptr); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_put); + /*---------------------------------------------------------------------------*/ #ifdef CONFIG_SND_PROC_FS diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 8cdd0ee53fb1..f05704e45ab4 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -88,4 +88,8 @@ void snd_seq_client_ioctl_unlock(int clientid); extern int seq_client_load[15]; +/* for internal use between kernel sequencer clients */ +struct snd_seq_client *snd_seq_kernel_client_get(int client); +void snd_seq_kernel_client_put(struct snd_seq_client *cptr); + #endif -- cgit v1.2.3 From 94c5b717ada970c0136a9369f620d11773b38a51 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:41 +0200 Subject: ALSA: seq: Drop dead code for the old broadcast support The broadcast and multicast supports have been never enabled. Let's drop the dead code. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-21-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 105 +---------------------------------------- 1 file changed, 1 insertion(+), 104 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 98e8032a32e2..019af1343325 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -711,93 +711,6 @@ static int deliver_to_subscribers(struct snd_seq_client *client, return (result < 0) ? result : num_ev; } - -#ifdef SUPPORT_BROADCAST -/* - * broadcast to all ports: - */ -static int port_broadcast_event(struct snd_seq_client *client, - struct snd_seq_event *event, - int atomic, int hop) -{ - int num_ev = 0, err, result = 0; - struct snd_seq_client *dest_client; - struct snd_seq_client_port *port; - - dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST); - if (dest_client == NULL) - return 0; /* no matching destination */ - - read_lock(&dest_client->ports_lock); - list_for_each_entry(port, &dest_client->ports_list_head, list) { - event->dest.port = port->addr.port; - /* pass NULL as source client to avoid error bounce */ - err = snd_seq_deliver_single_event(NULL, event, - SNDRV_SEQ_FILTER_BROADCAST, - atomic, hop); - if (err < 0) { - /* save first error that occurs and continue */ - if (!result) - result = err; - continue; - } - num_ev++; - } - read_unlock(&dest_client->ports_lock); - snd_seq_client_unlock(dest_client); - event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ - return (result < 0) ? result : num_ev; -} - -/* - * send the event to all clients: - * if destination port is also ADDRESS_BROADCAST, deliver to all ports. - */ -static int broadcast_event(struct snd_seq_client *client, - struct snd_seq_event *event, int atomic, int hop) -{ - int err, result = 0, num_ev = 0; - int dest; - struct snd_seq_addr addr; - - addr = event->dest; /* save */ - - for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) { - /* don't send to itself */ - if (dest == client->number) - continue; - event->dest.client = dest; - event->dest.port = addr.port; - if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST) - err = port_broadcast_event(client, event, atomic, hop); - else - /* pass NULL as source client to avoid error bounce */ - err = snd_seq_deliver_single_event(NULL, event, - SNDRV_SEQ_FILTER_BROADCAST, - atomic, hop); - if (err < 0) { - /* save first error that occurs and continue */ - if (!result) - result = err; - continue; - } - num_ev += err; - } - event->dest = addr; /* restore */ - return (result < 0) ? result : num_ev; -} - - -/* multicast - not supported yet */ -static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event, - int atomic, int hop) -{ - pr_debug("ALSA: seq: multicast not supported yet.\n"); - return 0; /* ignored */ -} -#endif /* SUPPORT_BROADCAST */ - - /* deliver an event to the destination port(s). * if the event is to subscribers or broadcast, the event is dispatched * to multiple targets. @@ -826,15 +739,6 @@ static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_e if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) result = deliver_to_subscribers(client, event, atomic, hop); -#ifdef SUPPORT_BROADCAST - else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST || - event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST) - result = broadcast_event(client, event, atomic, hop); - else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS) - result = multicast_event(client, event, atomic, hop); - else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST) - result = port_broadcast_event(client, event, atomic, hop); -#endif else result = snd_seq_deliver_single_event(client, event, 0, atomic, hop); @@ -936,14 +840,7 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client, if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; event->queue = SNDRV_SEQ_QUEUE_DIRECT; - } else -#ifdef SUPPORT_BROADCAST - if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) { - event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST; - event->queue = SNDRV_SEQ_QUEUE_DIRECT; - } -#endif - if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + } else if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { /* check presence of source port */ struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port); if (src_port == NULL) -- cgit v1.2.3 From 7c3f0d3d3a1147f7eee79e090d0b047ab5fd3068 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:42 +0200 Subject: ALSA: seq: Check the conflicting port at port creation We didn't check if a port with the given port number has been already present at creating a new port. Check it and return -EBUSY properly if the port number conflicts. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-22-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 12 ++++++++---- sound/core/seq/seq_ports.c | 25 ++++++++++++++++--------- sound/core/seq/seq_ports.h | 5 +++-- 3 files changed, 27 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 019af1343325..06743114cabf 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1194,15 +1194,19 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) struct snd_seq_port_info *info = arg; struct snd_seq_client_port *port; struct snd_seq_port_callback *callback; - int port_idx; + int port_idx, err; /* it is not allowed to create the port for an another client */ if (info->addr.client != client->number) return -EPERM; - port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1); - if (port == NULL) - return -ENOMEM; + if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) + port_idx = info->addr.port; + else + port_idx = -1; + err = snd_seq_create_port(client, port_idx, &port); + if (err < 0) + return err; if (client->type == USER_CLIENT && info->kernel) { port_idx = port->addr.port; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 25fcf5a2c71c..500b1a5a9679 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -107,33 +107,34 @@ static void port_subs_info_init(struct snd_seq_port_subs_info *grp) } -/* create a port, port number is returned (-1 on failure); +/* create a port, port number or a negative error code is returned * the caller needs to unref the port via snd_seq_port_unlock() appropriately */ -struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, - int port) +int snd_seq_create_port(struct snd_seq_client *client, int port, + struct snd_seq_client_port **port_ret) { struct snd_seq_client_port *new_port, *p; - int num = -1; + int num; + *port_ret = NULL; + /* sanity check */ if (snd_BUG_ON(!client)) - return NULL; + return -EINVAL; if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { pr_warn("ALSA: seq: too many ports for client %d\n", client->number); - return NULL; + return -EINVAL; } /* create a new port */ new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); if (!new_port) - return NULL; /* failure, out of memory */ + return -ENOMEM; /* failure, out of memory */ /* init port data */ new_port->addr.client = client->number; new_port->addr.port = -1; new_port->owner = THIS_MODULE; - sprintf(new_port->name, "port-%d", num); snd_use_lock_init(&new_port->use_lock); port_subs_info_init(&new_port->c_src); port_subs_info_init(&new_port->c_dest); @@ -143,6 +144,10 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, mutex_lock(&client->ports_mutex); write_lock_irq(&client->ports_lock); list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + num = -EBUSY; + goto unlock; + } if (p->addr.port > num) break; if (port < 0) /* auto-probe mode */ @@ -153,10 +158,12 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, client->num_ports++; new_port->addr.port = num; /* store the port number in the port */ sprintf(new_port->name, "port-%d", num); + *port_ret = new_port; + unlock: write_unlock_irq(&client->ports_lock); mutex_unlock(&client->ports_mutex); - return new_port; + return num; } /* */ diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index b1f2c4943174..44f0e9e96bbf 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -86,8 +86,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl /* unlock the port */ #define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) -/* create a port, port number is returned (-1 on failure) */ -struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index); +/* create a port, port number or a negative error code is returned */ +int snd_seq_create_port(struct snd_seq_client *client, int port_index, + struct snd_seq_client_port **port_ret); /* delete a port */ int snd_seq_delete_port(struct snd_seq_client *client, int port); -- cgit v1.2.3 From 4f92eb792e9336a46480655ed18e8b59e2a6505c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:43 +0200 Subject: ALSA: seq: Check validity before creating a port object The client type and the port info validity check should be done before actually creating a port, instead of unnecessary create-and-scratch. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-23-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 06743114cabf..2dac8c3355fd 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1199,6 +1199,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) /* it is not allowed to create the port for an another client */ if (info->addr.client != client->number) return -EPERM; + if (client->type == USER_CLIENT && info->kernel) + return -EINVAL; if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) port_idx = info->addr.port; @@ -1208,12 +1210,6 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) if (err < 0) return err; - if (client->type == USER_CLIENT && info->kernel) { - port_idx = port->addr.port; - snd_seq_port_unlock(port); - snd_seq_delete_port(client, port_idx); - return -EINVAL; - } if (client->type == KERNEL_CLIENT) { callback = info->kernel; if (callback) { -- cgit v1.2.3 From 1359905383834ed5fc294fad3954d40dbcf770af Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:44 +0200 Subject: ALSA: seq: Prohibit creating ports with special numbers Some port numbers are special, such as 254 for subscribers and 255 for broadcast. Return error if application tries to create such a port. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-24-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 2dac8c3355fd..0f26f20596d7 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1206,6 +1206,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) port_idx = info->addr.port; else port_idx = -1; + if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN) + return -EINVAL; err = snd_seq_create_port(client, port_idx, &port); if (err < 0) return err; -- cgit v1.2.3 From afb72505e4614a2ccefe3440d37dec3a2273c330 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:45 +0200 Subject: ALSA: seq: Introduce SNDRV_SEQ_IOCTL_USER_PVERSION ioctl For the future extension of ALSA sequencer ABI, introduce a new ioctl SNDRV_SEQ_IOCTL_USER_PVERSION. This is similar like the ioctls used in PCM and other interfaces, for an application to specify its supporting ABI version. The use of this ioctl will be mandatory for the upcoming UMP support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-25-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 8 ++++++++ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_compat.c | 1 + 4 files changed, 11 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 00d2703e8fca..4a3c5a718bae 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -561,6 +561,7 @@ struct snd_seq_query_subs { #define SNDRV_SEQ_IOCTL_CLIENT_ID _IOR ('S', 0x01, int) #define SNDRV_SEQ_IOCTL_SYSTEM_INFO _IOWR('S', 0x02, struct snd_seq_system_info) #define SNDRV_SEQ_IOCTL_RUNNING_MODE _IOWR('S', 0x03, struct snd_seq_running_info) +#define SNDRV_SEQ_IOCTL_USER_PVERSION _IOW('S', 0x04, int) #define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO _IOWR('S', 0x10, struct snd_seq_client_info) #define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO _IOW ('S', 0x11, struct snd_seq_client_info) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 0f26f20596d7..89a8d14df83b 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1056,6 +1056,12 @@ static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg) return 0; } +static int snd_seq_ioctl_user_pversion(struct snd_seq_client *client, void *arg) +{ + client->user_pversion = *(unsigned int *)arg; + return 0; +} + static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg) { int *client_id = arg; @@ -1985,6 +1991,7 @@ static const struct ioctl_handler { int (*func)(struct snd_seq_client *client, void *arg); } ioctl_handlers[] = { { SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion }, + { SNDRV_SEQ_IOCTL_USER_PVERSION, snd_seq_ioctl_user_pversion }, { SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id }, { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, @@ -2125,6 +2132,7 @@ int snd_seq_create_kernel_client(struct snd_card *card, int client_index, client->accept_input = 1; client->accept_output = 1; client->data.kernel.card = card; + client->user_pversion = SNDRV_SEQ_VERSION; va_start(args, name_fmt); vsnprintf(client->name, sizeof(client->name), name_fmt, args); diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index f05704e45ab4..abe0ceadf3da 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -35,6 +35,7 @@ struct snd_seq_client { snd_seq_client_type_t type; unsigned int accept_input: 1, accept_output: 1; + unsigned int user_pversion; char name[64]; /* client name */ int number; /* client number */ unsigned int filter; /* filter flags */ diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 54723566ce24..c0ce6236dc7f 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -81,6 +81,7 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l switch (cmd) { case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_USER_PVERSION: case SNDRV_SEQ_IOCTL_CLIENT_ID: case SNDRV_SEQ_IOCTL_SYSTEM_INFO: case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: -- cgit v1.2.3 From 46397622a3fa8372b8fda0f04b33d16923b03b1b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:46 +0200 Subject: ALSA: seq: Add UMP support Starting from this commit, we add the basic support of UMP (Universal MIDI Packet) events on ALSA sequencer infrastructure. The biggest change here is that, for transferring UMP packets that are up to 128 bits, we extend the data payload of ALSA sequencer event record when the client is declared to support for the new UMP events. A new event flag bit, SNDRV_SEQ_EVENT_UMP, is defined and it shall be set for the UMP packet events that have the larger payload of 128 bits, defined as struct snd_seq_ump_event. For controlling the UMP feature enablement in kernel, a new Kconfig, CONFIG_SND_SEQ_UMP is introduced. The extended event for UMP is available only when this Kconfig item is set. Similarly, the size of the internal snd_seq_event_cell also increases (in 4 bytes) when the Kconfig item is set. (But the size increase is effective only for 32bit architectures; 64bit archs already have padding there.) Overall, when CONFIG_SND_SEQ_UMP isn't set, there is no change in the event and cell, keeping the old sizes. For applications that want to access the UMP packets, first of all, a sequencer client has to declare the user-protocol to match with the latest one via the new SNDRV_SEQ_IOCTL_USER_PVERSION; otherwise it's treated as if a legacy client without UMP support. Then the client can switch to the new UMP mode (MIDI 1.0 or MIDI 2.0) with a new field, midi_version, in snd_seq_client_info. When switched to UMP mode (midi_version = 1 or 2), the client can write the UMP events with SNDRV_SEQ_EVENT_UMP flag. For reads, the alignment size is changed from snd_seq_event (28 bytes) to snd_seq_ump_event (32 bytes). When a UMP sequencer event is delivered to a legacy sequencer client, it's ignored or handled as an error. Conceptually, ALSA sequencer client and port correspond to the UMP Endpoint and Group, respectively; each client may have multiple ports and each port has the fixed number (16) of channels, total up to 256 channels. As of this commit, ALSA sequencer core just sends and receives the UMP events as-is from/to clients. The automatic conversions between the legacy events and the new UMP events will be implemented in a later patch. Along with this commit, bump the sequencer protocol version to 1.0.3. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-26-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/asequencer.h | 4 ++ include/sound/seq_kernel.h | 8 +++ include/uapi/sound/asequencer.h | 53 +++++++++----- sound/core/seq/Kconfig | 7 ++ sound/core/seq/seq_clientmgr.c | 154 ++++++++++++++++++++++++++++------------ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_memory.c | 10 ++- sound/core/seq/seq_memory.h | 19 ++++- 8 files changed, 193 insertions(+), 63 deletions(-) (limited to 'sound') diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h index 18d4bc3ee0b7..ddbb6bf801bb 100644 --- a/include/sound/asequencer.h +++ b/include/sound/asequencer.h @@ -65,6 +65,10 @@ #define snd_seq_ev_is_abstime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_ABS) #define snd_seq_ev_is_reltime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_REL) +/* check whether the given event is a UMP event */ +#define snd_seq_ev_is_ump(ev) \ + (IS_ENABLED(CONFIG_SND_SEQ_UMP) && ((ev)->flags & SNDRV_SEQ_EVENT_UMP)) + /* queue sync port */ #define snd_seq_queue_sync_port(q) ((q) + 16) diff --git a/include/sound/seq_kernel.h b/include/sound/seq_kernel.h index 527e7f8ad661..c8621671fa70 100644 --- a/include/sound/seq_kernel.h +++ b/include/sound/seq_kernel.h @@ -75,6 +75,14 @@ int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, int snd_seq_dump_var_event(const struct snd_seq_event *event, snd_seq_dump_func_t func, void *private_data); +/* size of the event packet; it can be greater than snd_seq_event size */ +static inline size_t snd_seq_event_packet_size(struct snd_seq_event *ev) +{ + if (snd_seq_ev_is_ump(ev)) + return sizeof(struct snd_seq_ump_event); + return sizeof(struct snd_seq_event); +} + /* interface for OSS emulation */ int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo); diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 4a3c5a718bae..b87950cbfb79 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, 2) +#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 3) /** * definition of sequencer event types @@ -174,6 +174,7 @@ struct snd_seq_connect { #define SNDRV_SEQ_PRIORITY_HIGH (1<<4) /* event should be processed before others */ #define SNDRV_SEQ_PRIORITY_MASK (1<<4) +#define SNDRV_SEQ_EVENT_UMP (1<<5) /* event holds a UMP packet */ /* note event */ struct snd_seq_ev_note { @@ -252,6 +253,19 @@ struct snd_seq_ev_quote { struct snd_seq_event *event; /* quoted event */ } __attribute__((packed)); +union snd_seq_event_data { /* event data... */ + struct snd_seq_ev_note note; + struct snd_seq_ev_ctrl control; + struct snd_seq_ev_raw8 raw8; + struct snd_seq_ev_raw32 raw32; + struct snd_seq_ev_ext ext; + struct snd_seq_ev_queue_control queue; + union snd_seq_timestamp time; + struct snd_seq_addr addr; + struct snd_seq_connect connect; + struct snd_seq_result result; + struct snd_seq_ev_quote quote; +}; /* sequencer event */ struct snd_seq_event { @@ -262,25 +276,27 @@ struct snd_seq_event { unsigned char queue; /* schedule queue */ union snd_seq_timestamp time; /* schedule time */ - struct snd_seq_addr source; /* source address */ struct snd_seq_addr dest; /* destination address */ - union { /* event data... */ - struct snd_seq_ev_note note; - struct snd_seq_ev_ctrl control; - struct snd_seq_ev_raw8 raw8; - struct snd_seq_ev_raw32 raw32; - struct snd_seq_ev_ext ext; - struct snd_seq_ev_queue_control queue; - union snd_seq_timestamp time; - struct snd_seq_addr addr; - struct snd_seq_connect connect; - struct snd_seq_result result; - struct snd_seq_ev_quote quote; - } data; + union snd_seq_event_data data; }; + /* (compatible) event for UMP-capable clients */ +struct snd_seq_ump_event { + snd_seq_event_type_t type; /* event type */ + unsigned char flags; /* event flags */ + char tag; + unsigned char queue; /* schedule queue */ + union snd_seq_timestamp time; /* schedule time */ + struct snd_seq_addr source; /* source address */ + struct snd_seq_addr dest; /* destination address */ + + union { + union snd_seq_event_data data; + unsigned int ump[4]; + }; +}; /* * bounce event - stored as variable size data @@ -344,9 +360,14 @@ struct snd_seq_client_info { int event_lost; /* number of lost events */ int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ - char reserved[56]; /* for future use */ + unsigned int midi_version; /* MIDI version */ + char reserved[52]; /* for future use */ }; +/* MIDI version numbers in client info */ +#define SNDRV_SEQ_CLIENT_LEGACY_MIDI 0 /* Legacy client */ +#define SNDRV_SEQ_CLIENT_UMP_MIDI_1_0 1 /* UMP MIDI 1.0 */ +#define SNDRV_SEQ_CLIENT_UMP_MIDI_2_0 2 /* UMP MIDI 2.0 */ /* client pool size */ struct snd_seq_client_pool { diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index f84718a44980..c69d8beb09fa 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -60,4 +60,11 @@ config SND_SEQ_MIDI_EMUL config SND_SEQ_VIRMIDI tristate +config SND_SEQ_UMP + bool "Support for UMP events" + help + Say Y here to enable the support for handling UMP (Universal MIDI + Packet) events via ALSA sequencer infrastructure, which is an + essential feature for enabling MIDI 2.0 support. + endif # SND_SEQUENCER diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 89a8d14df83b..801d5eee21eb 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -387,6 +387,15 @@ static int snd_seq_release(struct inode *inode, struct file *file) return 0; } +static bool event_is_compatible(const struct snd_seq_client *client, + const struct snd_seq_event *ev) +{ + if (snd_seq_ev_is_ump(ev) && !client->midi_version) + return false; + if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev)) + return false; + return true; +} /* handle client read() */ /* possible error values: @@ -400,6 +409,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, { struct snd_seq_client *client = file->private_data; struct snd_seq_fifo *fifo; + size_t aligned_size; int err; long result = 0; struct snd_seq_event_cell *cell; @@ -431,43 +441,54 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, err = 0; snd_seq_fifo_lock(fifo); + if (client->midi_version > 0) + aligned_size = sizeof(struct snd_seq_ump_event); + else + aligned_size = sizeof(struct snd_seq_event); + /* while data available in queue */ - while (count >= sizeof(struct snd_seq_event)) { + while (count >= aligned_size) { int nonblock; nonblock = (file->f_flags & O_NONBLOCK) || result > 0; err = snd_seq_fifo_cell_out(fifo, &cell, nonblock); if (err < 0) break; + if (!event_is_compatible(client, &cell->event)) { + snd_seq_cell_free(cell); + cell = NULL; + continue; + } if (snd_seq_ev_is_variable(&cell->event)) { - struct snd_seq_event tmpev; - tmpev = cell->event; + struct snd_seq_ump_event tmpev; + + memcpy(&tmpev, &cell->event, aligned_size); tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; - if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) { + if (copy_to_user(buf, &tmpev, aligned_size)) { err = -EFAULT; break; } - count -= sizeof(struct snd_seq_event); - buf += sizeof(struct snd_seq_event); + count -= aligned_size; + buf += aligned_size; err = snd_seq_expand_var_event(&cell->event, count, (char __force *)buf, 0, - sizeof(struct snd_seq_event)); + aligned_size); if (err < 0) break; result += err; count -= err; buf += err; } else { - if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) { + if (copy_to_user(buf, &cell->event, aligned_size)) { err = -EFAULT; break; } - count -= sizeof(struct snd_seq_event); - buf += sizeof(struct snd_seq_event); + count -= aligned_size; + buf += aligned_size; } snd_seq_cell_free(cell); cell = NULL; /* to be sure */ - result += sizeof(struct snd_seq_event); + result += aligned_size; } if (err < 0) { @@ -665,15 +686,17 @@ static int deliver_to_subscribers(struct snd_seq_client *client, { struct snd_seq_subscribers *subs; int err, result = 0, num_ev = 0; - struct snd_seq_event event_saved; struct snd_seq_client_port *src_port; + union __snd_seq_event event_saved; + size_t saved_size; struct snd_seq_port_subs_info *grp; src_port = snd_seq_port_use_ptr(client, event->source.port); if (src_port == NULL) return -EINVAL; /* invalid source port */ /* save original event record */ - event_saved = *event; + saved_size = snd_seq_event_packet_size(event); + memcpy(&event_saved, event, saved_size); grp = &src_port->c_src; /* lock list */ @@ -700,14 +723,13 @@ static int deliver_to_subscribers(struct snd_seq_client *client, } num_ev++; /* restore original event record */ - *event = event_saved; + memcpy(event, &event_saved, saved_size); } if (atomic) read_unlock(&grp->list_lock); else up_read(&grp->list_mutex); - *event = event_saved; /* restore */ - snd_seq_port_unlock(src_port); + memcpy(event, &event_saved, saved_size); return (result < 0) ? result : num_ev; } @@ -769,7 +791,8 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) return -EINVAL; } - if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + if (!snd_seq_ev_is_ump(&cell->event) && + cell->event.type == SNDRV_SEQ_EVENT_NOTE) { /* NOTE event: * the event cell is re-used as a NOTE-OFF event and * enqueued again. @@ -793,7 +816,7 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) /* add the duration time */ switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { case SNDRV_SEQ_TIME_STAMP_TICK: - ev->time.tick += ev->data.note.duration; + cell->event.time.tick += ev->data.note.duration; break; case SNDRV_SEQ_TIME_STAMP_REAL: /* unit for duration is ms */ @@ -850,7 +873,8 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client, /* direct event processing without enqueued */ if (snd_seq_ev_is_direct(event)) { - if (event->type == SNDRV_SEQ_EVENT_NOTE) + if (!snd_seq_ev_is_ump(event) && + event->type == SNDRV_SEQ_EVENT_NOTE) return -EINVAL; /* this event must be enqueued! */ return snd_seq_deliver_event(client, event, atomic, hop); } @@ -920,7 +944,8 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf, struct snd_seq_client *client = file->private_data; int written = 0, len; int err, handled; - struct snd_seq_event event; + union __snd_seq_event __event; + struct snd_seq_event *ev = &__event.legacy; if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) return -ENXIO; @@ -946,49 +971,66 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf, err = -EINVAL; while (count >= sizeof(struct snd_seq_event)) { /* Read in the event header from the user */ - len = sizeof(event); - if (copy_from_user(&event, buf, len)) { + len = sizeof(struct snd_seq_event); + if (copy_from_user(ev, buf, len)) { err = -EFAULT; break; } - event.source.client = client->number; /* fill in client number */ + /* read in the rest bytes for UMP events */ + if (snd_seq_ev_is_ump(ev)) { + if (count < sizeof(struct snd_seq_ump_event)) + break; + if (copy_from_user((char *)ev + len, buf + len, + sizeof(struct snd_seq_ump_event) - len)) { + err = -EFAULT; + break; + } + len = sizeof(struct snd_seq_ump_event); + } + + ev->source.client = client->number; /* fill in client number */ /* Check for extension data length */ - if (check_event_type_and_length(&event)) { + if (check_event_type_and_length(ev)) { err = -EINVAL; break; } - /* check for special events */ - if (event.type == SNDRV_SEQ_EVENT_NONE) - goto __skip_event; - else if (snd_seq_ev_is_reserved(&event)) { + if (!event_is_compatible(client, ev)) { err = -EINVAL; break; } - if (snd_seq_ev_is_variable(&event)) { - int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; + /* check for special events */ + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(ev)) { + err = -EINVAL; + break; + } + } + + if (snd_seq_ev_is_variable(ev)) { + int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK; if ((size_t)(extlen + len) > count) { /* back out, will get an error this time or next */ err = -EINVAL; break; } /* set user space pointer */ - event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; - event.data.ext.ptr = (char __force *)buf - + sizeof(struct snd_seq_event); + ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + ev->data.ext.ptr = (char __force *)buf + len; len += extlen; /* increment data length */ } else { #ifdef CONFIG_COMPAT - if (client->convert32 && snd_seq_ev_is_varusr(&event)) { - void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]); - event.data.ext.ptr = ptr; - } + if (client->convert32 && snd_seq_ev_is_varusr(ev)) + ev->data.ext.ptr = + (void __force *)compat_ptr(ev->data.raw32.d[1]); #endif } /* ok, enqueue it */ - err = snd_seq_client_enqueue_event(client, &event, file, + err = snd_seq_client_enqueue_event(client, ev, file, !(file->f_flags & O_NONBLOCK), 0, 0, &client->ioctl_mutex); if (err < 0) @@ -1146,6 +1188,7 @@ static void get_client_info(struct snd_seq_client *cptr, else info->card = -1; + info->midi_version = cptr->midi_version; memset(info->reserved, 0, sizeof(info->reserved)); } @@ -1180,12 +1223,19 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->type != client_info->type) return -EINVAL; + /* check validity of midi_version field */ + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) && + client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0) + return -EINVAL; + /* fill the info fields */ if (client_info->name[0]) strscpy(client->name, client_info->name, sizeof(client->name)); client->filter = client_info->filter; client->event_lost = client_info->event_lost; + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) + client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); return 0; @@ -2181,10 +2231,12 @@ int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, if (snd_BUG_ON(!ev)) return -EINVAL; - if (ev->type == SNDRV_SEQ_EVENT_NONE) - return 0; /* ignore this */ - if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) - return -EINVAL; /* quoted events can't be enqueued */ + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + } /* fill in client number */ ev->source.client = client; @@ -2376,6 +2428,19 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, mutex_unlock(&client->ports_mutex); } +static const char *midi_version_string(unsigned int version) +{ + switch (version) { + case SNDRV_SEQ_CLIENT_LEGACY_MIDI: + return "Legacy"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0: + return "UMP MIDI1"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0: + return "UMP MIDI2"; + default: + return "Unknown"; + } +} /* exported to seq_info.c */ void snd_seq_info_clients_read(struct snd_info_entry *entry, @@ -2400,9 +2465,10 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, continue; } - snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", + snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n", c, client->name, - client->type == USER_CLIENT ? "User" : "Kernel"); + client->type == USER_CLIENT ? "User" : "Kernel", + midi_version_string(client->midi_version)); snd_seq_info_dump_ports(buffer, client); if (snd_seq_write_pool_allocated(client)) { snd_iprintf(buffer, " Output pool :\n"); diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index abe0ceadf3da..5657f8091835 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -35,6 +35,7 @@ struct snd_seq_client { snd_seq_client_type_t type; unsigned int accept_input: 1, accept_output: 1; + unsigned int midi_version; unsigned int user_pversion; char name[64]; /* client name */ int number; /* client number */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index a8d2db439f86..174585bf59d2 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -340,6 +340,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, int ncells, err; unsigned int extlen; struct snd_seq_event_cell *cell; + int size; *cellp = NULL; @@ -357,7 +358,12 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, return err; /* copy the event */ - cell->event = *event; + size = snd_seq_event_packet_size(event); + memcpy(&cell->ump, event, size); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (size < sizeof(cell->event)) + cell->ump.raw.extra = 0; +#endif /* decompose */ if (snd_seq_ev_is_variable(event)) { @@ -375,7 +381,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, tail = NULL; while (ncells-- > 0) { - int size = sizeof(struct snd_seq_event); + size = sizeof(struct snd_seq_event); if (len < size) size = len; err = snd_seq_cell_alloc(pool, &tmp, nonblock, file, diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h index 7d7ff80f915e..7f7a2c0b187d 100644 --- a/sound/core/seq/seq_memory.h +++ b/sound/core/seq/seq_memory.h @@ -11,9 +11,26 @@ struct snd_info_buffer; +/* aliasing for legacy and UMP event packet handling */ +union __snd_seq_event { + struct snd_seq_event legacy; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_event ump; +#endif + struct { + struct snd_seq_event event; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + u32 extra; +#endif + } __packed raw; +}; + /* container for sequencer event (internal use) */ struct snd_seq_event_cell { - struct snd_seq_event event; + union { + struct snd_seq_event event; + union __snd_seq_event ump; + }; struct snd_seq_pool *pool; /* used pool */ struct snd_seq_event_cell *next; /* next cell */ }; -- cgit v1.2.3 From 74661932ac5ecb12e4378f41083be6ac17804e71 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:47 +0200 Subject: ALSA: seq: Add port inactive flag This extends the ALSA sequencer port capability bit to indicate the "inactive" flag. When this flag is set, the port is essentially invisible, and doesn't appear in the port query ioctls, while the direct access and the connection to this port are still allowed. The active/inactive state can be flipped dynamically, so that it can be visible at any time later. This feature is introduced basically for UMP; some UMP Groups in a UMP Block may be unassigned, hence those are practically invisible. On ALSA sequencer, the corresponding sequencer ports will get this new "inactive" flag to indicate the invisible state. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-27-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 2 ++ sound/core/seq/seq_ports.c | 4 ++++ 3 files changed, 7 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index b87950cbfb79..c6ca6609790b 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -427,6 +427,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_CAP_SUBS_READ (1<<5) /* allow read subscription */ #define SNDRV_SEQ_PORT_CAP_SUBS_WRITE (1<<6) /* allow write subscription */ #define SNDRV_SEQ_PORT_CAP_NO_EXPORT (1<<7) /* routing not allowed */ +#define SNDRV_SEQ_PORT_CAP_INACTIVE (1<<8) /* inactive port */ /* port type */ #define SNDRV_SEQ_PORT_TYPE_SPECIFIC (1<<0) /* hardware specific */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 801d5eee21eb..6508ce63f761 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2416,6 +2416,8 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, mutex_lock(&client->ports_mutex); list_for_each_entry(p, &client->ports_list_head, list) { + if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) + continue; snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", p->addr.port, p->name, FLAG_PERM_RD(p->capability), diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 500b1a5a9679..42f4172d4766 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -69,11 +69,15 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl { int num; struct snd_seq_client_port *port, *found; + bool check_inactive = (pinfo->capability & SNDRV_SEQ_PORT_CAP_INACTIVE); num = pinfo->addr.port; found = NULL; read_lock(&client->ports_lock); list_for_each_entry(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ if (port->addr.port < num) continue; if (port->addr.port == num) { -- cgit v1.2.3 From 177ccf811df4a893df339a72dc732bb26b66d055 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:48 +0200 Subject: ALSA: seq: Support MIDI 2.0 UMP Endpoint port This is an extension to ALSA sequencer infrastructure to support the MIDI 2.0 UMP Endpoint port. It's a "catch-all" port that is supposed to be present for each UMP Endpoint. When this port is read via subscription, it sends any events from all ports (UMP Groups) found in the same client. A UMP Endpoint port can be created with the new capability bit SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT. Although the port assignment isn't strictly defined, it should be the port number 0. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-28-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 47 ++++++++++++++++++++++++++++++++++------- sound/core/seq/seq_clientmgr.h | 1 + 3 files changed, 41 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c6ca6609790b..67532c46b115 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -428,6 +428,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_CAP_SUBS_WRITE (1<<6) /* allow write subscription */ #define SNDRV_SEQ_PORT_CAP_NO_EXPORT (1<<7) /* routing not allowed */ #define SNDRV_SEQ_PORT_CAP_INACTIVE (1<<8) /* inactive port */ +#define SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT (1<<9) /* MIDI 2.0 UMP Endpoint port */ /* port type */ #define SNDRV_SEQ_PORT_TYPE_SPECIFIC (1<<0) /* hardware specific */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 6508ce63f761..061b3e2bece1 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -239,6 +239,7 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) mutex_init(&client->ports_mutex); INIT_LIST_HEAD(&client->ports_list_head); mutex_init(&client->ioctl_mutex); + client->ump_endpoint_port = -1; /* find free slot in the client table */ spin_lock_irq(&clients_lock); @@ -680,20 +681,17 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, /* * send the event to all subscribers: */ -static int deliver_to_subscribers(struct snd_seq_client *client, - struct snd_seq_event *event, - int atomic, int hop) +static int __deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + struct snd_seq_client_port *src_port, + int atomic, int hop) { struct snd_seq_subscribers *subs; int err, result = 0, num_ev = 0; - struct snd_seq_client_port *src_port; union __snd_seq_event event_saved; size_t saved_size; struct snd_seq_port_subs_info *grp; - src_port = snd_seq_port_use_ptr(client, event->source.port); - if (src_port == NULL) - return -EINVAL; /* invalid source port */ /* save original event record */ saved_size = snd_seq_event_packet_size(event); memcpy(&event_saved, event, saved_size); @@ -733,6 +731,31 @@ static int deliver_to_subscribers(struct snd_seq_client *client, return (result < 0) ? result : num_ev; } +static int deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_client_port *src_port; + int ret = 0, ret2; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port) { + ret = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + } + + if (client->ump_endpoint_port < 0 || + event->source.port == client->ump_endpoint_port) + return ret; + + src_port = snd_seq_port_use_ptr(client, client->ump_endpoint_port); + if (!src_port) + return ret; + ret2 = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + return ret2 < 0 ? ret2 : ret; +} + /* deliver an event to the destination port(s). * if the event is to subscribers or broadcast, the event is dispatched * to multiple targets. @@ -1257,6 +1280,9 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) return -EPERM; if (client->type == USER_CLIENT && info->kernel) return -EINVAL; + if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) && + client->ump_endpoint_port >= 0) + return -EBUSY; if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) port_idx = info->addr.port; @@ -1286,6 +1312,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) info->addr = port->addr; snd_seq_set_port_info(port, info); + if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) + client->ump_endpoint_port = port->addr.port; snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); snd_seq_port_unlock(port); @@ -1305,8 +1333,11 @@ static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg) return -EPERM; err = snd_seq_delete_port(client, info->addr.port); - if (err >= 0) + if (err >= 0) { + if (client->ump_endpoint_port == info->addr.port) + client->ump_endpoint_port = -1; snd_seq_system_client_ev_port_exit(client->number, info->addr.port); + } return err; } diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 5657f8091835..bb973d36ce78 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -50,6 +50,7 @@ struct snd_seq_client { struct mutex ports_mutex; struct mutex ioctl_mutex; int convert32; /* convert 32->64bit */ + int ump_endpoint_port; /* output pool */ struct snd_seq_pool *pool; /* memory pool for this client */ -- cgit v1.2.3 From ff166a9d19fab3d77f50e9413df046fb1d7c01cc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:49 +0200 Subject: ALSA: seq: Add port direction to snd_seq_port_info Add a new field "direction" to snd_seq_port_info for allowing a client to tell the expected direction of the port access. A port might still allow subscriptions for read/write (e.g. for MIDI-CI) even if the primary usage of the port is a single direction (either input or output only). This new "direction" field can help to indicate such cases. When the direction is unspecified at creating a port and the port has either read or write capability, the corresponding direction bits are set automatically as default. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-29-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 9 ++++++++- sound/core/seq/seq_clientmgr.c | 16 ++++++++++++++-- sound/core/seq/seq_dummy.c | 1 + sound/core/seq/seq_midi.c | 4 ++++ sound/core/seq/seq_ports.c | 13 +++++++++++++ sound/core/seq/seq_ports.h | 2 ++ sound/core/seq/seq_virmidi.c | 1 + 7 files changed, 43 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 67532c46b115..eae1e0b0bf37 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -455,6 +455,12 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_FLG_TIMESTAMP (1<<1) #define SNDRV_SEQ_PORT_FLG_TIME_REAL (1<<2) +/* port direction */ +#define SNDRV_SEQ_PORT_DIR_UNKNOWN 0 +#define SNDRV_SEQ_PORT_DIR_INPUT 1 +#define SNDRV_SEQ_PORT_DIR_OUTPUT 2 +#define SNDRV_SEQ_PORT_DIR_BIDIRECTION 3 + struct snd_seq_port_info { struct snd_seq_addr addr; /* client/port numbers */ char name[64]; /* port name */ @@ -471,7 +477,8 @@ struct snd_seq_port_info { void *kernel; /* reserved for kernel use (must be NULL) */ unsigned int flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + char reserved[58]; /* for future use */ }; diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 061b3e2bece1..33aa6c5c5c9e 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2440,6 +2440,17 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, #define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') +static const char *port_direction_name(unsigned char dir) +{ + static const char *names[4] = { + "-", "In", "Out", "In/Out" + }; + + if (dir > SNDRV_SEQ_PORT_DIR_BIDIRECTION) + return "Invalid"; + return names[dir]; +} + static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, struct snd_seq_client *client) { @@ -2449,12 +2460,13 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, list_for_each_entry(p, &client->ports_list_head, list) { if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) continue; - snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]\n", p->addr.port, p->name, FLAG_PERM_RD(p->capability), FLAG_PERM_WR(p->capability), FLAG_PERM_EX(p->capability), - FLAG_PERM_DUPLEX(p->capability)); + FLAG_PERM_DUPLEX(p->capability), + port_direction_name(p->direction)); snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); } diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c index 8c18d8c4177e..2e8844ee32ed 100644 --- a/sound/core/seq/seq_dummy.c +++ b/sound/core/seq/seq_dummy.c @@ -127,6 +127,7 @@ create_port(int idx, int type) pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; if (duplex) pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_SOFTWARE | SNDRV_SEQ_PORT_TYPE_PORT; diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 2b5fff80de58..44302d98950e 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -367,6 +367,10 @@ snd_seq_midisynth_probe(struct device *_dev) if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (port->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_HARDWARE | SNDRV_SEQ_PORT_TYPE_PORT; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 42f4172d4766..5574341f49eb 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -356,6 +356,16 @@ int snd_seq_set_port_info(struct snd_seq_client_port * port, port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; port->time_queue = info->time_queue; + /* direction */ + port->direction = info->direction; + /* fill default port direction */ + if (!port->direction) { + if (info->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (info->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + return 0; } @@ -393,6 +403,9 @@ int snd_seq_get_port_info(struct snd_seq_client_port * port, info->time_queue = port->time_queue; } + /* direction */ + info->direction = port->direction; + return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 44f0e9e96bbf..dce733ab2398 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -72,6 +72,8 @@ struct snd_seq_client_port { int midi_voices; int synth_voices; + /* direction */ + unsigned char direction; }; struct snd_seq_client; diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index f5cae49500c8..1b9260108e48 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c @@ -385,6 +385,7 @@ static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev) pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_SOFTWARE | SNDRV_SEQ_PORT_TYPE_PORT; -- cgit v1.2.3 From a3ca3b30800da0a334e2d6eb68d123ec8e2d2bf6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:50 +0200 Subject: ALSA: seq: Add UMP group number to snd_seq_port_info Add yet more new filed "ump_group" to snd_seq_port_info for specifying the associated UMP Group number for each sequencer port. This will be referred in the upcoming automatic UMP conversion in sequencer core. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-30-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 3 ++- sound/core/seq/seq_ports.c | 9 +++++++-- sound/core/seq/seq_ports.h | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index eae1e0b0bf37..2470eaa5edc5 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -478,7 +478,8 @@ struct snd_seq_port_info { unsigned int flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ unsigned char direction; /* port usage direction (r/w/bidir) */ - char reserved[58]; /* for future use */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 5574341f49eb..9b80f8275026 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -356,8 +356,12 @@ int snd_seq_set_port_info(struct snd_seq_client_port * port, port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; port->time_queue = info->time_queue; - /* direction */ + /* UMP direction and group */ port->direction = info->direction; + port->ump_group = info->ump_group; + if (port->ump_group > SNDRV_UMP_MAX_GROUPS) + port->ump_group = 0; + /* fill default port direction */ if (!port->direction) { if (info->capability & SNDRV_SEQ_PORT_CAP_READ) @@ -403,8 +407,9 @@ int snd_seq_get_port_info(struct snd_seq_client_port * port, info->time_queue = port->time_queue; } - /* direction */ + /* UMP direction and group */ info->direction = port->direction; + info->ump_group = port->ump_group; return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index dce733ab2398..c6c138edceab 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -72,8 +72,9 @@ struct snd_seq_client_port { int midi_voices; int synth_voices; - /* direction */ + /* UMP direction and group */ unsigned char direction; + unsigned char ump_group; }; struct snd_seq_client; -- cgit v1.2.3 From e9e02819a98a50fefe2f8016b1e5237742637cd1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:51 +0200 Subject: ALSA: seq: Automatic conversion of UMP events This patch enables the automatic conversion of UMP events from/to the legacy ALSA sequencer MIDI events. Also, as UMP itself has two different modes (MIDI 1.0 and MIDI 2.0), yet another converters between them are needed, too. Namely, we have conversions between the legacy and UMP like: - seq legacy event -> seq UMP MIDI 1.0 event - seq legacy event -> seq UMP MIDI 2.0 event - seq UMP MIDI 1.0 event -> seq legacy event - seq UMP MIDI 2.0 event -> seq legacy event and the conversions between UMP MIDI 1.0 and 2.0 clients like: - seq UMP MIDI 1.0 event -> seq UMP MIDI 2.0 event - seq UMP MIDI 2.0 event -> seq UMP MIDI 1.0 event The translation is per best-effort; some MIDI 2.0 specific events are ignored when translated to MIDI 1.0. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-31-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/Kconfig | 2 + sound/core/seq/Makefile | 1 + sound/core/seq/seq_clientmgr.c | 48 +- sound/core/seq/seq_clientmgr.h | 15 + sound/core/seq/seq_ports.h | 15 + sound/core/seq/seq_ump_convert.c | 1190 ++++++++++++++++++++++++++++++++++++++ sound/core/seq/seq_ump_convert.h | 22 + 7 files changed, 1279 insertions(+), 14 deletions(-) create mode 100644 sound/core/seq/seq_ump_convert.c create mode 100644 sound/core/seq/seq_ump_convert.h (limited to 'sound') diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index c69d8beb09fa..f8336134153e 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -66,5 +66,7 @@ config SND_SEQ_UMP Say Y here to enable the support for handling UMP (Universal MIDI Packet) events via ALSA sequencer infrastructure, which is an essential feature for enabling MIDI 2.0 support. + It includes the automatic conversion of ALSA sequencer events + among legacy and UMP clients. endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile index 3a2177a7e50c..ba264a695643 100644 --- a/sound/core/seq/Makefile +++ b/sound/core/seq/Makefile @@ -8,6 +8,7 @@ snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ seq_fifo.o seq_prioq.o seq_timer.o \ seq_system.o seq_ports.o snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o +snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o snd-seq-midi-objs := seq_midi.o snd-seq-midi-emul-objs := seq_midi_emul.o snd-seq-midi-event-objs := seq_midi_event.o diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 33aa6c5c5c9e..07b090f76b5f 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -20,6 +20,7 @@ #include "seq_timer.h" #include "seq_info.h" #include "seq_system.h" +#include "seq_ump_convert.h" #include #ifdef CONFIG_COMPAT #include @@ -612,6 +613,27 @@ static int update_timestamp_of_queue(struct snd_seq_event *event, return 1; } +/* deliver a single event; called from below and UMP converter */ +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + switch (dest->type) { + case USER_CLIENT: + if (!dest->data.user.fifo) + return 0; + return snd_seq_fifo_event_in(dest->data.user.fifo, event); + case KERNEL_CLIENT: + if (!dest_port->event_input) + return 0; + return dest_port->event_input(event, + snd_seq_ev_is_direct(event), + dest_port->private_data, + atomic, hop); + } + return 0; +} /* * deliver an event to the specified destination. @@ -648,22 +670,20 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, update_timestamp_of_queue(event, dest_port->time_queue, dest_port->time_real); - switch (dest->type) { - case USER_CLIENT: - if (dest->data.user.fifo) - result = snd_seq_fifo_event_in(dest->data.user.fifo, event); - break; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (snd_seq_ev_is_ump(event)) { + result = snd_seq_deliver_from_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } else if (snd_seq_client_is_ump(dest)) { + result = snd_seq_deliver_to_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } +#endif /* CONFIG_SND_SEQ_UMP */ - case KERNEL_CLIENT: - if (dest_port->event_input == NULL) - break; - result = dest_port->event_input(event, direct, - dest_port->private_data, + result = __snd_seq_deliver_single_event(dest, dest_port, event, atomic, hop); - break; - default: - break; - } __skip: if (dest_port) diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index bb973d36ce78..97762892ffab 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -85,6 +85,11 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table int snd_seq_client_notify_subscription(int client, int port, struct snd_seq_port_subscribe *info, int evtype); +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + /* only for OSS sequencer */ bool snd_seq_client_ioctl_lock(int clientid); void snd_seq_client_ioctl_unlock(int clientid); @@ -95,4 +100,14 @@ extern int seq_client_load[15]; struct snd_seq_client *snd_seq_kernel_client_get(int client); void snd_seq_kernel_client_put(struct snd_seq_client *cptr); +static inline bool snd_seq_client_is_ump(struct snd_seq_client *c) +{ + return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI; +} + +static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c) +{ + return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; +} + #endif diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index c6c138edceab..b111382f697a 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -42,6 +42,17 @@ struct snd_seq_port_subs_info { int (*close)(void *private_data, struct snd_seq_port_subscribe *info); }; +/* context for converting from legacy control event to UMP packet */ +struct snd_seq_ump_midi2_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + struct snd_seq_client_port { struct snd_seq_addr addr; /* client/port number */ @@ -75,6 +86,10 @@ struct snd_seq_client_port { /* UMP direction and group */ unsigned char direction; unsigned char ump_group; + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_midi2_bank midi2_bank[16]; /* per channel */ +#endif }; struct snd_seq_client; diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c new file mode 100644 index 000000000000..433fe842947e --- /dev/null +++ b/sound/core/seq/seq_ump_convert.c @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ + +#include +#include +#include +#include +#include +#include +#include "seq_ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +static unsigned char get_ump_group(struct snd_seq_client_port *port) +{ + return port->ump_group ? (port->ump_group - 1) : 0; +} + +/* create a UMP header */ +#define make_raw_ump(port, type) \ + ump_compose(type, get_ump_group(port), 0, 0) + +/* + * UMP -> MIDI1 sequencer event + */ + +/* MIDI 1.0 CVM */ + +/* encode note event */ +static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = val->note.velocity; +} + +/* encode one parameter controls */ +static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = val->caf.data; +} + +/* encode pitch wheel change */ +static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb; + ev->data.control.value -= 8192; +} + +/* encode midi control change */ +static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = val->cc.data; +} + +/* Encoding MIDI 1.0 UMP packet */ +struct seq_ump_midi1_to_ev { + int seq_type; + void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI1 status 0x80-0xe0 */ +static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi1_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi1_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi1_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi1_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi1_to_ctrl_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi1_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi1_to_pitchbend_ev}, /* 0xe0 */ +}; + +static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + if (status < 0x8 || status > 0xe) + return 0; /* invalid - skip */ + status -= 8; + ev->type = midi1_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + midi1_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI System message */ + +/* encode one parameter value*/ +static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = val->system.parm1; +} + +/* encode song position */ +static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = (val->system.parm1 << 7) | val->system.parm2; +} + +/* Encoders for 0xf0 - 0xff */ +static struct seq_ump_midi1_to_ev system_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, ump_system_to_one_param_ev}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, ump_system_to_songpos_ev}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, ump_system_to_one_param_ev}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, NULL}, /* 0xff */ +}; + +static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->system.status; + + if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME) + return 0; /* invalid status - skip */ + status &= 0x0f; + ev->type = system_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; + if (system_msg_encoders[status].encode) + system_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI 2.0 CVM */ + +/* encode note event */ +static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity); + /* correct note-on velocity 0 to 1; + * it's no longer equivalent as not-off for MIDI 2.0 + */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && + !ev->data.note.velocity) + ev->data.note.velocity = 1; + return 1; +} + +/* encode pitch wheel change */ +static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = downscale_32_to_14bit(val->pb.data); + ev->data.control.value -= 8192; + return 1; +} + +/* encode midi control change */ +static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = downscale_32_to_7bit(val->cc.data); + return 1; +} + +/* encode midi program change */ +static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + int size = 1; + + ev->data.control.channel = val->pg.channel; + if (val->pg.bank_valid) { + ev->type = SNDRV_SEQ_EVENT_CONTROL14; + ev->data.control.param = UMP_CC_BANK_SELECT; + ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb; + ev[1] = ev[0]; + ev++; + ev->type = SNDRV_SEQ_EVENT_PGMCHANGE; + size = 2; + } + ev->data.control.value = val->pg.program; + return size; +} + +/* encode one parameter controls */ +static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = downscale_32_to_7bit(val->caf.data); + return 1; +} + +/* encode RPN/NRPN */ +static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->rpn.channel; + ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index; + ev->data.control.value = downscale_32_to_14bit(val->rpn.data); + return 1; +} + +/* Encoding MIDI 2.0 UMP Packet */ +struct seq_ump_midi2_to_ev { + int seq_type; + int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI2 status 0x00-0xf0 */ +static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x00 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x10 */ + {SNDRV_SEQ_EVENT_REGPARAM, ump_midi2_to_rpn_ev}, /* 0x20 */ + {SNDRV_SEQ_EVENT_NONREGPARAM, ump_midi2_to_rpn_ev}, /* 0x30 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x40 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x50 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x60 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x70 */ + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi2_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi2_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi2_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi2_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi2_to_pgm_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi2_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi2_to_pitchbend_ev}, /* 0xe0 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ +}; + +static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + ev->type = midi2_msg_encoders[status].seq_type; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* skip */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + return midi2_msg_encoders[status].encode(val, ev); +} + +/* parse and compose for a sysex var-length event */ +static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf, + struct snd_seq_event *ev) +{ + unsigned char status; + unsigned char bytes; + u32 val; + int size = 0; + + val = data[0]; + status = ump_sysex_message_status(val); + bytes = ump_sysex_message_length(val); + if (bytes > 6) + return 0; // skip + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + if (bytes > 0) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 1) + buf[size++] = val & 0x7f; + val = data[1]; + if (bytes > 2) + buf[size++] = (val >> 24) & 0x7f; + if (bytes > 3) + buf[size++] = (val >> 16) & 0x7f; + if (bytes > 4) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 5) + buf[size++] = val & 0x7f; + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->data.ext.len = size; + ev->data.ext.ptr = buf; + return 1; +} + +/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */ +static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE; + midi2->note.group = midi1->note.group; + midi2->note.status = midi1->note.status; + midi2->note.channel = midi1->note.channel; + switch (midi1->note.status) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = midi1->note.note; + midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = midi1->paf.note; + midi2->paf.data = upscale_7_to_32bit(midi1->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi2->cc.index = midi1->cc.index; + midi2->cc.data = upscale_7_to_32bit(midi1->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = midi1->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(midi1->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) | + midi1->pb.data_lsb); + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */ +static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump; + const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump; + u16 v; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE; + midi1->note.group = midi2->note.group; + midi1->note.status = midi2->note.status; + midi1->note.channel = midi2->note.channel; + switch (midi2->note.status << 4) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi1->note.note = midi2->note.note; + midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi1->paf.note = midi2->paf.note; + midi1->paf.data = downscale_32_to_7bit(midi2->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi1->cc.index = midi2->cc.index; + midi1->cc.data = downscale_32_to_7bit(midi2->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi1->pg.program = midi2->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi1->caf.data = downscale_32_to_7bit(midi2->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + midi1->pb.data_msb = v >> 7; + midi1->pb.data_lsb = v & 0x7f; + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP to a legacy ALSA seq event and deliver it */ +static int cvt_ump_to_any(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + unsigned char type, + int atomic, int hop) +{ + struct snd_seq_event ev_cvt[2]; /* up to two events */ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + /* use the second event as a temp buffer for saving stack usage */ + unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1); + unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP; + int i, len, err; + + ev_cvt[0] = ev_cvt[1] = *event; + ev_cvt[0].flags = flags; + ev_cvt[1].flags = flags; + switch (type) { + case UMP_MSG_TYPE_SYSTEM: + len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_DATA: + len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt); + break; + default: + return 0; + } + + for (i = 0; i < len; i++) { + err = __snd_seq_deliver_single_event(dest, dest_port, + &ev_cvt[i], atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Replace UMP group field with the destination and deliver */ +static int deliver_with_group_convert(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_ump_event *ump_ev, + int atomic, int hop) +{ + struct snd_seq_ump_event ev = *ump_ev; + + /* rewrite the group to the destination port */ + ev.ump[0] &= ~(0xfU << 24); + /* fill with the new group; the dest_port->ump_group field is 1-based */ + ev.ump[0] |= ((dest_port->ump_group - 1) << 24); + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev, + atomic, hop); +} + +/* Convert from UMP packet and deliver */ +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + unsigned char type; + + if (snd_seq_ev_is_variable(event)) + return 0; // skip, no variable event for UMP, so far + type = ump_message_type(ump_ev->ump[0]); + + if (snd_seq_client_is_ump(dest)) { + if (snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE) + return cvt_ump_midi1_to_midi2(dest, dest_port, + event, atomic, hop); + else if (!snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE) + return cvt_ump_midi2_to_midi1(dest, dest_port, + event, atomic, hop); + /* non-EP port and different group is set? */ + if (dest_port->ump_group && + ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) + return deliver_with_group_convert(dest, dest_port, + ump_ev, atomic, hop); + /* copy as-is */ + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + } + + return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop); +} + +/* + * MIDI1 sequencer event -> UMP conversion + */ + +/* Conversion to UMP MIDI 1.0 */ + +/* convert note on/off event to MIDI 1.0 UMP */ +static int note_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.velocity = event->data.note.velocity & 0x7f; + data->note.note = event->data.note.note & 0x7f; + return 1; +} + +/* convert CC event to MIDI 1.0 UMP */ +static int cc_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = status; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param; + data->cc.data = event->data.control.value; + return 1; +} + +/* convert one-parameter control event to MIDI 1.0 UMP */ +static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert pitchbend event to MIDI 1.0 UMP */ +static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data_msb = (val >> 7) & 0x7f; + data->pb.data_lsb = val & 0x7f; + return 1; +} + +/* convert 14bit control event to MIDI 1.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param & 0x7f; + if (event->data.control.param < 0x20) { + data->cc.data = (event->data.control.value >> 7) & 0x7f; + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = event->data.control.value & 0x7f; + return 2; + } + + data->cc.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */ +static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + bool is_rpn = (status == UMP_MSG_STATUS_RPN); + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data[1] = data[2] = data[3] = data[0]; + + data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + data[0].cc.data = (event->data.control.param >> 7) & 0x7f; + data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + data[1].cc.data = event->data.control.param & 0x7f; + data[2].cc.index = UMP_CC_DATA; + data[2].cc.data = (event->data.control.value >> 7) & 0x7f; + data[3].cc.index = UMP_CC_DATA_LSB; + data[3].cc.data = event->data.control.value & 0x7f; + return 4; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + return 1; +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = event->data.control.value & 0x7f; + return 1; +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = (event->data.control.value >> 7) & 0x7f; + data->system.parm1 = event->data.control.value & 0x7f; + return 1; +} + +/* Conversion to UMP MIDI 2.0 */ + +/* convert note on/off event to MIDI 2.0 UMP */ +static int note_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.note = event->data.note.note & 0x7f; + data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* convert PAF event to MIDI 2.0 UMP */ +static int paf_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->paf.status = status; + data->paf.channel = event->data.note.channel & 0x0f; + data->paf.note = event->data.note.note & 0x7f; + data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* set up the MIDI2 RPN/NRPN packet data from the parsed info */ +static void fill_rpn(struct snd_seq_ump_midi2_bank *cc, + union snd_ump_midi2_msg *data) +{ + if (cc->rpn_set) { + data->rpn.status = UMP_MSG_STATUS_RPN; + data->rpn.bank = cc->cc_rpn_msb; + data->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + data->rpn.status = UMP_MSG_STATUS_NRPN; + data->rpn.bank = cc->cc_nrpn_msb; + data->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert CC event to MIDI 2.0 UMP */ +static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + unsigned char val = event->data.control.value & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = val; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = val; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = val; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = val; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = val; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = val; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = status; + data->cc.channel = channel; + data->cc.index = index; + data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert one-parameter control event to MIDI 2.0 UMP */ +static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert program change event to MIDI 2.0 UMP */ +static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + data->pg.status = status; + data->pg.channel = channel; + data->pg.program = event->data.control.value & 0x7f; + if (cc->bank_set) { + data->pg.bank_valid = 1; + data->pg.bank_msb = cc->cc_bank_msb; + data->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + return 1; +} + +/* convert pitchbend event to MIDI 2.0 UMP */ +static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data = upscale_14_to_32bit(val); + return 1; +} + +/* convert 14bit control event to MIDI 2.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + unsigned char msb, lsb; + + msb = (event->data.control.value >> 7) & 0x7f; + lsb = event->data.control.value & 0x7f; + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_BANK_SELECT: + cc->cc_bank_msb = msb; + fallthrough; + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = lsb; + return 0; // skip + case UMP_CC_RPN_MSB: + cc->cc_rpn_msb = msb; + fallthrough; + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = lsb; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->cc_nrpn_msb = msb; + fallthrough; + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = lsb; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = msb; + fallthrough; + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = lsb; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = channel; + data->cc.index = index; + if (event->data.control.param < 0x20) { + data->cc.data = upscale_7_to_32bit(msb); + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = upscale_7_to_32bit(lsb); + return 2; + } + + data->cc.data = upscale_7_to_32bit(lsb); + return 1; +} + +/* convert RPN/NRPN event to MIDI 2.0 UMP */ +static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->rpn.status = status; + data->rpn.channel = event->data.control.channel; + data->rpn.bank = (event->data.control.param >> 7) & 0x7f; + data->rpn.index = event->data.control.param & 0x7f; + data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff); + return 1; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +struct seq_ev_to_ump { + int seq_type; + unsigned char status; + int (*midi1_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status); + int (*midi2_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status); +}; + +static const struct seq_ev_to_ump seq_ev_ump_encoders[] = { + { SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE, + note_ev_to_ump_midi1, paf_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC, + cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM, + ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE, + ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND, + pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROL14, 0, + ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION, + system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, +}; + +static const struct seq_ev_to_ump *find_ump_encoder(int type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++) + if (seq_ev_ump_encoders[i].seq_type == type) + return &seq_ev_ump_encoders[i]; + + return NULL; +} + +static void setup_ump_event(struct snd_seq_ump_event *dest, + const struct snd_seq_event *src) +{ + memcpy(dest, src, sizeof(*src)); + dest->type = 0; + dest->flags |= SNDRV_SEQ_EVENT_UMP; + dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + memset(dest->ump, 0, sizeof(dest->ump)); +} + +/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */ +static int cvt_to_ump_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg data[4]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE); + n = encoder->midi1_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + ev_cvt.ump[0] = data[i].raw; + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */ +static int cvt_to_ump_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi2_msg data[2]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE); + data->raw[1] = 0; + n = encoder->midi2_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + memcpy(ev_cvt.ump, &data[i], sizeof(data[i])); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Fill up a sysex7 UMP from the byte stream */ +static void fill_sysex7_ump(struct snd_seq_client_port *dest_port, + u32 *val, u8 status, u8 *buf, int len) +{ + memset(val, 0, 8); + memcpy((u8 *)val + 2, buf, len); +#ifdef __LITTLE_ENDIAN + swab32_array(val, 2); +#endif + val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port), + status, len); +} + +/* Convert sysex var event to UMP sysex7 packets and deliver them */ +static int cvt_sysex_to_ump(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event ev_cvt; + unsigned char status; + u8 buf[6], *xbuf; + int offset = 0; + int len, err; + + if (!snd_seq_ev_is_variable(event)) + return 0; + + setup_ump_event(&ev_cvt, event); + for (;;) { + len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset); + if (len <= 0) + break; + if (WARN_ON(len > 6)) + break; + offset += len; + xbuf = buf; + if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) { + status = UMP_SYSEX_STATUS_START; + xbuf++; + len--; + if (len > 0 && xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_SINGLE; + len--; + } + } else { + if (xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_END; + len--; + } else { + status = UMP_SYSEX_STATUS_CONTINUE; + } + } + fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + return 0; +} + +/* Convert to UMP packet and deliver */ +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + if (event->type == SNDRV_SEQ_EVENT_SYSEX) + return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop); + else if (snd_seq_client_is_midi2(dest)) + return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop); + else + return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop); +} diff --git a/sound/core/seq/seq_ump_convert.h b/sound/core/seq/seq_ump_convert.h new file mode 100644 index 000000000000..6c146d803280 --- /dev/null +++ b/sound/core/seq/seq_ump_convert.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ +#ifndef __SEQ_UMP_CONVERT_H +#define __SEQ_UMP_CONVERT_H + +#include "seq_clientmgr.h" +#include "seq_ports.h" + +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + +#endif /* __SEQ_UMP_CONVERT_H */ -- cgit v1.2.3 From 329ffe11a014834fdef9167c7ea24bd459829f86 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:52 +0200 Subject: ALSA: seq: Allow suppressing UMP conversions A sequencer client like seq_dummy rather doesn't want to convert UMP events but receives / sends as is. Add a new event filter flag to suppress the automatic UMP conversion and applies accordingly. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-32-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 18 ++++++++++-------- sound/core/seq/seq_dummy.c | 8 ++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 2470eaa5edc5..c4632bd9d3a0 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -347,6 +347,7 @@ typedef int __bitwise snd_seq_client_type_t; #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ #define SNDRV_SEQ_FILTER_MULTICAST (1U<<1) /* accept multicast messages */ #define SNDRV_SEQ_FILTER_BOUNCE (1U<<2) /* accept bounce event in error */ +#define SNDRV_SEQ_FILTER_NO_CONVERT (1U<<30) /* don't convert UMP events */ #define SNDRV_SEQ_FILTER_USE_EVENT (1U<<31) /* use event filter */ struct snd_seq_client_info { diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 07b090f76b5f..3b1adcb1ccdd 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -671,14 +671,16 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, dest_port->time_real); #if IS_ENABLED(CONFIG_SND_SEQ_UMP) - if (snd_seq_ev_is_ump(event)) { - result = snd_seq_deliver_from_ump(client, dest, dest_port, - event, atomic, hop); - goto __skip; - } else if (snd_seq_client_is_ump(dest)) { - result = snd_seq_deliver_to_ump(client, dest, dest_port, - event, atomic, hop); - goto __skip; + if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT)) { + if (snd_seq_ev_is_ump(event)) { + result = snd_seq_deliver_from_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } else if (snd_seq_client_is_ump(dest)) { + result = snd_seq_deliver_to_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } } #endif /* CONFIG_SND_SEQ_UMP */ diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c index 2e8844ee32ed..9308194b2d9a 100644 --- a/sound/core/seq/seq_dummy.c +++ b/sound/core/seq/seq_dummy.c @@ -152,6 +152,7 @@ static int __init register_client(void) { struct snd_seq_dummy_port *rec1, *rec2; + struct snd_seq_client *client; int i; if (ports < 1) { @@ -165,6 +166,13 @@ register_client(void) if (my_client < 0) return my_client; + /* don't convert events but just pass-through */ + client = snd_seq_kernel_client_get(my_client); + if (!client) + return -EINVAL; + client->filter = SNDRV_SEQ_FILTER_NO_CONVERT; + snd_seq_kernel_client_put(client); + /* create ports */ for (i = 0; i < ports; i++) { rec1 = create_port(i, 0); -- cgit v1.2.3 From 81fd444aa371261cd33f31d4ffd80faeeeab0cc9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:53 +0200 Subject: ALSA: seq: Bind UMP device This patch introduces a new ALSA sequencer client for the kernel UMP object, snd-seq-ump-client. It's a UMP version of snd-seq-midi driver, while this driver creates a sequencer client per UMP endpoint which contains (fixed) 16 ports. The UMP rawmidi device is opened in APPEND mode for output, so that multiple sequencer clients can share the same UMP endpoint, as well as the legacy UMP rawmidi devices that are opened in APPEND mode, too. For input, on the other hand, the incoming data is processed on the fly in the dedicated hook, hence it doesn't open a rawmidi device. The UMP packet group is updated upon delivery depending on the target sequencer port (which corresponds to the actual UMP group). Each sequencer port sets a new port type bit, SNDRV_SEQ_PORT_TYPE_MIDI_UMP, in addition to the other standard types for MIDI. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-33-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/seq_device.h | 1 + include/sound/ump.h | 15 +- include/uapi/sound/asequencer.h | 1 + sound/core/seq/Kconfig | 5 + sound/core/seq/Makefile | 2 + sound/core/seq/seq_ump_client.c | 389 ++++++++++++++++++++++++++++++++++++++++ sound/core/ump.c | 28 ++- 7 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 sound/core/seq/seq_ump_client.c (limited to 'sound') diff --git a/include/sound/seq_device.h b/include/sound/seq_device.h index 8899affe9155..dead74b022f4 100644 --- a/include/sound/seq_device.h +++ b/include/sound/seq_device.h @@ -78,5 +78,6 @@ void snd_seq_driver_unregister(struct snd_seq_driver *drv); */ #define SNDRV_SEQ_DEV_ID_MIDISYNTH "seq-midi" #define SNDRV_SEQ_DEV_ID_OPL3 "opl3-synth" +#define SNDRV_SEQ_DEV_ID_UMP "seq-ump-client" #endif /* __SOUND_SEQ_DEVICE_H */ diff --git a/include/sound/ump.h b/include/sound/ump.h index 45f4c9b673b5..e4fdf7cccf12 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -11,6 +11,7 @@ struct snd_ump_endpoint; struct snd_ump_block; struct snd_ump_ops; struct ump_cvt_to_ump; +struct snd_seq_ump_ops; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ @@ -30,9 +31,9 @@ struct snd_ump_endpoint { int input_buf_head; int input_pending; -#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) struct mutex open_mutex; +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spinlock_t legacy_locks[2]; struct snd_rawmidi *legacy_rmidi; struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS]; @@ -42,6 +43,12 @@ struct snd_ump_endpoint { struct snd_rawmidi_file legacy_out_rfile; struct ump_cvt_to_ump *out_cvts; #endif + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + struct snd_seq_device *seq_dev; + const struct snd_seq_ump_ops *seq_ops; + void *seq_client; +#endif }; /* ops filled by UMP drivers */ @@ -52,6 +59,12 @@ struct snd_ump_ops { void (*drain)(struct snd_ump_endpoint *ump, int dir); }; +/* ops filled by sequencer binding */ +struct snd_seq_ump_ops { + void (*input_receive)(struct snd_ump_endpoint *ump, + const u32 *data, int words); +}; + struct snd_ump_block { struct snd_ump_block_info info; struct snd_ump_endpoint *ump; diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c4632bd9d3a0..3fa6b17aa7a2 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -439,6 +439,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_TYPE_MIDI_XG (1<<4) /* XG compatible device */ #define SNDRV_SEQ_PORT_TYPE_MIDI_MT32 (1<<5) /* MT-32 compatible device */ #define SNDRV_SEQ_PORT_TYPE_MIDI_GM2 (1<<6) /* General MIDI 2 compatible device */ +#define SNDRV_SEQ_PORT_TYPE_MIDI_UMP (1<<7) /* UMP */ /* other standards...*/ #define SNDRV_SEQ_PORT_TYPE_SYNTH (1<<10) /* Synth device (no MIDI compatible - direct wavetable) */ diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index f8336134153e..c14981daf943 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -62,6 +62,7 @@ config SND_SEQ_VIRMIDI config SND_SEQ_UMP bool "Support for UMP events" + default y if SND_SEQ_UMP_CLIENT help Say Y here to enable the support for handling UMP (Universal MIDI Packet) events via ALSA sequencer infrastructure, which is an @@ -69,4 +70,8 @@ config SND_SEQ_UMP It includes the automatic conversion of ALSA sequencer events among legacy and UMP clients. +config SND_SEQ_UMP_CLIENT + tristate + def_tristate SND_UMP + endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile index ba264a695643..990eec7c83ad 100644 --- a/sound/core/seq/Makefile +++ b/sound/core/seq/Makefile @@ -14,12 +14,14 @@ snd-seq-midi-emul-objs := seq_midi_emul.o snd-seq-midi-event-objs := seq_midi_event.o snd-seq-dummy-objs := seq_dummy.o snd-seq-virmidi-objs := seq_virmidi.o +snd-seq-ump-client-objs := seq_ump_client.o obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/ obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o +obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c new file mode 100644 index 000000000000..8d360655ff5d --- /dev/null +++ b/sound/core/seq/seq_ump_client.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ALSA sequencer binding for UMP device */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "seq_clientmgr.h" + +struct seq_ump_client; +struct seq_ump_group; + +enum { + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT, + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT +}; + +/* object per UMP group; corresponding to a sequencer port */ +struct seq_ump_group { + int group; /* group index (0-based) */ + unsigned int dir_bits; /* directions */ + bool active; /* activeness */ + char name[64]; /* seq port name */ +}; + +/* context for UMP input parsing, per EP */ +struct seq_ump_input_buffer { + unsigned char len; /* total length in words */ + unsigned char pending; /* pending words */ + unsigned char type; /* parsed UMP packet type */ + unsigned char group; /* parsed UMP packet group */ + u32 buf[4]; /* incoming UMP packet */ +}; + +/* sequencer client, per UMP EP (rawmidi) */ +struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ + struct seq_ump_input_buffer input; /* input parser context */ + struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ +}; + +/* number of 32bit words for each UMP message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* conversion between UMP group and seq port; + * assume the port number is equal with UMP group number (1-based) + */ +static unsigned char ump_group_to_seq_port(unsigned char group) +{ + return group + 1; +} + +/* process the incoming rawmidi stream */ +static void seq_ump_input_receive(struct snd_ump_endpoint *ump, + const u32 *val, int words) +{ + struct seq_ump_client *client = ump->seq_client; + struct snd_seq_ump_event ev = {}; + + if (!client->opened[STR_IN]) + return; + + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + ev.flags = SNDRV_SEQ_EVENT_UMP; + memcpy(ev.ump, val, words << 2); + snd_seq_kernel_client_dispatch(client->seq_client, + (struct snd_seq_event *)&ev, + true, 0); +} + +/* process an input sequencer event; only deal with UMP types */ +static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_ump_client *client = private_data; + struct snd_rawmidi_substream *substream; + struct snd_seq_ump_event *ump_ev; + unsigned char type; + int len; + + substream = client->out_rfile.output; + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) + return 0; /* invalid event, skip */ + ump_ev = (struct snd_seq_ump_event *)ev; + type = ump_message_type(ump_ev->ump[0]); + len = ump_packet_words[type]; + if (len > 4) + return 0; // invalid - skip + snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2); + return 0; +} + +/* open the rawmidi */ +static int seq_ump_client_open(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + int err = 0; + + mutex_lock(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &client->out_rfile); + if (err < 0) + goto unlock; + } + client->opened[dir]++; + unlock: + mutex_unlock(&ump->open_mutex); + return err; +} + +/* close the rawmidi */ +static int seq_ump_client_close(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + + mutex_lock(&ump->open_mutex); + if (!--client->opened[dir]) + if (dir == STR_OUT) + snd_rawmidi_kernel_release(&client->out_rfile); + mutex_unlock(&ump->open_mutex); + return 0; +} + +/* sequencer subscription ops for each client */ +static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_IN); +} + +static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_IN); +} + +static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_OUT); +} + +static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_OUT); +} + +/* fill port_info from the given UMP EP and group info */ +static void fill_port_info(struct snd_seq_port_info *port, + struct seq_ump_client *client, + struct seq_ump_group *group) +{ + unsigned int rawmidi_info = client->ump->core.info_flags; + + port->addr.client = client->seq_client; + port->addr.port = ump_group_to_seq_port(group->group); + port->capability = 0; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (group->dir_bits & (1 << STR_IN)) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (group->dir_bits & (1 << STR_OUT)) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + port->ump_group = group->group + 1; + if (!group->active) + port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + if (*group->name) + snprintf(port->name, sizeof(port->name), "Group %d (%s)", + group->group + 1, group->name); + else + sprintf(port->name, "Group %d", group->group + 1); +} + +/* create a new sequencer port per UMP group */ +static int seq_ump_group_init(struct seq_ump_client *client, int group_index) +{ + struct seq_ump_group *group = &client->groups[group_index]; + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + err = -ENOMEM; + goto error; + } + + fill_port_info(port, client, group); + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + error: + kfree(port); + return err; +} + +/* update dir_bits and active flag for all groups in the client */ +static void update_group_attrs(struct seq_ump_client *client) +{ + struct snd_ump_block *fb; + struct seq_ump_group *group; + int i; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + group = &client->groups[i]; + *group->name = 0; + group->dir_bits = 0; + group->active = 0; + group->group = i; + } + + list_for_each_entry(fb, &client->ump->block_list, list) { + if (fb->info.first_group < 0 || + fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS) + break; + group = &client->groups[fb->info.first_group]; + for (i = 0; i < fb->info.num_groups; i++, group++) { + if (fb->info.active) + group->active = 1; + switch (fb->info.direction) { + case SNDRV_UMP_DIR_INPUT: + group->dir_bits |= (1 << STR_IN); + break; + case SNDRV_UMP_DIR_OUTPUT: + group->dir_bits |= (1 << STR_OUT); + break; + case SNDRV_UMP_DIR_BIDIRECTION: + group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN); + break; + } + if (!*fb->info.name) + continue; + if (!*group->name) { + /* store the first matching name */ + strscpy(group->name, fb->info.name, + sizeof(group->name)); + } else { + /* when overlapping, concat names */ + strlcat(group->name, ", ", sizeof(group->name)); + strlcat(group->name, fb->info.name, + sizeof(group->name)); + } + } + } +} + +/* release the client resources */ +static void seq_ump_client_free(struct seq_ump_client *client) +{ + if (client->seq_client >= 0) + snd_seq_delete_kernel_client(client->seq_client); + + client->ump->seq_ops = NULL; + client->ump->seq_client = NULL; + + kfree(client); +} + +/* update the MIDI version for the given client */ +static void setup_client_midi_version(struct seq_ump_client *client) +{ + struct snd_seq_client *cptr; + + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) + return; + if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2) + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; + else + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0; + snd_seq_kernel_client_put(cptr); +} + +static const struct snd_seq_ump_ops seq_ump_ops = { + .input_receive = seq_ump_input_receive, +}; + +/* create a sequencer client and ports for the given UMP endpoint */ +static int snd_seq_ump_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + struct snd_card *card = dev->card; + struct seq_ump_client *client; + int p, err; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + client->ump = ump; + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, + ump->core.name); + if (client->seq_client < 0) { + err = client->seq_client; + goto error; + } + + setup_client_midi_version(client); + update_group_attrs(client); + + for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) { + err = seq_ump_group_init(client, p); + if (err < 0) + goto error; + } + + ump->seq_client = client; + ump->seq_ops = &seq_ump_ops; + return 0; + + error: + seq_ump_client_free(client); + return err; +} + +/* remove a sequencer client */ +static int snd_seq_ump_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + + if (ump->seq_client) + seq_ump_client_free(ump->seq_client); + return 0; +} + +static struct snd_seq_driver seq_ump_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_ump_probe, + .remove = snd_seq_ump_remove, + }, + .id = SNDRV_SEQ_DEV_ID_UMP, + .argsize = 0, +}; + +module_snd_seq_driver(seq_ump_driver); + +MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi"); +MODULE_LICENSE("GPL"); diff --git a/sound/core/ump.c b/sound/core/ump.c index cbe704b5d90d..69993cad6772 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -132,8 +132,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, if (!ump) return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); -#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) mutex_init(&ump->open_mutex); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spin_lock_init(&ump->legacy_locks[0]); spin_lock_init(&ump->legacy_locks[1]); #endif @@ -166,8 +166,30 @@ EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); * Device register / unregister hooks; * do nothing, placeholders for avoiding the default rawmidi handling */ + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_ump_dev_seq_free(struct snd_seq_device *device) +{ + struct snd_ump_endpoint *ump = device->private_data; + + ump->seq_dev = NULL; +} +#endif + static int snd_ump_dev_register(struct snd_rawmidi *rmidi) { +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + int err; + + err = snd_seq_device_new(ump->core.card, ump->core.device, + SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev); + if (err < 0) + return err; + ump->seq_dev->private_data = ump; + ump->seq_dev->private_free = snd_ump_dev_seq_free; + snd_device_register(ump->core.card, ump->seq_dev); +#endif return 0; } @@ -280,6 +302,10 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) n = snd_ump_receive_ump_val(ump, *p++); if (!n) continue; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops) + ump->seq_ops->input_receive(ump, ump->input_buf, n); +#endif process_legacy_input(ump, ump->input_buf, n); } -- cgit v1.2.3 From 4025f0e627e127c79d372c6227b9a200406329f9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:54 +0200 Subject: ALSA: seq: ump: Create UMP Endpoint port for broadcast Create a sequencer port for broadcasting the all group inputs at the port number 0. This corresponds to a UMP Endpoint connection; application can read all UMP events from this port no matter which group the UMP packet belongs to. Unlike seq ports for other UMP groups, a UMP Endpoint port has no SND_SEQ_PORT_TYPE_MIDI_GENERIC bit, so that it won't be treated as a normal MIDI 1.0 device from legacy applications. The port is named as "MIDI 2.0" to align with representations on other operation systems. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-34-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ump_client.c | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 8d360655ff5d..600b061ac8c3 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -290,6 +290,62 @@ static void update_group_attrs(struct seq_ump_client *client) } } +/* create a UMP Endpoint port */ +static int create_ump_endpoint_port(struct seq_ump_client *client) +{ + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + unsigned int rawmidi_info = client->ump->core.info_flags; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->addr.client = client->seq_client; + port->addr.port = 0; /* fixed */ + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->ump_group = 0; /* no associated group, no conversion */ + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + strcpy(port->name, "MIDI 2.0"); + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + } + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + kfree(port); + return err; +} + /* release the client resources */ static void seq_ump_client_free(struct seq_ump_client *client) { @@ -353,6 +409,10 @@ static int snd_seq_ump_probe(struct device *_dev) goto error; } + err = create_ump_endpoint_port(client); + if (err < 0) + goto error; + ump->seq_client = client; ump->seq_ops = &seq_ump_ops; return 0; -- cgit v1.2.3 From d2d247e35eeea8331150d7708211a013aabccb5b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:55 +0200 Subject: ALSA: seq: Add ioctls for client UMP info query and setup Add new ioctls for sequencer clients to query and set the UMP endpoint and block information. As a sequencer client corresponds to a UMP Endpoint, one UMP Endpoint information can be assigned at most to a single sequencer client while multiple UMP block infos can be assigned by passing the type with the offset of block id (i.e. type = block_id + 1). For the kernel client, only SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO is allowed. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-35-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 14 +++++ sound/core/seq/seq_clientmgr.c | 120 +++++++++++++++++++++++++++++++++++++++- sound/core/seq/seq_clientmgr.h | 4 +- sound/core/seq/seq_compat.c | 2 + sound/core/seq/seq_ump_client.c | 15 +++++ 5 files changed, 153 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 3fa6b17aa7a2..c75f594f21e3 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -585,6 +585,18 @@ struct snd_seq_query_subs { char reserved[64]; /* for future use */ }; +/* + * UMP-specific information + */ +/* type of UMP info query */ +#define SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT 0 +#define SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK 1 + +struct snd_seq_client_ump_info { + int client; /* client number to inquire/set */ + int type; /* type to inquire/set */ + unsigned char info[512]; /* info (either UMP ep or block info) */ +} __packed; /* * IOCTL commands @@ -598,6 +610,8 @@ struct snd_seq_query_subs { #define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO _IOWR('S', 0x10, struct snd_seq_client_info) #define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO _IOW ('S', 0x11, struct snd_seq_client_info) +#define SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO _IOWR('S', 0x12, struct snd_seq_client_ump_info) +#define SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO _IOWR('S', 0x13, struct snd_seq_client_ump_info) #define SNDRV_SEQ_IOCTL_CREATE_PORT _IOWR('S', 0x20, struct snd_seq_port_info) #define SNDRV_SEQ_IOCTL_DELETE_PORT _IOW ('S', 0x21, struct snd_seq_port_info) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 3b1adcb1ccdd..03ca78ea2cce 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -14,6 +14,7 @@ #include #include +#include #include "seq_clientmgr.h" #include "seq_memory.h" #include "seq_queue.h" @@ -71,6 +72,10 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, struct snd_seq_event *event, int filter, int atomic, int hop); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) +static void free_ump_info(struct snd_seq_client *client); +#endif + /* */ static inline unsigned short snd_seq_file_flags(struct file *file) @@ -382,6 +387,9 @@ static int snd_seq_release(struct inode *inode, struct file *file) seq_free_client(client); if (client->data.user.fifo) snd_seq_fifo_delete(&client->data.user.fifo); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + free_ump_info(client); +#endif put_pid(client->data.user.owner); kfree(client); } @@ -1282,7 +1290,6 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); - return 0; } @@ -2087,6 +2094,108 @@ static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client, return 0; } +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) +#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1) + +static void free_ump_info(struct snd_seq_client *client) +{ + int i; + + if (!client->ump_info) + return; + for (i = 0; i < NUM_UMP_INFOS; i++) + kfree(client->ump_info[i]); + kfree(client->ump_info); + client->ump_info = NULL; +} + +static void terminate_ump_info_strings(void *p, int type) +{ + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) { + struct snd_ump_endpoint_info *ep = p; + ep->name[sizeof(ep->name) - 1] = 0; + } else { + struct snd_ump_block_info *bp = p; + bp->name[sizeof(bp->name) - 1] = 0; + } +} + +/* UMP-specific ioctls -- called directly without data copy */ +static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller, + unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client_ump_info __user *argp = + (struct snd_seq_client_ump_info __user *)arg; + struct snd_seq_client *cptr; + int client, type, err = 0; + size_t size; + void *p; + + if (get_user(client, &argp->client) || get_user(type, &argp->type)) + return -EFAULT; + if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO && + caller->number != client) + return -EPERM; + if (type < 0 || type >= NUM_UMP_INFOS) + return -EINVAL; + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) + size = sizeof(struct snd_ump_endpoint_info); + else + size = sizeof(struct snd_ump_block_info); + cptr = snd_seq_client_use_ptr(client); + if (!cptr) + return -ENOENT; + + mutex_lock(&cptr->ioctl_mutex); + if (!cptr->midi_version) { + err = -EBADFD; + goto error; + } + + if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) { + if (!cptr->ump_info) + p = NULL; + else + p = cptr->ump_info[type]; + if (!p) { + err = -ENODEV; + goto error; + } + if (copy_to_user(argp->info, p, size)) { + err = -EFAULT; + goto error; + } + } else { + if (cptr->type != USER_CLIENT) { + err = -EBADFD; + goto error; + } + if (!cptr->ump_info) { + cptr->ump_info = kcalloc(NUM_UMP_INFOS, + sizeof(void *), GFP_KERNEL); + if (!cptr->ump_info) { + err = -ENOMEM; + goto error; + } + } + p = memdup_user(argp->info, size); + if (IS_ERR(p)) { + err = PTR_ERR(p); + goto error; + } + kfree(cptr->ump_info[type]); + terminate_ump_info_strings(p, type); + cptr->ump_info[type] = p; + } + + error: + mutex_unlock(&cptr->ioctl_mutex); + snd_seq_client_unlock(cptr); + return err; +} +#endif + /* -------------------------------------------------------- */ static const struct ioctl_handler { @@ -2157,6 +2266,15 @@ static long snd_seq_ioctl(struct file *file, unsigned int cmd, if (snd_BUG_ON(!client)) return -ENXIO; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + /* exception - handling large data */ + switch (cmd) { + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: + return snd_seq_ioctl_client_ump_info(client, cmd, arg); + } +#endif + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { if (handler->cmd == cmd) break; diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 97762892ffab..be3fe555f233 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -12,7 +12,6 @@ #include "seq_ports.h" #include "seq_lock.h" - /* client manager */ struct snd_seq_user_client { @@ -59,6 +58,9 @@ struct snd_seq_client { struct snd_seq_user_client user; struct snd_seq_kernel_client kernel; } data; + + /* for UMP */ + void **ump_info; }; /* usage statistics */ diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index c0ce6236dc7f..1e35bf086a51 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -86,6 +86,8 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l case SNDRV_SEQ_IOCTL_SYSTEM_INFO: case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: case SNDRV_SEQ_IOCTL_CREATE_QUEUE: diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 600b061ac8c3..e24833804094 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -47,6 +47,7 @@ struct seq_ump_client { struct snd_rawmidi_file out_rfile; /* rawmidi for output */ struct seq_ump_input_buffer input; /* input parser context */ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ }; /* number of 32bit words for each UMP message type */ @@ -384,6 +385,8 @@ static int snd_seq_ump_probe(struct device *_dev) struct snd_ump_endpoint *ump = dev->private_data; struct snd_card *card = dev->card; struct seq_ump_client *client; + struct snd_ump_block *fb; + struct snd_seq_client *cptr; int p, err; client = kzalloc(sizeof(*client), GFP_KERNEL); @@ -400,6 +403,10 @@ static int snd_seq_ump_probe(struct device *_dev) goto error; } + client->ump_info[0] = &ump->info; + list_for_each_entry(fb, &ump->block_list, list) + client->ump_info[fb->info.block_id + 1] = &fb->info; + setup_client_midi_version(client); update_group_attrs(client); @@ -413,6 +420,14 @@ static int snd_seq_ump_probe(struct device *_dev) if (err < 0) goto error; + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) { + err = -EINVAL; + goto error; + } + cptr->ump_info = client->ump_info; + snd_seq_kernel_client_put(cptr); + ump->seq_client = client; ump->seq_ops = &seq_ump_ops; return 0; -- cgit v1.2.3 From e85b9260569dc3893bc084ec18ef48e199525ef8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:56 +0200 Subject: ALSA: seq: Print UMP Endpoint and Block information in proc outputs This patch enhances the /proc/asound/seq/clients output to show a few more information about the assigned UMP Endpoint and Blocks. The "Groups" are shown in 1-based group number to align with the sequencer client name and port number. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-36-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 03ca78ea2cce..8cce8061ca55 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2120,6 +2120,33 @@ static void terminate_ump_info_strings(void *p, int type) } } +#ifdef CONFIG_SND_PROC_FS +static void dump_ump_info(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_ump_endpoint_info *ep; + struct snd_ump_block_info *bp; + int i; + + if (!client->ump_info) + return; + ep = client->ump_info[SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT]; + if (ep && *ep->name) + snd_iprintf(buffer, " UMP Endpoint: \"%s\"\n", ep->name); + for (i = 0; i < SNDRV_UMP_MAX_BLOCKS; i++) { + bp = client->ump_info[i + 1]; + if (bp && *bp->name) { + snd_iprintf(buffer, " UMP Block %d: \"%s\" [%s]\n", + i, bp->name, + bp->active ? "Active" : "Inactive"); + snd_iprintf(buffer, " Groups: %d-%d\n", + bp->first_group + 1, + bp->first_group + bp->num_groups); + } + } +} +#endif + /* UMP-specific ioctls -- called directly without data copy */ static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller, unsigned int cmd, @@ -2654,6 +2681,9 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, c, client->name, client->type == USER_CLIENT ? "User" : "Kernel", midi_version_string(client->midi_version)); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + dump_ump_info(buffer, client); +#endif snd_seq_info_dump_ports(buffer, client); if (snd_seq_write_pool_allocated(client)) { snd_iprintf(buffer, " Output pool :\n"); -- cgit v1.2.3 From d2b706077792a366fac8c1db2f1b4406ad7da482 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:57 +0200 Subject: ALSA: seq: Add UMP group filter Add a new filter bitmap for UMP groups for reducing the unnecessary read/write when the client is connected to UMP EP seq port. The new group_filter field contains the bitmap for the groups, i.e. when the bit is set, the corresponding group is filtered out and the messages to that group won't be delivered. The filter bitmap consists of each bit of 1-based UMP Group number. The bit 0 is reserved for the future use. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-37-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 3 ++- sound/core/seq/seq_clientmgr.c | 2 ++ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_ump_convert.c | 13 +++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c75f594f21e3..5e91243665d8 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -362,7 +362,8 @@ struct snd_seq_client_info { int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ unsigned int midi_version; /* MIDI version */ - char reserved[52]; /* for future use */ + unsigned int group_filter; /* UMP group filter bitmap (for 1-based Group indices) */ + char reserved[48]; /* for future use */ }; /* MIDI version numbers in client info */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 8cce8061ca55..948ae45e0cc3 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1229,6 +1229,7 @@ static void get_client_info(struct snd_seq_client *cptr, info->filter = cptr->filter; info->event_lost = cptr->event_lost; memcpy(info->event_filter, cptr->event_filter, 32); + info->group_filter = cptr->group_filter; info->num_ports = cptr->num_ports; if (cptr->type == USER_CLIENT) @@ -1290,6 +1291,7 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); + client->group_filter = client_info->group_filter; return 0; } diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index be3fe555f233..915b1017286e 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -40,6 +40,7 @@ struct snd_seq_client { int number; /* client number */ unsigned int filter; /* filter flags */ DECLARE_BITMAP(event_filter, 256); + unsigned short group_filter; snd_use_lock_t use_lock; int event_lost; /* ports */ diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index 433fe842947e..14ba6fed9dd1 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -527,6 +527,17 @@ static int deliver_with_group_convert(struct snd_seq_client *dest, atomic, hop); } +/* apply the UMP event filter; return true to skip the event */ +static bool ump_event_filtered(struct snd_seq_client *dest, + const struct snd_seq_ump_event *ev) +{ + unsigned char group; + + group = ump_message_group(ev->ump[0]); + /* check the bitmap for 1-based group number */ + return dest->group_filter & (1U << (group + 1)); +} + /* Convert from UMP packet and deliver */ int snd_seq_deliver_from_ump(struct snd_seq_client *source, struct snd_seq_client *dest, @@ -539,6 +550,8 @@ int snd_seq_deliver_from_ump(struct snd_seq_client *source, if (snd_seq_ev_is_variable(event)) return 0; // skip, no variable event for UMP, so far + if (ump_event_filtered(dest, ump_ev)) + return 0; // skip if group filter is set and matching type = ump_message_type(ump_ev->ump[0]); if (snd_seq_client_is_ump(dest)) { -- cgit v1.2.3 From f5192e33810afde77cf8cba75f70af4348577fba Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 12:46:11 +0200 Subject: ALSA: emu10k1: introduce higher-level voice manipulation functions This adds snd_emu10k1_pcm_init_{voices,extra_voice}() and snd_emu10k1_playback_{un,}mute_voices() to slightly abstract by voice function and potential stereo property. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523104612.198884-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 70 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 17 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0036593cca7c..65af94d08b47 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -312,6 +312,30 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, spin_unlock_irqrestore(&emu->reg_lock, flags); } +static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, + unsigned int start_addr, + unsigned int end_addr, + struct snd_emu10k1_pcm_mixer *mix) +{ + snd_emu10k1_pcm_init_voice(emu, 1, 0, evoice, w_16, stereo, + start_addr, end_addr, mix); + if (stereo) + snd_emu10k1_pcm_init_voice(emu, 0, 0, evoice + 1, w_16, true, + start_addr, end_addr, mix); +} + +static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, + unsigned int start_addr, + unsigned int end_addr) +{ + snd_emu10k1_pcm_init_voice(emu, 1, 1, evoice, w_16, false, + start_addr, end_addr, NULL); +} + static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -393,18 +417,15 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, - start_addr, end_addr, NULL); + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, w_16, + start_addr, end_addr); start_addr >>= stereo; epcm->ccca_start_addr = start_addr; end_addr = start_addr + runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, - start_addr, end_addr, - &emu->pcm_mixer[substream->number]); - if (stereo) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[0] + 1, w_16, true, - start_addr, end_addr, - &emu->pcm_mixer[substream->number]); + snd_emu10k1_pcm_init_voices(emu, epcm->voices[0], w_16, stereo, + start_addr, end_addr, + &emu->pcm_mixer[substream->number]); + return 0; } @@ -421,8 +442,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) channel_size = runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, - start_addr, start_addr + (channel_size / 2), NULL); + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true, + start_addr, start_addr + (channel_size / 2)); epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { @@ -598,12 +619,31 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } +static void snd_emu10k1_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo, + struct snd_emu10k1_pcm_mixer *mix) +{ + snd_emu10k1_playback_unmute_voice(emu, evoice, stereo, true, mix); + if (stereo) + snd_emu10k1_playback_unmute_voice(emu, evoice + 1, true, false, mix); +} + static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { snd_emu10k1_playback_commit_volume(emu, evoice, 0); } +static void snd_emu10k1_playback_mute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo) +{ + snd_emu10k1_playback_mute_voice(emu, evoice); + if (stereo) + snd_emu10k1_playback_mute_voice(emu, evoice + 1); +} + static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, u32 voice, u32 pitch_target) { @@ -680,9 +720,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); - if (stereo) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0] + 1, true, false, mix); + snd_emu10k1_playback_unmute_voices(emu, epcm->voices[0], stereo, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -693,9 +731,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); - snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); - if (stereo) - snd_emu10k1_playback_mute_voice(emu, epcm->voices[0] + 1); + snd_emu10k1_playback_mute_voices(emu, epcm->voices[0], stereo); break; default: result = -EINVAL; -- cgit v1.2.3 From 7195fb46dafb8750ed4055804cb131f376eb854e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 12:46:12 +0200 Subject: ALSA: emu10k1: pass raw FX send config to snd_emu10k1_pcm_init_voice() ... instead of passing in a high-level mixer struct. Let the higher-level functions handle the differences between the voice types. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523104612.198884-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 54 +++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 65af94d08b47..0572dfb80943 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -233,37 +233,21 @@ static u16 emu10k1_send_target_from_amount(u8 amount) } static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, - int master, int extra, struct snd_emu10k1_voice *evoice, bool w_16, bool stereo, unsigned int start_addr, unsigned int end_addr, - struct snd_emu10k1_pcm_mixer *mix) + const unsigned char *send_routing, + const unsigned char *send_amount) { struct snd_pcm_substream *substream = evoice->epcm->substream; struct snd_pcm_runtime *runtime = substream->runtime; - unsigned int silent_page, tmp; + unsigned int silent_page; int voice; - unsigned char send_amount[8]; - unsigned char send_routing[8]; - unsigned long flags; unsigned int pitch_target; voice = evoice->number; - spin_lock_irqsave(&emu->reg_lock, flags); - - /* volume parameters */ - if (extra) { - for (int i = 0; i < 8; i++) - send_routing[i] = i; - memset(send_amount, 0, sizeof(send_amount)); - } else { - /* mono, left, right (master voice = left) */ - tmp = stereo ? (master ? 1 : 2) : 0; - memcpy(send_routing, &mix->send_routing[tmp][0], 8); - memcpy(send_amount, &mix->send_volume[tmp][0], 8); - } if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -308,8 +292,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, } emu->voices[voice].dirty = 1; - - spin_unlock_irqrestore(&emu->reg_lock, flags); } static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, @@ -319,11 +301,19 @@ static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, unsigned int end_addr, struct snd_emu10k1_pcm_mixer *mix) { - snd_emu10k1_pcm_init_voice(emu, 1, 0, evoice, w_16, stereo, - start_addr, end_addr, mix); + unsigned long flags; + + spin_lock_irqsave(&emu->reg_lock, flags); + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, stereo, + start_addr, end_addr, + &mix->send_routing[stereo][0], + &mix->send_volume[stereo][0]); if (stereo) - snd_emu10k1_pcm_init_voice(emu, 0, 0, evoice + 1, w_16, true, - start_addr, end_addr, mix); + snd_emu10k1_pcm_init_voice(emu, evoice + 1, w_16, true, + start_addr, end_addr, + &mix->send_routing[2][0], + &mix->send_volume[2][0]); + spin_unlock_irqrestore(&emu->reg_lock, flags); } static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, @@ -332,8 +322,12 @@ static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, unsigned int start_addr, unsigned int end_addr) { - snd_emu10k1_pcm_init_voice(emu, 1, 1, evoice, w_16, false, - start_addr, end_addr, NULL); + static const unsigned char send_routing[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + static const unsigned char send_amount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, false, + start_addr, end_addr, + send_routing, send_amount); } static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, @@ -447,9 +441,9 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, - start_addr, start_addr + channel_size, - &emu->efx_pcm_mixer[i]); + snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false, + start_addr, start_addr + channel_size, + &emu->efx_pcm_mixer[i]); start_addr += channel_size; } -- cgit v1.2.3 From 2f3092e77f98fcfc0d653846591401bfe2a5232e Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 17 May 2023 12:49:02 +0300 Subject: ASoC: do not include pm_runtime.h if not used Do not include pm_runtime.h header in files where APIs exported by pm_runtime.h are not used. Signed-off-by: Claudiu Beznea Acked-by: Jarkko Nikula # for omap-mcbsp-st.c Link: https://lore.kernel.org/r/20230517094903.2895238-2-claudiu.beznea@microchip.com Signed-off-by: Mark Brown --- sound/hda/hdac_regmap.c | 1 - sound/pci/hda/hda_bind.c | 1 - sound/soc/amd/acp/acp-pci.c | 1 - sound/soc/amd/acp/acp-platform.c | 1 - sound/soc/codecs/max98090.c | 1 - sound/soc/codecs/pcm186x.c | 1 - sound/soc/codecs/rk3328_codec.c | 1 - sound/soc/codecs/rt5682-i2c.c | 1 - sound/soc/codecs/rt5682s.c | 1 - sound/soc/codecs/tas2562.c | 1 - sound/soc/codecs/tas5720.c | 1 - sound/soc/codecs/tas6424.c | 1 - sound/soc/codecs/wm_adsp.c | 1 - sound/soc/fsl/imx-audmix.c | 1 - sound/soc/intel/atom/sst/sst_acpi.c | 1 - sound/soc/intel/atom/sst/sst_ipc.c | 1 - sound/soc/intel/atom/sst/sst_loader.c | 1 - sound/soc/intel/atom/sst/sst_pci.c | 1 - sound/soc/intel/atom/sst/sst_stream.c | 1 - sound/soc/mediatek/mt8186/mt8186-afe-control.c | 1 - sound/soc/mediatek/mt8186/mt8186-mt6366-da7219-max98357.c | 1 - sound/soc/mediatek/mt8186/mt8186-mt6366-rt1019-rt5682s.c | 1 - sound/soc/mediatek/mt8192/mt8192-afe-control.c | 2 -- sound/soc/soc-compress.c | 1 - sound/soc/soc-pcm.c | 1 - sound/soc/sof/intel/hda-loader-skl.c | 1 - sound/soc/sof/intel/hda-stream.c | 1 - sound/soc/sof/intel/skl.c | 1 - sound/soc/sof/mediatek/mt8186/mt8186-clk.c | 1 - sound/soc/sof/mediatek/mt8195/mt8195-clk.c | 1 - sound/soc/tegra/tegra20_ac97.c | 1 - sound/soc/ti/omap-mcbsp-st.c | 1 - 32 files changed, 33 deletions(-) (limited to 'sound') diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index fe3587547cfe..7cfaa908ff57 100644 --- a/sound/hda/hdac_regmap.c +++ b/sound/hda/hdac_regmap.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/pci/hda/hda_bind.c b/sound/pci/hda/hda_bind.c index 890c2f7c33fc..b7ca2a83fbb0 100644 --- a/sound/pci/hda/hda_bind.c +++ b/sound/pci/hda/hda_bind.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include "hda_local.h" diff --git a/sound/soc/amd/acp/acp-pci.c b/sound/soc/amd/acp/acp-pci.c index a0c84cd07fde..8154fbfd1229 100644 --- a/sound/soc/amd/acp/acp-pci.c +++ b/sound/soc/amd/acp/acp-pci.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "amd.h" diff --git a/sound/soc/amd/acp/acp-platform.c b/sound/soc/amd/acp/acp-platform.c index 447612a7a762..f220378ec20e 100644 --- a/sound/soc/amd/acp/acp-platform.c +++ b/sound/soc/amd/acp/acp-platform.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include "amd.h" diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index ad417d80691f..7bc463910d4f 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/pcm186x.c b/sound/soc/codecs/pcm186x.c index dd21803ba13c..451a8fd8fac5 100644 --- a/sound/soc/codecs/pcm186x.c +++ b/sound/soc/codecs/pcm186x.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/rk3328_codec.c b/sound/soc/codecs/rk3328_codec.c index 1d523bfd9d84..9697aefc6e03 100644 --- a/sound/soc/codecs/rk3328_codec.c +++ b/sound/soc/codecs/rk3328_codec.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/rt5682-i2c.c b/sound/soc/codecs/rt5682-i2c.c index d0041ba1e627..a88fcf507386 100644 --- a/sound/soc/codecs/rt5682-i2c.c +++ b/sound/soc/codecs/rt5682-i2c.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/rt5682s.c b/sound/soc/codecs/rt5682s.c index a01de405c22c..c77c675bd5f5 100644 --- a/sound/soc/codecs/rt5682s.c +++ b/sound/soc/codecs/rt5682s.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/tas2562.c b/sound/soc/codecs/tas2562.c index 2012d6f0071d..962c2cdfa017 100644 --- a/sound/soc/codecs/tas2562.c +++ b/sound/soc/codecs/tas2562.c @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/tas5720.c b/sound/soc/codecs/tas5720.c index b07c51dc3f72..6dd6c0896eff 100644 --- a/sound/soc/codecs/tas5720.c +++ b/sound/soc/codecs/tas5720.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/tas6424.c b/sound/soc/codecs/tas6424.c index a07cd1e016e0..da89e8c681dd 100644 --- a/sound/soc/codecs/tas6424.c +++ b/sound/soc/codecs/tas6424.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/codecs/wm_adsp.c b/sound/soc/codecs/wm_adsp.c index 6270cb2e9395..5a89abfe8784 100644 --- a/sound/soc/codecs/wm_adsp.c +++ b/sound/soc/codecs/wm_adsp.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c index efbcd4a65ca8..c93616e50f4d 100644 --- a/sound/soc/fsl/imx-audmix.c +++ b/sound/soc/fsl/imx-audmix.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "fsl_sai.h" #include "fsl_audmix.h" diff --git a/sound/soc/intel/atom/sst/sst_acpi.c b/sound/soc/intel/atom/sst/sst_acpi.c index d3973936426a..29d44c989e5f 100644 --- a/sound/soc/intel/atom/sst/sst_acpi.c +++ b/sound/soc/intel/atom/sst/sst_acpi.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/intel/atom/sst/sst_ipc.c b/sound/soc/intel/atom/sst/sst_ipc.c index 4e039c7173d8..3fc2c9a6c44d 100644 --- a/sound/soc/intel/atom/sst/sst_ipc.c +++ b/sound/soc/intel/atom/sst/sst_ipc.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/intel/atom/sst/sst_loader.c b/sound/soc/intel/atom/sst/sst_loader.c index eea889001c24..bf4ba6bcc429 100644 --- a/sound/soc/intel/atom/sst/sst_loader.c +++ b/sound/soc/intel/atom/sst/sst_loader.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/intel/atom/sst/sst_pci.c b/sound/soc/intel/atom/sst/sst_pci.c index 5862fe968083..4058b4f80a0c 100644 --- a/sound/soc/intel/atom/sst/sst_pci.c +++ b/sound/soc/intel/atom/sst/sst_pci.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/intel/atom/sst/sst_stream.c b/sound/soc/intel/atom/sst/sst_stream.c index ea1ef8a61fa6..862a19ae5429 100644 --- a/sound/soc/intel/atom/sst/sst_stream.c +++ b/sound/soc/intel/atom/sst/sst_stream.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/mediatek/mt8186/mt8186-afe-control.c b/sound/soc/mediatek/mt8186/mt8186-afe-control.c index d714e9641571..55edf6374578 100644 --- a/sound/soc/mediatek/mt8186/mt8186-afe-control.c +++ b/sound/soc/mediatek/mt8186/mt8186-afe-control.c @@ -6,7 +6,6 @@ // Author: Jiaxin Yu #include "mt8186-afe-common.h" -#include enum { MTK_AFE_RATE_8K = 0, diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366-da7219-max98357.c b/sound/soc/mediatek/mt8186/mt8186-mt6366-da7219-max98357.c index cdf54d1eb50d..0432f9d89020 100644 --- a/sound/soc/mediatek/mt8186/mt8186-mt6366-da7219-max98357.c +++ b/sound/soc/mediatek/mt8186/mt8186-mt6366-da7219-max98357.c @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/mediatek/mt8186/mt8186-mt6366-rt1019-rt5682s.c b/sound/soc/mediatek/mt8186/mt8186-mt6366-rt1019-rt5682s.c index 7538274641fd..9c11016f032c 100644 --- a/sound/soc/mediatek/mt8186/mt8186-mt6366-rt1019-rt5682s.c +++ b/sound/soc/mediatek/mt8186/mt8186-mt6366-rt1019-rt5682s.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/mediatek/mt8192/mt8192-afe-control.c b/sound/soc/mediatek/mt8192/mt8192-afe-control.c index 9163e05e54e1..d01b62e10088 100644 --- a/sound/soc/mediatek/mt8192/mt8192-afe-control.c +++ b/sound/soc/mediatek/mt8192/mt8192-afe-control.c @@ -6,8 +6,6 @@ // Author: Shane Chien // -#include - #include "mt8192-afe-common.h" enum { diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index d8715db5e415..02fdb683f75f 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -20,7 +20,6 @@ #include #include #include -#include static int snd_soc_compr_components_open(struct snd_compr_stream *cstream) { diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index adb69d7820a8..6365ad8ca7ef 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/sof/intel/hda-loader-skl.c b/sound/soc/sof/intel/hda-loader-skl.c index 69fdef8f89ae..1e77ca936f80 100644 --- a/sound/soc/sof/intel/hda-loader-skl.c +++ b/sound/soc/sof/intel/hda-loader-skl.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/sof/intel/hda-stream.c b/sound/soc/sof/intel/hda-stream.c index 8de422604ad5..b13acb959653 100644 --- a/sound/soc/sof/intel/hda-stream.c +++ b/sound/soc/sof/intel/hda-stream.c @@ -15,7 +15,6 @@ * Hardware interface for generic Intel audio DSP HDA IP */ -#include #include #include #include diff --git a/sound/soc/sof/intel/skl.c b/sound/soc/sof/intel/skl.c index 13efdb94d071..d24e64e71b58 100644 --- a/sound/soc/sof/intel/skl.c +++ b/sound/soc/sof/intel/skl.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c index 2df3b7ae1c6f..cb2ab5884b8c 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186-clk.c +++ b/sound/soc/sof/mediatek/mt8186/mt8186-clk.c @@ -8,7 +8,6 @@ // Hardware interface for mt8186 DSP clock #include -#include #include #include "../../sof-audio.h" diff --git a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c index 9ef08e43aa38..7cffcad00f9b 100644 --- a/sound/soc/sof/mediatek/mt8195/mt8195-clk.c +++ b/sound/soc/sof/mediatek/mt8195/mt8195-clk.c @@ -7,7 +7,6 @@ // Hardware interface for mt8195 DSP clock #include -#include #include #include "mt8195.h" #include "mt8195-clk.h" diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c index c498145e76e0..a4073a746ae3 100644 --- a/sound/soc/tegra/tegra20_ac97.c +++ b/sound/soc/tegra/tegra20_ac97.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/sound/soc/ti/omap-mcbsp-st.c b/sound/soc/ti/omap-mcbsp-st.c index 8163f453bf36..b047add5d887 100644 --- a/sound/soc/ti/omap-mcbsp-st.c +++ b/sound/soc/ti/omap-mcbsp-st.c @@ -19,7 +19,6 @@ #include #include #include -#include #include "omap-mcbsp.h" #include "omap-mcbsp-priv.h" -- cgit v1.2.3 From a9392efae9f5de42673cfc1b81ac6fb88bdb26b2 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 17 May 2023 12:49:03 +0300 Subject: ASoC: use pm.h instead of runtime_pm.h Do not include pm_runtime.h header in files where runtime PM support is not implemented. Use pm.h instead as suspend to RAM specific implementation is available. Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/20230517094903.2895238-3-claudiu.beznea@microchip.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98373-i2c.c | 2 +- sound/soc/qcom/lpass-sc7180.c | 2 +- sound/soc/qcom/lpass-sc7280.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98373-i2c.c b/sound/soc/codecs/max98373-i2c.c index 3d6da4f133de..0fa5ceca62a2 100644 --- a/sound/soc/codecs/max98373-i2c.c +++ b/sound/soc/codecs/max98373-i2c.c @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/sound/soc/qcom/lpass-sc7180.c b/sound/soc/qcom/lpass-sc7180.c index 41db6617e2ed..56db852f4eab 100644 --- a/sound/soc/qcom/lpass-sc7180.c +++ b/sound/soc/qcom/lpass-sc7180.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/sound/soc/qcom/lpass-sc7280.c b/sound/soc/qcom/lpass-sc7280.c index d43f480cbae3..bcf18fe8e14d 100644 --- a/sound/soc/qcom/lpass-sc7280.c +++ b/sound/soc/qcom/lpass-sc7280.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include -- cgit v1.2.3 From c6d15567a4d5dd51ecccc332d514c6dc21bce652 Mon Sep 17 00:00:00 2001 From: Rander Wang Date: Tue, 23 May 2023 13:32:16 +0300 Subject: ASoC: SOF: Intel: mtl: add core_get & put support on MeterLake platforms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In core_get case, driver can power up primary core and don't need to send ipc message to fw. Non-primary core should be powered up by fw with ipc message. In core_put case, driver should first send ipc message to fw to disable dsp core then power down primary core if the target is primary core. Signed-off-by: Rander Wang Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Signed-off-by: Peter Ujfalusi Link: https://lore.kernel.org/r/20230523103217.20412-3-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/mtl.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 46caf3ccde66..93dc2c9d8448 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -613,6 +613,36 @@ static u64 mtl_dsp_get_stream_hda_link_position(struct snd_sof_dev *sdev, return ((u64)llp_u << 32) | llp_l; } +static int mtl_dsp_core_get(struct snd_sof_dev *sdev, int core) +{ + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + + if (core == SOF_DSP_PRIMARY_CORE) + return mtl_dsp_core_power_up(sdev, SOF_DSP_PRIMARY_CORE); + + if (pm_ops->set_core_state) + return pm_ops->set_core_state(sdev, core, true); + + return 0; +} + +static int mtl_dsp_core_put(struct snd_sof_dev *sdev, int core) +{ + const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + int ret; + + if (pm_ops->set_core_state) { + ret = pm_ops->set_core_state(sdev, core, false); + if (ret < 0) + return ret; + } + + if (core == SOF_DSP_PRIMARY_CORE) + return mtl_dsp_core_power_down(sdev, SOF_DSP_PRIMARY_CORE); + + return 0; +} + /* Meteorlake ops */ struct snd_sof_dsp_ops sof_mtl_ops; EXPORT_SYMBOL_NS(sof_mtl_ops, SND_SOC_SOF_INTEL_HDA_COMMON); @@ -649,7 +679,8 @@ int sof_mtl_ops_init(struct snd_sof_dev *sdev) sof_mtl_ops.parse_platform_ext_manifest = NULL; /* dsp core get/put */ - /* TODO: add core_get and core_put */ + sof_mtl_ops.core_get = mtl_dsp_core_get; + sof_mtl_ops.core_put = mtl_dsp_core_put; sof_mtl_ops.get_stream_position = mtl_dsp_get_stream_hda_link_position; -- cgit v1.2.3 From 1b167ba8a20152041d3af0c0cbbfd710f1e93e4b Mon Sep 17 00:00:00 2001 From: Rander Wang Date: Tue, 23 May 2023 13:32:17 +0300 Subject: ASoC: SOF: Intel: tgl: unify core_put on IPC3 & IPC4 path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Firmware may do context saving before powering off primary core, so driver needs to send ipc msg by set_core_state. In IPC4 path, firmware needs to save current context to IMR before powering off primary core. Firmware does nothing for set_core_state message in IPC3 path. So IPC4 and IPC3 can share the same operation sequence. Signed-off-by: Rander Wang Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Signed-off-by: Peter Ujfalusi Link: https://lore.kernel.org/r/20230523103217.20412-4-peter.ujfalusi@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/tgl.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/tgl.c b/sound/soc/sof/intel/tgl.c index 2713b7dc7931..8e2b07e1612b 100644 --- a/sound/soc/sof/intel/tgl.c +++ b/sound/soc/sof/intel/tgl.c @@ -39,14 +39,18 @@ static int tgl_dsp_core_get(struct snd_sof_dev *sdev, int core) static int tgl_dsp_core_put(struct snd_sof_dev *sdev, int core) { const struct sof_ipc_pm_ops *pm_ops = sdev->ipc->ops->pm; + int ret; + + if (pm_ops->set_core_state) { + ret = pm_ops->set_core_state(sdev, core, false); + if (ret < 0) + return ret; + } /* power down primary core and return */ if (core == SOF_DSP_PRIMARY_CORE) return hda_dsp_core_reset_power_down(sdev, BIT(core)); - if (pm_ops->set_core_state) - return pm_ops->set_core_state(sdev, core, false); - return 0; } -- cgit v1.2.3 From fcbc3aaccfd57c7e71eac36bf1a8f063f19ceefa Mon Sep 17 00:00:00 2001 From: Yang Li Date: Tue, 16 May 2023 16:11:16 +0800 Subject: ASoC: SOF: ipc4-topology: Fix an unsigned comparison which can never be negative The return value from the call to sof_ipc4_get_valid_bits() is int. However, the return value is being assigned to an unsigned int variable 'out_ref_valid_bits', so making it an int. Eliminate the following warning: ./sound/soc/sof/ipc4-topology.c:1537:6-24: WARNING: Unsigned expression compared with zero: out_ref_valid_bits < 0 Reported-by: Abaci Robot Link: https://bugzilla.openanolis.cn/show_bug.cgi?id=4985 Signed-off-by: Yang Li Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230516081116.71370-1-yang.lee@linux.alibaba.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 681239dd54ee..56887e417cb8 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1386,8 +1386,8 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, void **ipc_config_data; int *ipc_config_size; u32 **data; - int ipc_size, ret; - u32 out_ref_rate, out_ref_channels, out_ref_valid_bits; + int ipc_size, ret, out_ref_valid_bits; + u32 out_ref_rate, out_ref_channels; u32 deep_buffer_dma_ms = 0; int output_fmt_index; -- cgit v1.2.3 From ed67a3404a8806a57c0015ce97bd3e6d61e7aa22 Mon Sep 17 00:00:00 2001 From: Harshit Mogalapalli Date: Thu, 18 May 2023 23:44:01 -0700 Subject: ASoC: SOF: Intel: hda-dai: Fix locking in hda_ipc4_pre_trigger() hda_ipc4_pre_trigger() has two issues: 1. In the default case, we are returning without unlocking the mutex. 2. In case SNDRV_PCM_TRIGGER_STOP: when ret is less than zero it goes to out, unlocks but returns zero instead of a negative value. Fix this by changing the final return value to 'ret' instead of zero, and initialize 'ret' to zero in the start of the function. Fixes: 225f37b578a9 ("ASoC: SOF: ipc4-pcm: reset all pipelines during FE DAI hw_free") Signed-off-by: Harshit Mogalapalli Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230519064404.1659637-1-harshit.m.mogalapalli@oracle.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-dai-ops.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index 5a508e118e3d..1e58256c8003 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -183,7 +183,7 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp struct sof_ipc4_pipeline *pipeline; struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; - int ret; + int ret = 0; w = snd_soc_dai_get_widget(cpu_dai, substream->stream); swidget = w->dobj.private; @@ -208,11 +208,11 @@ static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cp break; default: dev_err(sdev->dev, "unknown trigger command %d\n", cmd); - return -EINVAL; + ret = -EINVAL; } out: mutex_unlock(&ipc4_data->pipeline_state_mutex); - return 0; + return ret; } static int hda_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, -- cgit v1.2.3 From dc0ff0fa3a9bf9f7be3a9530f8f6079324f54fa5 Mon Sep 17 00:00:00 2001 From: David Rau Date: Tue, 23 May 2023 16:18:21 +0000 Subject: ASoC: da7219: Add Jack insertion detection polarity Add support of selecting insertion detection polarity - Default polarity (Low) - Inverted polarity (High) Correct the keywords of parsing `dlg,jack-det-rate` bases on the new DT binding. Signed-off-by: David Rau Link: https://lore.kernel.org/r/20230523161821.4260-4-David.Rau.opensource@dm.renesas.com Signed-off-by: Mark Brown --- include/sound/da7219-aad.h | 6 ++++++ sound/soc/codecs/da7219-aad.c | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/include/sound/da7219-aad.h b/include/sound/da7219-aad.h index 24ee7baa2589..41320522daa2 100644 --- a/include/sound/da7219-aad.h +++ b/include/sound/da7219-aad.h @@ -44,6 +44,11 @@ enum da7219_aad_jack_ins_deb { DA7219_AAD_JACK_INS_DEB_1S, }; +enum da7219_aad_jack_ins_det_pty { + DA7219_AAD_JACK_INS_DET_PTY_LOW = 0, + DA7219_AAD_JACK_INS_DET_PTY_HIGH, +}; + enum da7219_aad_jack_det_rate { DA7219_AAD_JACK_DET_RATE_32_64MS = 0, DA7219_AAD_JACK_DET_RATE_64_128MS, @@ -80,6 +85,7 @@ struct da7219_aad_pdata { enum da7219_aad_btn_cfg btn_cfg; enum da7219_aad_mic_det_thr mic_det_thr; enum da7219_aad_jack_ins_deb jack_ins_deb; + enum da7219_aad_jack_ins_det_pty jack_ins_det_pty; enum da7219_aad_jack_det_rate jack_det_rate; enum da7219_aad_jack_rem_deb jack_rem_deb; diff --git a/sound/soc/codecs/da7219-aad.c b/sound/soc/codecs/da7219-aad.c index 993a0d00bc48..c65256bd526d 100644 --- a/sound/soc/codecs/da7219-aad.c +++ b/sound/soc/codecs/da7219-aad.c @@ -571,16 +571,29 @@ static enum da7219_aad_jack_ins_deb } } +static enum da7219_aad_jack_ins_det_pty + da7219_aad_fw_jack_ins_det_pty(struct device *dev, const char *str) +{ + if (!strcmp(str, "low")) { + return DA7219_AAD_JACK_INS_DET_PTY_LOW; + } else if (!strcmp(str, "high")) { + return DA7219_AAD_JACK_INS_DET_PTY_HIGH; + } else { + dev_warn(dev, "Invalid jack insertion detection polarity"); + return DA7219_AAD_JACK_INS_DET_PTY_LOW; + } +} + static enum da7219_aad_jack_det_rate da7219_aad_fw_jack_det_rate(struct device *dev, const char *str) { - if (!strcmp(str, "32ms_64ms")) { + if (!strcmp(str, "32_64")) { return DA7219_AAD_JACK_DET_RATE_32_64MS; - } else if (!strcmp(str, "64ms_128ms")) { + } else if (!strcmp(str, "64_128")) { return DA7219_AAD_JACK_DET_RATE_64_128MS; - } else if (!strcmp(str, "128ms_256ms")) { + } else if (!strcmp(str, "128_256")) { return DA7219_AAD_JACK_DET_RATE_128_256MS; - } else if (!strcmp(str, "256ms_512ms")) { + } else if (!strcmp(str, "256_512")) { return DA7219_AAD_JACK_DET_RATE_256_512MS; } else { dev_warn(dev, "Invalid jack detect rate"); @@ -688,6 +701,12 @@ static struct da7219_aad_pdata *da7219_aad_fw_to_pdata(struct device *dev) else aad_pdata->jack_ins_deb = DA7219_AAD_JACK_INS_DEB_20MS; + if (!fwnode_property_read_string(aad_np, "dlg,jack-ins-det-pty", &fw_str)) + aad_pdata->jack_ins_det_pty = + da7219_aad_fw_jack_ins_det_pty(dev, fw_str); + else + aad_pdata->jack_ins_det_pty = DA7219_AAD_JACK_INS_DET_PTY_LOW; + if (!fwnode_property_read_string(aad_np, "dlg,jack-det-rate", &fw_str)) aad_pdata->jack_det_rate = da7219_aad_fw_jack_det_rate(dev, fw_str); @@ -849,6 +868,21 @@ static void da7219_aad_handle_pdata(struct snd_soc_component *component) mask |= DA7219_ADC_1_BIT_REPEAT_MASK; } snd_soc_component_update_bits(component, DA7219_ACCDET_CONFIG_7, mask, cfg); + + switch (aad_pdata->jack_ins_det_pty) { + case DA7219_AAD_JACK_INS_DET_PTY_LOW: + snd_soc_component_write(component, 0xF0, 0x8B); + snd_soc_component_write(component, 0x75, 0x80); + snd_soc_component_write(component, 0xF0, 0x00); + break; + case DA7219_AAD_JACK_INS_DET_PTY_HIGH: + snd_soc_component_write(component, 0xF0, 0x8B); + snd_soc_component_write(component, 0x75, 0x00); + snd_soc_component_write(component, 0xF0, 0x00); + break; + default: + break; + } } } -- cgit v1.2.3 From 299f6c752f8f7dabb62fe4df62ebd233b58402bd Mon Sep 17 00:00:00 2001 From: Paul Olaru Date: Wed, 3 May 2023 11:10:48 +0300 Subject: ASoC: sof: Improve sof_ipc3_bytes_ext_put function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is improved in the way that if the firmware returns a validation error on the newly sent bytes, then the kernel will automatically restore to the old bytes value for a given kcontrol. This way, if the firmware rejects a data blob then the kernel will also reject it, instead of saving it for the next suspend/resume cycle. The old behaviour is that the kernel would save it anyway and on next firmware boot it would apply the previously-rejected configuration, leading to errors during playback. Additionally, the function also saves previously validated configurations, so that if the firmware does end up rejecting a new bytes value the kernel can send an old, previously-valid configuration. Reviewed-by: Daniel Baluta Reviewed-by: Péter Ujfalusi Signed-off-by: Paul Olaru Signed-off-by: Daniel Baluta Link: https://lore.kernel.org/r/20230503081049.73847-2-daniel.baluta@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc3-control.c | 54 +++++++++++++++++++++++++++++++++++++++----- sound/soc/sof/sof-audio.h | 1 + 2 files changed, 49 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc3-control.c b/sound/soc/sof/ipc3-control.c index ad040e7bb850..a8deec7dc021 100644 --- a/sound/soc/sof/ipc3-control.c +++ b/sound/soc/sof/ipc3-control.c @@ -96,6 +96,26 @@ static int sof_ipc3_set_get_kcontrol_data(struct snd_sof_control *scontrol, cdata->elems_remaining = 0; ret = iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); + if (!set) + goto unlock; + + /* It is a set-data operation, and we have a backup that we can restore */ + if (ret < 0) { + if (!scontrol->old_ipc_control_data) + goto unlock; + /* + * Current ipc_control_data is not valid, we use the last known good + * configuration + */ + memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data, + scontrol->max_size); + kfree(scontrol->old_ipc_control_data); + scontrol->old_ipc_control_data = NULL; + /* Send the last known good configuration to firmware */ + ret = iops->set_get_data(sdev, cdata, cdata->rhdr.hdr.size, set); + if (ret < 0) + goto unlock; + } unlock: if (lock) @@ -351,6 +371,7 @@ static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, struct sof_ipc_ctrl_data *cdata = scontrol->ipc_control_data; struct snd_soc_component *scomp = scontrol->scomp; struct snd_ctl_tlv header; + int ret = -EINVAL; /* * The beginning of bytes data contains a header from where @@ -381,31 +402,52 @@ static int sof_ipc3_bytes_ext_put(struct snd_sof_control *scontrol, return -EINVAL; } - if (copy_from_user(cdata->data, tlvd->tlv, header.length)) - return -EFAULT; + if (!scontrol->old_ipc_control_data) { + /* Create a backup of the current, valid bytes control */ + scontrol->old_ipc_control_data = kmemdup(scontrol->ipc_control_data, + scontrol->max_size, GFP_KERNEL); + if (!scontrol->old_ipc_control_data) + return -ENOMEM; + } + + if (copy_from_user(cdata->data, tlvd->tlv, header.length)) { + ret = -EFAULT; + goto err_restore; + } if (cdata->data->magic != SOF_ABI_MAGIC) { dev_err_ratelimited(scomp->dev, "Wrong ABI magic 0x%08x\n", cdata->data->magic); - return -EINVAL; + goto err_restore; } if (SOF_ABI_VERSION_INCOMPATIBLE(SOF_ABI_VERSION, cdata->data->abi)) { dev_err_ratelimited(scomp->dev, "Incompatible ABI version 0x%08x\n", cdata->data->abi); - return -EINVAL; + goto err_restore; } /* be->max has been verified to be >= sizeof(struct sof_abi_hdr) */ if (cdata->data->size > scontrol->max_size - sizeof(struct sof_abi_hdr)) { dev_err_ratelimited(scomp->dev, "Mismatch in ABI data size (truncated?)\n"); - return -EINVAL; + goto err_restore; } /* notify DSP of byte control updates */ - if (pm_runtime_active(scomp->dev)) + if (pm_runtime_active(scomp->dev)) { + /* Actually send the data to the DSP; this is an opportunity to validate the data */ return sof_ipc3_set_get_kcontrol_data(scontrol, true, true); + } return 0; + +err_restore: + /* If we have an issue, we restore the old, valid bytes control data */ + if (scontrol->old_ipc_control_data) { + memcpy(cdata->data, scontrol->old_ipc_control_data, scontrol->max_size); + kfree(scontrol->old_ipc_control_data); + scontrol->old_ipc_control_data = NULL; + } + return ret; } static int _sof_ipc3_bytes_ext_get(struct snd_sof_control *scontrol, diff --git a/sound/soc/sof/sof-audio.h b/sound/soc/sof/sof-audio.h index a090a9eb4828..5d5eeb1a1a6f 100644 --- a/sound/soc/sof/sof-audio.h +++ b/sound/soc/sof/sof-audio.h @@ -362,6 +362,7 @@ struct snd_sof_control { size_t priv_size; /* size of private data */ size_t max_size; void *ipc_control_data; + void *old_ipc_control_data; int max; /* applicable to volume controls */ u32 size; /* cdata size */ u32 *volume_table; /* volume table computed from tlv data*/ -- cgit v1.2.3 From db38d86d0c54e0dbea063e915ce3e1fe394af444 Mon Sep 17 00:00:00 2001 From: Paul Olaru Date: Wed, 3 May 2023 11:10:49 +0300 Subject: ASoC: sof: Improve sof_ipc4_bytes_ext_put function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is improved in the way that if the firmware returns a validation error on the newly sent bytes, then the kernel will automatically restore to the old bytes value for a given kcontrol. This way, if the firmware rejects a data blob then the kernel will also reject it, instead of saving it for the next suspend/resume cycle. The old behaviour is that the kernel would save it anyway and on next firmware boot it would apply the previously-rejected configuration, leading to errors during playback. Additionally, the function also saves previously validated configurations, so that if the firmware does end up rejecting a new bytes value the kernel can send an old, previously-valid configuration. Reviewed-by: Daniel Baluta Reviewed-by: Péter Ujfalusi Signed-off-by: Paul Olaru Signed-off-by: Daniel Baluta Link: https://lore.kernel.org/r/20230503081049.73847-3-daniel.baluta@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-control.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-control.c b/sound/soc/sof/ipc4-control.c index 6f0698be9451..c6d404d44097 100644 --- a/sound/soc/sof/ipc4-control.c +++ b/sound/soc/sof/ipc4-control.c @@ -54,6 +54,26 @@ static int sof_ipc4_set_get_kcontrol_data(struct snd_sof_control *scontrol, msg->primary |= SOF_IPC4_MOD_INSTANCE(swidget->instance_id); ret = iops->set_get_data(sdev, msg, msg->data_size, set); + if (!set) + goto unlock; + + /* It is a set-data operation, and we have a valid backup that we can restore */ + if (ret < 0) { + if (!scontrol->old_ipc_control_data) + goto unlock; + /* + * Current ipc_control_data is not valid, we use the last known good + * configuration + */ + memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data, + scontrol->max_size); + kfree(scontrol->old_ipc_control_data); + scontrol->old_ipc_control_data = NULL; + /* Send the last known good configuration to firmware */ + ret = iops->set_get_data(sdev, msg, msg->data_size, set); + if (ret < 0) + goto unlock; + } unlock: if (lock) @@ -327,13 +347,24 @@ static int sof_ipc4_bytes_ext_put(struct snd_sof_control *scontrol, return -EINVAL; } + if (!scontrol->old_ipc_control_data) { + /* Create a backup of the current, valid bytes control */ + scontrol->old_ipc_control_data = kmemdup(scontrol->ipc_control_data, + scontrol->max_size, GFP_KERNEL); + if (!scontrol->old_ipc_control_data) + return -ENOMEM; + } + /* Copy the whole binary data which includes the ABI header and the payload */ - if (copy_from_user(data, tlvd->tlv, header.length)) + if (copy_from_user(data, tlvd->tlv, header.length)) { + memcpy(scontrol->ipc_control_data, scontrol->old_ipc_control_data, + scontrol->max_size); + kfree(scontrol->old_ipc_control_data); + scontrol->old_ipc_control_data = NULL; return -EFAULT; + } - sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true); - - return 0; + return sof_ipc4_set_get_bytes_data(sdev, scontrol, true, true); } static int _sof_ipc4_bytes_ext_get(struct snd_sof_control *scontrol, -- cgit v1.2.3 From 1a3eb4bb9826fd317358113ca048ed60184c6442 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Mon, 8 May 2023 15:15:29 +0800 Subject: ASoC: mediatek: mt6359: add supply for MTKAIF There are three output data pins MISO0, MISO1 and MISO2 for mt6359. UL_SRC should be enabled when MISO0 or MISO1 is used, and UL_SRC_34 should be enabled when MISO2 is used. Signed-off-by: Trevor Wu Link: https://lore.kernel.org/r/20230508071532.21665-2-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6359.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index cb487e63615c..d6a93da2644e 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -2358,6 +2358,10 @@ static const struct snd_soc_dapm_route mt6359_dapm_routes[] = { {"MISO2_MUX", "UL2_CH1", "UL2_SRC_MUX"}, {"MISO2_MUX", "UL2_CH2", "UL2_SRC_MUX"}, + {"MISO0_MUX", NULL, "UL_SRC"}, + {"MISO1_MUX", NULL, "UL_SRC"}, + {"MISO2_MUX", NULL, "UL_SRC_34"}, + {"UL_SRC_MUX", "AMIC", "ADC_L"}, {"UL_SRC_MUX", "AMIC", "ADC_R"}, {"UL_SRC_MUX", "DMIC", "DMIC0_MUX"}, -- cgit v1.2.3 From acd4d219798769a6c1080bcfa7953e165dd8d681 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Mon, 8 May 2023 15:15:30 +0800 Subject: ASoC: mediatek: mt6359: fix kselftest error of playback gain kselftest tries to read/write the default value. The default register value of playback gain is 0x1F(mute), but max gain we specified is 0x12. The range of the control is 0x0~0x12 and mute(0x1F) is only used in the driver internally. To solve the problem, implement a new callback mt6359_get_playback_volsw to report user configured volume instead of the register value. In addition, update max of "Headset Volume" to 0x12, so it can match the maximum seen on latest data sheet. Signed-off-by: Trevor Wu Link: https://lore.kernel.org/r/20230508071532.21665-3-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6359.c | 88 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index d6a93da2644e..65e6d4d08b6a 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -360,8 +360,34 @@ static int mt6359_put_volsw(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = 0; int index = ucontrol->value.integer.value[0]; + int orig_gain[2], new_gain[2]; int ret; + switch (mc->reg) { + case MT6359_ZCD_CON2: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL]; + orig_gain[1] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR]; + break; + case MT6359_ZCD_CON1: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTL]; + orig_gain[1] = priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR]; + break; + case MT6359_ZCD_CON3: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTL]; + break; + case MT6359_AUDENC_ANA_CON0: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP1]; + break; + case MT6359_AUDENC_ANA_CON1: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP2]; + break; + case MT6359_AUDENC_ANA_CON2: + orig_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP3]; + break; + default: + return -EINVAL; + } + ret = snd_soc_put_volsw(kcontrol, ucontrol); if (ret < 0) return ret; @@ -373,6 +399,8 @@ static int mt6359_put_volsw(struct snd_kcontrol *kcontrol, (reg >> RG_AUDHPLGAIN_SFT) & RG_AUDHPLGAIN_MASK; priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR] = (reg >> RG_AUDHPRGAIN_SFT) & RG_AUDHPRGAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL]; + new_gain[1] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR]; break; case MT6359_ZCD_CON1: regmap_read(priv->regmap, MT6359_ZCD_CON1, ®); @@ -380,35 +408,82 @@ static int mt6359_put_volsw(struct snd_kcontrol *kcontrol, (reg >> RG_AUDLOLGAIN_SFT) & RG_AUDLOLGAIN_MASK; priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR] = (reg >> RG_AUDLORGAIN_SFT) & RG_AUDLORGAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTL]; + new_gain[1] = priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR]; break; case MT6359_ZCD_CON3: regmap_read(priv->regmap, MT6359_ZCD_CON3, ®); priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTL] = (reg >> RG_AUDHSGAIN_SFT) & RG_AUDHSGAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTL]; break; case MT6359_AUDENC_ANA_CON0: regmap_read(priv->regmap, MT6359_AUDENC_ANA_CON0, ®); priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP1] = (reg >> RG_AUDPREAMPLGAIN_SFT) & RG_AUDPREAMPLGAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP1]; break; case MT6359_AUDENC_ANA_CON1: regmap_read(priv->regmap, MT6359_AUDENC_ANA_CON1, ®); priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP2] = (reg >> RG_AUDPREAMPRGAIN_SFT) & RG_AUDPREAMPRGAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP2]; break; case MT6359_AUDENC_ANA_CON2: regmap_read(priv->regmap, MT6359_AUDENC_ANA_CON2, ®); priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP3] = (reg >> RG_AUDPREAMP3GAIN_SFT) & RG_AUDPREAMP3GAIN_MASK; + new_gain[0] = priv->ana_gain[AUDIO_ANALOG_VOLUME_MICAMP3]; break; } + ret = 0; + if (orig_gain[0] != new_gain[0]) { + ret = 1; + } else if (snd_soc_volsw_is_stereo(mc)) { + if (orig_gain[1] != new_gain[1]) + ret = 1; + } + dev_dbg(priv->dev, "%s(), name %s, reg(0x%x) = 0x%x, set index = %x\n", __func__, kcontrol->id.name, mc->reg, reg, index); return ret; } +static int mt6359_get_playback_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct mt6359_priv *priv = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + switch (mc->reg) { + case MT6359_ZCD_CON2: + ucontrol->value.integer.value[0] = + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTL]; + ucontrol->value.integer.value[1] = + priv->ana_gain[AUDIO_ANALOG_VOLUME_HPOUTR]; + break; + case MT6359_ZCD_CON1: + ucontrol->value.integer.value[0] = + priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTL]; + ucontrol->value.integer.value[1] = + priv->ana_gain[AUDIO_ANALOG_VOLUME_LINEOUTR]; + break; + case MT6359_ZCD_CON3: + ucontrol->value.integer.value[0] = + priv->ana_gain[AUDIO_ANALOG_VOLUME_HSOUTL]; + break; + default: + return -EINVAL; + } + + return 0; +} + /* MUX */ /* LOL MUX */ @@ -2701,22 +2776,23 @@ static void mt6359_codec_remove(struct snd_soc_component *cmpnt) cmpnt->regmap = NULL; } -static const DECLARE_TLV_DB_SCALE(hp_playback_tlv, -2200, 100, 0); static const DECLARE_TLV_DB_SCALE(playback_tlv, -1000, 100, 0); static const DECLARE_TLV_DB_SCALE(capture_tlv, 0, 600, 0); static const struct snd_kcontrol_new mt6359_snd_controls[] = { /* dl pga gain */ SOC_DOUBLE_EXT_TLV("Headset Volume", - MT6359_ZCD_CON2, 0, 7, 0x1E, 0, - snd_soc_get_volsw, mt6359_put_volsw, - hp_playback_tlv), + MT6359_ZCD_CON2, 0, 7, 0x12, 0, + mt6359_get_playback_volsw, mt6359_put_volsw, + playback_tlv), SOC_DOUBLE_EXT_TLV("Lineout Volume", MT6359_ZCD_CON1, 0, 7, 0x12, 0, - snd_soc_get_volsw, mt6359_put_volsw, playback_tlv), + mt6359_get_playback_volsw, mt6359_put_volsw, + playback_tlv), SOC_SINGLE_EXT_TLV("Handset Volume", MT6359_ZCD_CON3, 0, 0x12, 0, - snd_soc_get_volsw, mt6359_put_volsw, playback_tlv), + mt6359_get_playback_volsw, mt6359_put_volsw, + playback_tlv), /* ul pga gain */ SOC_SINGLE_EXT_TLV("PGA1 Volume", -- cgit v1.2.3 From 24f398e74ba0a53bc95421f7eb139f4dc0207bb2 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Mon, 8 May 2023 15:15:31 +0800 Subject: ASoC: mediatek: mt6359: add mtkaif gpio setting Add mtkaif gpio driving to increase signal strength and smt setting to prevent from overshooting. Signed-off-by: Trevor Wu Link: https://lore.kernel.org/r/20230508071532.21665-4-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6359.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index 65e6d4d08b6a..a37ad61a8253 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -18,6 +18,20 @@ #include "mt6359.h" +static void mt6359_set_gpio_smt(struct mt6359_priv *priv) +{ + /* set gpio SMT mode */ + regmap_update_bits(priv->regmap, MT6359_SMT_CON1, 0x3ff0, 0x3ff0); +} + +static void mt6359_set_gpio_driving(struct mt6359_priv *priv) +{ + /* 8:4mA(default), a:8mA, c:12mA, e:16mA */ + regmap_update_bits(priv->regmap, MT6359_DRV_CON2, 0xffff, 0x8888); + regmap_update_bits(priv->regmap, MT6359_DRV_CON3, 0xffff, 0x8888); + regmap_update_bits(priv->regmap, MT6359_DRV_CON4, 0x00ff, 0x88); +} + static void mt6359_set_playback_gpio(struct mt6359_priv *priv) { /* set gpio mosi mode, clk / data mosi */ @@ -2745,6 +2759,8 @@ static int mt6359_codec_init_reg(struct snd_soc_component *cmpnt) 0x1 << RG_AUDLOLSCDISABLE_VAUDP32_SFT); /* set gpio */ + mt6359_set_gpio_smt(priv); + mt6359_set_gpio_driving(priv); mt6359_reset_playback_gpio(priv); mt6359_reset_capture_gpio(priv); -- cgit v1.2.3 From 104ce27bcbfb204001a300498aa192235bd0836f Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Mon, 8 May 2023 15:15:32 +0800 Subject: ASoC: mediatek: mt6359: update route for lineout mux Originally, lineout playback source can only be DAC_3RD. Some SoC masters only support stereo MTKAIF outputs, so lineout path can't be used in such case. MTKAIF connections are as follows. MOSI0 -> DAC_L MOSI1 -> DAC_R MOSI2 -> DAC_3rd In the patch, lineout playback source can be chosen between DAC_L and DAC_3rd, so sound can be outputted via lineout even though SoC only supports stereo MTKAIF outputs. Signed-off-by: Trevor Wu Link: https://lore.kernel.org/r/20230508071532.21665-5-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/codecs/mt6359.c | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/mt6359.c b/sound/soc/codecs/mt6359.c index a37ad61a8253..30690479ec17 100644 --- a/sound/soc/codecs/mt6359.c +++ b/sound/soc/codecs/mt6359.c @@ -1159,9 +1159,10 @@ static int mt_lo_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mt6359_priv *priv = snd_soc_component_get_drvdata(cmpnt); + unsigned int mux = dapm_kcontrol_get_value(w->kcontrols[0]); dev_dbg(priv->dev, "%s(), event 0x%x, mux %u\n", - __func__, event, dapm_kcontrol_get_value(w->kcontrols[0])); + __func__, event, mux); switch (event) { case SND_SOC_DAPM_PRE_PMU: @@ -1199,14 +1200,29 @@ static int mt_lo_event(struct snd_soc_dapm_widget *w, /* Enable AUD_CLK */ mt6359_set_decoder_clk(priv, true); - /* Enable Audio DAC (3rd DAC) */ - regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON7, 0x3113); - /* Enable low-noise mode of DAC */ - if (priv->dev_counter[DEVICE_HP] == 0) - regmap_write(priv->regmap, - MT6359_AUDDEC_ANA_CON9, 0x0001); - /* Switch LOL MUX to audio 3rd DAC */ - regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON7, 0x311b); + /* Switch LOL MUX to audio DAC */ + if (mux == LO_MUX_L_DAC) { + if (priv->dev_counter[DEVICE_HP] > 0) { + dev_info(priv->dev, "%s(), can not enable DAC, hp count %d\n", + __func__, priv->dev_counter[DEVICE_HP]); + break; + } + /* Enable DACL and switch HP MUX to open*/ + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON0, 0x3009); + /* Disable low-noise mode of DAC */ + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON9, 0xf200); + usleep_range(100, 120); + /* Switch LOL MUX to DACL */ + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON7, 0x0117); + } else if (mux == LO_MUX_3RD_DAC) { + /* Enable Audio DAC (3rd DAC) */ + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON7, 0x3113); + /* Enable low-noise mode of DAC */ + if (priv->dev_counter[DEVICE_HP] == 0) + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON9, 0x0001); + /* Switch LOL MUX to audio 3rd DAC */ + regmap_write(priv->regmap, MT6359_AUDDEC_ANA_CON7, 0x311b); + } break; case SND_SOC_DAPM_PRE_PMD: /* Switch LOL MUX to open */ @@ -1218,6 +1234,15 @@ static int mt_lo_event(struct snd_soc_dapm_widget *w, regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON0, 0x000f, 0x0000); + if (mux == LO_MUX_L_DAC) { + /* Disable HP driver core circuits */ + regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON0, + 0x3 << 4, 0x0); + /* Disable HP driver bias circuits */ + regmap_update_bits(priv->regmap, MT6359_AUDDEC_ANA_CON0, + 0x3 << 6, 0x0); + } + /* Disable AUD_CLK */ mt6359_set_decoder_clk(priv, false); @@ -2590,6 +2615,7 @@ static const struct snd_soc_dapm_route mt6359_dapm_routes[] = { /* Lineout Path */ {"LOL Mux", "Playback", "DAC_3RD"}, + {"LOL Mux", "Playback_L_DAC", "DACL"}, {"LINEOUT L", NULL, "LOL Mux"}, /* Headphone Path */ -- cgit v1.2.3 From 6dbecb9b51321bfaf8e7f26fc163d547abcbee86 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:07 +0200 Subject: ALSA: emu10k1: don't limit multi-channel playback to two periods For unclear reasons, the extra voice was set up with half the buffer size instead of the period size. Commit 27ae958cf6 ("emu10k1 driver - add multichannel device hw:x,3 [2-8/8]") mentions half-loop interrupts, so maybe this was an artifact of an earlier iteration of the patch. While at it, also fix periods_min of the regular playback - one period makes just no sense. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0572dfb80943..2764e7867b33 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -429,15 +429,16 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; unsigned int start_addr; - unsigned int channel_size; + unsigned int extra_size, channel_size; int i; start_addr = epcm->start_addr >> 1; // 16-bit voices + extra_size = runtime->period_size; channel_size = runtime->buffer_size; snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true, - start_addr, start_addr + (channel_size / 2)); + start_addr, start_addr + extra_size); epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { @@ -465,7 +466,7 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), .periods_min = 2, - .periods_max = 2, + .periods_max = 1024, .fifo_size = 0, }; @@ -925,7 +926,7 @@ static const struct snd_pcm_hardware snd_emu10k1_playback = .channels_max = 2, .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), - .periods_min = 1, + .periods_min = 2, .periods_max = 1024, .fifo_size = 0, }; -- cgit v1.2.3 From 11ee59bdac36ae4b500301a6a3ccf586d3968d92 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:08 +0200 Subject: ALSA: emu10k1: add synchronized start of multi-channel playback We use independent voices for the channels, so we need to make an effort to ensure that they are actually in sync. The hardware doesn't provide atomicity, so we may need to retry a few times, due to NMIs, PCI contention, and the wrong phase of the moon. Solution inspired by kX-project. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 10 ++++- sound/pci/emu10k1/emupcm.c | 100 +++++++++++++++++++++++++++++++++++++-------- sound/pci/emu10k1/io.c | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 0780f39f4bb6..164a2374b4c2 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -422,7 +422,8 @@ SUB_REG(HCFG, LOCKTANKCACHE, 0x00000004) /* 1 = Cancel bustmaster accesses to ta #define CPF 0x00 /* Current pitch and fraction register */ SUB_REG(CPF, CURRENTPITCH, 0xffff0000) /* Current pitch (linear, 0x4000 == unity pitch shift) */ #define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ -#define CPF_STOP_MASK 0x00004000 /* 1 = Current pitch forced to 0 */ +SUB_REG(CPF, STOP, 0x00004000) /* 1 = Current pitch forced to 0 */ + /* Can be set only while matching bit in SOLEx is 1 */ #define CPF_FRACADDRESS_MASK 0x00003fff /* Linear fractional address of the current channel */ #define PTRX 0x01 /* Pitch target and send A/B amounts register */ @@ -771,6 +772,9 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define CLIPL 0x5a /* Channel loop interrupt pending low register */ #define CLIPH 0x5b /* Channel loop interrupt pending high register */ +// These cause CPF_STOP_MASK to be set shortly after CCCA_CURRADDR passes DSL_LOOPENDADDR. +// Subsequent changes to the address registers don't resume; clearing the bit here or in CPF does. +// The registers are NOT synchronized; the next serviced channel picks up immediately. #define SOLEL 0x5c /* Stop on loop enable low register */ #define SOLEH 0x5d /* Stop on loop enable high register */ @@ -1476,6 +1480,7 @@ struct snd_emu10k1_pcm { struct snd_emu10k1_voice *extra; unsigned short running; unsigned short first_ptr; + snd_pcm_uframes_t resume_pos; struct snd_util_memblk *memblk; unsigned int start_addr; unsigned int ccca_start_addr; @@ -1820,6 +1825,9 @@ void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); #endif +void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices); +void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices); +int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices); void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 2764e7867b33..4df6f5285993 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -560,7 +560,7 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, // we need to compensate for two circumstances: // - The actual position is delayed by the cache size (64 frames) // - The interpolator is centered around the 4th frame - loop_start += 64 - 3; + loop_start += (epcm->resume_pos + 64 - 3) % loop_size; for (int i = 0; i < channels; i++) { unsigned voice = epcm->voices[i]->number; snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, voice, loop_start); @@ -584,7 +584,7 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, // This is why all other (open) drivers for these chips use timer-based // interrupts. // - eloop_start += eloop_size - 3; + eloop_start += (epcm->resume_pos + eloop_size - 3) % eloop_size; snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, epcm->extra->number, eloop_start); // It takes a moment until the cache fills complete, @@ -844,6 +844,49 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream * return ptr; } +static u64 snd_emu10k1_efx_playback_voice_mask(struct snd_emu10k1_pcm *epcm, + int channels) +{ + u64 mask = 0; + + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + mask |= 1ULL << voice; + } + return mask; +} + +static void snd_emu10k1_efx_playback_freeze_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1); + snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16); + } +} + +static void snd_emu10k1_efx_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, + &emu->efx_pcm_mixer[i]); +} + +static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); + snd_emu10k1_playback_set_stopped(emu, epcm); + + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); +} static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, int cmd) @@ -851,41 +894,62 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; - int i; + u64 mask; int result = 0; spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_emu10k1_playback_prepare_voices(emu, epcm, true, false, NUM_EFX_PLAYBACK); - fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, - &emu->efx_pcm_mixer[i]); + mask = snd_emu10k1_efx_playback_voice_mask( + epcm, NUM_EFX_PLAYBACK); + for (int i = 0; i < 10; i++) { + // Note that the freeze is not interruptible, so we make no + // effort to reset the bits outside the error handling here. + snd_emu10k1_voice_set_loop_stop_multiple(emu, mask); + snd_emu10k1_efx_playback_freeze_voices( + emu, epcm, NUM_EFX_PLAYBACK); + snd_emu10k1_playback_prepare_voices( + emu, epcm, true, false, NUM_EFX_PLAYBACK); + + // It might seem to make more sense to unmute the voices only after + // they have been started, to potentially avoid torturing the speakers + // if something goes wrong. However, we cannot unmute atomically, + // which means that we'd get some mild artifacts in the regular case. + snd_emu10k1_efx_playback_unmute_voices(emu, epcm, NUM_EFX_PLAYBACK); + + snd_emu10k1_playback_set_running(emu, epcm); + result = snd_emu10k1_voice_clear_loop_stop_multiple_atomic(emu, mask); + if (result == 0) { + // The extra voice is allowed to lag a bit + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + goto leave; + } - snd_emu10k1_playback_set_running(emu, epcm); - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i]); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, NUM_EFX_PLAYBACK); + + if (result != -EAGAIN) + break; + // The sync start can legitimately fail due to NMIs, etc. + } + snd_emu10k1_voice_clear_loop_stop_multiple(emu, mask); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); - } snd_emu10k1_playback_stop_voice(emu, epcm->extra); - snd_emu10k1_playback_set_stopped(emu, epcm); + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, NUM_EFX_PLAYBACK); - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); + epcm->resume_pos = snd_emu10k1_playback_pointer(substream); break; default: result = -EINVAL; break; } +leave: spin_unlock(&emu->reg_lock); return result; } diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 6419719c739c..9a839e7d283f 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -505,6 +505,88 @@ void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voi } #endif +void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)(voices >> 32), emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); +} + +void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)~voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)(~voices >> 32), emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); +} + +int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + u32 soll, solh; + int ret = -EIO; + + spin_lock_irqsave(&emu->emu_lock, flags); + + outl(SOLEL << 16, emu->port + PTR); + soll = inl(emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + solh = inl(emu->port + DATA); + + soll &= (u32)~voices; + solh &= (u32)(~voices >> 32); + + for (int tries = 0; tries < 1000; tries++) { + const u32 quart = 1U << (REG_SIZE(WC_CURRENTCHANNEL) - 2); + // First we wait for the third quarter of the sample cycle ... + u32 wc = inl(emu->port + WC); + u32 cc = REG_VAL_GET(WC_CURRENTCHANNEL, wc); + if (cc >= quart * 2 && cc < quart * 3) { + // ... and release the low voices, while the high ones are serviced. + outl(SOLEL << 16, emu->port + PTR); + outl(soll, emu->port + DATA); + // Then we wait for the first quarter of the next sample cycle ... + for (; tries < 1000; tries++) { + cc = REG_VAL_GET(WC_CURRENTCHANNEL, inl(emu->port + WC)); + if (cc < quart) + goto good; + // We will block for 10+ us with interrupts disabled. This is + // not nice at all, but necessary for reasonable reliability. + udelay(1); + } + break; + good: + // ... and release the high voices, while the low ones are serviced. + outl(SOLEH << 16, emu->port + PTR); + outl(solh, emu->port + DATA); + // Finally we verify that nothing interfered in fact. + if (REG_VAL_GET(WC_SAMPLECOUNTER, inl(emu->port + WC)) == + ((REG_VAL_GET(WC_SAMPLECOUNTER, wc) + 1) & REG_MASK0(WC_SAMPLECOUNTER))) { + ret = 0; + } else { + ret = -EAGAIN; + } + break; + } + // Don't block for too long + spin_unlock_irqrestore(&emu->emu_lock, flags); + udelay(1); + spin_lock_irqsave(&emu->emu_lock, flags); + } + + spin_unlock_irqrestore(&emu->emu_lock, flags); + return ret; +} + void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait) { volatile unsigned count; -- cgit v1.2.3 From f4ab59503989cce2c12a3c6337779218a5538ddd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:09 +0200 Subject: ALSA: emu10k1: make channel count of multi-channel playback flexible There is no reason to nail it to 16 channels. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4df6f5285993..e34b02e9f890 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -430,7 +430,7 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm = runtime->private_data; unsigned int start_addr; unsigned int extra_size, channel_size; - int i; + unsigned int i; start_addr = epcm->start_addr >> 1; // 16-bit voices @@ -441,7 +441,7 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) start_addr, start_addr + extra_size); epcm->ccca_start_addr = start_addr; - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { + for (i = 0; i < runtime->channels; i++) { snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[i]); @@ -461,7 +461,7 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, - .channels_min = NUM_EFX_PLAYBACK, + .channels_min = 1, .channels_max = NUM_EFX_PLAYBACK, .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), @@ -903,21 +903,21 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: mask = snd_emu10k1_efx_playback_voice_mask( - epcm, NUM_EFX_PLAYBACK); + epcm, runtime->channels); for (int i = 0; i < 10; i++) { // Note that the freeze is not interruptible, so we make no // effort to reset the bits outside the error handling here. snd_emu10k1_voice_set_loop_stop_multiple(emu, mask); snd_emu10k1_efx_playback_freeze_voices( - emu, epcm, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); snd_emu10k1_playback_prepare_voices( - emu, epcm, true, false, NUM_EFX_PLAYBACK); + emu, epcm, true, false, runtime->channels); // It might seem to make more sense to unmute the voices only after // they have been started, to potentially avoid torturing the speakers // if something goes wrong. However, we cannot unmute atomically, // which means that we'd get some mild artifacts in the regular case. - snd_emu10k1_efx_playback_unmute_voices(emu, epcm, NUM_EFX_PLAYBACK); + snd_emu10k1_efx_playback_unmute_voices(emu, epcm, runtime->channels); snd_emu10k1_playback_set_running(emu, epcm); result = snd_emu10k1_voice_clear_loop_stop_multiple_atomic(emu, mask); @@ -928,7 +928,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, } snd_emu10k1_efx_playback_stop_voices( - emu, epcm, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); if (result != -EAGAIN) break; @@ -941,7 +941,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_PAUSE_PUSH: snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_efx_playback_stop_voices( - emu, epcm, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); epcm->resume_pos = snd_emu10k1_playback_pointer(substream); break; -- cgit v1.2.3 From d2baa153c328efbbc3c4b939662e058b2cb544fd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:06 +0200 Subject: ALSA: emu10k1: fix capture buffer size confusion The buffer size register sets the size of the whole buffer, not just one period. We actually handled it like that, except that the constraint was set on the wrong parameter. The period size is implicitly constrained by the buffer size and the fixed period count of 2. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index e34b02e9f890..40616d343d48 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -122,7 +122,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, return 0; } -static const unsigned int capture_period_sizes[31] = { +static const unsigned int capture_buffer_sizes[31] = { 384, 448, 512, 640, 384*2, 448*2, 512*2, 640*2, 384*4, 448*4, 512*4, 640*4, @@ -133,9 +133,9 @@ static const unsigned int capture_period_sizes[31] = { 384*128,448*128,512*128 }; -static const struct snd_pcm_hw_constraint_list hw_constraints_capture_period_sizes = { +static const struct snd_pcm_hw_constraint_list hw_constraints_capture_buffer_sizes = { .count = 31, - .list = capture_period_sizes, + .list = capture_buffer_sizes, .mask = 0 }; @@ -499,7 +499,7 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream); epcm->capture_bs_val = 0; for (idx = 0; idx < 31; idx++) { - if (capture_period_sizes[idx] == epcm->capture_bufsize) { + if (capture_buffer_sizes[idx] == epcm->capture_bufsize) { epcm->capture_bs_val = idx + 1; break; } @@ -1219,9 +1219,10 @@ static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt; emu->pcm_capture_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates); return 0; } @@ -1258,9 +1259,10 @@ static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream) runtime->hw.rates = SNDRV_PCM_RATE_8000; runtime->hw.rate_min = runtime->hw.rate_max = 8000; runtime->hw.channels_min = 1; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt; emu->pcm_capture_mic_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); return 0; } @@ -1365,9 +1367,10 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) epcm->capture_cr_val = emu->efx_voices_mask[0]; epcm->capture_cr_val2 = emu->efx_voices_mask[1]; spin_unlock_irq(&emu->reg_lock); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt; emu->pcm_capture_efx_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); return 0; } -- cgit v1.2.3 From 872e5b2b5ee3f5b346d58dc4b89f0ca92065b61e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:07 +0200 Subject: ALSA: emu10k1: fix support for 24 kHz capture We need to specify that the hardware supports non-standard rates, as otherwise the sound core creates a constraint which limits the rate to the specified standard rates. That also made the rate constraint we were already adding meaningless. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 40616d343d48..8279d8a4f589 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1006,7 +1006,7 @@ static const struct snd_pcm_hardware snd_emu10k1_capture = SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_8000_48000, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, .rate_min = 8000, .rate_max = 48000, .channels_min = 1, -- cgit v1.2.3 From 848ec6cf413d151eaae11ada2953d9078f8bcda9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:08 +0200 Subject: ALSA: emu10k1: don't restrict capture channel count to powers of two The hardware can deal with primes up to 7 and power-of-two multiples thereof; the limitation is reflected by the possible buffer sizes. Note that setting the voice mask will not allow more than 16 channels even on Sound Blaster Audigy anymore, as 32 seems a bit excessive (the code overall appears to think so, just not in this case). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 8279d8a4f589..4216df399305 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -122,6 +122,17 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, return 0; } +// Primes 2-7 and 2^n multiples thereof, up to 16. +static const unsigned int efx_capture_channels[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_efx_capture_channels = { + .count = ARRAY_SIZE(efx_capture_channels), + .list = efx_capture_channels, + .mask = 0 +}; + static const unsigned int capture_buffer_sizes[31] = { 384, 448, 512, 640, 384*2, 448*2, 512*2, 640*2, @@ -1281,7 +1292,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm; struct snd_pcm_runtime *runtime = substream->runtime; int nefx = emu->audigy ? 64 : 32; - int idx; + int idx, err; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1367,6 +1378,12 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) epcm->capture_cr_val = emu->efx_voices_mask[0]; epcm->capture_cr_val2 = emu->efx_voices_mask[1]; spin_unlock_irq(&emu->reg_lock); + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_efx_capture_channels); + if (err < 0) { + kfree(epcm); + return err; + } snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt; @@ -1531,7 +1548,6 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int nval[2], bits; int nefx = emu->audigy ? 64 : 32; - int nefxb = emu->audigy ? 7 : 6; int change, idx; nval[0] = nval[1] = 0; @@ -1541,12 +1557,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st bits++; } - // Check that the number of requested channels is a power of two - // not bigger than the number of available channels. - for (idx = 0; idx < nefxb; idx++) - if (1 << idx == bits) - break; - if (idx >= nefxb) + if (bits == 9 || bits == 11 || bits == 13 || bits == 15 || bits > 16) return -EINVAL; spin_lock_irq(&emu->reg_lock); -- cgit v1.2.3 From 0006fa2d3fa0320a385bd19c9c98b70ad3db7197 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:09 +0200 Subject: ALSA: emu10k1: fix multi-channel capture config for E-MU cards On SB cards the number of captured channels is derived from the voice mask mixer control. But for E-MU cards this wasn't actually "wired up", so changing the mask would simply mess up the recording. We could fix that, but the channel routing through the FPGA makes the masking redundant. So instead we hide the control, and let the user specify the PCM channel count the traditional way. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 76 +++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 44 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4216df399305..550caefa0ce4 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -495,6 +495,12 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) snd_emu10k1_ptr_write(emu, ADCCR, 0, 0); break; case CAPTURE_EFX: + if (emu->card_capabilities->emu_model) { + // The upper 32 16-bit capture voices, two for each of the 16 32-bit channels. + // The lower voices are occupied by A_EXTOUT_*_CAP*. + epcm->capture_cr_val = 0; + epcm->capture_cr_val2 = 0xffffffff >> (32 - runtime->channels * 2); + } if (emu->audigy) { snd_emu10k1_ptr_write_multiple(emu, 0, A_FXWC1, 0, @@ -1042,8 +1048,8 @@ static const struct snd_pcm_hardware snd_emu10k1_capture_efx = SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, .rate_min = 44100, .rate_max = 192000, - .channels_min = 8, - .channels_max = 8, + .channels_min = 1, + .channels_max = 16, .buffer_bytes_max = (64*1024), .period_bytes_min = 384, .period_bytes_max = (64*1024), @@ -1269,7 +1275,6 @@ static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream) runtime->hw = snd_emu10k1_capture; runtime->hw.rates = SNDRV_PCM_RATE_8000; runtime->hw.rate_min = runtime->hw.rate_max = 8000; - runtime->hw.channels_min = 1; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt; @@ -1310,7 +1315,6 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw = snd_emu10k1_capture_efx; runtime->hw.rates = SNDRV_PCM_RATE_48000; runtime->hw.rate_min = runtime->hw.rate_max = 48000; - spin_lock_irq(&emu->reg_lock); if (emu->card_capabilities->emu_model) { /* TODO * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | @@ -1318,8 +1322,6 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 * rate_min = 44100, * rate_max = 192000, - * channels_min = 16, - * channels_max = 16, * Need to add mixer control to fix sample rate * * There are 32 mono channels of 16bits each. @@ -1338,15 +1340,11 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) /* For 44.1kHz */ runtime->hw.rates = SNDRV_PCM_RATE_44100; runtime->hw.rate_min = runtime->hw.rate_max = 44100; - runtime->hw.channels_min = - runtime->hw.channels_max = 16; break; case 1: /* For 48kHz */ runtime->hw.rates = SNDRV_PCM_RATE_48000; runtime->hw.rate_min = runtime->hw.rate_max = 48000; - runtime->hw.channels_min = - runtime->hw.channels_max = 16; break; } #endif @@ -1363,10 +1361,8 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw.channels_min = runtime->hw.channels_max = 2; #endif runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - /* efx_voices_mask[0] is expected to be zero - * efx_voices_mask[1] is expected to have 32bits set - */ } else { + spin_lock_irq(&emu->reg_lock); runtime->hw.channels_min = runtime->hw.channels_max = 0; for (idx = 0; idx < nefx; idx++) { if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) { @@ -1374,10 +1370,10 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw.channels_max++; } } + epcm->capture_cr_val = emu->efx_voices_mask[0]; + epcm->capture_cr_val2 = emu->efx_voices_mask[1]; + spin_unlock_irq(&emu->reg_lock); } - epcm->capture_cr_val = emu->efx_voices_mask[0]; - epcm->capture_cr_val2 = emu->efx_voices_mask[1]; - spin_unlock_irq(&emu->reg_lock); err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_efx_capture_channels); if (err < 0) { @@ -1844,37 +1840,29 @@ int snd_emu10k1_pcm_efx(struct snd_emu10k1 *emu, int device) strcpy(pcm->name, "Multichannel Capture/PT Playback"); emu->pcm_efx = pcm; - /* EFX capture - record the "FXBUS2" channels, by default we connect the EXTINs - * to these - */ - - if (emu->audigy) { - emu->efx_voices_mask[0] = 0; - if (emu->card_capabilities->emu_model) - /* Pavel Hofman - 32 voices will be used for - * capture (write mode) - - * each bit = corresponding voice - */ - emu->efx_voices_mask[1] = 0xffffffff; - else + if (!emu->card_capabilities->emu_model) { + // On Sound Blasters, the DSP code copies the EXTINs to FXBUS2. + // The mask determines which of these and the EXTOUTs the multi- + // channel capture actually records (the channel order is fixed). + if (emu->audigy) { + emu->efx_voices_mask[0] = 0; emu->efx_voices_mask[1] = 0xffff; + } else { + emu->efx_voices_mask[0] = 0xffff0000; + emu->efx_voices_mask[1] = 0; + } + kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = device; + err = snd_ctl_add(emu->card, kctl); + if (err < 0) + return err; } else { - emu->efx_voices_mask[0] = 0xffff0000; - emu->efx_voices_mask[1] = 0; + // On E-MU cards, the DSP code copies the P16VINs/EMU32INs to + // FXBUS2. These are already selected & routed by the FPGA, + // so there is no need to apply additional masking. } - /* For emu1010, the control has to set 32 upper bits (voices) - * out of the 64 bits (voices) to true for the 16-channels capture - * to work correctly. Correct A_FXWC2 initial value (0xffffffff) - * is already defined but the snd_emu10k1_pcm_efx_voices_mask - * control can override this register's value. - */ - kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); - if (!kctl) - return -ENOMEM; - kctl->id.device = device; - err = snd_ctl_add(emu->card, kctl); - if (err < 0) - return err; snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &emu->pci->dev, 64*1024, 64*1024); -- cgit v1.2.3 From ab2335daa6ef70df56c98c216261a93e28ae52b3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 25 May 2023 10:31:23 +0200 Subject: ALSA: ump: Drop redundant check of note-on with zero velocity The check of a note-on event with zero velocity is done twice, and the latter one is superfluous. Let's drop it. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/4683198a-84f6-4238-9e87-c70667d84523@kili.mountain Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20230525083124.15277-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'sound') diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index cb7c2f959a27..164829d3e305 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -340,9 +340,6 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, switch (status) { case UMP_MSG_STATUS_NOTE_ON: - if (!buf[2]) - status = UMP_MSG_STATUS_NOTE_OFF; - fallthrough; case UMP_MSG_STATUS_NOTE_OFF: midi2->note.note = buf[1]; midi2->note.velocity = upscale_7_to_16bit(buf[2]); -- cgit v1.2.3 From 77700b81bd0e47d89d50eb4b3f2f323492f79998 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 25 May 2023 10:31:24 +0200 Subject: ALSA: ump: Fix parsing of 0xFx command The MIDI 1.0 parser retrieved the 0xFx command with a wrong bit shift, resulting in the bogus type. Fix the bit shift size. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/0fbc0b27-54b8-4cda-800e-37e7a03fed39@kili.mountain Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20230525083124.15277-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 164829d3e305..48ab3e1bd62e 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -454,7 +454,7 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, } if (c & 0x80) { - bytes = cmd_bytes[(c >> 8) & 7]; + bytes = cmd_bytes[(c >> 4) & 7]; cvt->buf[0] = c; cvt->len = 1; cvt->cmd_bytes = bytes; -- cgit v1.2.3 From f9f46d05003ea6120fa27e01628770a2dac0fa75 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 10 May 2023 10:25:34 +0100 Subject: ASoC: cs35l45: Relicense to GPL only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cirrus never intended to upstream dual licensed code, convert to GPL only. Signed-off-by: Charles Keepax Acked-by: Pierre-Louis Bossart Acked-by: Uwe Kleine-König Reviewed-by: Richard Fitzgerald Reviewed-by: Vlad Karpovich Link: https://lore.kernel.org/r/20230510092534.3919120-1-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l45-i2c.c | 4 ++-- sound/soc/codecs/cs35l45-spi.c | 4 ++-- sound/soc/codecs/cs35l45-tables.c | 2 +- sound/soc/codecs/cs35l45.c | 4 ++-- sound/soc/codecs/cs35l45.h | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l45-i2c.c b/sound/soc/codecs/cs35l45-i2c.c index 5832ebb90c2b..77e0f8750f37 100644 --- a/sound/soc/codecs/cs35l45-i2c.c +++ b/sound/soc/codecs/cs35l45-i2c.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0 // // cs35l45-i2c.c -- CS35L45 I2C driver // @@ -72,5 +72,5 @@ module_i2c_driver(cs35l45_i2c_driver); MODULE_DESCRIPTION("I2C CS35L45 driver"); MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, "); -MODULE_LICENSE("Dual BSD/GPL"); +MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(SND_SOC_CS35L45); diff --git a/sound/soc/codecs/cs35l45-spi.c b/sound/soc/codecs/cs35l45-spi.c index a00b23b4180c..5efb77530cc3 100644 --- a/sound/soc/codecs/cs35l45-spi.c +++ b/sound/soc/codecs/cs35l45-spi.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0 // // cs35l45-spi.c -- CS35L45 SPI driver // @@ -74,5 +74,5 @@ module_spi_driver(cs35l45_spi_driver); MODULE_DESCRIPTION("SPI CS35L45 driver"); MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, "); -MODULE_LICENSE("Dual BSD/GPL"); +MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(SND_SOC_CS35L45); diff --git a/sound/soc/codecs/cs35l45-tables.c b/sound/soc/codecs/cs35l45-tables.c index 46610e64e818..066f83c0c7ac 100644 --- a/sound/soc/codecs/cs35l45-tables.c +++ b/sound/soc/codecs/cs35l45-tables.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0 // // cs35l45-tables.c -- CS35L45 ALSA SoC audio driver // diff --git a/sound/soc/codecs/cs35l45.c b/sound/soc/codecs/cs35l45.c index c31597f6bfae..d1edb9876c10 100644 --- a/sound/soc/codecs/cs35l45.c +++ b/sound/soc/codecs/cs35l45.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// SPDX-License-Identifier: GPL-2.0 // // cs35l45.c - CS35L45 ALSA SoC audio driver // @@ -1296,4 +1296,4 @@ EXPORT_SYMBOL_NS_GPL(cs35l45_pm_ops, SND_SOC_CS35L45); MODULE_DESCRIPTION("ASoC CS35L45 driver"); MODULE_AUTHOR("James Schulman, Cirrus Logic Inc, "); MODULE_AUTHOR("Richard Fitzgerald "); -MODULE_LICENSE("Dual BSD/GPL"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l45.h b/sound/soc/codecs/cs35l45.h index 0da28439f628..61135a316df3 100644 --- a/sound/soc/codecs/cs35l45.h +++ b/sound/soc/codecs/cs35l45.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ +/* SPDX-License-Identifier: GPL-2.0 */ /* * cs35l45.h - CS35L45 ALSA SoC audio driver * -- cgit v1.2.3 From 13e75f4b03217226f110c5bb5d11720adb5ca9d1 Mon Sep 17 00:00:00 2001 From: Vitaly Rodionov Date: Wed, 24 May 2023 13:52:36 +0100 Subject: ASoC: cs42l42: Add PLL ratio table values Add 4.8Mhz 9.6Mhz and 19.2MHz SCLK values for MCLK 12MHz and 12.288MHz requested by Intel. Signed-off-by: Vitaly Rodionov Reviewed-by: Richard Fitzgerald Link: https://lore.kernel.org/r/20230524125236.57149-1-vitalyr@opensource.cirrus.com Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l42.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index e3edaa1a2761..8aa6af21e52c 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -646,12 +646,19 @@ static const struct cs42l42_pll_params pll_ratio_table[] = { { 3072000, 1, 0x00, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, { 4000000, 1, 0x00, 0x30, 0x800000, 0x03, 0x10, 12000000, 96, 1}, { 4096000, 1, 0x00, 0x2E, 0xE00000, 0x03, 0x10, 12000000, 94, 1}, + { 4800000, 1, 0x01, 0x50, 0x000000, 0x03, 0x10, 12000000, 80, 2}, + { 4800000, 1, 0x01, 0x50, 0x000000, 0x01, 0x10, 12288000, 82, 2}, { 5644800, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, { 6000000, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, { 6144000, 1, 0x01, 0x3E, 0x800000, 0x03, 0x10, 12000000, 125, 1}, + { 6144000, 1, 0x01, 0x40, 0x000000, 0x03, 0x10, 12288000, 128, 1}, + { 9600000, 1, 0x02, 0x50, 0x000000, 0x03, 0x10, 12000000, 80, 2}, + { 9600000, 1, 0x02, 0x50, 0x000000, 0x01, 0x10, 12288000, 82, 2}, { 11289600, 0, 0, 0, 0, 0, 0, 11289600, 0, 1}, { 12000000, 0, 0, 0, 0, 0, 0, 12000000, 0, 1}, { 12288000, 0, 0, 0, 0, 0, 0, 12288000, 0, 1}, + { 19200000, 1, 0x03, 0x50, 0x000000, 0x03, 0x10, 12000000, 80, 2}, + { 19200000, 1, 0x03, 0x50, 0x000000, 0x01, 0x10, 12288000, 82, 2}, { 22579200, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 11289600, 128, 1}, { 24000000, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 12000000, 128, 1}, { 24576000, 1, 0x03, 0x40, 0x000000, 0x03, 0x10, 12288000, 128, 1} -- cgit v1.2.3 From 0f3d5585ad20a23bf70d09deae2e0d84e745055e Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Tue, 23 May 2023 10:59:32 +0800 Subject: ASoC: SOF: mediatek: add mt8188 audio support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add mt8188 dai driver and specify of_machine to support mt8188 audio. Signed-off-by: Trevor Wu Reviewed-by: Pierre-Louis Bossart Reviewed-by: Yaochun Hung Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20230523025933.30494-2-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/sof/mediatek/mt8186/mt8186.c | 61 +++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c index 419913c8474d..cc91c2928fb6 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186.c +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -594,7 +594,65 @@ static const struct sof_dev_desc sof_of_mt8186_desc = { .ops = &sof_mt8186_ops, }; +/* + * DL2, DL3, UL4, UL5 are registered as SOF FE, so creating the corresponding + * SOF BE to complete the pipeline. + */ +static struct snd_soc_dai_driver mt8188_dai[] = { +{ + .name = "SOF_DL2", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_DL3", + .playback = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL4", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +{ + .name = "SOF_UL5", + .capture = { + .channels_min = 1, + .channels_max = 2, + }, +}, +}; + +/* mt8188 ops */ +static struct snd_sof_dsp_ops sof_mt8188_ops; + +static int sof_mt8188_ops_init(struct snd_sof_dev *sdev) +{ + /* common defaults */ + memcpy(&sof_mt8188_ops, &sof_mt8186_ops, sizeof(sof_mt8188_ops)); + + sof_mt8188_ops.drv = mt8188_dai; + sof_mt8188_ops.num_drv = ARRAY_SIZE(mt8188_dai); + + return 0; +} + +static struct snd_sof_of_mach sof_mt8188_machs[] = { + { + .compatible = "mediatek,mt8188", + .sof_tplg_filename = "sof-mt8188.tplg", + }, + {} +}; + static const struct sof_dev_desc sof_of_mt8188_desc = { + .of_machines = sof_mt8188_machs, .ipc_supported_mask = BIT(SOF_IPC), .ipc_default = SOF_IPC, .default_fw_path = { @@ -607,7 +665,8 @@ static const struct sof_dev_desc sof_of_mt8188_desc = { [SOF_IPC] = "sof-mt8188.ri", }, .nocodec_tplg_filename = "sof-mt8188-nocodec.tplg", - .ops = &sof_mt8186_ops, + .ops = &sof_mt8188_ops, + .ops_init = sof_mt8188_ops_init, }; static const struct of_device_id sof_of_mt8186_ids[] = { -- cgit v1.2.3 From e89f45edb747ed88e97a5771dd6d3dd1eb517873 Mon Sep 17 00:00:00 2001 From: Venkata Prasad Potturu Date: Tue, 30 May 2023 16:37:58 +0530 Subject: ASoC: amd: vangogh: Add check for acp config flags in vangogh platform We have SOF and generic ACP support enabled for Vangogh platform on some machines. Since we have same PCI id used for probing, add check for machine configuration flag to avoid conflict with newer pci drivers. Such machine flag has been initialized via dmi match on few Vangogh based machines. If no flag is specified probe and register older platform device. Signed-off-by: Venkata Prasad Potturu Link: https://lore.kernel.org/r/20230530110802.674939-1-venkataprasad.potturu@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/vangogh/acp5x.h | 2 ++ sound/soc/amd/vangogh/pci-acp5x.c | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/amd/vangogh/acp5x.h b/sound/soc/amd/vangogh/acp5x.h index bd9f1c5684d1..ac1936a8c43f 100644 --- a/sound/soc/amd/vangogh/acp5x.h +++ b/sound/soc/amd/vangogh/acp5x.h @@ -147,6 +147,8 @@ static inline void acp_writel(u32 val, void __iomem *base_addr) writel(val, base_addr - ACP5x_PHY_BASE_ADDRESS); } +int snd_amd_acp_find_config(struct pci_dev *pci); + static inline u64 acp_get_byte_count(struct i2s_stream_instance *rtd, int direction) { diff --git a/sound/soc/amd/vangogh/pci-acp5x.c b/sound/soc/amd/vangogh/pci-acp5x.c index e0df17c88e8e..c4634a8a17cd 100644 --- a/sound/soc/amd/vangogh/pci-acp5x.c +++ b/sound/soc/amd/vangogh/pci-acp5x.c @@ -125,10 +125,15 @@ static int snd_acp5x_probe(struct pci_dev *pci, { struct acp5x_dev_data *adata; struct platform_device_info pdevinfo[ACP5x_DEVS]; - unsigned int irqflags; + unsigned int irqflags, flag; int ret, i; u32 addr, val; + /* Return if acp config flag is defined */ + flag = snd_amd_acp_find_config(pci); + if (flag) + return -ENODEV; + irqflags = IRQF_SHARED; if (pci->revision != 0x50) return -ENODEV; -- cgit v1.2.3 From c3079282fdf7285b4133d6d1a7901b7923d6db09 Mon Sep 17 00:00:00 2001 From: Min-Hua Chen Date: Sat, 20 May 2023 05:16:36 +0800 Subject: ASoC: ti: davinci-mcasp: Use pcm_for_each_format() macro Use pcm_for_each_format for the PCM format iteration and fix the following sparse warnings. sound/soc/ti/davinci-mcasp.c:1336:26: sparse: warning: restricted snd_pcm_format_t degrades to integer sound/soc/ti/davinci-mcasp.c:1358:26: sparse: warning: restricted snd_pcm_format_t degrades to integer sound/soc/ti/davinci-mcasp.c:1438:26: sparse: warning: restricted snd_pcm_format_t degrades to integer No functional changes. Signed-off-by: Min-Hua Chen Link: https://lore.kernel.org/r/20230519211636.3699-1-minhuadotchen@gmail.com Signed-off-by: Mark Brown --- sound/soc/ti/davinci-mcasp.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/soc/ti/davinci-mcasp.c b/sound/soc/ti/davinci-mcasp.c index c0892be2992b..172fea764a31 100644 --- a/sound/soc/ti/davinci-mcasp.c +++ b/sound/soc/ti/davinci-mcasp.c @@ -1328,15 +1328,16 @@ static int davinci_mcasp_hw_rule_slot_width(struct snd_pcm_hw_params *params, struct davinci_mcasp_ruledata *rd = rule->private; struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_mask nfmt; - int i, slot_width; + int slot_width; + snd_pcm_format_t i; snd_mask_none(&nfmt); slot_width = rd->mcasp->slot_width; - for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { - if (snd_mask_test(fmt, i)) { + pcm_for_each_format(i) { + if (snd_mask_test_format(fmt, i)) { if (snd_pcm_format_width(i) <= slot_width) { - snd_mask_set(&nfmt, i); + snd_mask_set_format(&nfmt, i); } } } @@ -1350,15 +1351,16 @@ static int davinci_mcasp_hw_rule_format_width(struct snd_pcm_hw_params *params, struct davinci_mcasp_ruledata *rd = rule->private; struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); struct snd_mask nfmt; - int i, format_width; + int format_width; + snd_pcm_format_t i; snd_mask_none(&nfmt); format_width = rd->mcasp->max_format_width; - for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { - if (snd_mask_test(fmt, i)) { + pcm_for_each_format(i) { + if (snd_mask_test_format(fmt, i)) { if (snd_pcm_format_width(i) == format_width) { - snd_mask_set(&nfmt, i); + snd_mask_set_format(&nfmt, i); } } } @@ -1431,12 +1433,13 @@ static int davinci_mcasp_hw_rule_format(struct snd_pcm_hw_params *params, struct snd_mask nfmt; int rate = params_rate(params); int slots = rd->mcasp->tdm_slots; - int i, count = 0; + int count = 0; + snd_pcm_format_t i; snd_mask_none(&nfmt); - for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; i++) { - if (snd_mask_test(fmt, i)) { + pcm_for_each_format(i) { + if (snd_mask_test_format(fmt, i)) { uint sbits = snd_pcm_format_width(i); unsigned int sysclk_freq; int ppm; @@ -1454,7 +1457,7 @@ static int davinci_mcasp_hw_rule_format(struct snd_pcm_hw_params *params, sbits * slots * rate, false); if (abs(ppm) < DAVINCI_MAX_RATE_ERROR_PPM) { - snd_mask_set(&nfmt, i); + snd_mask_set_format(&nfmt, i); count++; } } -- cgit v1.2.3 From e018e0b346706d0a0d7d7f884f3850cc0903abc2 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 26 May 2023 15:41:47 -0500 Subject: ASoC: topology: Allow partial matching when finding DAI link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows for setting shorter link names in topology. For example, for the HDA Analog DAI link, just "Analog" would suffice instead of "Analog Playback and Capture" Signed-off-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Reviewed-by: Bard Liao Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20230526204149.456068-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 20fd46a41cbb..8add361e87c6 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -2030,11 +2030,11 @@ static struct snd_soc_dai_link *snd_soc_find_dai_link(struct snd_soc_card *card, if (link->id != id) continue; - if (name && (!link->name || strcmp(name, link->name))) + if (name && (!link->name || !strstr(link->name, name))) continue; - if (stream_name && (!link->stream_name - || strcmp(stream_name, link->stream_name))) + if (stream_name && (!link->stream_name || + !strstr(link->stream_name, stream_name))) continue; return link; -- cgit v1.2.3 From fe88788779fc30a4117dc2f9db4b50182679bb67 Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 26 May 2023 15:41:48 -0500 Subject: ASoC: SOF: topology: Use partial match for connecting DAI link and DAI widget This allows setting shorter names for the widget stream names in topology. For example, in the case of HDA Analog DAI link, the stream name is "Analog Playback and Capture". But it is enough to match "Analog" in the DAI link stream name with a widget's stream name. This is needed to set more meaningful names for the DAI widgets using the stream name in topology. Signed-off-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230526204149.456068-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/topology.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/topology.c b/sound/soc/sof/topology.c index d3d536b0a8f5..b572c809581d 100644 --- a/sound/soc/sof/topology.c +++ b/sound/soc/sof/topology.c @@ -1073,7 +1073,7 @@ static int sof_connect_dai_widget(struct snd_soc_component *scomp, list_for_each_entry(rtd, &card->rtd_list, list) { /* does stream match DAI link ? */ if (!rtd->dai_link->stream_name || - strcmp(w->sname, rtd->dai_link->stream_name)) + !strstr(rtd->dai_link->stream_name, w->sname)) continue; for_each_rtd_cpu_dais(rtd, i, cpu_dai) { -- cgit v1.2.3 From 0f7b6a433097808e7f3e82f837ccc1353f070e4a Mon Sep 17 00:00:00 2001 From: Ranjani Sridharan Date: Fri, 26 May 2023 15:41:49 -0500 Subject: ASoC: SOF: Intel: HDA: Limit the number of dai drivers for nocodec mode With a common kernel config for nocodec and codec modes, the number of DAI drivers will be set to 15 for nocodec as well. So adjust this when set the machine params for the nocodec mode if the debug flag is set. Signed-off-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230526204149.456068-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda.c | 6 +++++- sound/soc/sof/intel/hda.h | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 3153e21f100a..835c2568dd60 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1562,7 +1562,11 @@ void hda_set_mach_params(struct snd_soc_acpi_mach *mach, mach_params = &mach->mach_params; mach_params->platform = dev_name(sdev->dev); - mach_params->num_dai_drivers = desc->ops->num_drv; + if (IS_ENABLED(CONFIG_SND_SOC_SOF_NOCODEC_DEBUG_SUPPORT) && + sof_debug_check_flag(SOF_DBG_FORCE_NOCODEC)) + mach_params->num_dai_drivers = SOF_SKL_NUM_DAIS_NOCODEC; + else + mach_params->num_dai_drivers = desc->ops->num_drv; mach_params->dai_drivers = desc->ops->drv; } diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 65832a38bffa..5b3dad2dadf4 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -414,10 +414,12 @@ (HDA_DSP_BDL_SIZE / sizeof(struct sof_intel_dsp_bdl)) /* Number of DAIs */ +#define SOF_SKL_NUM_DAIS_NOCODEC 8 + #if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC) #define SOF_SKL_NUM_DAIS 15 #else -#define SOF_SKL_NUM_DAIS 8 +#define SOF_SKL_NUM_DAIS SOF_SKL_NUM_DAIS_NOCODEC #endif /* Intel HD Audio SRAM Window 0*/ -- cgit v1.2.3 From 6f073429037cd79d7311cd8236311c53f5ea8f01 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 30 May 2023 21:11:38 +0300 Subject: ASoC: es8316: Increment max value for ALC Capture Target Volume control The following error occurs when trying to restore a previously saved ALSA mixer state (tested on a Rock 5B board): $ alsactl --no-ucm -f /tmp/asound.state store hw:Analog $ alsactl --no-ucm -I -f /tmp/asound.state restore hw:Analog alsactl: set_control:1475: Cannot write control '2:0:0:ALC Capture Target Volume:0' : Invalid argument According to ES8316 datasheet, the register at address 0x2B, which is related to the above mixer control, contains by default the value 0xB0. Considering the corresponding ALC target bits (ALCLVL) are 7:4, the control is initialized with 11, which is one step above the maximum value allowed by the driver: ALCLVL | dB gain -------+-------- 0000 | -16.5 0001 | -15.0 0010 | -13.5 .... | ..... 0111 | -6.0 1000 | -4.5 1001 | -3.0 1010 | -1.5 .... | ..... 1111 | -1.5 The tests performed using the VU meter feature (--vumeter=TYPE) of arecord/aplay confirm the specs are correct and there is no measured gain if the 1011-1111 range would have been mapped to 0 dB: dB gain | VU meter % --------+----------- -6.0 | 30-31 -4.5 | 35-36 -3.0 | 42-43 -1.5 | 50-51 0.0 | 50-51 Increment the max value allowed for ALC Capture Target Volume control, so that it matches the hardware default. Additionally, update the related TLV to prevent an artificial extension of the dB gain range. Fixes: b8b88b70875a ("ASoC: add es8316 codec driver") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20230530181140.483936-2-cristian.ciocaltea@collabora.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index a27d80956459..18d485e6921a 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -52,7 +52,12 @@ static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(dac_vol_tlv, -9600, 50, 1); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(adc_vol_tlv, -9600, 50, 1); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_max_gain_tlv, -650, 150, 0); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_min_gain_tlv, -1200, 150, 0); -static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(alc_target_tlv, -1650, 150, 0); + +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(alc_target_tlv, + 0, 10, TLV_DB_SCALE_ITEM(-1650, 150, 0), + 11, 11, TLV_DB_SCALE_ITEM(-150, 0, 0), +); + static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(hpmixer_gain_tlv, 0, 4, TLV_DB_SCALE_ITEM(-1200, 150, 0), 8, 11, TLV_DB_SCALE_ITEM(-450, 150, 0), @@ -115,7 +120,7 @@ static const struct snd_kcontrol_new es8316_snd_controls[] = { alc_max_gain_tlv), SOC_SINGLE_TLV("ALC Capture Min Volume", ES8316_ADC_ALC2, 0, 28, 0, alc_min_gain_tlv), - SOC_SINGLE_TLV("ALC Capture Target Volume", ES8316_ADC_ALC3, 4, 10, 0, + SOC_SINGLE_TLV("ALC Capture Target Volume", ES8316_ADC_ALC3, 4, 11, 0, alc_target_tlv), SOC_SINGLE("ALC Capture Hold Time", ES8316_ADC_ALC3, 0, 10, 0), SOC_SINGLE("ALC Capture Decay Time", ES8316_ADC_ALC4, 4, 10, 0), -- cgit v1.2.3 From 60413129ee2b38a80347489270af7f6e1c1de4d0 Mon Sep 17 00:00:00 2001 From: Cristian Ciocaltea Date: Tue, 30 May 2023 21:11:39 +0300 Subject: ASoC: es8316: Do not set rate constraints for unsupported MCLKs When using the codec through the generic audio graph card, there are at least two calls of es8316_set_dai_sysclk(), with the effect of limiting the allowed sample rates according to the MCLK/LRCK ratios supported by the codec: 1. During audio card setup, to set the initial MCLK - see asoc_simple_init_dai(). 2. Before opening a stream, to update MCLK, according to the stream sample rate and the multiplication factor - see asoc_simple_hw_params(). In some cases the initial MCLK might be set to a frequency that doesn't match any of the supported ratios, e.g. 12287999 instead of 12288000, which is only 1 Hz below the supported clock, as that is what the hardware reports. This creates an empty list of rate constraints, which is further passed to snd_pcm_hw_constraint_list() via es8316_pcm_startup(), and causes the following error on the very first access of the sound card: $ speaker-test -D hw:Analog,0 -F S16_LE -c 2 -t wav Broken configuration for playback: no configurations available: Invalid argument Setting of hwparams failed: Invalid argument Note that all subsequent retries succeed thanks to the updated MCLK set at point 2 above, which uses a computed frequency value instead of a reading from the hardware registers. Normally this would have mitigated the issue, but es8316_pcm_startup() executes before the 2nd call to es8316_set_dai_sysclk(), hence it cannot make use of the updated constraints. Since es8316_pcm_hw_params() performs anyway a final validation of MCLK against the stream sample rate and the supported MCLK/LRCK ratios, fix the issue by ensuring that sysclk_constraints list is only set when at least one supported sample rate is autodetected by the codec. Fixes: b8b88b70875a ("ASoC: add es8316 codec driver") Signed-off-by: Cristian Ciocaltea Link: https://lore.kernel.org/r/20230530181140.483936-3-cristian.ciocaltea@collabora.com Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 18d485e6921a..ccecfdf70064 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -369,13 +369,11 @@ static int es8316_set_dai_sysclk(struct snd_soc_dai *codec_dai, int count = 0; es8316->sysclk = freq; + es8316->sysclk_constraints.list = NULL; + es8316->sysclk_constraints.count = 0; - if (freq == 0) { - es8316->sysclk_constraints.list = NULL; - es8316->sysclk_constraints.count = 0; - + if (freq == 0) return 0; - } ret = clk_set_rate(es8316->mclk, freq); if (ret) @@ -391,8 +389,10 @@ static int es8316_set_dai_sysclk(struct snd_soc_dai *codec_dai, es8316->allowed_rates[count++] = freq / ratio; } - es8316->sysclk_constraints.list = es8316->allowed_rates; - es8316->sysclk_constraints.count = count; + if (count) { + es8316->sysclk_constraints.list = es8316->allowed_rates; + es8316->sysclk_constraints.count = count; + } return 0; } -- cgit v1.2.3 From 092830cf550667d5fa6286605167d232f2c1f61e Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 30 May 2023 00:49:43 +0000 Subject: ASoC: soc-pcm.c: indicate error if stream has no playback no capture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_get_playback_capture() (A) returns number of substreams for playback/capture (B). ASoC will probe the Sound Card and mapps CPU<->Codec pair. (A) static int soc_get_playback_capture(..., (B) int *playback, int *capture) { ... if (rtd->dai_link->playback_only) { *playback = 1; *capture = 0; } if (rtd->dai_link->capture_only) { *playback = 0; *capture = 1; } (C) return 0; } But it might be no playback no capture if it returns playback=0, capture=0. It is very difficult to notice about it. This patch indicates error at (C) then. Signed-off-by: Kuninori Morimoto Reviewed-by: Amadeusz Sławiński Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/87y1l6zlqx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 6365ad8ca7ef..f3ce825c0b20 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2814,6 +2814,13 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, *capture = 1; } + if (!*playback && !*capture) { + dev_err(rtd->dev, "substream %s has no playback, no capture\n", + rtd->dai_link->stream_name); + + return -EINVAL; + } + return 0; } -- cgit v1.2.3 From cfcb31c456b15e298f88fb5ebedf7b32b009d32d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 30 May 2023 00:49:50 +0000 Subject: ASoC: soc-pcm.c: use dai_link on soc_get_playback_capture() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_get_playback_capture() (A) is using rtd->dai_link->xxx everywhere. Because of that, 1 line is unnecessarily long and not readable. (A) static int soc_get_playback_capture(...) { if (rtd->dai_link->dynamic ...) { ^^^^^^^^^^^^^ ... } else { int cpu_capture = rtd->dai_link->c2c_params ? ^^^^^^^^^^^^^ ... } if (rtd->dai_link->playback_only) { ^^^^^^^^^^^^^ ... } ... } This patch uses variable "dai_link" to be clear code. Nothing changes the meanings. Signed-off-by: Kuninori Morimoto Reviewed-by: Amadeusz Sławiński Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/87wn0qzlqp.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index f3ce825c0b20..1e6d5da569a5 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2730,19 +2730,20 @@ open_end: static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, int *playback, int *capture) { + struct snd_soc_dai_link *dai_link = rtd->dai_link; struct snd_soc_dai *cpu_dai; int i; - if (rtd->dai_link->dynamic && rtd->dai_link->num_cpus > 1) { + if (dai_link->dynamic && dai_link->num_cpus > 1) { dev_err(rtd->dev, "DPCM doesn't support Multi CPU for Front-Ends yet\n"); return -EINVAL; } - if (rtd->dai_link->dynamic || rtd->dai_link->no_pcm) { + if (dai_link->dynamic || dai_link->no_pcm) { int stream; - if (rtd->dai_link->dpcm_playback) { + if (dai_link->dpcm_playback) { stream = SNDRV_PCM_STREAM_PLAYBACK; for_each_rtd_cpu_dais(rtd, i, cpu_dai) { @@ -2754,11 +2755,11 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, if (!*playback) { dev_err(rtd->card->dev, "No CPU DAIs support playback for stream %s\n", - rtd->dai_link->stream_name); + dai_link->stream_name); return -EINVAL; } } - if (rtd->dai_link->dpcm_capture) { + if (dai_link->dpcm_capture) { stream = SNDRV_PCM_STREAM_CAPTURE; for_each_rtd_cpu_dais(rtd, i, cpu_dai) { @@ -2771,7 +2772,7 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, if (!*capture) { dev_err(rtd->card->dev, "No CPU DAIs support capture for stream %s\n", - rtd->dai_link->stream_name); + dai_link->stream_name); return -EINVAL; } } @@ -2779,15 +2780,15 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *codec_dai; /* Adapt stream for codec2codec links */ - int cpu_capture = rtd->dai_link->c2c_params ? + int cpu_capture = dai_link->c2c_params ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; - int cpu_playback = rtd->dai_link->c2c_params ? + int cpu_playback = dai_link->c2c_params ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; for_each_rtd_codec_dais(rtd, i, codec_dai) { - if (rtd->dai_link->num_cpus == 1) { + if (dai_link->num_cpus == 1) { cpu_dai = asoc_rtd_to_cpu(rtd, 0); - } else if (rtd->dai_link->num_cpus == rtd->dai_link->num_codecs) { + } else if (dai_link->num_cpus == dai_link->num_codecs) { cpu_dai = asoc_rtd_to_cpu(rtd, i); } else { dev_err(rtd->card->dev, @@ -2804,19 +2805,19 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, } } - if (rtd->dai_link->playback_only) { + if (dai_link->playback_only) { *playback = 1; *capture = 0; } - if (rtd->dai_link->capture_only) { + if (dai_link->capture_only) { *playback = 0; *capture = 1; } if (!*playback && !*capture) { dev_err(rtd->dev, "substream %s has no playback, no capture\n", - rtd->dai_link->stream_name); + dai_link->stream_name); return -EINVAL; } -- cgit v1.2.3 From a1c0221fa5baeae6c9dc30294c2c6d01f1f4379b Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 30 May 2023 00:49:56 +0000 Subject: ASoC: soc-pcm.c: cleanup soc_get_playback_capture() error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_get_playback_capture() (A) checks dai_link status, and indicate error if it was not matching (B). (A) static int soc_get_playback_capture(...) { ... ^ if (dai_link->dynamic && dai_link->num_cpus > 1) { | dev_err(rtd->dev, (B) "DPCM doesn't support Multi CPU for Front-Ends yet\n"); | return -EINVAL; v } ... } We can use 100 char for 1 line today. This patch cleanup error code line. Signed-off-by: Kuninori Morimoto Reviewed-by: Amadeusz Sławiński Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/87v8gazlqk.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 1e6d5da569a5..e752089a4227 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2735,8 +2735,7 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, int i; if (dai_link->dynamic && dai_link->num_cpus > 1) { - dev_err(rtd->dev, - "DPCM doesn't support Multi CPU for Front-Ends yet\n"); + dev_err(rtd->dev, "DPCM doesn't support Multi CPU for Front-Ends yet\n"); return -EINVAL; } -- cgit v1.2.3 From c3e9b6d6ef5a0a3e841c3aa29e7afc48a0b73806 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 30 May 2023 00:50:01 +0000 Subject: ASoC: soc-pcm.c: use temporary variable at soc_get_playback_capture() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_get_playback_capture() (A) returns number of substreams for playback/capture (B). (A) static int soc_get_playback_capture(..., (B) int *playback, int *capture) { ... for_each_xxx(...) { if (xxx) return -EINVAL; => *playback = 1; ... => *capture = 1; ... } ... } But, it is directly updating playback/capture which is the result of this function even though it might be error. It should be updated in case of succeed only. This patch updates it. Signed-off-by: Kuninori Morimoto Reviewed-by: Amadeusz Sławiński Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/87ttvuzlqe.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index e752089a4227..765e43ca637d 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2732,6 +2732,8 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, { struct snd_soc_dai_link *dai_link = rtd->dai_link; struct snd_soc_dai *cpu_dai; + int has_playback = 0; + int has_capture = 0; int i; if (dai_link->dynamic && dai_link->num_cpus > 1) { @@ -2747,11 +2749,11 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, for_each_rtd_cpu_dais(rtd, i, cpu_dai) { if (snd_soc_dai_stream_valid(cpu_dai, stream)) { - *playback = 1; + has_playback = 1; break; } } - if (!*playback) { + if (!has_playback) { dev_err(rtd->card->dev, "No CPU DAIs support playback for stream %s\n", dai_link->stream_name); @@ -2763,12 +2765,12 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, for_each_rtd_cpu_dais(rtd, i, cpu_dai) { if (snd_soc_dai_stream_valid(cpu_dai, stream)) { - *capture = 1; + has_capture = 1; break; } } - if (!*capture) { + if (!has_capture) { dev_err(rtd->card->dev, "No CPU DAIs support capture for stream %s\n", dai_link->stream_name); @@ -2797,30 +2799,33 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) && snd_soc_dai_stream_valid(cpu_dai, cpu_playback)) - *playback = 1; + has_playback = 1; if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_CAPTURE) && snd_soc_dai_stream_valid(cpu_dai, cpu_capture)) - *capture = 1; + has_capture = 1; } } if (dai_link->playback_only) { - *playback = 1; - *capture = 0; + has_playback = 1; + has_capture = 0; } if (dai_link->capture_only) { - *playback = 0; - *capture = 1; + has_playback = 0; + has_capture = 1; } - if (!*playback && !*capture) { + if (!has_playback && !has_capture) { dev_err(rtd->dev, "substream %s has no playback, no capture\n", dai_link->stream_name); return -EINVAL; } + *playback = has_playback; + *capture = has_capture; + return 0; } -- cgit v1.2.3 From e1f653ce847bab7285dd135cabe3ce544e574c75 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 30 May 2023 00:50:08 +0000 Subject: ASoC: soc-pcm.c: tidyup playback/capture_only at soc_get_playback_capture() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit soc_get_playback_capture() (A) returns number of substreams for playback/capture, and then, we can use playback/capture_only flag (X)(Y). (A) static int soc_get_playback_capture(...) { ... (X) if (dai_link->playback_only) { (*) *playback = 1; *capture = 0; } (Y) if (dai_link->capture_only) { *playback = 0; (*) *capture = 1; } ... } But this flag should not have effect to opposite side stream (*). This patch tidyup it. Signed-off-by: Kuninori Morimoto Reviewed-by: Amadeusz Sławiński Reviewed-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/87sfbezlq8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-pcm.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 765e43ca637d..fc0817dd0d83 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2806,15 +2806,11 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, } } - if (dai_link->playback_only) { - has_playback = 1; + if (dai_link->playback_only) has_capture = 0; - } - if (dai_link->capture_only) { + if (dai_link->capture_only) has_playback = 0; - has_capture = 1; - } if (!has_playback && !has_capture) { dev_err(rtd->dev, "substream %s has no playback, no capture\n", -- cgit v1.2.3 From 8315d8adc048bd7f8eb7ee5722ecef4e6e7d52ff Mon Sep 17 00:00:00 2001 From: David Lin Date: Wed, 31 May 2023 15:53:35 +0800 Subject: ASoC: nau8825: Add the management of headset detection for power saving The patch is to manage HSD feature for power saving. The detail is to disable HSD feature after the headset detection is done. When the jack is inserted, the HSD feature will be enabled again. Signed-off-by: David Lin Link: https://lore.kernel.org/r/20230531075334.168637-1-CTLIN0@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index cc3e18207c42..f6dd84b32e0b 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -1704,6 +1704,10 @@ static void nau8825_setup_auto_irq(struct nau8825 *nau8825) { struct regmap *regmap = nau8825->regmap; + /* Enable HSD function */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, + NAU8825_HSD_AUTO_MODE, NAU8825_HSD_AUTO_MODE); + /* Enable headset jack type detection complete interruption and * jack ejection interruption. */ @@ -1955,6 +1959,9 @@ static int nau8825_jack_insert(struct nau8825 *nau8825) regmap_update_bits(regmap, NAU8825_REG_CLK_DIVIDER, NAU8825_CLK_MCLK_SRC_MASK, 0xf); + /* Disable HSD function */ + regmap_update_bits(regmap, NAU8825_REG_HSD_CTRL, NAU8825_HSD_AUTO_MODE, 0); + /* Leaving HPOL/R grounded after jack insert by default. They will be * ungrounded as part of the widget power up sequence at the beginning * of playback to reduce pop. -- cgit v1.2.3 From fd4762b6b5cfa27bf44f5d624ce74b7dce4a479c Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Fri, 26 May 2023 22:54:01 +0800 Subject: ASoC: starfive: Add JH7110 TDM driver Add tdm driver support for the StarFive JH7110 SoC. Signed-off-by: Walker Chen Reviewed-by: Claudiu Beznea Link: https://lore.kernel.org/r/20230526145402.450-3-walker.chen@starfivetech.com Signed-off-by: Mark Brown --- MAINTAINERS | 6 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/starfive/Kconfig | 15 + sound/soc/starfive/Makefile | 2 + sound/soc/starfive/jh7110_tdm.c | 679 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 704 insertions(+) create mode 100644 sound/soc/starfive/Kconfig create mode 100644 sound/soc/starfive/Makefile create mode 100644 sound/soc/starfive/jh7110_tdm.c (limited to 'sound') diff --git a/MAINTAINERS b/MAINTAINERS index 27ef11624748..fc758fc19589 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20127,6 +20127,12 @@ F: Documentation/devicetree/bindings/power/starfive* F: drivers/soc/starfive/jh71xx_pmu.c F: include/dt-bindings/power/starfive,jh7110-pmu.h +STARFIVE JH7110 TDM DRIVER +M: Walker Chen +S: Maintained +F: Documentation/devicetree/bindings/sound/starfive,jh7110-tdm.yaml +F: sound/soc/starfive/jh7110_tdm.c + STARFIVE SOC DRIVERS M: Conor Dooley S: Maintained diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 48e778c18912..4b6e5a802880 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -92,6 +92,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sof/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sprd/Kconfig" +source "sound/soc/starfive/Kconfig" source "sound/soc/sti/Kconfig" source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index d5cc3eb710f9..9d9b228e4508 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sof/ obj-$(CONFIG_SND_SOC) += spear/ obj-$(CONFIG_SND_SOC) += sprd/ +obj-$(CONFIG_SND_SOC) += starfive/ obj-$(CONFIG_SND_SOC) += sti/ obj-$(CONFIG_SND_SOC) += stm/ obj-$(CONFIG_SND_SOC) += sunxi/ diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig new file mode 100644 index 000000000000..fafb681f8c0a --- /dev/null +++ b/sound/soc/starfive/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_SOC_STARFIVE + tristate "Audio support for StarFive SoC" + depends on COMPILE_TEST || ARCH_STARFIVE + help + Say Y or M if you want to add support for codecs attached to + the Starfive SoCs' Audio interfaces. You will also need to + select the audio interfaces to support below. + +config SND_SOC_JH7110_TDM + tristate "JH7110 TDM device driver" + depends on HAVE_CLK && SND_SOC_STARFIVE + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for StarFive TDM driver. diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile new file mode 100644 index 000000000000..f7d960211d72 --- /dev/null +++ b/sound/soc/starfive/Makefile @@ -0,0 +1,2 @@ +# StarFive Platform Support +obj-$(CONFIG_SND_SOC_JH7110_TDM) += jh7110_tdm.o diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c new file mode 100644 index 000000000000..973b910d2d3e --- /dev/null +++ b/sound/soc/starfive/jh7110_tdm.c @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * jh7110_tdm.c -- StarFive JH7110 TDM driver + * + * Copyright (C) 2023 StarFive Technology Co., Ltd. + * + * Author: Walker Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TDM_PCMGBCR 0x00 + #define PCMGBCR_MASK 0x1e + #define PCMGBCR_ENABLE BIT(0) + #define PCMGBCR_TRITXEN BIT(4) + #define CLKPOL_BIT 5 + #define TRITXEN_BIT 4 + #define ELM_BIT 3 + #define SYNCM_BIT 2 + #define MS_BIT 1 +#define TDM_PCMTXCR 0x04 + #define PCMTXCR_TXEN BIT(0) + #define IFL_BIT 11 + #define WL_BIT 8 + #define SSCALE_BIT 4 + #define SL_BIT 2 + #define LRJ_BIT 1 +#define TDM_PCMRXCR 0x08 + #define PCMRXCR_RXEN BIT(0) + #define PCMRXCR_RXSL_MASK 0xc + #define PCMRXCR_RXSL_16BIT 0x4 + #define PCMRXCR_RXSL_32BIT 0x8 + #define PCMRXCR_SCALE_MASK 0xf0 + #define PCMRXCR_SCALE_1CH 0x10 +#define TDM_PCMDIV 0x0c + +#define JH7110_TDM_FIFO 0x170c0000 +#define JH7110_TDM_FIFO_DEPTH 32 + +enum TDM_MASTER_SLAVE_MODE { + TDM_AS_MASTER = 0, + TDM_AS_SLAVE, +}; + +enum TDM_CLKPOL { + /* tx raising and rx falling */ + TDM_TX_RASING_RX_FALLING = 0, + /* tx falling and rx raising */ + TDM_TX_FALLING_RX_RASING, +}; + +enum TDM_ELM { + /* only work while SYNCM=0 */ + TDM_ELM_LATE = 0, + TDM_ELM_EARLY, +}; + +enum TDM_SYNCM { + /* short frame sync */ + TDM_SYNCM_SHORT = 0, + /* long frame sync */ + TDM_SYNCM_LONG, +}; + +enum TDM_IFL { + /* FIFO to send or received : half-1/2, Quarter-1/4 */ + TDM_FIFO_HALF = 0, + TDM_FIFO_QUARTER, +}; + +enum TDM_WL { + /* send or received word length */ + TDM_8BIT_WORD_LEN = 0, + TDM_16BIT_WORD_LEN, + TDM_20BIT_WORD_LEN, + TDM_24BIT_WORD_LEN, + TDM_32BIT_WORD_LEN, +}; + +enum TDM_SL { + /* send or received slot length */ + TDM_8BIT_SLOT_LEN = 0, + TDM_16BIT_SLOT_LEN, + TDM_32BIT_SLOT_LEN, +}; + +enum TDM_LRJ { + /* left-justify or right-justify */ + TDM_RIGHT_JUSTIFY = 0, + TDM_LEFT_JUSTIFT, +}; + +struct tdm_chan_cfg { + enum TDM_IFL ifl; + enum TDM_WL wl; + unsigned char sscale; + enum TDM_SL sl; + enum TDM_LRJ lrj; + unsigned char enable; +}; + +struct jh7110_tdm_dev { + void __iomem *tdm_base; + struct device *dev; + struct clk_bulk_data clks[6]; + struct reset_control *resets; + + enum TDM_CLKPOL clkpolity; + enum TDM_ELM elm; + enum TDM_SYNCM syncm; + enum TDM_MASTER_SLAVE_MODE ms_mode; + + struct tdm_chan_cfg tx; + struct tdm_chan_cfg rx; + + u16 syncdiv; + u32 samplerate; + u32 pcmclk; + + /* data related to DMA transfers b/w tdm and DMAC */ + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; + u32 saved_pcmgbcr; + u32 saved_pcmtxcr; + u32 saved_pcmrxcr; + u32 saved_pcmdiv; +}; + +static inline u32 jh7110_tdm_readl(struct jh7110_tdm_dev *tdm, u16 reg) +{ + return readl_relaxed(tdm->tdm_base + reg); +} + +static inline void jh7110_tdm_writel(struct jh7110_tdm_dev *tdm, u16 reg, u32 val) +{ + writel_relaxed(val, tdm->tdm_base + reg); +} + +static void jh7110_tdm_save_context(struct jh7110_tdm_dev *tdm, + struct snd_pcm_substream *substream) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tdm->saved_pcmtxcr = jh7110_tdm_readl(tdm, TDM_PCMTXCR); + else + tdm->saved_pcmrxcr = jh7110_tdm_readl(tdm, TDM_PCMRXCR); +} + +static void jh7110_tdm_start(struct jh7110_tdm_dev *tdm, + struct snd_pcm_substream *substream) +{ + u32 data; + + data = jh7110_tdm_readl(tdm, TDM_PCMGBCR); + jh7110_tdm_writel(tdm, TDM_PCMGBCR, data | PCMGBCR_ENABLE); + + /* restore context */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + jh7110_tdm_writel(tdm, TDM_PCMTXCR, tdm->saved_pcmtxcr | PCMTXCR_TXEN); + else + jh7110_tdm_writel(tdm, TDM_PCMRXCR, tdm->saved_pcmrxcr | PCMRXCR_RXEN); +} + +static void jh7110_tdm_stop(struct jh7110_tdm_dev *tdm, + struct snd_pcm_substream *substream) +{ + unsigned int val; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val = jh7110_tdm_readl(tdm, TDM_PCMTXCR); + val &= ~PCMTXCR_TXEN; + jh7110_tdm_writel(tdm, TDM_PCMTXCR, val); + } else { + val = jh7110_tdm_readl(tdm, TDM_PCMRXCR); + val &= ~PCMRXCR_RXEN; + jh7110_tdm_writel(tdm, TDM_PCMRXCR, val); + } +} + +static int jh7110_tdm_syncdiv(struct jh7110_tdm_dev *tdm) +{ + u32 sl, sscale, syncdiv; + + if (tdm->rx.sl >= tdm->tx.sl) + sl = tdm->rx.sl; + else + sl = tdm->tx.sl; + + if (tdm->rx.sscale >= tdm->tx.sscale) + sscale = tdm->rx.sscale; + else + sscale = tdm->tx.sscale; + + syncdiv = tdm->pcmclk / tdm->samplerate - 1; + + if ((syncdiv + 1) < (sl * sscale)) { + dev_err(tdm->dev, "Failed to set syncdiv!\n"); + return -EINVAL; + } + + if (tdm->syncm == TDM_SYNCM_LONG && + (tdm->rx.sscale <= 1 || tdm->tx.sscale <= 1) && + ((syncdiv + 1) <= sl)) { + dev_err(tdm->dev, "Wrong syncdiv! It must be (syncdiv+1) > max[tx.sl, rx.sl]\n"); + return -EINVAL; + } + + jh7110_tdm_writel(tdm, TDM_PCMDIV, syncdiv); + return 0; +} + +static int jh7110_tdm_config(struct jh7110_tdm_dev *tdm, + struct snd_pcm_substream *substream) +{ + u32 datarx, datatx; + int ret; + + ret = jh7110_tdm_syncdiv(tdm); + if (ret) + return ret; + + datarx = (tdm->rx.ifl << IFL_BIT) | + (tdm->rx.wl << WL_BIT) | + (tdm->rx.sscale << SSCALE_BIT) | + (tdm->rx.sl << SL_BIT) | + (tdm->rx.lrj << LRJ_BIT); + + datatx = (tdm->tx.ifl << IFL_BIT) | + (tdm->tx.wl << WL_BIT) | + (tdm->tx.sscale << SSCALE_BIT) | + (tdm->tx.sl << SL_BIT) | + (tdm->tx.lrj << LRJ_BIT); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + jh7110_tdm_writel(tdm, TDM_PCMTXCR, datatx); + else + jh7110_tdm_writel(tdm, TDM_PCMRXCR, datarx); + + return 0; +} + +static void jh7110_tdm_clk_disable(struct jh7110_tdm_dev *tdm) +{ + clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), tdm->clks); +} + +static int jh7110_tdm_clk_enable(struct jh7110_tdm_dev *tdm) +{ + int ret; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(tdm->clks), tdm->clks); + if (ret) { + dev_err(tdm->dev, "Failed to enable tdm clocks\n"); + return ret; + } + + ret = reset_control_deassert(tdm->resets); + if (ret) { + dev_err(tdm->dev, "Failed to deassert tdm resets\n"); + goto dis_tdm_clk; + } + + /* select tdm_ext clock as the clock source for tdm */ + ret = clk_set_parent(tdm->clks[5].clk, tdm->clks[4].clk); + if (ret) { + dev_err(tdm->dev, "Can't set extern clock source for clk_tdm\n"); + goto dis_tdm_clk; + } + + return 0; + +dis_tdm_clk: + clk_bulk_disable_unprepare(ARRAY_SIZE(tdm->clks), tdm->clks); + + return ret; +} + +static int jh7110_tdm_runtime_suspend(struct device *dev) +{ + struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); + + jh7110_tdm_clk_disable(tdm); + return 0; +} + +static int jh7110_tdm_runtime_resume(struct device *dev) +{ + struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); + + return jh7110_tdm_clk_enable(tdm); +} + +static int jh7110_tdm_system_suspend(struct device *dev) +{ + struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); + + /* save context */ + tdm->saved_pcmgbcr = jh7110_tdm_readl(tdm, TDM_PCMGBCR); + tdm->saved_pcmdiv = jh7110_tdm_readl(tdm, TDM_PCMDIV); + + return pm_runtime_force_suspend(dev); +} + +static int jh7110_tdm_system_resume(struct device *dev) +{ + struct jh7110_tdm_dev *tdm = dev_get_drvdata(dev); + + /* restore context */ + jh7110_tdm_writel(tdm, TDM_PCMGBCR, tdm->saved_pcmgbcr); + jh7110_tdm_writel(tdm, TDM_PCMDIV, tdm->saved_pcmdiv); + + return pm_runtime_force_resume(dev); +} + +static const struct snd_soc_component_driver jh7110_tdm_component = { + .name = "jh7110-tdm", +}; + +static int jh7110_tdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai_link *dai_link = rtd->dai_link; + + dai_link->stop_dma_first = 1; + + return 0; +} + +static int jh7110_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); + int chan_wl, chan_sl, chan_nr; + unsigned int data_width; + unsigned int dma_bus_width; + struct snd_dmaengine_dai_dma_data *dma_data = NULL; + int ret; + + data_width = params_width(params); + + tdm->samplerate = params_rate(params); + tdm->pcmclk = params_channels(params) * tdm->samplerate * data_width; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + chan_wl = TDM_16BIT_WORD_LEN; + chan_sl = TDM_16BIT_SLOT_LEN; + dma_bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + chan_wl = TDM_32BIT_WORD_LEN; + chan_sl = TDM_32BIT_SLOT_LEN; + dma_bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + + default: + dev_err(tdm->dev, "tdm: unsupported PCM fmt"); + return -EINVAL; + } + + chan_nr = params_channels(params); + switch (chan_nr) { + case 1: + case 2: + case 4: + case 6: + case 8: + break; + default: + dev_err(tdm->dev, "channel not supported\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tdm->tx.wl = chan_wl; + tdm->tx.sl = chan_sl; + tdm->tx.sscale = chan_nr; + tdm->play_dma_data.addr_width = dma_bus_width; + dma_data = &tdm->play_dma_data; + } else { + tdm->rx.wl = chan_wl; + tdm->rx.sl = chan_sl; + tdm->rx.sscale = chan_nr; + tdm->capture_dma_data.addr_width = dma_bus_width; + dma_data = &tdm->capture_dma_data; + } + + snd_soc_dai_set_dma_data(dai, substream, dma_data); + + ret = jh7110_tdm_config(tdm, substream); + if (ret) + return ret; + + jh7110_tdm_save_context(tdm, substream); + return 0; +} + +static int jh7110_tdm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + jh7110_tdm_start(tdm, substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + jh7110_tdm_stop(tdm, substream); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int jh7110_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(cpu_dai); + unsigned int gbcr; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + /* cpu is master */ + tdm->ms_mode = TDM_AS_MASTER; + break; + case SND_SOC_DAIFMT_BC_FC: + /* codec is master */ + tdm->ms_mode = TDM_AS_SLAVE; + break; + case SND_SOC_DAIFMT_BC_FP: + case SND_SOC_DAIFMT_BP_FC: + return -EINVAL; + default: + dev_dbg(tdm->dev, "dwc : Invalid clock provider format\n"); + return -EINVAL; + } + + gbcr = (tdm->clkpolity << CLKPOL_BIT) | + (tdm->elm << ELM_BIT) | + (tdm->syncm << SYNCM_BIT) | + (tdm->ms_mode << MS_BIT); + jh7110_tdm_writel(tdm, TDM_PCMGBCR, gbcr); + + return 0; +} + +static const struct snd_soc_dai_ops jh7110_tdm_dai_ops = { + .startup = jh7110_tdm_startup, + .hw_params = jh7110_tdm_hw_params, + .trigger = jh7110_tdm_trigger, + .set_fmt = jh7110_tdm_set_dai_fmt, +}; + +static int jh7110_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct jh7110_tdm_dev *tdm = snd_soc_dai_get_drvdata(dai); + + snd_soc_dai_init_dma_data(dai, &tdm->play_dma_data, &tdm->capture_dma_data); + snd_soc_dai_set_drvdata(dai, tdm); + return 0; +} + +#define JH7110_TDM_RATES SNDRV_PCM_RATE_8000_48000 + +#define JH7110_TDM_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_driver jh7110_tdm_dai = { + .name = "sf_tdm", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 8, + .rates = JH7110_TDM_RATES, + .formats = JH7110_TDM_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 8, + .rates = JH7110_TDM_RATES, + .formats = JH7110_TDM_FORMATS, + }, + .ops = &jh7110_tdm_dai_ops, + .probe = jh7110_tdm_dai_probe, + .symmetric_rate = 1, +}; + +static const struct snd_pcm_hardware jh7110_pcm_hardware = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER), + .buffer_bytes_max = 192512, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 48, + .fifo_size = 16, +}; + +static const struct snd_dmaengine_pcm_config jh7110_dmaengine_pcm_config = { + .pcm_hardware = &jh7110_pcm_hardware, + .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, + .prealloc_buffer_size = 192512, +}; + +static void jh7110_tdm_init_params(struct jh7110_tdm_dev *tdm) +{ + tdm->clkpolity = TDM_TX_RASING_RX_FALLING; + tdm->elm = TDM_ELM_LATE; + tdm->syncm = TDM_SYNCM_SHORT; + + tdm->rx.ifl = TDM_FIFO_HALF; + tdm->tx.ifl = TDM_FIFO_HALF; + tdm->rx.wl = TDM_16BIT_WORD_LEN; + tdm->tx.wl = TDM_16BIT_WORD_LEN; + tdm->rx.sscale = 2; + tdm->tx.sscale = 2; + tdm->rx.lrj = TDM_LEFT_JUSTIFT; + tdm->tx.lrj = TDM_LEFT_JUSTIFT; + + tdm->play_dma_data.addr = JH7110_TDM_FIFO; + tdm->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + tdm->play_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2; + tdm->play_dma_data.maxburst = 16; + + tdm->capture_dma_data.addr = JH7110_TDM_FIFO; + tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + tdm->capture_dma_data.fifo_size = JH7110_TDM_FIFO_DEPTH / 2; + tdm->capture_dma_data.maxburst = 8; +} + +static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, + struct jh7110_tdm_dev *tdm) +{ + int ret; + + tdm->clks[0].id = "mclk_inner"; + tdm->clks[1].id = "tdm_ahb"; + tdm->clks[2].id = "tdm_apb"; + tdm->clks[3].id = "tdm_internal"; + tdm->clks[4].id = "tdm_ext"; + tdm->clks[5].id = "tdm"; + + ret = devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(tdm->clks), tdm->clks); + if (ret) { + dev_err(&pdev->dev, "Failed to get tdm clocks\n"); + return ret; + } + + tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); + if (IS_ERR_OR_NULL(tdm->resets)) { + ret = PTR_ERR(tdm->resets); + dev_err(&pdev->dev, "Failed to get tdm resets"); + return ret; + } + + return 0; +} + +static int jh7110_tdm_probe(struct platform_device *pdev) +{ + struct jh7110_tdm_dev *tdm; + int ret; + + tdm = devm_kzalloc(&pdev->dev, sizeof(*tdm), GFP_KERNEL); + if (!tdm) + return -ENOMEM; + + tdm->tdm_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(tdm->tdm_base)) + return PTR_ERR(tdm->tdm_base); + + tdm->dev = &pdev->dev; + + ret = jh7110_tdm_clk_reset_get(pdev, tdm); + if (ret) { + dev_err(&pdev->dev, "Failed to enable audio-tdm clock\n"); + return ret; + } + + jh7110_tdm_init_params(tdm); + + dev_set_drvdata(&pdev->dev, tdm); + ret = devm_snd_soc_register_component(&pdev->dev, &jh7110_tdm_component, + &jh7110_tdm_dai, 1); + if (ret) { + dev_err(&pdev->dev, "Failed to register dai\n"); + return ret; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, + &jh7110_dmaengine_pcm_config, + SND_DMAENGINE_PCM_FLAG_COMPAT); + if (ret) { + dev_err(&pdev->dev, "Could not register pcm: %d\n", ret); + return ret; + } + + pm_runtime_enable(&pdev->dev); + if (!pm_runtime_enabled(&pdev->dev)) { + ret = jh7110_tdm_runtime_resume(&pdev->dev); + if (ret) + goto err_pm_disable; + } + + return 0; + +err_pm_disable: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int jh7110_tdm_dev_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id jh7110_tdm_of_match[] = { + { .compatible = "starfive,jh7110-tdm", }, + {} +}; + +MODULE_DEVICE_TABLE(of, jh7110_tdm_of_match); + +static const struct dev_pm_ops jh7110_tdm_pm_ops = { + RUNTIME_PM_OPS(jh7110_tdm_runtime_suspend, + jh7110_tdm_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(jh7110_tdm_system_suspend, + jh7110_tdm_system_resume) +}; + +static struct platform_driver jh7110_tdm_driver = { + .driver = { + .name = "jh7110-tdm", + .of_match_table = jh7110_tdm_of_match, + .pm = pm_ptr(&jh7110_tdm_pm_ops), + }, + .probe = jh7110_tdm_probe, + .remove = jh7110_tdm_dev_remove, +}; +module_platform_driver(jh7110_tdm_driver); + +MODULE_DESCRIPTION("StarFive JH7110 TDM ASoC Driver"); +MODULE_AUTHOR("Walker Chen "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 089adf33701426869dd50d1b8b8a4abd25ae39ae Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Thu, 1 Jun 2023 11:49:39 +0800 Subject: ASoC: SOF: mediatek: add adsp debug dump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add mt8188 and mt8186 .dbg_dump callback to print some information when DSP panic occurs. Signed-off-by: Trevor Wu Reviewed-by: Pierre-Louis Bossart Reviewed-by: Yaochun Hung Reviewed-by: Péter Ujfalusi Reviewed-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20230601034939.15802-2-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/sof/mediatek/mt8186/mt8186.c | 22 ++++++++++++++++++++++ sound/soc/sof/mediatek/mt8186/mt8186.h | 5 +++++ 2 files changed, 27 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.c b/sound/soc/sof/mediatek/mt8186/mt8186.c index cc91c2928fb6..3e0ea0e109e2 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186.c +++ b/sound/soc/sof/mediatek/mt8186/mt8186.c @@ -24,6 +24,7 @@ #include "../../sof-of-dev.h" #include "../../sof-audio.h" #include "../adsp_helper.h" +#include "../mtk-adsp-common.h" #include "mt8186.h" #include "mt8186-clk.h" @@ -473,6 +474,26 @@ static snd_pcm_uframes_t mt8186_pcm_pointer(struct snd_sof_dev *sdev, return pos; } +static void mt8186_adsp_dump(struct snd_sof_dev *sdev, u32 flags) +{ + u32 dbg_pc, dbg_data, dbg_inst, dbg_ls0stat, dbg_status, faultinfo; + + /* dump debug registers */ + dbg_pc = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGPC); + dbg_data = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGDATA); + dbg_inst = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGINST); + dbg_ls0stat = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGLS0STAT); + dbg_status = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PDEBUGSTATUS); + faultinfo = snd_sof_dsp_read(sdev, DSP_REG_BAR, DSP_PFAULTINFO); + + dev_info(sdev->dev, "adsp dump : pc %#x, data %#x, dbg_inst %#x,", + dbg_pc, dbg_data, dbg_inst); + dev_info(sdev->dev, "ls0stat %#x, status %#x, faultinfo %#x", + dbg_ls0stat, dbg_status, faultinfo); + + mtk_adsp_dump(sdev, flags); +} + static struct snd_soc_dai_driver mt8186_dai[] = { { .name = "SOF_DL1", @@ -555,6 +576,7 @@ static struct snd_sof_dsp_ops sof_mt8186_ops = { .num_drv = ARRAY_SIZE(mt8186_dai), /* Debug information */ + .dbg_dump = mt8186_adsp_dump, .debugfs_add_region_item = snd_sof_debugfs_add_region_item_iomem, /* PM */ diff --git a/sound/soc/sof/mediatek/mt8186/mt8186.h b/sound/soc/sof/mediatek/mt8186/mt8186.h index 5b521c60b4e3..91323f492a1e 100644 --- a/sound/soc/sof/mediatek/mt8186/mt8186.h +++ b/sound/soc/sof/mediatek/mt8186/mt8186.h @@ -38,6 +38,11 @@ struct snd_sof_dev; #define DSP_MBOX3_IRQ_EN BIT(3) #define DSP_MBOX4_IRQ_EN BIT(4) #define DSP_PDEBUGPC 0x013C +#define DSP_PDEBUGDATA 0x0140 +#define DSP_PDEBUGINST 0x0144 +#define DSP_PDEBUGLS0STAT 0x0148 +#define DSP_PDEBUGSTATUS 0x014C +#define DSP_PFAULTINFO 0x0150 #define ADSP_CK_EN 0x1000 #define CORE_CLK_EN BIT(0) #define COREDBG_EN BIT(1) -- cgit v1.2.3 From b81a2cc9a2f2314dad78ca14f12d2fbf8e071c3e Mon Sep 17 00:00:00 2001 From: David Lin Date: Fri, 2 Jun 2023 12:09:22 +0800 Subject: ASoC: nau8825: Add registers patch for NAU8825C The patch is to update default regmap and register a set of registers for NAU8825C. Signed-off-by: David Lin Link: https://lore.kernel.org/r/20230602040924.188913-2-CTLIN0@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 37 ++++++++++++++++++++++++++++++++++--- sound/soc/codecs/nau8825.h | 4 ++++ 2 files changed, 38 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index f6dd84b32e0b..91eb05899a88 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -178,6 +178,8 @@ static const struct reg_default nau8825_reg_defaults[] = { { NAU8825_REG_CLASSG_CTRL, 0x0 }, { NAU8825_REG_OPT_EFUSE_CTRL, 0x0 }, { NAU8825_REG_MISC_CTRL, 0x0 }, + { NAU8825_REG_FLL2_LOWER, 0x0 }, + { NAU8825_REG_FLL2_UPPER, 0x0 }, { NAU8825_REG_BIAS_ADJ, 0x0 }, { NAU8825_REG_TRIM_SETTINGS, 0x0 }, { NAU8825_REG_ANALOG_CONTROL_1, 0x0 }, @@ -200,6 +202,23 @@ static struct reg_default nau8825_xtalk_baktab[] = { { NAU8825_REG_DACR_CTRL, 0x02cf }, }; +/* The regmap patch for Rev C */ +static const struct reg_sequence nau8825_regmap_patch[] = { + { NAU8825_REG_FLL2, 0x0000 }, + { NAU8825_REG_FLL4, 0x8010 }, + { NAU8825_REG_FLL_VCO_RSV, 0x0bc0 }, + { NAU8825_REG_INTERRUPT_MASK, 0x0800 }, + { NAU8825_REG_DACL_CTRL, 0x00cf }, + { NAU8825_REG_DACR_CTRL, 0x02cf }, + { NAU8825_REG_OPT_EFUSE_CTRL, 0x0400 }, + { NAU8825_REG_FLL2_LOWER, 0x26e9 }, + { NAU8825_REG_FLL2_UPPER, 0x0031 }, + { NAU8825_REG_ANALOG_CONTROL_2, 0x0020 }, + { NAU8825_REG_ANALOG_ADC_2, 0x0220 }, + { NAU8825_REG_MIC_BIAS, 0x0046 }, +}; + + static const unsigned short logtable[256] = { 0x0000, 0x0171, 0x02e0, 0x044e, 0x05ba, 0x0725, 0x088e, 0x09f7, 0x0b5d, 0x0cc3, 0x0e27, 0x0f8a, 0x10eb, 0x124b, 0x13aa, 0x1508, @@ -855,7 +874,7 @@ static bool nau8825_readable_reg(struct device *dev, unsigned int reg) case NAU8825_REG_IMM_MODE_CTRL ... NAU8825_REG_IMM_RMS_R: case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: case NAU8825_REG_MISC_CTRL: - case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_SARDOUT_RAM_STATUS: + case NAU8825_REG_I2C_DEVICE_ID ... NAU8825_REG_FLL2_UPPER: case NAU8825_REG_BIAS_ADJ: case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: @@ -881,6 +900,7 @@ static bool nau8825_writeable_reg(struct device *dev, unsigned int reg) case NAU8825_REG_IMM_MODE_CTRL: case NAU8825_REG_CLASSG_CTRL ... NAU8825_REG_OPT_EFUSE_CTRL: case NAU8825_REG_MISC_CTRL: + case NAU8825_REG_FLL2_LOWER ... NAU8825_REG_FLL2_UPPER: case NAU8825_REG_BIAS_ADJ: case NAU8825_REG_TRIM_SETTINGS ... NAU8825_REG_ANALOG_CONTROL_2: case NAU8825_REG_ANALOG_ADC_1 ... NAU8825_REG_MIC_BIAS: @@ -2930,8 +2950,19 @@ static int nau8825_i2c_probe(struct i2c_client *i2c) ret); return ret; } - if ((value & NAU8825_SOFTWARE_ID_MASK) != - NAU8825_SOFTWARE_ID_NAU8825) { + nau8825->sw_id = value & NAU8825_SOFTWARE_ID_MASK; + switch (nau8825->sw_id) { + case NAU8825_SOFTWARE_ID_NAU8825: + break; + case NAU8825_SOFTWARE_ID_NAU8825C: + ret = regmap_register_patch(nau8825->regmap, nau8825_regmap_patch, + ARRAY_SIZE(nau8825_regmap_patch)); + if (ret) { + dev_err(dev, "Failed to register Rev C patch: %d\n", ret); + return ret; + } + break; + default: dev_err(dev, "Not a NAU8825 chip\n"); return -ENODEV; } diff --git a/sound/soc/codecs/nau8825.h b/sound/soc/codecs/nau8825.h index 38ce052aed50..2abfbb5184da 100644 --- a/sound/soc/codecs/nau8825.h +++ b/sound/soc/codecs/nau8825.h @@ -75,6 +75,8 @@ #define NAU8825_REG_MISC_CTRL 0x55 #define NAU8825_REG_I2C_DEVICE_ID 0x58 #define NAU8825_REG_SARDOUT_RAM_STATUS 0x59 +#define NAU8825_REG_FLL2_LOWER 0x5a +#define NAU8825_REG_FLL2_UPPER 0x5b #define NAU8825_REG_BIAS_ADJ 0x66 #define NAU8825_REG_TRIM_SETTINGS 0x68 #define NAU8825_REG_ANALOG_CONTROL_1 0x69 @@ -386,6 +388,7 @@ #define NAU8825_GPIO2JD1 (1 << 7) #define NAU8825_SOFTWARE_ID_MASK 0x3 #define NAU8825_SOFTWARE_ID_NAU8825 0x0 +#define NAU8825_SOFTWARE_ID_NAU8825C 0x1 /* BIAS_ADJ (0x66) */ #define NAU8825_BIAS_HPR_IMP (1 << 15) @@ -497,6 +500,7 @@ struct nau8825 { struct clk *mclk; struct work_struct xtalk_work; struct semaphore xtalk_sem; + int sw_id; int irq; int mclk_freq; /* 0 - mclk is disabled */ int button_pressed; -- cgit v1.2.3 From 6d64c33f0f0018bd26836680175b4ee05f3cf54c Mon Sep 17 00:00:00 2001 From: David Lin Date: Fri, 2 Jun 2023 12:09:23 +0800 Subject: ASoC: nau8825: Update the calculation of FLL for NAU8825C The FLL is updated to 24 bit with lower power consumption. Signed-off-by: David Lin Link: https://lore.kernel.org/r/20230602040924.188913-3-CTLIN0@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index 91eb05899a88..e62f3d615b40 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -53,6 +53,7 @@ struct nau8825_fll { int mclk_src; int ratio; int fll_frac; + int fll_frac_num; int fll_int; int clk_ref_div; }; @@ -2360,9 +2361,12 @@ static int nau8825_calc_fll_param(unsigned int fll_in, unsigned int fs, /* Calculate the FLL 10-bit integer input and the FLL 16-bit fractional * input based on FDCO, FREF and FLL ratio. */ - fvco = div_u64(fvco_max << 16, fref * fll_param->ratio); - fll_param->fll_int = (fvco >> 16) & 0x3FF; - fll_param->fll_frac = fvco & 0xFFFF; + fvco = div_u64(fvco_max << fll_param->fll_frac_num, fref * fll_param->ratio); + fll_param->fll_int = (fvco >> fll_param->fll_frac_num) & 0x3FF; + if (fll_param->fll_frac_num == 16) + fll_param->fll_frac = fvco & 0xFFFF; + else + fll_param->fll_frac = fvco & 0xFFFFFF; return 0; } @@ -2376,8 +2380,16 @@ static void nau8825_fll_apply(struct nau8825 *nau8825, regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL1, NAU8825_FLL_RATIO_MASK | NAU8825_ICTRL_LATCH_MASK, fll_param->ratio | (0x6 << NAU8825_ICTRL_LATCH_SFT)); - /* FLL 16-bit fractional input */ - regmap_write(nau8825->regmap, NAU8825_REG_FLL2, fll_param->fll_frac); + /* FLL 16/24 bit fractional input */ + if (fll_param->fll_frac_num == 16) + regmap_write(nau8825->regmap, NAU8825_REG_FLL2, + fll_param->fll_frac); + else { + regmap_write(nau8825->regmap, NAU8825_REG_FLL2_LOWER, + fll_param->fll_frac & 0xffff); + regmap_write(nau8825->regmap, NAU8825_REG_FLL2_UPPER, + (fll_param->fll_frac >> 16) & 0xff); + } /* FLL 10-bit integer input */ regmap_update_bits(nau8825->regmap, NAU8825_REG_FLL3, NAU8825_FLL_INTEGER_MASK, fll_param->fll_int); @@ -2419,6 +2431,11 @@ static int nau8825_set_pll(struct snd_soc_component *component, int pll_id, int struct nau8825_fll fll_param; int ret, fs; + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + fll_param.fll_frac_num = 16; + else + fll_param.fll_frac_num = 24; + fs = freq_out / 256; ret = nau8825_calc_fll_param(freq_in, fs, &fll_param); if (ret < 0) { -- cgit v1.2.3 From 955b503b6317859632c7ea214babfa22305d1de4 Mon Sep 17 00:00:00 2001 From: David Lin Date: Fri, 2 Jun 2023 12:09:24 +0800 Subject: ASoC: nau8825: Update output control for NAU8825C Update the output control for NAU8825C. Signed-off-by: David Lin Link: https://lore.kernel.org/r/20230602040924.188913-4-CTLIN0@nuvoton.com Signed-off-by: Mark Brown --- sound/soc/codecs/nau8825.c | 47 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/nau8825.c b/sound/soc/codecs/nau8825.c index e62f3d615b40..9e0e4ddf128e 100644 --- a/sound/soc/codecs/nau8825.c +++ b/sound/soc/codecs/nau8825.c @@ -628,8 +628,13 @@ static void nau8825_xtalk_prepare(struct nau8825 *nau8825) regmap_update_bits(nau8825->regmap, NAU8825_REG_INTERRUPT_MASK, NAU8825_IRQ_RMS_EN, 0); /* Power up left and right DAC */ - regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, - NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); + else + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); } static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825) @@ -642,9 +647,14 @@ static void nau8825_xtalk_clean_dac(struct nau8825 *nau8825) NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L, NAU8825_SPKR_DWN1R | NAU8825_SPKR_DWN1L); /* Power down left and right DAC */ - regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, - NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, - NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + else + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); + /* Enable the TESTDAC and disable L/R HP impedance */ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, NAU8825_BIAS_HPR_IMP | NAU8825_BIAS_HPL_IMP | @@ -1017,10 +1027,25 @@ static int nau8825_output_dac_event(struct snd_soc_dapm_widget *w, /* Disables the TESTDAC to let DAC signal pass through. */ regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, NAU8825_BIAS_TESTDAC_EN, 0); + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); + else + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); break; case SND_SOC_DAPM_POST_PMD: regmap_update_bits(nau8825->regmap, NAU8825_REG_BIAS_ADJ, NAU8825_BIAS_TESTDAC_EN, NAU8825_BIAS_TESTDAC_EN); + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + else + regmap_update_bits(nau8825->regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, 0); + break; default: return -EINVAL; @@ -1228,12 +1253,13 @@ static const struct snd_soc_dapm_widget nau8825_dapm_widgets[] = { NAU8825_REG_POWER_UP_CONTROL, 0, 0, NULL, 0), SND_SOC_DAPM_PGA_S("Output DACL", 7, - NAU8825_REG_CHARGE_PUMP, 8, 1, nau8825_output_dac_event, + SND_SOC_NOPM, 0, 0, nau8825_output_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_PGA_S("Output DACR", 7, - NAU8825_REG_CHARGE_PUMP, 9, 1, nau8825_output_dac_event, + SND_SOC_NOPM, 0, 0, nau8825_output_dac_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + /* HPOL/R are ungrounded by disabling 16 Ohm pull-downs on playback */ SND_SOC_DAPM_PGA_S("HPOL Pulldown", 8, NAU8825_REG_HSD_CTRL, 0, 1, NULL, 0), @@ -2227,9 +2253,10 @@ static void nau8825_init_regs(struct nau8825 *nau8825) regmap_update_bits(regmap, NAU8825_REG_DAC_CTRL1, NAU8825_DAC_OVERSAMPLE_MASK, NAU8825_DAC_OVERSAMPLE_64); /* Disable DACR/L power */ - regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP, - NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, - NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); + if (nau8825->sw_id == NAU8825_SOFTWARE_ID_NAU8825) + regmap_update_bits(regmap, NAU8825_REG_CHARGE_PUMP, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL, + NAU8825_POWER_DOWN_DACR | NAU8825_POWER_DOWN_DACL); /* Enable TESTDAC. This sets the analog DAC inputs to a '0' input * signal to avoid any glitches due to power up transients in both * the analog and digital DAC circuit. -- cgit v1.2.3 From 2a7a1ae95c84d4199736872bfbc39d01f4b6b0ab Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Fri, 26 May 2023 17:31:44 +0800 Subject: ASoC: mediatek: mt8188: separate ADDA playback dai from capture dai MT8188 will support SOF. In SOF, be_hw_params_fixup callback are used to configure BE hardware parameters. However, playback and capture stream share the same callback function in which it can't know the stream type. It's possible to require different parameters for playback and capture stream, so separate them into two dais for SOF usage. Signed-off-by: Trevor Wu Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230526093150.22923-2-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-afe-common.h | 3 +- sound/soc/mediatek/mt8188/mt8188-dai-adda.c | 73 ++++++++++++++------------- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 34 ++++++++++--- 3 files changed, 65 insertions(+), 45 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-afe-common.h b/sound/soc/mediatek/mt8188/mt8188-afe-common.h index eb7e57c239bd..1304d685a306 100644 --- a/sound/soc/mediatek/mt8188/mt8188-afe-common.h +++ b/sound/soc/mediatek/mt8188/mt8188-afe-common.h @@ -39,7 +39,7 @@ enum { MT8188_AFE_MEMIF_END, MT8188_AFE_MEMIF_NUM = (MT8188_AFE_MEMIF_END - MT8188_AFE_MEMIF_START), MT8188_AFE_IO_START = MT8188_AFE_MEMIF_END, - MT8188_AFE_IO_ADDA = MT8188_AFE_IO_START, + MT8188_AFE_IO_DL_SRC = MT8188_AFE_IO_START, MT8188_AFE_IO_DMIC_IN, MT8188_AFE_IO_DPTX, MT8188_AFE_IO_ETDM_START, @@ -52,6 +52,7 @@ enum { MT8188_AFE_IO_ETDM_NUM = (MT8188_AFE_IO_ETDM_END - MT8188_AFE_IO_ETDM_START), MT8188_AFE_IO_PCM = MT8188_AFE_IO_ETDM_END, + MT8188_AFE_IO_UL_SRC, MT8188_AFE_IO_END, MT8188_AFE_IO_NUM = (MT8188_AFE_IO_END - MT8188_AFE_IO_START), MT8188_DAI_END = MT8188_AFE_IO_END, diff --git a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c index fed9f927e623..7dc029f2b428 100644 --- a/sound/soc/mediatek/mt8188/mt8188-dai-adda.c +++ b/sound/soc/mediatek/mt8188/mt8188-dai-adda.c @@ -53,8 +53,7 @@ enum { }; struct mtk_dai_adda_priv { - unsigned int dl_rate; - unsigned int ul_rate; + bool hires_required; }; static unsigned int afe_adda_dl_rate_transform(struct mtk_base_afe *afe, @@ -241,42 +240,35 @@ static int mtk_adda_ul_event(struct snd_soc_dapm_widget *w, return 0; } -static int mtk_afe_adc_hires_connect(struct snd_soc_dapm_widget *source, - struct snd_soc_dapm_widget *sink) +static struct mtk_dai_adda_priv *get_adda_priv_by_name(struct mtk_base_afe *afe, + const char *name) { - struct snd_soc_dapm_widget *w = source; - struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); - struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); struct mt8188_afe_private *afe_priv = afe->platform_priv; - struct mtk_dai_adda_priv *adda_priv; - - adda_priv = afe_priv->dai_priv[MT8188_AFE_IO_ADDA]; - if (!adda_priv) { - dev_err(afe->dev, "%s adda_priv == NULL", __func__); - return 0; - } - - return !!(adda_priv->ul_rate > ADDA_HIRES_THRES); + if (strstr(name, "aud_adc_hires")) + return afe_priv->dai_priv[MT8188_AFE_IO_UL_SRC]; + else if (strstr(name, "aud_dac_hires")) + return afe_priv->dai_priv[MT8188_AFE_IO_DL_SRC]; + else + return NULL; } -static int mtk_afe_dac_hires_connect(struct snd_soc_dapm_widget *source, - struct snd_soc_dapm_widget *sink) +static int mtk_afe_adda_hires_connect(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) { struct snd_soc_dapm_widget *w = source; struct snd_soc_component *cmpnt = snd_soc_dapm_to_component(w->dapm); struct mtk_base_afe *afe = snd_soc_component_get_drvdata(cmpnt); - struct mt8188_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_adda_priv *adda_priv; - adda_priv = afe_priv->dai_priv[MT8188_AFE_IO_ADDA]; + adda_priv = get_adda_priv_by_name(afe, w->name); if (!adda_priv) { - dev_err(afe->dev, "%s adda_priv == NULL", __func__); + dev_dbg(afe->dev, "adda_priv == NULL"); return 0; } - return !!(adda_priv->dl_rate > ADDA_HIRES_THRES); + return (adda_priv->hires_required) ? 1 : 0; } static const struct snd_kcontrol_new mtk_dai_adda_o176_mix[] = { @@ -361,7 +353,7 @@ static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = { {"ADDA Capture", NULL, "ADDA Capture Enable"}, {"ADDA Capture", NULL, "ADDA_MTKAIF_CFG"}, {"ADDA Capture", NULL, "aud_adc"}, - {"ADDA Capture", NULL, "aud_adc_hires", mtk_afe_adc_hires_connect}, + {"ADDA Capture", NULL, "aud_adc_hires", mtk_afe_adda_hires_connect}, {"I168", NULL, "ADDA Capture"}, {"I169", NULL, "ADDA Capture"}, @@ -369,7 +361,7 @@ static const struct snd_soc_dapm_route mtk_dai_adda_routes[] = { {"ADDA Playback", NULL, "ADDA Enable"}, {"ADDA Playback", NULL, "ADDA Playback Enable"}, {"ADDA Playback", NULL, "aud_dac"}, - {"ADDA Playback", NULL, "aud_dac_hires", mtk_afe_dac_hires_connect}, + {"ADDA Playback", NULL, "aud_dac_hires", mtk_afe_adda_hires_connect}, {"DL_GAIN", NULL, "O176"}, {"DL_GAIN", NULL, "O177"}, @@ -503,13 +495,12 @@ static int mtk_dai_adda_hw_params(struct snd_pcm_substream *substream, dev_dbg(afe->dev, "%s(), id %d, stream %d, rate %u\n", __func__, id, substream->stream, rate); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - adda_priv->dl_rate = rate; + adda_priv->hires_required = (rate > ADDA_HIRES_THRES); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ret = mtk_dai_da_configure(afe, rate, id); - } else { - adda_priv->ul_rate = rate; + else ret = mtk_dai_ad_configure(afe, rate, id); - } return ret; } @@ -536,8 +527,8 @@ static const struct snd_soc_dai_ops mtk_dai_adda_ops = { static struct snd_soc_dai_driver mtk_dai_adda_driver[] = { { - .name = "ADDA", - .id = MT8188_AFE_IO_ADDA, + .name = "DL_SRC", + .id = MT8188_AFE_IO_DL_SRC, .playback = { .stream_name = "ADDA Playback", .channels_min = 1, @@ -545,6 +536,11 @@ static struct snd_soc_dai_driver mtk_dai_adda_driver[] = { .rates = MTK_ADDA_PLAYBACK_RATES, .formats = MTK_ADDA_FORMATS, }, + .ops = &mtk_dai_adda_ops, + }, + { + .name = "UL_SRC", + .id = MT8188_AFE_IO_UL_SRC, .capture = { .stream_name = "ADDA Capture", .channels_min = 1, @@ -560,13 +556,18 @@ static int init_adda_priv_data(struct mtk_base_afe *afe) { struct mt8188_afe_private *afe_priv = afe->platform_priv; struct mtk_dai_adda_priv *adda_priv; + int adda_dai_list[] = {MT8188_AFE_IO_DL_SRC, MT8188_AFE_IO_UL_SRC}; + int i; - adda_priv = devm_kzalloc(afe->dev, sizeof(struct mtk_dai_adda_priv), - GFP_KERNEL); - if (!adda_priv) - return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(adda_dai_list); i++) { + adda_priv = devm_kzalloc(afe->dev, + sizeof(struct mtk_dai_adda_priv), + GFP_KERNEL); + if (!adda_priv) + return -ENOMEM; - afe_priv->dai_priv[MT8188_AFE_IO_ADDA] = adda_priv; + afe_priv->dai_priv[adda_dai_list[i]] = adda_priv; + } return 0; } diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 919d74ea1934..833bc362dad2 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -99,8 +99,8 @@ SND_SOC_DAILINK_DEFS(capture10, DAILINK_COMP_ARRAY(COMP_EMPTY())); /* BE */ -SND_SOC_DAILINK_DEFS(adda, - DAILINK_COMP_ARRAY(COMP_CPU("ADDA")), +SND_SOC_DAILINK_DEFS(dl_src, + DAILINK_COMP_ARRAY(COMP_CPU("DL_SRC")), DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", "mt6359-snd-codec-aif1")), DAILINK_COMP_ARRAY(COMP_EMPTY())); @@ -140,6 +140,12 @@ SND_SOC_DAILINK_DEFS(pcm1, DAILINK_COMP_ARRAY(COMP_DUMMY()), DAILINK_COMP_ARRAY(COMP_EMPTY())); +SND_SOC_DAILINK_DEFS(ul_src, + DAILINK_COMP_ARRAY(COMP_CPU("UL_SRC")), + DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", + "mt6359-snd-codec-aif1")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + struct mt8188_mt6359_priv { struct snd_soc_jack dp_jack; struct snd_soc_jack hdmi_jack; @@ -345,7 +351,7 @@ enum { DAI_LINK_UL8_FE, DAI_LINK_UL9_FE, DAI_LINK_UL10_FE, - DAI_LINK_ADDA_BE, + DAI_LINK_DL_SRC_BE, DAI_LINK_DPTX_BE, DAI_LINK_ETDM1_IN_BE, DAI_LINK_ETDM2_IN_BE, @@ -353,6 +359,7 @@ enum { DAI_LINK_ETDM2_OUT_BE, DAI_LINK_ETDM3_OUT_BE, DAI_LINK_PCM1_BE, + DAI_LINK_UL_SRC_BE, }; static int mt8188_dptx_hw_params(struct snd_pcm_substream *substream, @@ -604,13 +611,11 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { SND_SOC_DAILINK_REG(capture10), }, /* BE */ - [DAI_LINK_ADDA_BE] = { - .name = "ADDA_BE", + [DAI_LINK_DL_SRC_BE] = { + .name = "DL_SRC_BE", .no_pcm = 1, .dpcm_playback = 1, - .dpcm_capture = 1, - .init = mt8188_mt6359_init, - SND_SOC_DAILINK_REG(adda), + SND_SOC_DAILINK_REG(dl_src), }, [DAI_LINK_DPTX_BE] = { .name = "DPTX_BE", @@ -676,6 +681,12 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { .dpcm_capture = 1, SND_SOC_DAILINK_REG(pcm1), }, + [DAI_LINK_UL_SRC_BE] = { + .name = "UL_SRC_BE", + .no_pcm = 1, + .dpcm_capture = 1, + SND_SOC_DAILINK_REG(ul_src), + }, }; static struct snd_soc_card mt8188_mt6359_soc_card = { @@ -695,6 +706,7 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev) struct mt8188_mt6359_priv *priv; struct mt8188_card_data *card_data; struct snd_soc_dai_link *dai_link; + bool init_mt6359 = false; int ret, i; card_data = (struct mt8188_card_data *)of_device_get_match_data(&pdev->dev); @@ -739,6 +751,12 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev) } else if (strcmp(dai_link->name, "ETDM3_OUT_BE") == 0) { if (strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai")) dai_link->init = mt8188_hdmi_codec_init; + } else if (strcmp(dai_link->name, "DL_SRC_BE") == 0 || + strcmp(dai_link->name, "UL_SRC_BE") == 0) { + if (!init_mt6359) { + dai_link->init = mt8188_mt6359_init; + init_mt6359 = true; + } } } -- cgit v1.2.3 From 73cf2b3f2b45fa4c231e8e84ae5d8cc80947d799 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Fri, 26 May 2023 17:31:46 +0800 Subject: ASoC: mediatek: mt8188-mt6359: register hdmi/dp jack pins Some userspace applications need jack control events, so register hdmi and dp jack pins to activate jack control events. Signed-off-by: Trevor Wu Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230526093150.22923-4-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 833bc362dad2..6c3f36e2fffd 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -151,6 +151,20 @@ struct mt8188_mt6359_priv { struct snd_soc_jack hdmi_jack; }; +static struct snd_soc_jack_pin mt8188_hdmi_jack_pins[] = { + { + .pin = "HDMI", + .mask = SND_JACK_LINEOUT, + }, +}; + +static struct snd_soc_jack_pin mt8188_dp_jack_pins[] = { + { + .pin = "DP", + .mask = SND_JACK_LINEOUT, + }, +}; + struct mt8188_card_data { const char *name; unsigned long quirk; @@ -159,6 +173,8 @@ struct mt8188_card_data { static const struct snd_soc_dapm_widget mt8188_mt6359_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SINK("HDMI"), + SND_SOC_DAPM_SINK("DP"), }; static const struct snd_kcontrol_new mt8188_mt6359_controls[] = { @@ -396,8 +412,10 @@ static int mt8188_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int ret = 0; - ret = snd_soc_card_jack_new(rtd->card, "HDMI Jack", SND_JACK_LINEOUT, - &priv->hdmi_jack); + ret = snd_soc_card_jack_new_pins(rtd->card, "HDMI Jack", + SND_JACK_LINEOUT, &priv->hdmi_jack, + mt8188_hdmi_jack_pins, + ARRAY_SIZE(mt8188_hdmi_jack_pins)); if (ret) { dev_info(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); return ret; @@ -417,8 +435,9 @@ static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; int ret = 0; - ret = snd_soc_card_jack_new(rtd->card, "DP Jack", SND_JACK_LINEOUT, - &priv->dp_jack); + ret = snd_soc_card_jack_new_pins(rtd->card, "DP Jack", SND_JACK_LINEOUT, + &priv->dp_jack, mt8188_dp_jack_pins, + ARRAY_SIZE(mt8188_dp_jack_pins)); if (ret) { dev_info(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); return ret; -- cgit v1.2.3 From c0e7390e6d3f42b9a15a0e72add21facb8e17790 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Fri, 26 May 2023 17:31:47 +0800 Subject: ASoC: mediatek: common: soundcard driver add dai_fmt support There are two changes included in the patch. First, add set_dailink_daifmt() function, so dai_fmt can be updated by the configuration in dai-link sub node. Second, remove codec phandle from required property in dai-link sub node. For example, user possibly needs to update dai-format for all etdm co-clock dai-links, but codec doesn't need to be specified in capture dai-link for a speaker amp. Signed-off-by: Trevor Wu Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230526093150.22923-5-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/mediatek/common/mtk-soundcard-driver.c | 53 +++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/common/mtk-soundcard-driver.c b/sound/soc/mediatek/common/mtk-soundcard-driver.c index 738093451ccb..a58e1e3674de 100644 --- a/sound/soc/mediatek/common/mtk-soundcard-driver.c +++ b/sound/soc/mediatek/common/mtk-soundcard-driver.c @@ -21,8 +21,10 @@ static int set_card_codec_info(struct snd_soc_card *card, int ret; codec_node = of_get_child_by_name(sub_node, "codec"); - if (!codec_node) - return -EINVAL; + if (!codec_node) { + dev_dbg(dev, "%s no specified codec\n", dai_link->name); + return 0; + } /* set card codec info */ ret = snd_soc_of_get_dai_link_codecs(dev, codec_node, dai_link); @@ -36,6 +38,47 @@ static int set_card_codec_info(struct snd_soc_card *card, return 0; } +static int set_dailink_daifmt(struct snd_soc_card *card, + struct device_node *sub_node, + struct snd_soc_dai_link *dai_link) +{ + unsigned int daifmt; + const char *str; + int ret; + struct { + char *name; + unsigned int val; + } of_clk_table[] = { + { "cpu", SND_SOC_DAIFMT_CBC_CFC }, + { "codec", SND_SOC_DAIFMT_CBP_CFP }, + }; + + daifmt = snd_soc_daifmt_parse_format(sub_node, NULL); + if (daifmt) { + dai_link->dai_fmt &= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK; + dai_link->dai_fmt |= daifmt; + } + + /* + * check "mediatek,clk-provider = xxx" + * SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK area + */ + ret = of_property_read_string(sub_node, "mediatek,clk-provider", &str); + if (ret == 0) { + int i; + + for (i = 0; i < ARRAY_SIZE(of_clk_table); i++) { + if (strcmp(str, of_clk_table[i].name) == 0) { + dai_link->dai_fmt &= ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK; + dai_link->dai_fmt |= of_clk_table[i].val; + break; + } + } + } + + return 0; +} + int parse_dai_link_info(struct snd_soc_card *card) { struct device *dev = card->dev; @@ -67,6 +110,12 @@ int parse_dai_link_info(struct snd_soc_card *card) of_node_put(sub_node); return ret; } + + ret = set_dailink_daifmt(card, sub_node, dai_link); + if (ret < 0) { + of_node_put(sub_node); + return ret; + } } return 0; -- cgit v1.2.3 From 8ad13cdc92f66333ae475251ae7722313f84e496 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Fri, 26 May 2023 17:31:48 +0800 Subject: ASoC: soc-dapm.c: clean up debugfs for freed widget When a widget is added to dapm via snd_soc_dapm_new_widgets, dapm_debugfs_add_widget is also called to create a corresponding debugfs file. However, when a widget is freed by snd_soc_dapm_free_widget, the corresponding debugfs is not cleared. As a result, the freed widget is still seen in the dapm directory. This patch adds dapm_debugfs_free_widget to free the debugfs of a specified widget, and it's called at snd_soc_dapm_free_widget to clean up the debugfs for freed widget. Signed-off-by: Trevor Wu Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230526093150.22923-6-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'sound') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index f2f04ce693a1..c65cc374bb3f 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2216,6 +2216,16 @@ static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) &dapm_widget_power_fops); } +static void dapm_debugfs_free_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + + if (!dapm->debugfs_dapm || !w->name) + return; + + debugfs_lookup_and_remove(w->name, dapm->debugfs_dapm); +} + static void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) { debugfs_remove_recursive(dapm->debugfs_dapm); @@ -2232,6 +2242,10 @@ static inline void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) { } +static inline void dapm_debugfs_free_widget(struct snd_soc_dapm_widget *w) +{ +} + static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) { } @@ -2495,6 +2509,8 @@ void snd_soc_dapm_free_widget(struct snd_soc_dapm_widget *w) dapm_free_path(p); } + dapm_debugfs_free_widget(w); + kfree(w->kcontrols); kfree_const(w->name); kfree_const(w->sname); -- cgit v1.2.3 From 9f08dcbddeb307793bbfff036db213d2cdf03a50 Mon Sep 17 00:00:00 2001 From: Trevor Wu Date: Fri, 26 May 2023 17:31:49 +0800 Subject: ASoC: mediatek: mt8188-mt6359: support new board with nau88255 This patch adds multiple i2s codecs support including NAU88L25, MAX98390, and the dumb amp like NAU8318 usage. In addition, dmic-codec is also added to skip the beginning pop noise. Signed-off-by: Trevor Wu Link: https://lore.kernel.org/r/20230526093150.22923-7-trevor.wu@mediatek.com Signed-off-by: Mark Brown --- sound/soc/mediatek/Kconfig | 4 + sound/soc/mediatek/mt8188/mt8188-mt6359.c | 327 +++++++++++++++++++++++++++++- 2 files changed, 330 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 4baac72677d9..4ea012342b52 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -225,6 +225,10 @@ config SND_SOC_MT8188_MT6359 depends on SND_SOC_MT8188 && MTK_PMIC_WRAP select SND_SOC_MT6359 select SND_SOC_HDMI_CODEC + select SND_SOC_DMIC + select SND_SOC_MAX98390 + select SND_SOC_NAU8315 + select SND_SOC_NAU8825 help This adds support for ASoC machine driver for MediaTek MT8188 boards with the MT6359 and other I2S audio codecs. diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 6c3f36e2fffd..bc4b74970a46 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -6,6 +6,7 @@ * Author: Trevor Wu */ +#include #include #include #include @@ -13,10 +14,27 @@ #include #include #include "mt8188-afe-common.h" +#include "../../codecs/nau8825.h" #include "../../codecs/mt6359.h" #include "../common/mtk-afe-platform-driver.h" #include "../common/mtk-soundcard-driver.h" +#define NAU8825_HS_PRESENT BIT(0) + +/* + * Maxim MAX98390 + */ +#define MAX98390_CODEC_DAI "max98390-aif1" +#define MAX98390_DEV0_NAME "max98390.0-0038" /* rear right */ +#define MAX98390_DEV1_NAME "max98390.0-0039" /* rear left */ +#define MAX98390_DEV2_NAME "max98390.0-003a" /* front right */ +#define MAX98390_DEV3_NAME "max98390.0-003b" /* front left */ + +/* + * Nau88l25 + */ +#define NAU8825_CODEC_DAI "nau8825-hifi" + /* FE */ SND_SOC_DAILINK_DEFS(playback2, DAILINK_COMP_ARRAY(COMP_CPU("DL2")), @@ -143,12 +161,16 @@ SND_SOC_DAILINK_DEFS(pcm1, SND_SOC_DAILINK_DEFS(ul_src, DAILINK_COMP_ARRAY(COMP_CPU("UL_SRC")), DAILINK_COMP_ARRAY(COMP_CODEC("mt6359-sound", - "mt6359-snd-codec-aif1")), + "mt6359-snd-codec-aif1"), + COMP_CODEC("dmic-codec", + "dmic-hifi")), DAILINK_COMP_ARRAY(COMP_EMPTY())); struct mt8188_mt6359_priv { struct snd_soc_jack dp_jack; struct snd_soc_jack hdmi_jack; + struct snd_soc_jack headset_jack; + void *private_data; }; static struct snd_soc_jack_pin mt8188_hdmi_jack_pins[] = { @@ -165,11 +187,50 @@ static struct snd_soc_jack_pin mt8188_dp_jack_pins[] = { }, }; +static struct snd_soc_jack_pin nau8825_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + struct mt8188_card_data { const char *name; unsigned long quirk; }; +static const struct snd_kcontrol_new mt8188_dumb_spk_controls[] = { + SOC_DAPM_PIN_SWITCH("Ext Spk"), +}; + +static const struct snd_soc_dapm_widget mt8188_dumb_spk_widgets[] = { + SND_SOC_DAPM_SPK("Ext Spk", NULL), +}; + +static const struct snd_kcontrol_new mt8188_dual_spk_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget mt8188_dual_spk_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_kcontrol_new mt8188_rear_spk_controls[] = { + SOC_DAPM_PIN_SWITCH("Rear Left Spk"), + SOC_DAPM_PIN_SWITCH("Rear Right Spk"), +}; + +static const struct snd_soc_dapm_widget mt8188_rear_spk_widgets[] = { + SND_SOC_DAPM_SPK("Rear Left Spk", NULL), + SND_SOC_DAPM_SPK("Rear Right Spk", NULL), +}; + static const struct snd_soc_dapm_widget mt8188_mt6359_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), @@ -182,6 +243,14 @@ static const struct snd_kcontrol_new mt8188_mt6359_controls[] = { SOC_DAPM_PIN_SWITCH("Headset Mic"), }; +static const struct snd_soc_dapm_widget mt8188_nau8825_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new mt8188_nau8825_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), +}; + #define CKSYS_AUD_TOP_CFG 0x032c #define CKSYS_AUD_TOP_MON 0x0330 @@ -451,6 +520,189 @@ static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; } +static int mt8188_dumb_amp_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret = 0; + + ret = snd_soc_dapm_new_controls(&card->dapm, mt8188_dumb_spk_widgets, + ARRAY_SIZE(mt8188_dumb_spk_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add Dumb Speaker dapm, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, mt8188_dumb_spk_controls, + ARRAY_SIZE(mt8188_dumb_spk_controls)); + if (ret) { + dev_err(rtd->dev, "unable to add Dumb card controls, ret %d\n", ret); + return ret; + } + + return ret; +} + +static int mt8188_max98390_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + unsigned int bit_width = params_width(params); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + int i; + + snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0xf, 4, bit_width); + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX98390_DEV0_NAME)) + snd_soc_dai_set_tdm_slot(codec_dai, 0x8, 0x3, 4, bit_width); + + if (!strcmp(codec_dai->component->name, MAX98390_DEV1_NAME)) + snd_soc_dai_set_tdm_slot(codec_dai, 0x4, 0x3, 4, bit_width); + + if (!strcmp(codec_dai->component->name, MAX98390_DEV2_NAME)) + snd_soc_dai_set_tdm_slot(codec_dai, 0x2, 0x3, 4, bit_width); + + if (!strcmp(codec_dai->component->name, MAX98390_DEV3_NAME)) + snd_soc_dai_set_tdm_slot(codec_dai, 0x1, 0x3, 4, bit_width); + } + return 0; +} + +static const struct snd_soc_ops mt8188_max98390_ops = { + .hw_params = mt8188_max98390_hw_params, +}; + +static int mt8188_max98390_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + /* add regular speakers dapm route */ + ret = snd_soc_dapm_new_controls(&card->dapm, mt8188_dual_spk_widgets, + ARRAY_SIZE(mt8188_dual_spk_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add Left/Right Speaker widget, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, mt8188_dual_spk_controls, + ARRAY_SIZE(mt8188_dual_spk_controls)); + if (ret) { + dev_err(rtd->dev, "unable to add Left/Right card controls, ret %d\n", ret); + return ret; + } + + if (rtd->dai_link->num_codecs <= 2) + return ret; + + /* add widgets/controls/dapm for rear speakers */ + ret = snd_soc_dapm_new_controls(&card->dapm, mt8188_rear_spk_widgets, + ARRAY_SIZE(mt8188_rear_spk_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add Rear Speaker widget, ret %d\n", ret); + /* Don't need to add routes if widget addition failed */ + return ret; + } + + ret = snd_soc_add_card_controls(card, mt8188_rear_spk_controls, + ARRAY_SIZE(mt8188_rear_spk_controls)); + if (ret) { + dev_err(rtd->dev, "unable to add Rear card controls, ret %d\n", ret); + return ret; + } + + return ret; +} + +static int mt8188_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(card); + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + struct snd_soc_jack *jack = &priv->headset_jack; + int ret; + + ret = snd_soc_dapm_new_controls(&card->dapm, mt8188_nau8825_widgets, + ARRAY_SIZE(mt8188_nau8825_widgets)); + if (ret) { + dev_err(rtd->dev, "unable to add nau8825 card widget, ret %d\n", ret); + return ret; + } + + ret = snd_soc_add_card_controls(card, mt8188_nau8825_controls, + ARRAY_SIZE(mt8188_nau8825_controls)); + if (ret) { + dev_err(rtd->dev, "unable to add nau8825 card controls, ret %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + jack, + nau8825_jack_pins, + ARRAY_SIZE(nau8825_jack_pins)); + if (ret) { + dev_err(rtd->dev, "Headset Jack creation failed: %d\n", ret); + return ret; + } + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) { + dev_err(rtd->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return ret; +}; + +static void mt8188_nau8825_codec_exit(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(rtd, 0)->component; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int mt8188_nau8825_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + unsigned int rate = params_rate(params); + unsigned int bit_width = params_width(params); + int clk_freq, ret; + + clk_freq = rate * 2 * bit_width; + + /* Configure clock for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_BLK, 0, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set BCLK clock %d\n", ret); + return ret; + } + + /* Configure pll for codec */ + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, clk_freq, + params_rate(params) * 256); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set BCLK: %d\n", ret); + return ret; + } + + return ret; +} + +static const struct snd_soc_ops mt8188_nau8825_ops = { + .hw_params = mt8188_nau8825_hw_params, +}; static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { /* FE */ [DAI_LINK_DL2_FE] = { @@ -708,6 +960,40 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = { }, }; +static struct snd_kcontrol *ctl_find(struct snd_card *card, const char *name) +{ + struct snd_ctl_elem_id sid; + + memset(&sid, 0, sizeof(sid)); + strcpy(sid.name, name); + sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + return snd_ctl_find_id(card, &sid); +} + +static void mt8188_fixup_controls(struct snd_soc_card *card) +{ + struct mt8188_mt6359_priv *priv = snd_soc_card_get_drvdata(card); + struct mt8188_card_data *card_data = (struct mt8188_card_data *)priv->private_data; + struct snd_kcontrol *kctl; + + if (card_data->quirk & NAU8825_HS_PRESENT) { + struct snd_soc_dapm_widget *w, *next_w; + + for_each_card_widgets_safe(card, w, next_w) { + if (strcmp(w->name, "Headphone")) + continue; + + snd_soc_dapm_free_widget(w); + } + + kctl = ctl_find(card->snd_card, "Headphone Switch"); + if (kctl) + snd_ctl_remove(card->snd_card, kctl); + else + dev_warn(card->dev, "Cannot find ctl : Headphone Switch\n"); + } +} + static struct snd_soc_card mt8188_mt6359_soc_card = { .owner = THIS_MODULE, .dai_link = mt8188_mt6359_dai_links, @@ -716,6 +1002,7 @@ static struct snd_soc_card mt8188_mt6359_soc_card = { .num_dapm_widgets = ARRAY_SIZE(mt8188_mt6359_widgets), .controls = mt8188_mt6359_controls, .num_controls = ARRAY_SIZE(mt8188_mt6359_controls), + .fixup_controls = mt8188_fixup_controls, }; static int mt8188_mt6359_dev_probe(struct platform_device *pdev) @@ -726,6 +1013,9 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev) struct mt8188_card_data *card_data; struct snd_soc_dai_link *dai_link; bool init_mt6359 = false; + bool init_nau8825 = false; + bool init_max98390 = false; + bool init_dumb = false; int ret, i; card_data = (struct mt8188_card_data *)of_device_get_match_data(&pdev->dev); @@ -776,9 +1066,35 @@ static int mt8188_mt6359_dev_probe(struct platform_device *pdev) dai_link->init = mt8188_mt6359_init; init_mt6359 = true; } + } else if (strcmp(dai_link->name, "ETDM1_OUT_BE") == 0 || + strcmp(dai_link->name, "ETDM2_OUT_BE") == 0 || + strcmp(dai_link->name, "ETDM1_IN_BE") == 0 || + strcmp(dai_link->name, "ETDM2_IN_BE") == 0) { + if (!strcmp(dai_link->codecs->dai_name, MAX98390_CODEC_DAI)) { + dai_link->ops = &mt8188_max98390_ops; + if (!init_max98390) { + dai_link->init = mt8188_max98390_codec_init; + init_max98390 = true; + } + } else if (!strcmp(dai_link->codecs->dai_name, NAU8825_CODEC_DAI)) { + dai_link->ops = &mt8188_nau8825_ops; + if (!init_nau8825) { + dai_link->init = mt8188_nau8825_codec_init; + dai_link->exit = mt8188_nau8825_codec_exit; + init_nau8825 = true; + } + } else { + if (strcmp(dai_link->codecs->dai_name, "snd-soc-dummy-dai")) { + if (!init_dumb) { + dai_link->init = mt8188_dumb_amp_init; + init_dumb = true; + } + } + } } } + priv->private_data = card_data; snd_soc_card_set_drvdata(card, priv); ret = devm_snd_soc_register_card(&pdev->dev, card); @@ -795,11 +1111,20 @@ static struct mt8188_card_data mt8188_evb_card = { .name = "mt8188_mt6359", }; +static struct mt8188_card_data mt8188_nau8825_card = { + .name = "mt8188_nau8825", + .quirk = NAU8825_HS_PRESENT, +}; + static const struct of_device_id mt8188_mt6359_dt_match[] = { { .compatible = "mediatek,mt8188-mt6359-evb", .data = &mt8188_evb_card, }, + { + .compatible = "mediatek,mt8188-nau8825", + .data = &mt8188_nau8825_card, + }, {}, }; MODULE_DEVICE_TABLE(of, mt8188_mt6359_dt_match); -- cgit v1.2.3 From 812a05256d673b2b9c5db906775d1e6625ba4787 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 2 Jun 2023 14:44:39 +0200 Subject: ASoC: amd: vangogh: select CONFIG_SND_AMD_ACP_CONFIG The vangogh driver just gained a link time dependency that now causes randconfig builds to fail: x86_64-linux-ld: sound/soc/amd/vangogh/pci-acp5x.o: in function `snd_acp5x_probe': pci-acp5x.c:(.text+0xbb): undefined reference to `snd_amd_acp_find_config' Fixes: e89f45edb747e ("ASoC: amd: vangogh: Add check for acp config flags in vangogh platform") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230602124447.863476-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index 08e42082f5e9..e724cb3c70b7 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -81,6 +81,7 @@ config SND_SOC_AMD_VANGOGH_MACH tristate "AMD Vangogh support for NAU8821 CS35L41" select SND_SOC_NAU8821 select SND_SOC_CS35L41_SPI + select SND_AMD_ACP_CONFIG depends on SND_SOC_AMD_ACP5x && I2C && SPI_MASTER help This option enables machine driver for Vangogh platform -- cgit v1.2.3 From c894ec016c9d0418dd832202225a8c64f450d71e Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 25 May 2023 22:36:40 +0200 Subject: ALSA: Switch i2c drivers back to use .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After commit b8a1a4cd5a98 ("i2c: Provide a temporary .probe_new() call-back type"), all drivers being converted to .probe_new() and then 03c835f498b5 ("i2c: Switch .probe() to not take an id parameter") convert back to (the new) .probe() to be able to eventually drop .probe_new() from struct i2c_driver. Signed-off-by: Uwe Kleine-König Reviewed-by: Luca Ceresoli Link: https://lore.kernel.org/r/20230525203640.677826-1-u.kleine-koenig@pengutronix.de Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 2 +- sound/aoa/codecs/tas.c | 2 +- sound/pci/hda/cs35l41_hda_i2c.c | 2 +- sound/ppc/keywest.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index 4c75381f5ab8..a8a59d71dcec 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -1048,7 +1048,7 @@ static struct i2c_driver onyx_driver = { .driver = { .name = "aoa_codec_onyx", }, - .probe_new = onyx_i2c_probe, + .probe = onyx_i2c_probe, .remove = onyx_i2c_remove, .id_table = onyx_i2c_id, }; diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c index f906e9aaddcf..ab1472390061 100644 --- a/sound/aoa/codecs/tas.c +++ b/sound/aoa/codecs/tas.c @@ -936,7 +936,7 @@ static struct i2c_driver tas_driver = { .driver = { .name = "aoa_codec_tas", }, - .probe_new = tas_i2c_probe, + .probe = tas_i2c_probe, .remove = tas_i2c_remove, .id_table = tas_i2c_id, }; diff --git a/sound/pci/hda/cs35l41_hda_i2c.c b/sound/pci/hda/cs35l41_hda_i2c.c index 7826b1a12d7d..b44536fbba17 100644 --- a/sound/pci/hda/cs35l41_hda_i2c.c +++ b/sound/pci/hda/cs35l41_hda_i2c.c @@ -58,7 +58,7 @@ static struct i2c_driver cs35l41_i2c_driver = { .pm = &cs35l41_hda_pm_ops, }, .id_table = cs35l41_hda_i2c_id, - .probe_new = cs35l41_hda_i2c_probe, + .probe = cs35l41_hda_i2c_probe, .remove = cs35l41_hda_i2c_remove, }; module_i2c_driver(cs35l41_i2c_driver); diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c index 0c4f43963c75..dfc1fc9b701d 100644 --- a/sound/ppc/keywest.c +++ b/sound/ppc/keywest.c @@ -90,7 +90,7 @@ static struct i2c_driver keywest_driver = { .driver = { .name = "PMac Keywest Audio", }, - .probe_new = keywest_probe, + .probe = keywest_probe, .remove = keywest_remove, .id_table = keywest_i2c_id, }; -- cgit v1.2.3 From 219153c6ed46b064f9c2b0f70dacf21f719751ee Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:54 +0200 Subject: ALSA: emu10k1: hide absent 2nd pointer-offset register set from /proc The 2nd register set belongs to the P16V chip (or embedded P17V module), so there is nothing to show when no such part is present. Gen2 E-MU cards have a P17V, but it's entirely unused, so we hide it there as well. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 89ea3adff322..6cf4a7e16b1d 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -561,15 +561,19 @@ int snd_emu10k1_proc_init(struct snd_emu10k1 *emu) snd_card_rw_proc_new(emu->card, "ptr_regs00b", emu, snd_emu_proc_ptr_reg_read00b, snd_emu_proc_ptr_reg_write00); - snd_card_rw_proc_new(emu->card, "ptr_regs20a", emu, - snd_emu_proc_ptr_reg_read20a, - snd_emu_proc_ptr_reg_write20); - snd_card_rw_proc_new(emu->card, "ptr_regs20b", emu, - snd_emu_proc_ptr_reg_read20b, - snd_emu_proc_ptr_reg_write20); - snd_card_rw_proc_new(emu->card, "ptr_regs20c", emu, - snd_emu_proc_ptr_reg_read20c, - snd_emu_proc_ptr_reg_write20); + if (!emu->card_capabilities->emu_model && + (emu->card_capabilities->ca0151_chip || emu->card_capabilities->ca0108_chip)) { + snd_card_rw_proc_new(emu->card, "ptr_regs20a", emu, + snd_emu_proc_ptr_reg_read20a, + snd_emu_proc_ptr_reg_write20); + snd_card_rw_proc_new(emu->card, "ptr_regs20b", emu, + snd_emu_proc_ptr_reg_read20b, + snd_emu_proc_ptr_reg_write20); + if (emu->card_capabilities->ca0108_chip) + snd_card_rw_proc_new(emu->card, "ptr_regs20c", emu, + snd_emu_proc_ptr_reg_read20c, + snd_emu_proc_ptr_reg_write20); + } #endif snd_card_ro_proc_new(emu->card, "emu10k1", emu, snd_emu10k1_proc_read); -- cgit v1.2.3 From 67ff2add9e2cef1d5d60cf5a37f1f52c65bf97c7 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:55 +0200 Subject: ALSA: emu10k1: fix writing 1st pointer-offset register set through /proc The limits were appropriate only for the 2nd set. FWIW, the channel count 4 for the 2nd set is suspicious as well - at least P17V_PLAYBACK_FIFO_PTR actually has 8 channels, and comments on HCFG2 hint at that as well. But all bitmasks are documented only for 4 channels. Anyway, rectifying that is out of scope for this patch. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 6cf4a7e16b1d..81d48cd478b7 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -483,7 +483,8 @@ static void snd_emu_proc_ptr_reg_read(struct snd_info_entry *entry, } static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, - struct snd_info_buffer *buffer, int iobase) + struct snd_info_buffer *buffer, + int iobase, int length, int voices) { struct snd_emu10k1 *emu = entry->private_data; char line[64]; @@ -491,7 +492,7 @@ static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, while (!snd_info_get_line(buffer, line, sizeof(line))) { if (sscanf(line, "%x %x %x", ®, &channel_id, &val) != 3) continue; - if (reg < 0xa0 && val <= 0xffffffff && channel_id <= 3) + if (reg < length && channel_id < voices) snd_ptr_write(emu, iobase, reg, channel_id, val); } } @@ -499,13 +500,15 @@ static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, static void snd_emu_proc_ptr_reg_write00(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - snd_emu_proc_ptr_reg_write(entry, buffer, 0); + snd_emu_proc_ptr_reg_write(entry, buffer, 0, 0x80, 64); } static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - snd_emu_proc_ptr_reg_write(entry, buffer, 0x20); + struct snd_emu10k1 *emu = entry->private_data; + snd_emu_proc_ptr_reg_write(entry, buffer, 0x20, + emu->card_capabilities->ca0108_chip ? 0xa0 : 0x80, 4); } -- cgit v1.2.3 From 6e91a93d1e7417e5f700fb1d10de994d1539de8e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 29 May 2023 11:55:04 +0200 Subject: ALSA: emu10k1: actually disassemble DSP instructions in /proc fx8010_acode is supposed to be a human-readable representation; the binary is already in fx8010_code. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230529095504.559054-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 149 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 81d48cd478b7..750ac6a8cc24 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -275,37 +275,148 @@ static void snd_emu10k1_proc_rates_read(struct snd_info_entry *entry, } } -static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, +struct emu10k1_reg_entry { + unsigned short base, size; + const char *name; +}; + +static const struct emu10k1_reg_entry sblive_reg_entries[] = { + { 0, 0x10, "FXBUS" }, + { 0x10, 0x10, "EXTIN" }, + { 0x20, 0x10, "EXTOUT" }, + { 0x30, 0x10, "FXBUS2" }, + { 0x40, 0x20, NULL }, // Constants + { 0x100, 0x100, "GPR" }, + { 0x200, 0x80, "ITRAM_DATA" }, + { 0x280, 0x20, "ETRAM_DATA" }, + { 0x300, 0x80, "ITRAM_ADDR" }, + { 0x380, 0x20, "ETRAM_ADDR" }, + { 0x400, 0, NULL } +}; + +static const struct emu10k1_reg_entry audigy_reg_entries[] = { + { 0, 0x40, "FXBUS" }, + { 0x40, 0x10, "EXTIN" }, + { 0x50, 0x10, "P16VIN" }, + { 0x60, 0x20, "EXTOUT" }, + { 0x80, 0x20, "FXBUS2" }, + { 0xa0, 0x10, "EMU32OUTH" }, + { 0xb0, 0x10, "EMU32OUTL" }, + { 0xc0, 0x20, NULL }, // Constants + // This can't be quite right - overlap. + //{ 0x100, 0xc0, "ITRAM_CTL" }, + //{ 0x1c0, 0x40, "ETRAM_CTL" }, + { 0x160, 0x20, "A3_EMU32IN" }, + { 0x1e0, 0x20, "A3_EMU32OUT" }, + { 0x200, 0xc0, "ITRAM_DATA" }, + { 0x2c0, 0x40, "ETRAM_DATA" }, + { 0x300, 0xc0, "ITRAM_ADDR" }, + { 0x3c0, 0x40, "ETRAM_ADDR" }, + { 0x400, 0x200, "GPR" }, + { 0x600, 0, NULL } +}; + +static const char * const emu10k1_const_entries[] = { + "C_00000000", + "C_00000001", + "C_00000002", + "C_00000003", + "C_00000004", + "C_00000008", + "C_00000010", + "C_00000020", + "C_00000100", + "C_00010000", + "C_00000800", + "C_10000000", + "C_20000000", + "C_40000000", + "C_80000000", + "C_7fffffff", + "C_ffffffff", + "C_fffffffe", + "C_c0000000", + "C_4f1bbcdc", + "C_5a7ef9db", + "C_00100000", + "GPR_ACCU", + "GPR_COND", + "GPR_NOISE0", + "GPR_NOISE1", + "GPR_IRQ", + "GPR_DBAC", + "GPR_DBACE", + "???", +}; + +static int disasm_emu10k1_reg(char *buffer, + const struct emu10k1_reg_entry *entries, + unsigned reg, const char *pfx) +{ + for (int i = 0; ; i++) { + unsigned base = entries[i].base; + unsigned size = entries[i].size; + if (!size) + return sprintf(buffer, "%s0x%03x", pfx, reg); + if (reg >= base && reg < base + size) { + const char *name = entries[i].name; + reg -= base; + if (name) + return sprintf(buffer, "%s%s(%u)", pfx, name, reg); + return sprintf(buffer, "%s%s", pfx, emu10k1_const_entries[reg]); + } + } +} + +static int disasm_sblive_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, sblive_reg_entries, reg, pfx); +} + +static int disasm_audigy_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, audigy_reg_entries, reg, pfx); +} + +static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { u32 pc; struct snd_emu10k1 *emu = entry->private_data; + static const char * const insns[16] = { + "MAC0", "MAC1", "MAC2", "MAC3", "MACINT0", "MACINT1", "ACC3", "MACMV", + "ANDXOR", "TSTNEG", "LIMITGE", "LIMITLT", "LOG", "EXP", "INTERP", "SKIP", + }; + static const char spaces[] = " "; + const int nspaces = sizeof(spaces) - 1; snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name); snd_iprintf(buffer, " Code dump :\n"); for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) { u32 low, high; + int len; + char buf[100]; + char *bufp = buf; low = snd_emu10k1_efx_read(emu, pc * 2); high = snd_emu10k1_efx_read(emu, pc * 2 + 1); - if (emu->audigy) - snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n", - (high >> 24) & 0x0f, - (high >> 12) & 0x7ff, - (high >> 0) & 0x7ff, - (low >> 12) & 0x7ff, - (low >> 0) & 0x7ff, - pc, - high, low); - else - snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n", - (high >> 20) & 0x0f, - (high >> 10) & 0x3ff, - (high >> 0) & 0x3ff, - (low >> 10) & 0x3ff, - (low >> 0) & 0x3ff, - pc, - high, low); + if (emu->audigy) { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 24) & 0x0f]); + bufp += disasm_audigy_reg(bufp, (high >> 12) & 0x7ff, ""); + bufp += disasm_audigy_reg(bufp, (high >> 0) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 12) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 0) & 0x7ff, ", "); + } else { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 20) & 0x0f]); + bufp += disasm_sblive_reg(bufp, (high >> 10) & 0x3ff, ""); + bufp += disasm_sblive_reg(bufp, (high >> 0) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 10) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 0) & 0x3ff, ", "); + } + len = (int)(ptrdiff_t)(bufp - buf); + snd_iprintf(buffer, "%s %s /* 0x%04x: 0x%08x%08x */\n", + buf, &spaces[nspaces - clamp(65 - len, 0, nspaces)], + pc, high, low); } } -- cgit v1.2.3 From ad326d4a1364f9d677204b1e005ee8eb2a0b6558 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:57 +0200 Subject: ALSA: emu10k1: include FX send amounts in /proc output It seems to make little sense to include the FX send routing, but not the amounts. This also simplifies the code somewhat, and lines up the output. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 750ac6a8cc24..f1e7084d7693 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -170,7 +170,7 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, }; struct snd_emu10k1 *emu = entry->private_data; - unsigned int val, val1; + unsigned int val, val1, ptrx, psst, dsl, snda; int nefx = emu->audigy ? 64 : 32; const char * const *outputs = emu->audigy ? audigy_outs : creative_outs; int idx; @@ -180,34 +180,35 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative")); snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size); snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2); - snd_iprintf(buffer, "\n"); - snd_iprintf(buffer, "Effect Send Routing :\n"); + + snd_iprintf(buffer, "\nEffect Send Routing & Amounts:\n"); for (idx = 0; idx < NUM_G; idx++) { - val = emu->audigy ? - snd_emu10k1_ptr_read(emu, A_FXRT1, idx) : - snd_emu10k1_ptr_read(emu, FXRT, idx); - val1 = emu->audigy ? - snd_emu10k1_ptr_read(emu, A_FXRT2, idx) : - 0; + ptrx = snd_emu10k1_ptr_read(emu, PTRX, idx); + psst = snd_emu10k1_ptr_read(emu, PSST, idx); + dsl = snd_emu10k1_ptr_read(emu, DSL, idx); if (emu->audigy) { - snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ", + val = snd_emu10k1_ptr_read(emu, A_FXRT1, idx); + val1 = snd_emu10k1_ptr_read(emu, A_FXRT2, idx); + snda = snd_emu10k1_ptr_read(emu, A_SENDAMOUNTS, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x, ", idx, - val & 0x3f, - (val >> 8) & 0x3f, - (val >> 16) & 0x3f, - (val >> 24) & 0x3f); - snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n", - val1 & 0x3f, - (val1 >> 8) & 0x3f, - (val1 >> 16) & 0x3f, - (val1 >> 24) & 0x3f); + val & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 8) & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 16) & 0x3f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 24) & 0x3f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); + snd_iprintf(buffer, "E=%2i:%02x, F=%2i:%02x, G=%2i:%02x, H=%2i:%02x\n", + val1 & 0x3f, (snda >> 24) & 0xff, + (val1 >> 8) & 0x3f, (snda >> 16) & 0xff, + (val1 >> 16) & 0x3f, (snda >> 8) & 0xff, + (val1 >> 24) & 0x3f, snda & 0xff); } else { - snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n", + val = snd_emu10k1_ptr_read(emu, FXRT, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x\n", idx, - (val >> 16) & 0x0f, - (val >> 20) & 0x0f, - (val >> 24) & 0x0f, - (val >> 28) & 0x0f); + (val >> 16) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 20) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 24) & 0x0f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 28) & 0x0f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); } } snd_iprintf(buffer, "\nCaptured FX Outputs :\n"); -- cgit v1.2.3 From 6ab13291ba82e6f0c8778cb45726dffffb9205f5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:58 +0200 Subject: ALSA: emu10k1: make E-MU FPGA register dump in /proc more useful Include the routing information, which can be actually read back. Somewhat as a drive-by, make the register dump format less obscure - the previous one made no sense at all. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emuproc.c | 43 ++++++++++++++++++++++++++++++++++++++++++- sound/pci/emu10k1/io.c | 28 +++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 164a2374b4c2..4b9dda449917 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1812,6 +1812,7 @@ int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); +u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index f1e7084d7693..0d376d6e66ab 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -496,6 +496,15 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, } #ifdef CONFIG_SND_DEBUG + +static void snd_emu_proc_emu1010_link_read(struct snd_emu10k1 *emu, + struct snd_info_buffer *buffer, + u32 dst) +{ + u32 src = snd_emu1010_fpga_link_dst_src_read(emu, dst); + snd_iprintf(buffer, "%04x: %04x\n", dst, src); +} + static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { @@ -506,7 +515,39 @@ static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry, for(i = 0; i < 0x40; i+=1) { snd_emu1010_fpga_read(emu, i, &value); - snd_iprintf(buffer, "%02X: %08X, %02X\n", i, value, (value >> 8) & 0x7f); + snd_iprintf(buffer, "%02x: %02x\n", i, value); + } + + snd_iprintf(buffer, "\nEMU1010 Routes:\n\n"); + + for (i = 0; i < 16; i++) // To Alice2/Tina[2] via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) + for (i = 0; i < 32; i++) // To Dock via EDI + snd_emu_proc_emu1010_link_read(emu, buffer, 0x100 + i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU1616) + for (i = 0; i < 8; i++) // To Hamoa/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x200 + i); + for (i = 0; i < 8; i++) // To Hamoa/Mana/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x300 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { + for (i = 0; i < 16; i++) // To Tina2 via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + } else if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + for (i = 0; i < 8; i++) // To Hana ADAT + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) { + for (i = 0; i < 16; i++) // To Tina via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500 + i); + } else { + // To Alice2 via I2S + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x501); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x600); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x601); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x700); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x701); + } } } diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 9a839e7d283f..abe69ae40499 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -298,21 +298,27 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) spin_unlock_irqrestore(&emu->emu_lock, flags); } -void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) +static void snd_emu1010_fpga_read_locked(struct snd_emu10k1 *emu, u32 reg, u32 *value) { // The higest input pin is used as the designated interrupt trigger, // so it needs to be masked out. u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f; - unsigned long flags; if (snd_BUG_ON(reg > 0x3f)) return; reg += 0x40; /* 0x40 upwards are registers. */ - spin_lock_irqsave(&emu->emu_lock, flags); outw(reg, emu->port + A_GPIO); udelay(10); outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ udelay(10); *value = ((inw(emu->port + A_GPIO) >> 8) & mask); +} + +void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_read_locked(emu, reg, value); spin_unlock_irqrestore(&emu->emu_lock, flags); } @@ -335,6 +341,22 @@ void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 s spin_unlock_irqrestore(&emu->emu_lock, flags); } +u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) +{ + unsigned long flags; + u32 hi, lo; + + if (snd_BUG_ON(dst & ~0x71f)) + return 0; + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCHI, &hi); + snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCLO, &lo); + spin_unlock_irqrestore(&emu->emu_lock, flags); + return (hi << 8) | lo; +} + void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) { unsigned long flags; -- cgit v1.2.3 From db987421b57cdf3ecb4859e0c7b49726baae895e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:59 +0200 Subject: ALSA: emu10k1: vastly improve usefulness of info in /proc - Include the FX bus map, without which the already present send routing info would require looking up the documentation. - Include the physical I/O channels as known to the driver - Make the multi-channel capture map actually name the mapped input channels rather than "FXBUS" (Audigy) or even "???" (SbLive) - The latter two are omitted for E-MU cards, as their physical I/O is routed through the FPGA - While at it, make the "Card" field somewhat more useful This includes de-duplicating the label tables between emuproc and emufx, updating/improving the FX bus label table, and making the SB Live! 5.1 multi-track capture channel mapping hack data-driven. Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 10 +++ sound/pci/emu10k1/emufx.c | 87 +++++++++++++++-------- sound/pci/emu10k1/emuproc.c | 167 ++++++++++++++------------------------------ 3 files changed, 120 insertions(+), 144 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 4b9dda449917..cc0151e7c828 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1440,6 +1440,16 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM /* 0x600 and 0x700 no used */ + +/* ------------------- CONSTANTS -------------------- */ + +extern const char * const snd_emu10k1_fxbus[32]; +extern const char * const snd_emu10k1_sblive_ins[16]; +extern const char * const snd_emu10k1_audigy_ins[16]; +extern const char * const snd_emu10k1_sblive_outs[32]; +extern const char * const snd_emu10k1_audigy_outs[32]; +extern const s8 snd_emu10k1_sblive51_fxbus2_map[16]; + /* ------------------- STRUCTURES -------------------- */ enum { diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index e9855d37fa5c..9904bcfee106 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -46,26 +46,45 @@ MODULE_PARM_DESC(high_res_gpr_volume, "GPR mixer controls use 31-bit range."); * Tables */ -static const char * const fxbuses[16] = { +// Playback channel labels; corresponds with the public FXBUS_* defines. +// Unlike the tables below, this is not determined by the hardware. +const char * const snd_emu10k1_fxbus[32] = { /* 0x00 */ "PCM Left", /* 0x01 */ "PCM Right", - /* 0x02 */ "PCM Surround Left", - /* 0x03 */ "PCM Surround Right", + /* 0x02 */ "PCM Rear Left", + /* 0x03 */ "PCM Rear Right", /* 0x04 */ "MIDI Left", /* 0x05 */ "MIDI Right", - /* 0x06 */ "Center", - /* 0x07 */ "LFE", - /* 0x08 */ NULL, - /* 0x09 */ NULL, + /* 0x06 */ "PCM Center", + /* 0x07 */ "PCM LFE", + /* 0x08 */ "PCM Front Left", + /* 0x09 */ "PCM Front Right", /* 0x0a */ NULL, /* 0x0b */ NULL, /* 0x0c */ "MIDI Reverb", /* 0x0d */ "MIDI Chorus", - /* 0x0e */ NULL, - /* 0x0f */ NULL + /* 0x0e */ "PCM Side Left", + /* 0x0f */ "PCM Side Right", + /* 0x10 */ NULL, + /* 0x11 */ NULL, + /* 0x12 */ NULL, + /* 0x13 */ NULL, + /* 0x14 */ "Passthrough Left", + /* 0x15 */ "Passthrough Right", + /* 0x16 */ NULL, + /* 0x17 */ NULL, + /* 0x18 */ NULL, + /* 0x19 */ NULL, + /* 0x1a */ NULL, + /* 0x1b */ NULL, + /* 0x1c */ NULL, + /* 0x1d */ NULL, + /* 0x1e */ NULL, + /* 0x1f */ NULL }; -static const char * const creative_ins[16] = { +// Physical inputs; corresponds with the public EXTIN_* defines. +const char * const snd_emu10k1_sblive_ins[16] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "TTL IEC958 Left", @@ -84,7 +103,8 @@ static const char * const creative_ins[16] = { /* 0x0f */ NULL }; -static const char * const audigy_ins[16] = { +// Physical inputs; corresponds with the public A_EXTIN_* defines. +const char * const snd_emu10k1_audigy_ins[16] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "Audigy CD Left", @@ -103,7 +123,8 @@ static const char * const audigy_ins[16] = { /* 0x0f */ NULL }; -static const char * const creative_outs[32] = { +// Physical outputs; corresponds with the public EXTOUT_* defines. +const char * const snd_emu10k1_sblive_outs[32] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "Optical IEC958 Left", @@ -120,6 +141,7 @@ static const char * const creative_outs[32] = { /* 0x0d */ "AC97 Surround Left", /* 0x0e */ "AC97 Surround Right", /* 0x0f */ NULL, + // This is actually the FXBUS2 range; SB Live! 5.1 only. /* 0x10 */ NULL, /* 0x11 */ "Analog Center", /* 0x12 */ "Analog LFE", @@ -138,7 +160,8 @@ static const char * const creative_outs[32] = { /* 0x1f */ NULL, }; -static const char * const audigy_outs[32] = { +// Physical outputs; corresponds with the public A_EXTOUT_* defines. +const char * const snd_emu10k1_audigy_outs[32] = { /* 0x00 */ "Digital Front Left", /* 0x01 */ "Digital Front Right", /* 0x02 */ "Digital Center", @@ -173,6 +196,18 @@ static const char * const audigy_outs[32] = { /* 0x1f */ NULL, }; +// On the SB Live! 5.1, FXBUS2[1] and FXBUS2[2] are occupied by EXTOUT_ACENTER +// and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording. +// +// Since only 14 of the 16 EXTINs are used, this is not a big problem. +// We route AC97 to FX capture 14 and 15, SPDIF_CD to FX capture 0 and 3, +// and the rest of the EXTINs to the corresponding FX capture channel. +// Multitrack recorders will still see the center/LFE output signal +// on the second and third "input" channel. +const s8 snd_emu10k1_sblive51_fxbus2_map[16] = { + 2, -1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1 +}; + static const u32 bass_table[41][5] = { { 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 }, { 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d }, @@ -2290,21 +2325,11 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* EFX capture - capture the 16 EXTINS */ if (emu->card_capabilities->sblive51) { - /* On the Live! 5.1, FXBUS2(1) and FXBUS(2) are shared with EXTOUT_ACENTER - * and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording. - * - * Since only 14 of the 16 EXTINs are used, this is not a big problem. - * We route AC97L and R to FX capture 14 and 15, SPDIF CD in to FX capture - * 0 and 3, then the rest of the EXTINs to the corresponding FX capture - * channel. Multitrack recorders will still see the center/lfe output signal - * on the second and third channels. - */ - OP(icode, &ptr, iACC3, FXBUS2(14), C_00000000, C_00000000, EXTIN(0)); - OP(icode, &ptr, iACC3, FXBUS2(15), C_00000000, C_00000000, EXTIN(1)); - OP(icode, &ptr, iACC3, FXBUS2(0), C_00000000, C_00000000, EXTIN(2)); - OP(icode, &ptr, iACC3, FXBUS2(3), C_00000000, C_00000000, EXTIN(3)); - for (z = 4; z < 14; z++) - OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z)); + for (z = 0; z < 16; z++) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[z]; + if (c != -1) + OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(c)); + } } else { for (z = 0; z < 16; z++) OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z)); @@ -2448,9 +2473,9 @@ static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu, info->internal_tram_size = emu->fx8010.itram_size; info->external_tram_size = emu->fx8010.etram_pages.bytes / 2; - fxbus = fxbuses; - extin = emu->audigy ? audigy_ins : creative_ins; - extout = emu->audigy ? audigy_outs : creative_outs; + fxbus = snd_emu10k1_fxbus; + extin = emu->audigy ? snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + extout = emu->audigy ? snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; for (res = 0; res < 16; res++, fxbus++, extin++, extout++) { diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 0d376d6e66ab..ca7b4dddbea8 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -66,118 +66,22 @@ static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu, static void snd_emu10k1_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - /* FIXME - output names are in emufx.c too */ - static const char * const creative_outs[32] = { - /* 00 */ "AC97 Left", - /* 01 */ "AC97 Right", - /* 02 */ "Optical IEC958 Left", - /* 03 */ "Optical IEC958 Right", - /* 04 */ "Center", - /* 05 */ "LFE", - /* 06 */ "Headphone Left", - /* 07 */ "Headphone Right", - /* 08 */ "Surround Left", - /* 09 */ "Surround Right", - /* 10 */ "PCM Capture Left", - /* 11 */ "PCM Capture Right", - /* 12 */ "MIC Capture", - /* 13 */ "AC97 Surround Left", - /* 14 */ "AC97 Surround Right", - /* 15 */ "???", - /* 16 */ "???", - /* 17 */ "Analog Center", - /* 18 */ "Analog LFE", - /* 19 */ "???", - /* 20 */ "???", - /* 21 */ "???", - /* 22 */ "???", - /* 23 */ "???", - /* 24 */ "???", - /* 25 */ "???", - /* 26 */ "???", - /* 27 */ "???", - /* 28 */ "???", - /* 29 */ "???", - /* 30 */ "???", - /* 31 */ "???" - }; - - static const char * const audigy_outs[64] = { - /* 00 */ "Digital Front Left", - /* 01 */ "Digital Front Right", - /* 02 */ "Digital Center", - /* 03 */ "Digital LEF", - /* 04 */ "Headphone Left", - /* 05 */ "Headphone Right", - /* 06 */ "Digital Rear Left", - /* 07 */ "Digital Rear Right", - /* 08 */ "Front Left", - /* 09 */ "Front Right", - /* 10 */ "Center", - /* 11 */ "LFE", - /* 12 */ "???", - /* 13 */ "???", - /* 14 */ "Rear Left", - /* 15 */ "Rear Right", - /* 16 */ "AC97 Front Left", - /* 17 */ "AC97 Front Right", - /* 18 */ "ADC Capture Left", - /* 19 */ "ADC Capture Right", - /* 20 */ "???", - /* 21 */ "???", - /* 22 */ "???", - /* 23 */ "???", - /* 24 */ "???", - /* 25 */ "???", - /* 26 */ "???", - /* 27 */ "???", - /* 28 */ "???", - /* 29 */ "???", - /* 30 */ "???", - /* 31 */ "???", - /* 32 */ "FXBUS2_0", - /* 33 */ "FXBUS2_1", - /* 34 */ "FXBUS2_2", - /* 35 */ "FXBUS2_3", - /* 36 */ "FXBUS2_4", - /* 37 */ "FXBUS2_5", - /* 38 */ "FXBUS2_6", - /* 39 */ "FXBUS2_7", - /* 40 */ "FXBUS2_8", - /* 41 */ "FXBUS2_9", - /* 42 */ "FXBUS2_10", - /* 43 */ "FXBUS2_11", - /* 44 */ "FXBUS2_12", - /* 45 */ "FXBUS2_13", - /* 46 */ "FXBUS2_14", - /* 47 */ "FXBUS2_15", - /* 48 */ "FXBUS2_16", - /* 49 */ "FXBUS2_17", - /* 50 */ "FXBUS2_18", - /* 51 */ "FXBUS2_19", - /* 52 */ "FXBUS2_20", - /* 53 */ "FXBUS2_21", - /* 54 */ "FXBUS2_22", - /* 55 */ "FXBUS2_23", - /* 56 */ "FXBUS2_24", - /* 57 */ "FXBUS2_25", - /* 58 */ "FXBUS2_26", - /* 59 */ "FXBUS2_27", - /* 60 */ "FXBUS2_28", - /* 61 */ "FXBUS2_29", - /* 62 */ "FXBUS2_30", - /* 63 */ "FXBUS2_31" - }; - struct snd_emu10k1 *emu = entry->private_data; + const char * const *inputs = emu->audigy ? + snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + const char * const *outputs = emu->audigy ? + snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; + unsigned short extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; + unsigned short extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; unsigned int val, val1, ptrx, psst, dsl, snda; - int nefx = emu->audigy ? 64 : 32; - const char * const *outputs = emu->audigy ? audigy_outs : creative_outs; + int nefx = emu->audigy ? 32 : 16; int idx; snd_iprintf(buffer, "EMU10K1\n\n"); snd_iprintf(buffer, "Card : %s\n", - emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative")); + emu->card_capabilities->emu_model ? "E-MU D.A.S." : + emu->card_capabilities->ecard ? "E-MU A.P.S." : + emu->audigy ? "SB Audigy" : "SB Live!"); snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size); snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2); @@ -211,14 +115,51 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, (val >> 28) & 0x0f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); } } - snd_iprintf(buffer, "\nCaptured FX Outputs :\n"); - for (idx = 0; idx < nefx; idx++) { - if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) - snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]); + snd_iprintf(buffer, "\nEffect Send Targets:\n"); + // Audigy actually has 64, but we don't use them all. + for (idx = 0; idx < 32; idx++) { + const char *c = snd_emu10k1_fxbus[idx]; + if (c) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, c); + } + if (!emu->card_capabilities->emu_model) { + snd_iprintf(buffer, "\nOutput Channels:\n"); + for (idx = 0; idx < 32; idx++) + if (outputs[idx] && (extout_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, outputs[idx]); + snd_iprintf(buffer, "\nInput Channels:\n"); + for (idx = 0; idx < 16; idx++) + if (inputs[idx] && (extin_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, inputs[idx]); + snd_iprintf(buffer, "\nMultichannel Capture Sources:\n"); + for (idx = 0; idx < nefx; idx++) + if (emu->efx_voices_mask[0] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx, outputs[idx] ? outputs[idx] : "???"); + if (emu->audigy) { + for (idx = 0; idx < 32; idx++) + if (emu->efx_voices_mask[1] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 32, inputs[idx] ? inputs[idx] : "???"); + } else { + for (idx = 0; idx < 16; idx++) { + if (emu->efx_voices_mask[0] & ((1 << 16) << idx)) { + if (emu->card_capabilities->sblive51) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[idx]; + if (c == -1) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx + 16, outputs[idx + 16]); + else + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[c]); + } else { + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[idx] ? inputs[idx] : "???"); + } + } + } + } } - snd_iprintf(buffer, "\nAll FX Outputs :\n"); - for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++) - snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]); } static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, -- cgit v1.2.3 From fd0a7ec379dbf21b7bfd81914381ae5281706ef5 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 5 Jun 2023 10:58:29 +0200 Subject: ASoC: amd: vangogh: select CONFIG_SND_AMD_ACP_CONFIG The vangogh driver just gained a link time dependency that now causes randconfig builds to fail: x86_64-linux-ld: sound/soc/amd/vangogh/pci-acp5x.o: in function `snd_acp5x_probe': pci-acp5x.c:(.text+0xbb): undefined reference to `snd_amd_acp_find_config' Fixes: e89f45edb747e ("ASoC: amd: vangogh: Add check for acp config flags in vangogh platform") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230605085839.2157268-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/amd/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index e724cb3c70b7..57d5e342a8eb 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -71,6 +71,7 @@ config SND_SOC_AMD_RENOIR_MACH config SND_SOC_AMD_ACP5x tristate "AMD Audio Coprocessor-v5.x I2S support" depends on X86 && PCI + select SND_AMD_ACP_CONFIG help This option enables ACP v5.x support on AMD platform -- cgit v1.2.3 From ba032909bb2d15fd3014c829fdc7a2a74a8b88ad Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:21:58 -0500 Subject: ASoC: Intel: sof_sdw: add missing exit callback Somehow .exit = sof_sdw_rt_amp_exit was missing in rt1318 codec info. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index d16ceef702a7..2dadde7a7ab9 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -609,6 +609,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt1318-aif", .init = sof_sdw_rt_amp_init, + .exit = sof_sdw_rt_amp_exit, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { -- cgit v1.2.3 From 07140abbbf9e3dc412a34ed4a60c4b0d58fbe192 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:21:59 -0500 Subject: ASoC: Intel: sof_sdw: add dai info The existing code create a dailink for a codec. However, we may need multi dailinks for a codec. This commit adds a new struct in sof_sdw_codec_info{} to store the dai info of a codec. The initial assumption if that we will create at most 3 dailink types for a codec, since this is the max known with upcoming SDCA devices. We may need to increase this number as new SDCA 'functions' become available. One strong assumption is that all dailinks exposed are independent, as per SDCA directions. This commit just moves some items into the new sof_sdw_dai_info struct. There is no function changed. Multi dais supported will be added in the follow up commits. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 225 +++++++++++++++++++++++--------- sound/soc/intel/boards/sof_sdw_common.h | 31 +++-- 2 files changed, 183 insertions(+), 73 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 2dadde7a7ab9..cf12f1ae67c1 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -563,134 +563,231 @@ static const struct snd_soc_ops sdw_ops = { static struct sof_sdw_codec_info codec_info_list[] = { { .part_id = 0x700, - .direction = {true, true}, - .dai_name = "rt700-aif1", - .init = sof_sdw_rt700_init, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt700-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = sof_sdw_rt700_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, .version_id = 3, - .direction = {true, true}, - .dai_name = "rt711-sdca-aif1", - .init = sof_sdw_rt711_sdca_init, - .exit = sof_sdw_rt711_sdca_exit, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt711-sdca-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = sof_sdw_rt711_sdca_init, + .exit = sof_sdw_rt711_sdca_exit, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, .version_id = 2, - .direction = {true, true}, - .dai_name = "rt711-aif1", - .init = sof_sdw_rt711_init, - .exit = sof_sdw_rt711_exit, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt711-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = sof_sdw_rt711_init, + .exit = sof_sdw_rt711_exit, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x1308, .acpi_id = "10EC1308", - .direction = {true, false}, - .dai_name = "rt1308-aif", + .dais = { + { + .direction = {true, false}, + .dai_name = "rt1308-aif", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .init = sof_sdw_rt_amp_init, + .exit = sof_sdw_rt_amp_exit, + }, + }, + .dai_num = 1, .ops = &sof_sdw_rt1308_i2s_ops, - .init = sof_sdw_rt_amp_init, - .exit = sof_sdw_rt_amp_exit, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x1316, - .direction = {true, true}, - .dai_name = "rt1316-aif", - .init = sof_sdw_rt_amp_init, - .exit = sof_sdw_rt_amp_exit, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt1316-aif", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .init = sof_sdw_rt_amp_init, + .exit = sof_sdw_rt_amp_exit, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x1318, - .direction = {true, true}, - .dai_name = "rt1318-aif", - .init = sof_sdw_rt_amp_init, - .exit = sof_sdw_rt_amp_exit, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt1318-aif", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .init = sof_sdw_rt_amp_init, + .exit = sof_sdw_rt_amp_exit, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x714, .version_id = 3, - .direction = {false, true}, .ignore_pch_dmic = true, - .dai_name = "rt715-aif2", - .init = sof_sdw_rt715_sdca_init, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt715-aif2", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .init = sof_sdw_rt715_sdca_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, .version_id = 3, - .direction = {false, true}, .ignore_pch_dmic = true, - .dai_name = "rt715-aif2", - .init = sof_sdw_rt715_sdca_init, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt715-aif2", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .init = sof_sdw_rt715_sdca_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x714, .version_id = 2, - .direction = {false, true}, .ignore_pch_dmic = true, - .dai_name = "rt715-aif2", - .init = sof_sdw_rt715_init, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt715-aif2", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .init = sof_sdw_rt715_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, .version_id = 2, - .direction = {false, true}, .ignore_pch_dmic = true, - .dai_name = "rt715-aif2", - .init = sof_sdw_rt715_init, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt715-aif2", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .init = sof_sdw_rt715_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x8373, - .direction = {true, true}, - .dai_name = "max98373-aif1", - .init = sof_sdw_mx8373_init, + .dais = { + { + .direction = {true, true}, + .dai_name = "max98373-aif1", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .init = sof_sdw_mx8373_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5682, - .direction = {true, true}, - .dai_name = "rt5682-sdw", - .init = sof_sdw_rt5682_init, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt5682-sdw", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = sof_sdw_rt5682_init, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaaaa, /* generic codec mockup */ .version_id = 0, - .direction = {true, true}, - .dai_name = "sdw-mockup-aif1", - .init = NULL, + .dais = { + { + .direction = {true, true}, + .dai_name = "sdw-mockup-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = NULL, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaa55, /* headset codec mockup */ .version_id = 0, - .direction = {true, true}, - .dai_name = "sdw-mockup-aif1", - .init = NULL, + .dais = { + { + .direction = {true, true}, + .dai_name = "sdw-mockup-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .init = NULL, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x55aa, /* amplifier mockup */ .version_id = 0, - .direction = {true, false}, - .dai_name = "sdw-mockup-aif1", - .init = NULL, + .dais = { + { + .direction = {true, false}, + .dai_name = "sdw-mockup-aif1", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .init = NULL, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5555, .version_id = 0, - .direction = {false, true}, - .dai_name = "sdw-mockup-aif1", + .dais = { + { + .dai_name = "sdw-mockup-aif1", + .direction = {false, true}, + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .init = NULL, + }, + }, + .dai_num = 1, .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, }; @@ -780,7 +877,7 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li /* count DAI number for playback and capture */ for_each_pcm_streams(stream) { - if (!codec_info_list[codec_index].direction[stream]) + if (!codec_info_list[codec_index].dais[0].direction[stream]) continue; (*sdw_cpu_dai_num)++; @@ -920,7 +1017,7 @@ static int create_codec_dai_name(struct device *dev, _codec_index = codec_index; codec[comp_index].dai_name = - codec_info_list[codec_index].dai_name; + codec_info_list[codec_index].dais[0].dai_name; codec_conf[*codec_conf_index].dlc = codec[comp_index]; codec_conf[*codec_conf_index].name_prefix = link->adr_d[i].name_prefix; @@ -957,8 +1054,8 @@ static int set_codec_init_func(struct snd_soc_card *card, /* The group_id is > 0 iff the codec is aggregated */ if (link->adr_d[i].endpoints->group_id != group_id) continue; - if (codec_info_list[codec_index].init) - codec_info_list[codec_index].init(card, + if (codec_info_list[codec_index].dais[0].init) + codec_info_list[codec_index].dais[0].init(card, link, dai_links, &codec_info_list[codec_index], @@ -1154,7 +1251,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, "SDW%d-Capture-%s", }; - if (!codec_info_list[codec_index].direction[stream]) + if (!codec_info_list[codec_index].dais[0].direction[stream]) continue; /* create stream name according to first link id */ @@ -1458,18 +1555,18 @@ SSP: return -ENOMEM; ssp_components->name = codec_name; - ssp_components->dai_name = info->dai_name; + ssp_components->dai_name = info->dais[0].dai_name; cpus[cpu_id].dai_name = cpu_name; - playback = info->direction[SNDRV_PCM_STREAM_PLAYBACK]; - capture = info->direction[SNDRV_PCM_STREAM_CAPTURE]; + playback = info->dais[0].direction[SNDRV_PCM_STREAM_PLAYBACK]; + capture = info->dais[0].direction[SNDRV_PCM_STREAM_CAPTURE]; init_dai_link(dev, links + link_index, be_id, name, playback, capture, cpus + cpu_id, 1, ssp_components, 1, NULL, info->ops); - ret = info->init(card, NULL, links + link_index, info, 0); + ret = info->dais[0].init(card, NULL, links + link_index, info, 0); if (ret < 0) return ret; @@ -1606,7 +1703,7 @@ static void mc_dailink_exit_loop(struct snd_soc_card *card) int i, j; for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { - if (!codec_info_list[i].exit) + if (!codec_info_list[i].dais[0].exit) continue; /* * We don't need to call .exit function if there is no matched @@ -1614,8 +1711,8 @@ static void mc_dailink_exit_loop(struct snd_soc_card *card) */ for_each_card_prelinks(card, j, link) { if (!strcmp(link->codecs[0].dai_name, - codec_info_list[i].dai_name)) { - ret = codec_info_list[i].exit(card, link); + codec_info_list[i].dais[0].dai_name)) { + ret = codec_info_list[i].dais[0].exit(card, link); if (ret) dev_warn(card->dev, "codec exit failed %d\n", diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index 081ab7eac5b6..e6d539bd63ec 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -56,24 +56,37 @@ enum { #define SOF_SDW_CODEC_TYPE_AMP 1 #define SOF_SDW_CODEC_TYPE_MIC 2 +#define SOF_SDW_DAI_TYPE_JACK 0 +#define SOF_SDW_DAI_TYPE_AMP 1 +#define SOF_SDW_DAI_TYPE_MIC 2 + +#define SOF_SDW_MAX_DAI_NUM 3 + +struct sof_sdw_codec_info; + +struct sof_sdw_dai_info { + const bool direction[2]; /* playback & capture support */ + const char *dai_name; + const int dai_type; + int (*init)(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + int (*exit)(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); +}; + struct sof_sdw_codec_info { const int part_id; const int version_id; const int codec_type; int amp_num; const u8 acpi_id[ACPI_ID_LEN]; - const bool direction[2]; // playback & capture support const bool ignore_pch_dmic; - const char *dai_name; const struct snd_soc_ops *ops; + struct sof_sdw_dai_info dais[SOF_SDW_MAX_DAI_NUM]; + const int dai_num; - int (*init)(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback); - - int (*exit)(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); int (*codec_card_late_probe)(struct snd_soc_card *card); }; -- cgit v1.2.3 From b274586533f516b35519f409a4d089341a9c2690 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:00 -0500 Subject: ASoC: Intel: sof_sdw: use predefine dailink id Currently, we assign dailink ids in order, and shift with codec type. The purpose is to have consistent dailink ids for topologies. This can be simplified if we have a predefined dailink id in sof_sdw_dai_info. We reuse the existing ids as the predefine ids. So the dailink ids will not be changed by this commit. With this change, we no longer need to check the adr order described in a snd_soc_acpi_link_adr array. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 42 +++++++++++++++++---------------- sound/soc/intel/boards/sof_sdw_common.h | 7 +++++- 2 files changed, 28 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index cf12f1ae67c1..1df489c7e2bd 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -568,6 +568,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt700-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = sof_sdw_rt700_init, }, }, @@ -582,6 +583,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt711-sdca-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = sof_sdw_rt711_sdca_init, .exit = sof_sdw_rt711_sdca_exit, }, @@ -597,6 +599,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt711-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = sof_sdw_rt711_init, .exit = sof_sdw_rt711_exit, }, @@ -612,6 +615,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, false}, .dai_name = "rt1308-aif", .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_UNUSED_DAI_ID}, .init = sof_sdw_rt_amp_init, .exit = sof_sdw_rt_amp_exit, }, @@ -627,6 +631,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt1316-aif", .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_AMP_IN_DAI_ID}, .init = sof_sdw_rt_amp_init, .exit = sof_sdw_rt_amp_exit, }, @@ -641,6 +646,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt1318-aif", .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_AMP_IN_DAI_ID}, .init = sof_sdw_rt_amp_init, .exit = sof_sdw_rt_amp_exit, }, @@ -657,6 +663,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {false, true}, .dai_name = "rt715-aif2", .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, .init = sof_sdw_rt715_sdca_init, }, }, @@ -672,6 +679,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {false, true}, .dai_name = "rt715-aif2", .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, .init = sof_sdw_rt715_sdca_init, }, }, @@ -687,6 +695,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {false, true}, .dai_name = "rt715-aif2", .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, .init = sof_sdw_rt715_init, }, }, @@ -702,6 +711,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {false, true}, .dai_name = "rt715-aif2", .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, .init = sof_sdw_rt715_init, }, }, @@ -715,6 +725,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "max98373-aif1", .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_AMP_IN_DAI_ID}, .init = sof_sdw_mx8373_init, }, }, @@ -728,6 +739,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "rt5682-sdw", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = sof_sdw_rt5682_init, }, }, @@ -742,6 +754,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "sdw-mockup-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = NULL, }, }, @@ -756,6 +769,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, true}, .dai_name = "sdw-mockup-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, .init = NULL, }, }, @@ -770,6 +784,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .direction = {true, false}, .dai_name = "sdw-mockup-aif1", .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_UNUSED_DAI_ID}, .init = NULL, }, }, @@ -784,6 +799,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .dai_name = "sdw-mockup-aif1", .direction = {false, true}, .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, .init = NULL, }, }, @@ -840,7 +856,6 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li int *sdw_be_num, int *sdw_cpu_dai_num) { const struct snd_soc_acpi_link_adr *link; - int _codec_type = SOF_SDW_CODEC_TYPE_JACK; bool group_visited[SDW_MAX_GROUPS]; bool no_aggregation; int i; @@ -867,12 +882,6 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li if (codec_index < 0) return codec_index; - if (codec_info_list[codec_index].codec_type < _codec_type) - dev_warn(dev, - "Unexpected address table ordering. Expected order: jack -> amp -> mic\n"); - - _codec_type = codec_info_list[codec_index].codec_type; - endpoint = link->adr_d[i].endpoints; /* count DAI number for playback and capture */ @@ -1227,19 +1236,6 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (codec_info_list[codec_index].ignore_pch_dmic) *ignore_pch_dmic = true; - /* Shift the first amplifier's *link_id to SDW_AMP_DAI_ID */ - if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_AMP && - *link_id < SDW_AMP_DAI_ID) - *link_id = SDW_AMP_DAI_ID; - - /* - * DAI ID is fixed at SDW_DMIC_DAI_ID for MICs to - * keep sdw DMIC and HDMI setting static in UCM - */ - if (codec_info_list[codec_index].codec_type == SOF_SDW_CODEC_TYPE_MIC && - *link_id < SDW_DMIC_DAI_ID) - *link_id = SDW_DMIC_DAI_ID; - cpu_dai_index = *cpu_id; for_each_pcm_streams(stream) { char *name, *cpu_name; @@ -1254,6 +1250,12 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (!codec_info_list[codec_index].dais[0].direction[stream]) continue; + *link_id = codec_info_list[codec_index].dais[0].dailink[stream]; + if (*link_id < 0) { + dev_err(dev, "Invalid dailink id %d\n", *link_id); + return -EINVAL; + } + /* create stream name according to first link id */ if (append_codec_type) { name = devm_kasprintf(dev, GFP_KERNEL, diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index e6d539bd63ec..def2d47323bf 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -15,7 +15,11 @@ #define MAX_NO_PROPS 2 #define MAX_HDMI_NUM 4 -#define SDW_AMP_DAI_ID 2 +#define SDW_UNUSED_DAI_ID -1 +#define SDW_JACK_OUT_DAI_ID 0 +#define SDW_JACK_IN_DAI_ID 1 +#define SDW_AMP_OUT_DAI_ID 2 +#define SDW_AMP_IN_DAI_ID 3 #define SDW_DMIC_DAI_ID 4 #define SDW_MAX_CPU_DAIS 16 #define SDW_INTEL_BIDIR_PDI_BASE 2 @@ -68,6 +72,7 @@ struct sof_sdw_dai_info { const bool direction[2]; /* playback & capture support */ const char *dai_name; const int dai_type; + const int dailink[2]; /* dailink id for each direction */ int (*init)(struct snd_soc_card *card, const struct snd_soc_acpi_link_adr *link, struct snd_soc_dai_link *dai_links, -- cgit v1.2.3 From cededa5a6486821402c5e9bb7fd3cfd71d7999bc Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:01 -0500 Subject: ASoC: Intel: sof_sdw: add codec_info pointer codec_info_list[codec_index] is used multiple times in the create_sdw_dailink() function. Adding a codec_info pointer to shorten the code. This is a preparation for the following up patches. No function changed. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-5-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 1df489c7e2bd..b197c2920e80 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -872,6 +872,7 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li for (link = links; link->num_adr; link++) { const struct snd_soc_acpi_endpoint *endpoint; + struct sof_sdw_codec_info *codec_info; int codec_index; int stream; u64 adr; @@ -881,12 +882,13 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li codec_index = find_codec_info_part(adr); if (codec_index < 0) return codec_index; + codec_info = &codec_info_list[codec_index]; endpoint = link->adr_d[i].endpoints; /* count DAI number for playback and capture */ for_each_pcm_streams(stream) { - if (!codec_info_list[codec_index].dais[0].direction[stream]) + if (!codec_info->dais[0].direction[stream]) continue; (*sdw_cpu_dai_num)++; @@ -1184,6 +1186,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, { const struct snd_soc_acpi_link_adr *link_next; struct snd_soc_dai_link_component *codecs; + struct sof_sdw_codec_info *codec_info; int cpu_dai_id[SDW_MAX_CPU_DAIS]; int cpu_dai_num, cpu_dai_index; unsigned int group_id; @@ -1232,8 +1235,9 @@ static int create_sdw_dailink(struct snd_soc_card *card, codec_index = find_codec_info_part(link->adr_d[adr_index].adr); if (codec_index < 0) return codec_index; + codec_info = &codec_info_list[codec_index]; - if (codec_info_list[codec_index].ignore_pch_dmic) + if (codec_info->ignore_pch_dmic) *ignore_pch_dmic = true; cpu_dai_index = *cpu_id; @@ -1247,10 +1251,10 @@ static int create_sdw_dailink(struct snd_soc_card *card, "SDW%d-Capture-%s", }; - if (!codec_info_list[codec_index].dais[0].direction[stream]) + if (!codec_info->dais[0].direction[stream]) continue; - *link_id = codec_info_list[codec_index].dais[0].dailink[stream]; + *link_id = codec_info->dais[0].dailink[stream]; if (*link_id < 0) { dev_err(dev, "Invalid dailink id %d\n", *link_id); return -EINVAL; @@ -1260,7 +1264,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (append_codec_type) { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream + 2], cpu_dai_id[0], - type_strings[codec_info_list[codec_index].codec_type]); + type_strings[codec_info->codec_type]); } else { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream], cpu_dai_id[0]); -- cgit v1.2.3 From 5714aabdf9713297947615fd2325719a6f9db316 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:02 -0500 Subject: ASoC: Intel: sdw_sof: append dai_type and remove codec_type We append codec type to dailink name to distinguish different dailink on the same sdw link and direction. But we could create multi dailinks for a codec and the dailink name will be duplicated if we append codec type to the dailink name. Appending dai type instead of codec type can solve the issue. For example, if a codec supports JACK on dai 0 and AMP on dai 1, the existing code will create dailinks SDW0-Playback-SimpleJack or SDW0-Playback-SmartAmp for both dailinks, and it will be SDW0-Playback-SimpleJack for dailink 0 and SDW0-Playback-SmartAmp for dailink 1 after this change. Then codec type is not used any more and can be removed. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-6-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 28 ++++++---------------------- sound/soc/intel/boards/sof_sdw_common.h | 5 ----- 2 files changed, 6 insertions(+), 27 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index b197c2920e80..6c4c05addb50 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -573,7 +573,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, @@ -589,7 +588,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x711, @@ -605,7 +603,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x1308, @@ -622,7 +619,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, .ops = &sof_sdw_rt1308_i2s_ops, - .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x1316, @@ -637,7 +633,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x1318, @@ -652,7 +647,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x714, @@ -668,7 +662,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, @@ -684,7 +677,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x714, @@ -700,7 +692,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x715, @@ -716,7 +707,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, { .part_id = 0x8373, @@ -730,7 +720,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5682, @@ -744,7 +733,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaaaa, /* generic codec mockup */ @@ -759,7 +747,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0xaa55, /* headset codec mockup */ @@ -774,7 +761,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_JACK, }, { .part_id = 0x55aa, /* amplifier mockup */ @@ -789,7 +775,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_AMP, }, { .part_id = 0x5555, @@ -804,7 +789,6 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, }, .dai_num = 1, - .codec_type = SOF_SDW_CODEC_TYPE_MIC, }, }; @@ -1181,7 +1165,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, int codec_count, int *link_id, int *codec_conf_index, bool *ignore_pch_dmic, - bool append_codec_type, + bool append_dai_type, int adr_index) { const struct snd_soc_acpi_link_adr *link_next; @@ -1261,10 +1245,10 @@ static int create_sdw_dailink(struct snd_soc_card *card, } /* create stream name according to first link id */ - if (append_codec_type) { + if (append_dai_type) { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream + 2], cpu_dai_id[0], - type_strings[codec_info->codec_type]); + type_strings[codec_info->dais[0].dai_type]); } else { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream], cpu_dai_id[0]); @@ -1384,7 +1368,7 @@ static int sof_card_dai_links_create(struct device *dev, const struct snd_soc_acpi_link_adr *adr_link; struct snd_soc_dai_link_component *cpus; struct snd_soc_codec_conf *codec_conf; - bool append_codec_type = false; + bool append_dai_type = false; bool ignore_pch_dmic = false; int codec_conf_count; int codec_conf_index = 0; @@ -1489,7 +1473,7 @@ static int sof_card_dai_links_create(struct device *dev, SDW_PART_ID(adr_link->adr_d[j].adr)) || (SDW_MFG_ID(adr_link->adr_d[i].adr) != SDW_MFG_ID(adr_link->adr_d[i].adr))) { - append_codec_type = true; + append_dai_type = true; goto out; } } @@ -1519,7 +1503,7 @@ out: &cpu_id, group_generated, codec_conf, codec_conf_count, &be_id, &codec_conf_index, - &ignore_pch_dmic, append_codec_type, i); + &ignore_pch_dmic, append_dai_type, i); if (ret < 0) { dev_err(dev, "failed to create dai link %d", link_index); return ret; diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index def2d47323bf..65b3f6eee924 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -56,10 +56,6 @@ enum { (((quirk) << SOF_BT_OFFLOAD_SSP_SHIFT) & SOF_BT_OFFLOAD_SSP_MASK) #define SOF_SSP_BT_OFFLOAD_PRESENT BIT(18) -#define SOF_SDW_CODEC_TYPE_JACK 0 -#define SOF_SDW_CODEC_TYPE_AMP 1 -#define SOF_SDW_CODEC_TYPE_MIC 2 - #define SOF_SDW_DAI_TYPE_JACK 0 #define SOF_SDW_DAI_TYPE_AMP 1 #define SOF_SDW_DAI_TYPE_MIC 2 @@ -84,7 +80,6 @@ struct sof_sdw_dai_info { struct sof_sdw_codec_info { const int part_id; const int version_id; - const int codec_type; int amp_num; const u8 acpi_id[ACPI_ID_LEN]; const bool ignore_pch_dmic; -- cgit v1.2.3 From d3fc5c4da599482a3ada60b26b22fa7de9c6da42 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:03 -0500 Subject: ASoC: Intel: sof_sdw: add multi dailink support for a codec A codec may support multiple dais for different purpose. For example, the rt712 codec supports jack and amp on different dais and machine driver needs to create different dailink for those dais. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-7-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 129 +++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 40 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 6c4c05addb50..8405c3231448 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -843,6 +843,7 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li bool group_visited[SDW_MAX_GROUPS]; bool no_aggregation; int i; + int j; no_aggregation = sof_sdw_quirk & SOF_SDW_NO_AGGREGATION; *sdw_cpu_dai_num = 0; @@ -870,17 +871,19 @@ static int get_sdw_dailink_info(struct device *dev, const struct snd_soc_acpi_li endpoint = link->adr_d[i].endpoints; - /* count DAI number for playback and capture */ - for_each_pcm_streams(stream) { - if (!codec_info->dais[0].direction[stream]) - continue; + for (j = 0; j < codec_info->dai_num; j++) { + /* count DAI number for playback and capture */ + for_each_pcm_streams(stream) { + if (!codec_info->dais[j].direction[stream]) + continue; - (*sdw_cpu_dai_num)++; + (*sdw_cpu_dai_num)++; - /* count BE for each non-aggregated slave or group */ - if (!endpoint->aggregated || no_aggregation || - !group_visited[endpoint->group_id]) - (*sdw_be_num)++; + /* count BE for each non-aggregated slave or group */ + if (!endpoint->aggregated || no_aggregation || + !group_visited[endpoint->group_id]) + (*sdw_be_num)++; + } } if (endpoint->aggregated) @@ -956,7 +959,8 @@ static int create_codec_dai_name(struct device *dev, struct snd_soc_codec_conf *codec_conf, int codec_count, int *codec_conf_index, - int adr_index) + int adr_index, + int dai_index) { int _codec_index = -1; int i; @@ -1012,7 +1016,7 @@ static int create_codec_dai_name(struct device *dev, _codec_index = codec_index; codec[comp_index].dai_name = - codec_info_list[codec_index].dais[0].dai_name; + codec_info_list[codec_index].dais[dai_index].dai_name; codec_conf[*codec_conf_index].dlc = codec[comp_index]; codec_conf[*codec_conf_index].name_prefix = link->adr_d[i].name_prefix; @@ -1026,7 +1030,7 @@ static int create_codec_dai_name(struct device *dev, static int set_codec_init_func(struct snd_soc_card *card, const struct snd_soc_acpi_link_adr *link, struct snd_soc_dai_link *dai_links, - bool playback, int group_id, int adr_index) + bool playback, int group_id, int adr_index, int dai_index) { int i = adr_index; @@ -1046,11 +1050,13 @@ static int set_codec_init_func(struct snd_soc_card *card, if (codec_index < 0) return codec_index; + /* The group_id is > 0 iff the codec is aggregated */ if (link->adr_d[i].endpoints->group_id != group_id) continue; - if (codec_info_list[codec_index].dais[0].init) - codec_info_list[codec_index].dais[0].init(card, + + if (codec_info_list[codec_index].dais[dai_index].init) + codec_info_list[codec_index].dais[dai_index].init(card, link, dai_links, &codec_info_list[codec_index], @@ -1166,7 +1172,8 @@ static int create_sdw_dailink(struct snd_soc_card *card, int *codec_conf_index, bool *ignore_pch_dmic, bool append_dai_type, - int adr_index) + int adr_index, + int dai_index) { const struct snd_soc_acpi_link_adr *link_next; struct snd_soc_dai_link_component *codecs; @@ -1206,7 +1213,8 @@ static int create_sdw_dailink(struct snd_soc_card *card, continue; ret = create_codec_dai_name(dev, link_next, codecs, codec_idx, - codec_conf, codec_count, codec_conf_index, adr_index); + codec_conf, codec_count, codec_conf_index, + adr_index, dai_index); if (ret < 0) return ret; @@ -1235,10 +1243,10 @@ static int create_sdw_dailink(struct snd_soc_card *card, "SDW%d-Capture-%s", }; - if (!codec_info->dais[0].direction[stream]) + if (!codec_info->dais[dai_index].direction[stream]) continue; - *link_id = codec_info->dais[0].dailink[stream]; + *link_id = codec_info->dais[dai_index].dailink[stream]; if (*link_id < 0) { dev_err(dev, "Invalid dailink id %d\n", *link_id); return -EINVAL; @@ -1248,7 +1256,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (append_dai_type) { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream + 2], cpu_dai_id[0], - type_strings[codec_info->dais[0].dai_type]); + type_strings[codec_info->dais[dai_index].dai_type]); } else { name = devm_kasprintf(dev, GFP_KERNEL, sdw_stream_name[stream], cpu_dai_id[0]); @@ -1305,7 +1313,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, dai_links[*link_index].nonatomic = true; ret = set_codec_init_func(card, link, dai_links + (*link_index)++, - playback, group_id, adr_index); + playback, group_id, adr_index, dai_index); if (ret < 0) { dev_err(dev, "failed to init codec %d", codec_index); return ret; @@ -1328,6 +1336,7 @@ static int sof_card_codec_conf_alloc(struct device *dev, const struct snd_soc_acpi_link_adr *adr_link; struct snd_soc_codec_conf *c_conf; int num_codecs = 0; + int codec_index; int i; adr_link = mach_params->links; @@ -1342,8 +1351,11 @@ static int sof_card_codec_conf_alloc(struct device *dev, adr_link->adr_d[i].adr); return -EINVAL; } + codec_index = find_codec_info_part(adr_link->adr_d[i].adr); + if (codec_index < 0) + return codec_index; + num_codecs += codec_info_list[codec_index].dai_num; } - num_codecs += adr_link->num_adr; } c_conf = devm_kzalloc(dev, num_codecs * sizeof(*c_conf), GFP_KERNEL); @@ -1380,6 +1392,7 @@ static int sof_card_dai_links_create(struct device *dev, int total_cpu_dai_num; int sdw_cpu_dai_num; int i, j, be_id = 0; + int codec_index; int cpu_id = 0; int comp_num; int ret; @@ -1468,6 +1481,14 @@ static int sof_card_dai_links_create(struct device *dev, * snd_soc_acpi_adr_device array. They won't be described in different adr_links. */ for (i = 0; i < adr_link->num_adr; i++) { + /* find codec info to get dai_num */ + codec_index = find_codec_info_part(adr_link->adr_d[i].adr); + if (codec_index < 0) + return codec_index; + if (codec_info_list[codec_index].dai_num > 1) { + append_dai_type = true; + goto out; + } for (j = 0; j < i; j++) { if ((SDW_PART_ID(adr_link->adr_d[i].adr) != SDW_PART_ID(adr_link->adr_d[j].adr)) || @@ -1498,15 +1519,22 @@ out: group_generated[endpoint->group_id]) continue; - ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num, - sdw_cpu_dai_num, cpus, adr_link, - &cpu_id, group_generated, - codec_conf, codec_conf_count, - &be_id, &codec_conf_index, - &ignore_pch_dmic, append_dai_type, i); - if (ret < 0) { - dev_err(dev, "failed to create dai link %d", link_index); - return ret; + /* find codec info to get dai_num */ + codec_index = find_codec_info_part(adr_link->adr_d[i].adr); + if (codec_index < 0) + return codec_index; + + for (j = 0; j < codec_info_list[codec_index].dai_num ; j++) { + ret = create_sdw_dailink(card, dev, &link_index, links, sdw_be_num, + sdw_cpu_dai_num, cpus, adr_link, + &cpu_id, group_generated, + codec_conf, codec_conf_count, + &be_id, &codec_conf_index, + &ignore_pch_dmic, append_dai_type, i, j); + if (ret < 0) { + dev_err(dev, "failed to create dai link %d", link_index); + return ret; + } } } } @@ -1545,6 +1573,7 @@ SSP: return -ENOMEM; ssp_components->name = codec_name; + /* TODO: support multi codec dai on SSP when it is needed */ ssp_components->dai_name = info->dais[0].dai_name; cpus[cpu_id].dai_name = cpu_name; @@ -1686,6 +1715,24 @@ static struct snd_soc_card card_sof_sdw = { .late_probe = sof_sdw_card_late_probe, }; +/* helper to get the link that the codec DAI is used */ +static struct snd_soc_dai_link *mc_find_codec_dai_used(struct snd_soc_card *card, + const char *dai_name) +{ + struct snd_soc_dai_link *link; + int i; + int j; + + for_each_card_prelinks(card, i, link) { + for (j = 0; j < link->num_codecs; j++) { + /* Check each codec in a link */ + if (!strcmp(link->codecs[j].dai_name, dai_name)) + return link; + } + } + return NULL; +} + static void mc_dailink_exit_loop(struct snd_soc_card *card) { struct snd_soc_dai_link *link; @@ -1693,16 +1740,18 @@ static void mc_dailink_exit_loop(struct snd_soc_card *card) int i, j; for (i = 0; i < ARRAY_SIZE(codec_info_list); i++) { - if (!codec_info_list[i].dais[0].exit) - continue; - /* - * We don't need to call .exit function if there is no matched - * dai link found. - */ - for_each_card_prelinks(card, j, link) { - if (!strcmp(link->codecs[0].dai_name, - codec_info_list[i].dais[0].dai_name)) { - ret = codec_info_list[i].dais[0].exit(card, link); + for (j = 0; j < codec_info_list[i].dai_num; j++) { + /* Check each dai in codec_info_lis to see if it is used in the link */ + if (!codec_info_list[i].dais[j].exit) + continue; + /* + * We don't need to call .exit function if there is no matched + * dai link found. + */ + link = mc_find_codec_dai_used(card, codec_info_list[i].dais[j].dai_name); + if (link) { + /* Do the .exit function if the codec dai is used in the link */ + ret = codec_info_list[i].dais[j].exit(card, link); if (ret) dev_warn(card->dev, "codec exit failed %d\n", -- cgit v1.2.3 From 526a1876fc48e2d0c0ea8ad63b58bdb2cc13047f Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:04 -0500 Subject: ASoC: Intel: sof_sdw_rt_sdca_jack_common: test SOF_JACK_JDSRC in _exit if (!SOF_RT711_JDSRC(sof_sdw_quirk)) is tested in rt711_sdca_add_codec_ device_props(), and we don't add software node to the device if jack source is not set. We need to do the same test in sof_sdw_rt711_sdca_exit(), and avoid removing software node if jack source is not set. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-8-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw_rt711_sdca.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c index 7f16304d025b..cf8b9793fe0e 100644 --- a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c +++ b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c @@ -143,6 +143,9 @@ int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link * if (!ctx->headset_codec_dev) return 0; + if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + return 0; + device_remove_software_node(ctx->headset_codec_dev); put_device(ctx->headset_codec_dev); -- cgit v1.2.3 From 752d4de4c614d639fdb636e4a1ce102328696453 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:05 -0500 Subject: ASoC: Intel: sof_sdw: rename SOF_RT711_JDSRC to SOF_JACK_JDSRC Jack Detection source can be applied to all jacks, not only rt711. No function changes. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-9-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 4 ++-- sound/soc/intel/boards/sof_sdw_common.h | 2 +- sound/soc/intel/boards/sof_sdw_rt711.c | 4 ++-- sound/soc/intel/boards/sof_sdw_rt711_sdca.c | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 8405c3231448..d925e3005394 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -24,9 +24,9 @@ MODULE_PARM_DESC(quirk, "Board-specific quirk override"); static void log_quirks(struct device *dev) { - if (SOF_RT711_JDSRC(sof_sdw_quirk)) + if (SOF_JACK_JDSRC(sof_sdw_quirk)) dev_dbg(dev, "quirk realtek,jack-detect-source %ld\n", - SOF_RT711_JDSRC(sof_sdw_quirk)); + SOF_JACK_JDSRC(sof_sdw_quirk)); if (sof_sdw_quirk & SOF_SDW_FOUR_SPK) dev_dbg(dev, "quirk SOF_SDW_FOUR_SPK enabled\n"); if (sof_sdw_quirk & SOF_SDW_TGL_HDMI) diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index 65b3f6eee924..9640fd6dbd12 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -41,7 +41,7 @@ enum { SOF_I2S_SSP5 = BIT(5), }; -#define SOF_RT711_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) +#define SOF_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define SOF_SDW_FOUR_SPK BIT(4) #define SOF_SDW_TGL_HDMI BIT(5) #define SOF_SDW_PCH_DMIC BIT(6) diff --git a/sound/soc/intel/boards/sof_sdw_rt711.c b/sound/soc/intel/boards/sof_sdw_rt711.c index 8291967f23f3..2b05e2a707de 100644 --- a/sound/soc/intel/boards/sof_sdw_rt711.c +++ b/sound/soc/intel/boards/sof_sdw_rt711.c @@ -27,9 +27,9 @@ static int rt711_add_codec_device_props(struct device *sdw_dev) struct fwnode_handle *fwnode; int ret; - if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + if (!SOF_JACK_JDSRC(sof_sdw_quirk)) return 0; - props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_RT711_JDSRC(sof_sdw_quirk)); + props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_JACK_JDSRC(sof_sdw_quirk)); fwnode = fwnode_create_software_node(props, NULL); if (IS_ERR(fwnode)) diff --git a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c index cf8b9793fe0e..a9ae0aa5ce0a 100644 --- a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c +++ b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c @@ -27,10 +27,10 @@ static int rt711_sdca_add_codec_device_props(struct device *sdw_dev) struct fwnode_handle *fwnode; int ret; - if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + if (!SOF_JACK_JDSRC(sof_sdw_quirk)) return 0; - props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_RT711_JDSRC(sof_sdw_quirk)); + props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_JACK_JDSRC(sof_sdw_quirk)); fwnode = fwnode_create_software_node(props, NULL); if (IS_ERR(fwnode)) @@ -143,7 +143,7 @@ int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link * if (!ctx->headset_codec_dev) return 0; - if (!SOF_RT711_JDSRC(sof_sdw_quirk)) + if (!SOF_JACK_JDSRC(sof_sdw_quirk)) return 0; device_remove_software_node(ctx->headset_codec_dev); -- cgit v1.2.3 From 43f8012c3a6e2b33003ba7ec8c23fbb5bed2ca30 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:06 -0500 Subject: ASoC: Intel: sof_sdw: make rt711_sdca be generic Let rename rt711_sdca to rt_sdca_jack and let it be used for all Realtek sdca jacks. The commit uses component->name_prefix to construct card->components, and determine which codec it is. So, we have to set name_prefix properly. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-10-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Makefile | 2 +- sound/soc/intel/boards/sof_sdw.c | 4 +- sound/soc/intel/boards/sof_sdw_common.h | 12 +- sound/soc/intel/boards/sof_sdw_rt711_sdca.c | 186 -------------------- .../soc/intel/boards/sof_sdw_rt_sdca_jack_common.c | 191 +++++++++++++++++++++ 5 files changed, 200 insertions(+), 195 deletions(-) delete mode 100644 sound/soc/intel/boards/sof_sdw_rt711_sdca.c create mode 100644 sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c (limited to 'sound') diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index d1fd7a2b32db..7fa45569cfb1 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -39,7 +39,7 @@ snd-soc-sof-ssp-amp-objs := sof_ssp_amp.o snd-soc-sof-sdw-objs += sof_sdw.o \ sof_sdw_max98373.o sof_sdw_rt_amp.o \ sof_sdw_rt5682.o sof_sdw_rt700.o \ - sof_sdw_rt711.o sof_sdw_rt711_sdca.o \ + sof_sdw_rt711.o sof_sdw_rt_sdca_jack_common.o \ sof_sdw_rt715.o sof_sdw_rt715_sdca.o \ sof_sdw_dmic.o sof_sdw_hdmi.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index d925e3005394..8310fb094d15 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -583,8 +583,8 @@ static struct sof_sdw_codec_info codec_info_list[] = { .dai_name = "rt711-sdca-aif1", .dai_type = SOF_SDW_DAI_TYPE_JACK, .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, - .init = sof_sdw_rt711_sdca_init, - .exit = sof_sdw_rt711_sdca_exit, + .init = sof_sdw_rt_sdca_jack_init, + .exit = sof_sdw_rt_sdca_jack_exit, }, }, .dai_num = 1, diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index 9640fd6dbd12..bc9dfa626c32 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -123,12 +123,12 @@ int sof_sdw_rt711_init(struct snd_soc_card *card, int sof_sdw_rt711_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); /* RT711-SDCA support */ -int sof_sdw_rt711_sdca_init(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback); -int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); +int sof_sdw_rt_sdca_jack_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); +int sof_sdw_rt_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); /* RT700 support */ int sof_sdw_rt700_init(struct snd_soc_card *card, diff --git a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c b/sound/soc/intel/boards/sof_sdw_rt711_sdca.c deleted file mode 100644 index a9ae0aa5ce0a..000000000000 --- a/sound/soc/intel/boards/sof_sdw_rt711_sdca.c +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -// Copyright (c) 2020 Intel Corporation - -/* - * sof_sdw_rt711_sdca - Helpers to handle RT711-SDCA from generic machine driver - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "sof_sdw_common.h" - -/* - * Note this MUST be called before snd_soc_register_card(), so that the props - * are in place before the codec component driver's probe function parses them. - */ -static int rt711_sdca_add_codec_device_props(struct device *sdw_dev) -{ - struct property_entry props[MAX_NO_PROPS] = {}; - struct fwnode_handle *fwnode; - int ret; - - if (!SOF_JACK_JDSRC(sof_sdw_quirk)) - return 0; - - props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_JACK_JDSRC(sof_sdw_quirk)); - - fwnode = fwnode_create_software_node(props, NULL); - if (IS_ERR(fwnode)) - return PTR_ERR(fwnode); - - ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); - - fwnode_handle_put(fwnode); - - return ret; -} - -static const struct snd_soc_dapm_widget rt711_sdca_widgets[] = { - SND_SOC_DAPM_HP("Headphone", NULL), - SND_SOC_DAPM_MIC("Headset Mic", NULL), -}; - -static const struct snd_soc_dapm_route rt711_sdca_map[] = { - /* Headphones */ - { "Headphone", NULL, "rt711 HP" }, - { "rt711 MIC2", NULL, "Headset Mic" }, -}; - -static const struct snd_kcontrol_new rt711_sdca_controls[] = { - SOC_DAPM_PIN_SWITCH("Headphone"), - SOC_DAPM_PIN_SWITCH("Headset Mic"), -}; - -static struct snd_soc_jack_pin rt711_sdca_jack_pins[] = { - { - .pin = "Headphone", - .mask = SND_JACK_HEADPHONE, - }, - { - .pin = "Headset Mic", - .mask = SND_JACK_MICROPHONE, - }, -}; - -static int rt711_sdca_rtd_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_card *card = rtd->card; - struct mc_private *ctx = snd_soc_card_get_drvdata(card); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); - struct snd_soc_component *component = codec_dai->component; - struct snd_soc_jack *jack; - int ret; - - card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s hs:rt711-sdca", - card->components); - if (!card->components) - return -ENOMEM; - - ret = snd_soc_add_card_controls(card, rt711_sdca_controls, - ARRAY_SIZE(rt711_sdca_controls)); - if (ret) { - dev_err(card->dev, "rt711-sdca controls addition failed: %d\n", ret); - return ret; - } - - ret = snd_soc_dapm_new_controls(&card->dapm, rt711_sdca_widgets, - ARRAY_SIZE(rt711_sdca_widgets)); - if (ret) { - dev_err(card->dev, "rt711-sdca widgets addition failed: %d\n", ret); - return ret; - } - - ret = snd_soc_dapm_add_routes(&card->dapm, rt711_sdca_map, - ARRAY_SIZE(rt711_sdca_map)); - - if (ret) { - dev_err(card->dev, "rt711-sdca map addition failed: %d\n", ret); - return ret; - } - - ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", - SND_JACK_HEADSET | SND_JACK_BTN_0 | - SND_JACK_BTN_1 | SND_JACK_BTN_2 | - SND_JACK_BTN_3, - &ctx->sdw_headset, - rt711_sdca_jack_pins, - ARRAY_SIZE(rt711_sdca_jack_pins)); - if (ret) { - dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", - ret); - return ret; - } - - jack = &ctx->sdw_headset; - - snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); - snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); - snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); - snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); - - ret = snd_soc_component_set_jack(component, jack, NULL); - - if (ret) - dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", - ret); - - return ret; -} - -int sof_sdw_rt711_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) -{ - struct mc_private *ctx = snd_soc_card_get_drvdata(card); - - if (!ctx->headset_codec_dev) - return 0; - - if (!SOF_JACK_JDSRC(sof_sdw_quirk)) - return 0; - - device_remove_software_node(ctx->headset_codec_dev); - put_device(ctx->headset_codec_dev); - - return 0; -} - -int sof_sdw_rt711_sdca_init(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback) -{ - struct mc_private *ctx = snd_soc_card_get_drvdata(card); - struct device *sdw_dev; - int ret; - - /* - * headset should be initialized once. - * Do it with dai link for playback. - */ - if (!playback) - return 0; - - sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); - if (!sdw_dev) - return -EPROBE_DEFER; - - ret = rt711_sdca_add_codec_device_props(sdw_dev); - if (ret < 0) { - put_device(sdw_dev); - return ret; - } - ctx->headset_codec_dev = sdw_dev; - - dai_links->init = rt711_sdca_rtd_init; - - return 0; -} diff --git a/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c b/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c new file mode 100644 index 000000000000..399f28a79110 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation + +/* + * sof_sdw_rt711_sdca - Helpers to handle RT711-SDCA from generic machine driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sof_sdw_common.h" + +/* + * Note this MUST be called before snd_soc_register_card(), so that the props + * are in place before the codec component driver's probe function parses them. + */ +static int rt_sdca_jack_add_codec_device_props(struct device *sdw_dev) +{ + struct property_entry props[MAX_NO_PROPS] = {}; + struct fwnode_handle *fwnode; + int ret; + + if (!SOF_JACK_JDSRC(sof_sdw_quirk)) + return 0; + + props[0] = PROPERTY_ENTRY_U32("realtek,jd-src", SOF_JACK_JDSRC(sof_sdw_quirk)); + + fwnode = fwnode_create_software_node(props, NULL); + if (IS_ERR(fwnode)) + return PTR_ERR(fwnode); + + ret = device_add_software_node(sdw_dev, to_software_node(fwnode)); + + fwnode_handle_put(fwnode); + + return ret; +} + +static const struct snd_soc_dapm_widget rt_sdca_jack_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route rt711_sdca_map[] = { + /* Headphones */ + { "Headphone", NULL, "rt711 HP" }, + { "rt711 MIC2", NULL, "Headset Mic" }, +}; + +static const struct snd_kcontrol_new rt_sdca_jack_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_jack_pin rt_sdca_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int rt_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:%s-sdca", + card->components, component->name_prefix); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt_sdca_jack_controls, + ARRAY_SIZE(rt_sdca_jack_controls)); + if (ret) { + dev_err(card->dev, "rt sdca jack controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt_sdca_jack_widgets, + ARRAY_SIZE(rt_sdca_jack_widgets)); + if (ret) { + dev_err(card->dev, "rt sdca jack widgets addition failed: %d\n", ret); + return ret; + } + + if (strstr(component->name_prefix, "rt711")) { + ret = snd_soc_dapm_add_routes(&card->dapm, rt711_sdca_map, + ARRAY_SIZE(rt711_sdca_map)); + } else { + dev_err(card->dev, "%s is not supported\n", component->name_prefix); + return -EINVAL; + } + + if (ret) { + dev_err(card->dev, "rt sdca jack map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + rt_sdca_jack_pins, + ARRAY_SIZE(rt_sdca_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_rt_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + + if (!ctx->headset_codec_dev) + return 0; + + if (!SOF_JACK_JDSRC(sof_sdw_quirk)) + return 0; + + device_remove_software_node(ctx->headset_codec_dev); + put_device(ctx->headset_codec_dev); + + return 0; +} + +int sof_sdw_rt_sdca_jack_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct device *sdw_dev; + int ret; + + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name); + if (!sdw_dev) + return -EPROBE_DEFER; + + ret = rt_sdca_jack_add_codec_device_props(sdw_dev); + if (ret < 0) { + put_device(sdw_dev); + return ret; + } + ctx->headset_codec_dev = sdw_dev; + + dai_links->init = rt_sdca_jack_rtd_init; + + return 0; +} -- cgit v1.2.3 From 5360c67046385f90406ec17e367ba9aeb42d5459 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:07 -0500 Subject: ASoC: Intel: sof_sdw: add rt712 support Rt712 is a multi function codec which shpports headset, amp, and dmic functions. Rt712 has two sdw interfaces and codec drivers, one for jack and amp, the other for dmic. part id 0x712 is for jack and amp, and 0x1712 is for dmic. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-11-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 2 + sound/soc/intel/boards/Makefile | 5 +- sound/soc/intel/boards/sof_sdw.c | 36 ++++++++ sound/soc/intel/boards/sof_sdw_common.h | 18 ++++ sound/soc/intel/boards/sof_sdw_rt712_sdca.c | 102 +++++++++++++++++++++ .../soc/intel/boards/sof_sdw_rt_sdca_jack_common.c | 9 +- 6 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 sound/soc/intel/boards/sof_sdw_rt712_sdca.c (limited to 'sound') diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 99308ed85277..3f9fa1c78675 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -667,6 +667,8 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH select SND_SOC_RT700_SDW select SND_SOC_RT711_SDW select SND_SOC_RT711_SDCA_SDW + select SND_SOC_RT712_SDCA_SDW + select SND_SOC_RT712_SDCA_DMIC_SDW select SND_SOC_RT1308_SDW select SND_SOC_RT1308 select SND_SOC_RT1316_SDW diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 7fa45569cfb1..50f0191076e3 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -40,8 +40,9 @@ snd-soc-sof-sdw-objs += sof_sdw.o \ sof_sdw_max98373.o sof_sdw_rt_amp.o \ sof_sdw_rt5682.o sof_sdw_rt700.o \ sof_sdw_rt711.o sof_sdw_rt_sdca_jack_common.o \ - sof_sdw_rt715.o sof_sdw_rt715_sdca.o \ - sof_sdw_dmic.o sof_sdw_hdmi.o + sof_sdw_rt712_sdca.o sof_sdw_rt715.o \ + sof_sdw_rt715_sdca.o sof_sdw_dmic.o \ + sof_sdw_hdmi.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o obj-$(CONFIG_SND_SOC_INTEL_SOF_CS42L42_MACH) += snd-soc-sof_cs42l42.o obj-$(CONFIG_SND_SOC_INTEL_SOF_ES8336_MACH) += snd-soc-sof_es8336.o diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 8310fb094d15..4ab7cd7f9178 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -604,6 +604,42 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + { + .part_id = 0x712, + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt712-sdca-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, + .init = sof_sdw_rt_sdca_jack_init, + .exit = sof_sdw_rt_sdca_jack_exit, + }, + { + .direction = {true, false}, + .dai_name = "rt712-sdca-aif2", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_UNUSED_DAI_ID}, + .init = sof_sdw_rt712_spk_init, + }, + }, + .dai_num = 2, + }, + { + .part_id = 0x1712, + .version_id = 3, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt712-sdca-dmic-aif1", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, + .init = sof_sdw_rt712_sdca_dmic_init, + }, + }, + .dai_num = 1, + }, { .part_id = 0x1308, .acpi_id = "10EC1308", diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index bc9dfa626c32..0d7b1be3a2d0 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -130,6 +130,24 @@ int sof_sdw_rt_sdca_jack_init(struct snd_soc_card *card, bool playback); int sof_sdw_rt_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); +/* RT712-SDCA support */ +int sof_sdw_rt712_sdca_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); +int sof_sdw_rt712_sdca_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link); +int sof_sdw_rt712_spk_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); +int sof_sdw_rt712_sdca_dmic_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + /* RT700 support */ int sof_sdw_rt700_init(struct snd_soc_card *card, const struct snd_soc_acpi_link_adr *link, diff --git a/sound/soc/intel/boards/sof_sdw_rt712_sdca.c b/sound/soc/intel/boards/sof_sdw_rt712_sdca.c new file mode 100644 index 000000000000..84c8025d24e3 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_rt712_sdca.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023 Intel Corporation + +/* + * sof_sdw_rt712_sdca - Helpers to handle RT712-SDCA from generic machine driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget rt712_spk_widgets[] = { + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* + * dapm routes for rt712 spk will be registered dynamically according + * to the number of rt712 spk used. The first two entries will be registered + * for one codec case, and the last two entries are also registered + * if two rt712s are used. + */ +static const struct snd_soc_dapm_route rt712_spk_map[] = { + { "Speaker", NULL, "rt712 SPOL" }, + { "Speaker", NULL, "rt712 SPOR" }, +}; + +static const struct snd_kcontrol_new rt712_spk_controls[] = { + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static int rt712_spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s spk:rt712", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, rt712_spk_controls, + ARRAY_SIZE(rt712_spk_controls)); + if (ret) { + dev_err(card->dev, "rt712 spk controls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, rt712_spk_widgets, + ARRAY_SIZE(rt712_spk_widgets)); + if (ret) { + dev_err(card->dev, "rt712 spk widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, rt712_spk_map, ARRAY_SIZE(rt712_spk_map)); + if (ret) + dev_err(rtd->dev, "failed to add SPK map: %d\n", ret); + + return ret; +} + +int sof_sdw_rt712_spk_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + dai_links->init = rt712_spk_init; + + return 0; +} + +static int rt712_sdca_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s mic:rt712-sdca-dmic", + card->components); + if (!card->components) + return -ENOMEM; + + return 0; +} + +int sof_sdw_rt712_sdca_dmic_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + dai_links->init = rt712_sdca_dmic_rtd_init; + + return 0; +} diff --git a/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c b/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c index 399f28a79110..623e3bebb888 100644 --- a/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c +++ b/sound/soc/intel/boards/sof_sdw_rt_sdca_jack_common.c @@ -49,11 +49,15 @@ static const struct snd_soc_dapm_widget rt_sdca_jack_widgets[] = { }; static const struct snd_soc_dapm_route rt711_sdca_map[] = { - /* Headphones */ { "Headphone", NULL, "rt711 HP" }, { "rt711 MIC2", NULL, "Headset Mic" }, }; +static const struct snd_soc_dapm_route rt712_sdca_map[] = { + { "Headphone", NULL, "rt712 HP" }, + { "rt712 MIC2", NULL, "Headset Mic" }, +}; + static const struct snd_kcontrol_new rt_sdca_jack_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Headset Mic"), @@ -102,6 +106,9 @@ static int rt_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd) if (strstr(component->name_prefix, "rt711")) { ret = snd_soc_dapm_add_routes(&card->dapm, rt711_sdca_map, ARRAY_SIZE(rt711_sdca_map)); + } else if (strstr(component->name_prefix, "rt712")) { + ret = snd_soc_dapm_add_routes(&card->dapm, rt712_sdca_map, + ARRAY_SIZE(rt712_sdca_map)); } else { dev_err(card->dev, "%s is not supported\n", component->name_prefix); return -EINVAL; -- cgit v1.2.3 From a2f4d70921f218db768cf3e879fe87dea0a354a5 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:08 -0500 Subject: ASoC: Intel: soc-acpi-intel-tgl-match: add rt712 ID Add rt712 ID for TGL. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-12-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-tgl-match.c | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c index ef19150e7b2e..5804926c8b56 100644 --- a/sound/soc/intel/common/soc-acpi-intel-tgl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-tgl-match.c @@ -41,6 +41,21 @@ static const struct snd_soc_acpi_endpoint spk_r_endpoint = { .group_id = 1, }; +static const struct snd_soc_acpi_endpoint rt712_endpoints[] = { + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + static const struct snd_soc_acpi_adr_device rt711_0_adr[] = { { .adr = 0x000020025D071100ull, @@ -170,6 +185,24 @@ static const struct snd_soc_acpi_adr_device rt1316_1_single_adr[] = { } }; +static const struct snd_soc_acpi_adr_device rt712_0_single_adr[] = { + { + .adr = 0x000030025D071201ull, + .num_endpoints = ARRAY_SIZE(rt712_endpoints), + .endpoints = rt712_endpoints, + .name_prefix = "rt712" + } +}; + +static const struct snd_soc_acpi_adr_device rt1712_1_single_adr[] = { + { + .adr = 0x000130025D171201ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt712-dmic" + } +}; + static const struct snd_soc_acpi_adr_device rt1316_1_group1_adr[] = { { .adr = 0x000131025D131601ull, /* unique ID is set for some reason */ @@ -353,6 +386,20 @@ static const struct snd_soc_acpi_link_adr tgl_3_in_1_sdca_mono[] = { {} }; +static const struct snd_soc_acpi_link_adr tgl_712_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt712_0_single_adr), + .adr_d = rt712_0_single_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1712_1_single_adr), + .adr_d = rt1712_1_single_adr, + }, + {} +}; + static const struct snd_soc_acpi_codecs tgl_max98373_amp = { .num_codecs = 1, .codecs = {"MX98373"} @@ -435,6 +482,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_tgl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-tgl-rt715-rt711-rt1308-mono.tplg", }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = tgl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-tgl-rt712.tplg", + }, { .link_mask = 0x7, .links = tgl_sdw_rt711_link1_rt1308_link2_rt715_link0, -- cgit v1.2.3 From 9efa6f46bc8b606df4226630c668af0e9d25ba7f Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:09 -0500 Subject: ASoC: Intel: soc-acpi-intel-mtl-match: add rt712 ID Add rt712 ID for MTL. Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-13-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-mtl-match.c | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c index 8fd4d0db201e..2c2bece6cd77 100644 --- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -65,6 +65,21 @@ static const struct snd_soc_acpi_endpoint spk_r_endpoint = { .group_id = 1, }; +static const struct snd_soc_acpi_endpoint rt712_endpoints[] = { + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + { + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { { .adr = 0x000030025D071101ull, @@ -74,6 +89,24 @@ static const struct snd_soc_acpi_adr_device rt711_sdca_0_adr[] = { } }; +static const struct snd_soc_acpi_adr_device rt712_0_single_adr[] = { + { + .adr = 0x000030025D071201ull, + .num_endpoints = ARRAY_SIZE(rt712_endpoints), + .endpoints = rt712_endpoints, + .name_prefix = "rt712" + } +}; + +static const struct snd_soc_acpi_adr_device rt1712_3_single_adr[] = { + { + .adr = 0x000330025D171201ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt712-dmic" + } +}; + static const struct snd_soc_acpi_adr_device mx8373_0_adr[] = { { .adr = 0x000023019F837300ull, @@ -125,6 +158,20 @@ static const struct snd_soc_acpi_adr_device rt714_1_adr[] = { } }; +static const struct snd_soc_acpi_link_adr mtl_712_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt712_0_single_adr), + .adr_d = rt712_0_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1712_3_single_adr), + .adr_d = rt1712_3_single_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr rt5682_link2_max98373_link0[] = { /* Expected order: jack -> amp */ { @@ -194,6 +241,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-mtl-rt715-rt711-rt1308-mono.tplg", }, + { + .link_mask = BIT(3) | BIT(0), + .links = mtl_712_only, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-rt712-l0-rt1712-l3.tplg", + }, { .link_mask = GENMASK(3, 0), .links = mtl_3_in_1_sdca, -- cgit v1.2.3 From fbaaf80d8cf6f5da4397108efceca99abfaebbc8 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:10 -0500 Subject: ASoC: Intel: sof_sdw: add rt713 support rt713 is rt712 but without amp. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-14-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 4ab7cd7f9178..73e5a6aed776 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -640,6 +640,35 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + { + .part_id = 0x713, + .version_id = 3, + .dais = { + { + .direction = {true, true}, + .dai_name = "rt712-sdca-aif1", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, + .init = sof_sdw_rt_sdca_jack_init, + .exit = sof_sdw_rt_sdca_jack_exit, + }, + }, + .dai_num = 1, + }, + { + .part_id = 0x1713, + .version_id = 3, + .dais = { + { + .direction = {false, true}, + .dai_name = "rt712-sdca-dmic-aif1", + .dai_type = SOF_SDW_DAI_TYPE_MIC, + .dailink = {SDW_UNUSED_DAI_ID, SDW_DMIC_DAI_ID}, + .init = sof_sdw_rt712_sdca_dmic_init, + }, + }, + .dai_num = 1, + }, { .part_id = 0x1308, .acpi_id = "10EC1308", -- cgit v1.2.3 From 35d28ccd185cfbf5748d4d25dd013e41286a4bf2 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 2 Jun 2023 15:22:11 -0500 Subject: ASoC: Intel: sof_sdw: increase sdw pin index for each sdw link To support multiple codecs per SoundWire link, we have to assign multiple CPU DAIs to different DAI links sharing the same physical link. This is not possible with the existing code since we assume that only 'Pin2' is used for playback and 'Pin3' used for capture - additional DAIs cannot be handled. This patch enables more CPU DAIs to be used, e.g. "SDW0 Pin2", "SDW0 Pin3", and "SDW0 Pin4" for SDW0-Playback-SimpleJack, SDW0-Capture-SimpleJack, and SDW0-Playback-SmartAmp DAI links on physical link #0. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-15-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 73e5a6aed776..a032628f8925 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -22,6 +22,11 @@ MODULE_PARM_DESC(quirk, "Board-specific quirk override"); #define INC_ID(BE, CPU, LINK) do { (BE)++; (CPU)++; (LINK)++; } while (0) +#define SDW_MAX_LINKS 4 + +/* To store SDW Pin index for each SoundWire link */ +static unsigned int sdw_pin_index[SDW_MAX_LINKS]; + static void log_quirks(struct device *dev) { if (SOF_JACK_JDSRC(sof_sdw_quirk)) @@ -1247,10 +1252,10 @@ static int create_sdw_dailink(struct snd_soc_card *card, int cpu_dai_num, cpu_dai_index; unsigned int group_id; int codec_idx = 0; - int i = 0, j = 0; int codec_index; int codec_num; int stream; + int i = 0; int ret; int k; @@ -1336,7 +1341,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, for (k = 0; k < cpu_dai_num; k++) { cpu_name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", cpu_dai_id[k], - j + SDW_INTEL_BIDIR_PDI_BASE); + sdw_pin_index[cpu_dai_id[k]]++); if (!cpu_name) return -ENOMEM; @@ -1385,7 +1390,6 @@ static int create_sdw_dailink(struct snd_soc_card *card, } *cpu_id += cpu_dai_num; - j++; } return 0; @@ -1538,6 +1542,9 @@ static int sof_card_dai_links_create(struct device *dev, for (i = 0; i < SDW_MAX_GROUPS; i++) group_generated[i] = false; + for (i = 0; i < SDW_MAX_LINKS; i++) + sdw_pin_index[i] = SDW_INTEL_BIDIR_PDI_BASE; + for (; adr_link->num_adr; adr_link++) { /* * If there are two or more different devices on the same sdw link, we have to -- cgit v1.2.3 From eeb9f9f7e59d75c97909c3bd51574191d205765a Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:22:12 -0500 Subject: ASoC: Intel: soc-acpi: add table for RPL Dell SKU 0BDA This is a standard configuration we've seen before for TGL. Closes: https://github.com/thesofproject/linux/issues/4380 Reviewed-by: Bard Liao Reviewed-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-16-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-rpl-match.c | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-rpl-match.c b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c index c61d654eb1e2..4dc9ba70f481 100644 --- a/sound/soc/intel/common/soc-acpi-intel-rpl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c @@ -179,6 +179,30 @@ static const struct snd_soc_acpi_link_adr rpl_sdca_3_in_1[] = { {} }; +static const struct snd_soc_acpi_link_adr rpl_sdw_rt711_link0_rt1316_link12_rt714_link3[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt711_sdca_0_adr), + .adr_d = rt711_sdca_0_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_group1_adr), + .adr_d = rt1316_1_group1_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(rt1316_2_group1_adr), + .adr_d = rt1316_2_group1_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt714_3_adr), + .adr_d = rt714_3_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr rpl_sdw_rt711_link2_rt1316_link01_rt714_link3[] = { { .mask = BIT(2), @@ -341,6 +365,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-rpl-rt711-l2-rt1316-l01-rt714-l3.tplg", }, + { + .link_mask = 0xF, /* 4 active links required */ + .links = rpl_sdw_rt711_link0_rt1316_link12_rt714_link3, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-rpl-rt711-l0-rt1316-l12-rt714-l3.tplg", + }, { .link_mask = 0xF, /* 4 active links required */ .links = rpl_sdw_rt711_link0_rt1318_link12_rt714_link3, -- cgit v1.2.3 From 3daf02819ac3fd8d7605804a00213cf123ac880d Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:22:13 -0500 Subject: ASoC: Intel: sof_sdw: add quick for Dell SKU 0BDA The SKU numbering isn't quite consistent with the existing RaptorLake SKUs but the PCI ID is definitively RaptorLake. Closes: https://github.com/thesofproject/linux/issues/4380 Reviewed-by: Bard Liao Reviewed-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-17-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index a032628f8925..582f8e908e8c 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -386,6 +386,16 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { RT711_JD2), }, /* RaptorLake devices */ + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0BDA") + }, + .driver_data = (void *)(SOF_SDW_TGL_HDMI | + RT711_JD2 | + SOF_SDW_FOUR_SPK), + }, { .callback = sof_sdw_quirk_cb, .matches = { -- cgit v1.2.3 From b62a1a839b48f55046727089c3ba7a8ebbf97f0e Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:22:14 -0500 Subject: ASoC: Intel: soc-acpi: add tables for Dell SKU 0B34 Yet another permutation of devices. Closes: https://github.com/thesofproject/linux/issues/4399 Reviewed-by: Bard Liao Reviewed-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-18-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-adl-match.c | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c index d8c80041388a..ac18a6c83a4e 100644 --- a/sound/soc/intel/common/soc-acpi-intel-adl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c @@ -133,6 +133,15 @@ static const struct snd_soc_acpi_adr_device rt1316_1_group2_adr[] = { } }; +static const struct snd_soc_acpi_adr_device rt1316_1_single_adr[] = { + { + .adr = 0x000130025D131601ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1316-1" + } +}; + static const struct snd_soc_acpi_adr_device rt1316_2_single_adr[] = { { .adr = 0x000230025D131601ull, @@ -312,6 +321,20 @@ static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link12_rt714_link0[] = {} }; +static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link1_rt714_link0[] = { + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1316_1_single_adr), + .adr_d = rt1316_1_single_adr, + }, + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt714_0_adr), + .adr_d = rt714_0_adr, + }, + {} +}; + static const struct snd_soc_acpi_link_adr adl_sdw_rt1316_link2_rt714_link3[] = { { .mask = BIT(2), @@ -620,6 +643,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-adl-rt1316-l2-mono-rt714-l3.tplg", }, + { + .link_mask = 0x3, /* rt1316 on link1 & rt714 on link0 */ + .links = adl_sdw_rt1316_link1_rt714_link0, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-adl-rt1316-l1-mono-rt714-l0.tplg", + }, { .link_mask = 0x7, /* rt714 on link0 & two rt1316s on link1 and link2 */ .links = adl_sdw_rt1316_link12_rt714_link0, -- cgit v1.2.3 From 332f618756e61bee564e0919f97faef788c6a6e6 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:22:15 -0500 Subject: ASoC: Intel: sof-sdw: add Dell SKU 0B34 This device has no 3.5mm jack, only a single amplifier and mic codec. Closes: https://github.com/thesofproject/linux/issues/4399 Reviewed-by: Bard Liao Reviewed-by: Ranjani Sridharan Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-19-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 582f8e908e8c..50e672caccb3 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -376,6 +376,15 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { RT711_JD2 | SOF_SDW_FOUR_SPK), }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0B34"), + }, + /* No Jack */ + .driver_data = (void *)SOF_SDW_TGL_HDMI, + }, { .callback = sof_sdw_quirk_cb, .matches = { -- cgit v1.2.3 From 5376d37b2a8bf7382cd627504e27c5e42cdc820f Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Fri, 2 Jun 2023 15:22:16 -0500 Subject: ASoC: Intel: ADL: Enable HDMI-In capture feature support for non-I2S codec boards. Adding HDMI-In capture support for the products doesn't have onboard I2S codec.but need to support HDMI-In capture via I2S and audio playback through HDMI/DP monitor. Reviewed-by: Bard Liao Signed-off-by: Balamurugan C Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-20-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_ssp_amp.c | 9 +++++++++ sound/soc/intel/common/soc-acpi-intel-adl-match.c | 5 +++++ 2 files changed, 14 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_ssp_amp.c b/sound/soc/intel/boards/sof_ssp_amp.c index b33f720b3e6d..56fa0c196daf 100644 --- a/sound/soc/intel/boards/sof_ssp_amp.c +++ b/sound/soc/intel/boards/sof_ssp_amp.c @@ -463,6 +463,15 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_BT_OFFLOAD_PRESENT | SOF_CS35L41_SPEAKER_AMP_PRESENT), }, + { + .name = "adl_lt6911_hdmi_ssp", + .driver_data = (kernel_ulong_t)(SOF_NO_OF_HDMI_CAPTURE_SSP(2) | + SOF_HDMI_CAPTURE_1_SSP(0) | + SOF_HDMI_CAPTURE_2_SSP(2) | + SOF_SSP_HDMI_CAPTURE_PRESENT | + SOF_NO_OF_HDMI_PLAYBACK(3) | + SOF_HDMI_PLAYBACK_PRESENT), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c index ac18a6c83a4e..3ecbeaecdc63 100644 --- a/sound/soc/intel/common/soc-acpi-intel-adl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c @@ -601,6 +601,11 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, }, + { + .id = "INTC10B0", + .drv_name = "adl_lt6911_hdmi_ssp", + .sof_tplg_filename = "sof-adl-nocodec-hdmi-ssp02.tplg" + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_adl_machines); -- cgit v1.2.3 From c3a3c06e05c244374fb773c80e4055a5e8aa45f7 Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Fri, 2 Jun 2023 15:22:17 -0500 Subject: ASoC: Intel: ADL: Moving amp only boards into end of the table. Moving amp only boards into end of the match table to have better order and maintenance. Reviewed-by: Bard Liao Signed-off-by: Balamurugan C Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-21-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-adl-match.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-adl-match.c b/sound/soc/intel/common/soc-acpi-intel-adl-match.c index 3ecbeaecdc63..bcd66e0094b4 100644 --- a/sound/soc/intel/common/soc-acpi-intel-adl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-adl-match.c @@ -580,12 +580,6 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { .quirk_data = &adl_max98360a_amp, .sof_tplg_filename = "sof-adl-max98360a-cs42l42.tplg", }, - /* place amp-only boards in the end of table */ - { - .id = "CSC3541", - .drv_name = "adl_cs35l41", - .sof_tplg_filename = "sof-adl-cs35l41.tplg", - }, { .comp_ids = &essx_83x6, .drv_name = "adl_es83x6_c1_h02", @@ -601,6 +595,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_adl_machines[] = { SND_SOC_ACPI_TPLG_INTEL_SSP_MSB | SND_SOC_ACPI_TPLG_INTEL_DMIC_NUMBER, }, + /* place amp-only boards in the end of table */ + { + .id = "CSC3541", + .drv_name = "adl_cs35l41", + .sof_tplg_filename = "sof-adl-cs35l41.tplg", + }, { .id = "INTC10B0", .drv_name = "adl_lt6911_hdmi_ssp", -- cgit v1.2.3 From 1529d344dd49059c114c200dbe1c1a55d45ea120 Mon Sep 17 00:00:00 2001 From: Balamurugan C Date: Fri, 2 Jun 2023 15:22:18 -0500 Subject: ASoC: Intel: Sof_ssp_amp: Correcting author name. Corrected the author name camel case and initial. Reviewed-by: Bard Liao Signed-off-by: Balamurugan C Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-22-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_ssp_amp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_ssp_amp.c b/sound/soc/intel/boards/sof_ssp_amp.c index 56fa0c196daf..0aef718e82b2 100644 --- a/sound/soc/intel/boards/sof_ssp_amp.c +++ b/sound/soc/intel/boards/sof_ssp_amp.c @@ -487,7 +487,7 @@ static struct platform_driver sof_ssp_amp_driver = { module_platform_driver(sof_ssp_amp_driver); MODULE_DESCRIPTION("ASoC Intel(R) SOF Amplifier Machine driver"); -MODULE_AUTHOR("balamurugan.c "); +MODULE_AUTHOR("Balamurugan C "); MODULE_AUTHOR("Brent Lu "); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS(SND_SOC_INTEL_HDA_DSP_COMMON); -- cgit v1.2.3 From 5dc51e50457a1ddafad47fcd668910a5bd91106f Mon Sep 17 00:00:00 2001 From: Terry Cheong Date: Fri, 2 Jun 2023 15:22:19 -0500 Subject: ASoC: Intel: Add rpl_rt1019_rt5682 driver Boards were using this in older kernels before adl and rpl ids were split. Add this back to maintain support. Reviewed-by: Curtis Malainey Signed-off-by: Terry Cheong Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-23-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 11 +++++++++++ sound/soc/intel/common/soc-acpi-intel-rpl-match.c | 12 ++++++++++++ 2 files changed, 23 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 7f4783592668..86bbc1fea6ff 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -1119,6 +1119,17 @@ static const struct platform_device_id board_ids[] = { SOF_BT_OFFLOAD_SSP(2) | SOF_SSP_BT_OFFLOAD_PRESENT), }, + { + .name = "rpl_rt1019_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(0) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1019_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), + }, { .name = "mtl_mx98357_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | diff --git a/sound/soc/intel/common/soc-acpi-intel-rpl-match.c b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c index 4dc9ba70f481..302a08018572 100644 --- a/sound/soc/intel/common/soc-acpi-intel-rpl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-rpl-match.c @@ -332,6 +332,11 @@ static const struct snd_soc_acpi_codecs rpl_max98373_amp = { .codecs = {"MX98373"} }; +static const struct snd_soc_acpi_codecs rpl_rt1019p_amp = { + .num_codecs = 1, + .codecs = {"RTL1019"} +}; + struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_machines[] = { { .comp_ids = &rpl_rt5682_hp, @@ -347,6 +352,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_rpl_machines[] = { .quirk_data = &rpl_max98373_amp, .sof_tplg_filename = "sof-rpl-max98373-nau8825.tplg", }, + { + .comp_ids = &rpl_rt5682_hp, + .drv_name = "rpl_rt1019_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &rpl_rt1019p_amp, + .sof_tplg_filename = "sof-rpl-rt1019-rt5682.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_rpl_machines); -- cgit v1.2.3 From 43cdea08a4acc8f61daf0050f713314f0bfbedf7 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:20 -0500 Subject: ASoC: Intel: sof_sdw: Add helper function for cs42l42 codec Helper functions added to support CS42l42 soundwire codec. Build configuration is updated to include this codec. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-24-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 1 + sound/soc/intel/boards/Makefile | 1 + sound/soc/intel/boards/sof_sdw.c | 13 +++ sound/soc/intel/boards/sof_sdw_common.h | 7 ++ sound/soc/intel/boards/sof_sdw_cs42l42.c | 131 +++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 sound/soc/intel/boards/sof_sdw_cs42l42.c (limited to 'sound') diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 3f9fa1c78675..799a51f23b84 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -676,6 +676,7 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH select SND_SOC_RT715_SDW select SND_SOC_RT715_SDCA_SDW select SND_SOC_RT5682_SDW + select SND_SOC_CS42L42_SDW select SND_SOC_DMIC select SND_SOC_INTEL_HDA_DSP_COMMON select SND_SOC_INTEL_SOF_MAXIM_COMMON diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 50f0191076e3..2de930b1ef31 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -42,6 +42,7 @@ snd-soc-sof-sdw-objs += sof_sdw.o \ sof_sdw_rt711.o sof_sdw_rt_sdca_jack_common.o \ sof_sdw_rt712_sdca.o sof_sdw_rt715.o \ sof_sdw_rt715_sdca.o sof_sdw_dmic.o \ + sof_sdw_cs42l42.o \ sof_sdw_hdmi.o obj-$(CONFIG_SND_SOC_INTEL_SOF_RT5682_MACH) += snd-soc-sof_rt5682.o obj-$(CONFIG_SND_SOC_INTEL_SOF_CS42L42_MACH) += snd-soc-sof_cs42l42.o diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 50e672caccb3..60ce8100e1dc 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -823,6 +823,19 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + { + .part_id = 0x4242, + .dais = { + { + .direction = {true, true}, + .dai_name = "cs42l42-sdw", + .dai_type = SOF_SDW_DAI_TYPE_JACK, + .dailink = {SDW_JACK_OUT_DAI_ID, SDW_JACK_IN_DAI_ID}, + .init = sof_sdw_cs42l42_init, + }, + }, + .dai_num = 1, + }, { .part_id = 0xaaaa, /* generic codec mockup */ .version_id = 0, diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index 0d7b1be3a2d0..f98d1ded5b1a 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -196,4 +196,11 @@ int sof_sdw_rt5682_init(struct snd_soc_card *card, struct sof_sdw_codec_info *info, bool playback); +/* CS42L42 support */ +int sof_sdw_cs42l42_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); + #endif diff --git a/sound/soc/intel/boards/sof_sdw_cs42l42.c b/sound/soc/intel/boards/sof_sdw_cs42l42.c new file mode 100644 index 000000000000..c4a16e4c9f69 --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_cs42l42.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2023 Intel Corporation + +/* + * sof_sdw_cs42l42 - Helpers to handle CS42L42 from generic machine driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sof_sdw_common.h" + +static const struct snd_soc_dapm_widget cs42l42_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route cs42l42_map[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone", NULL, "cs42l42 HP"}, + + /* other jacks */ + {"cs42l42 HS", NULL, "Headset Mic"}, +}; + +static const struct snd_kcontrol_new cs42l42_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static struct snd_soc_jack_pin cs42l42_jack_pins[] = { + { + .pin = "Headphone", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int cs42l42_rtd_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + struct mc_private *ctx = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack *jack; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s hs:cs42l42", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, cs42l42_controls, + ARRAY_SIZE(cs42l42_controls)); + if (ret) { + dev_err(card->dev, "cs42l42 control addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, cs42l42_widgets, + ARRAY_SIZE(cs42l42_widgets)); + if (ret) { + dev_err(card->dev, "cs42l42 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, cs42l42_map, + ARRAY_SIZE(cs42l42_map)); + + if (ret) { + dev_err(card->dev, "cs42l42 map addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3, + &ctx->sdw_headset, + cs42l42_jack_pins, + ARRAY_SIZE(cs42l42_jack_pins)); + if (ret) { + dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n", + ret); + return ret; + } + + jack = &ctx->sdw_headset; + + snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + + ret = snd_soc_component_set_jack(component, jack, NULL); + + if (ret) + dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n", + ret); + + return ret; +} + +int sof_sdw_cs42l42_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + /* + * headset should be initialized once. + * Do it with dai link for playback. + */ + if (!playback) + return 0; + + dai_links->init = cs42l42_rtd_init; + + return 0; +} -- cgit v1.2.3 From 85565f8047668b6727127df539f7a6ecc0f9b9c0 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:21 -0500 Subject: ASoC: Intel: sof_sdw: Rename sof_sdw_max98373.c file to sof_sdw_maxim.c This is needed to use the common implementation for other maxim soundwire codecs Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-25-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Makefile | 2 +- sound/soc/intel/boards/sof_sdw_max98373.c | 148 ------------------------------ sound/soc/intel/boards/sof_sdw_maxim.c | 148 ++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 149 deletions(-) delete mode 100644 sound/soc/intel/boards/sof_sdw_max98373.c create mode 100644 sound/soc/intel/boards/sof_sdw_maxim.c (limited to 'sound') diff --git a/sound/soc/intel/boards/Makefile b/sound/soc/intel/boards/Makefile index 2de930b1ef31..931415d9cf6f 100644 --- a/sound/soc/intel/boards/Makefile +++ b/sound/soc/intel/boards/Makefile @@ -37,7 +37,7 @@ snd-soc-sof_da7219_max98373-objs := sof_da7219_max98373.o snd-soc-ehl-rt5660-objs := ehl_rt5660.o snd-soc-sof-ssp-amp-objs := sof_ssp_amp.o snd-soc-sof-sdw-objs += sof_sdw.o \ - sof_sdw_max98373.o sof_sdw_rt_amp.o \ + sof_sdw_maxim.o sof_sdw_rt_amp.o \ sof_sdw_rt5682.o sof_sdw_rt700.o \ sof_sdw_rt711.o sof_sdw_rt_sdca_jack_common.o \ sof_sdw_rt712_sdca.o sof_sdw_rt715.o \ diff --git a/sound/soc/intel/boards/sof_sdw_max98373.c b/sound/soc/intel/boards/sof_sdw_max98373.c deleted file mode 100644 index 3d7df58c0f1d..000000000000 --- a/sound/soc/intel/boards/sof_sdw_max98373.c +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -// Copyright (c) 2020 Intel Corporation -// -// sof_sdw_max98373 - Helpers to handle 2x MAX98373 -// codec devices from generic machine driver - -#include -#include -#include -#include -#include -#include -#include "sof_sdw_common.h" -#include "sof_maxim_common.h" - -static const struct snd_soc_dapm_widget mx8373_widgets[] = { - SND_SOC_DAPM_SPK("Left Spk", NULL), - SND_SOC_DAPM_SPK("Right Spk", NULL), -}; - -static const struct snd_kcontrol_new mx8373_controls[] = { - SOC_DAPM_PIN_SWITCH("Left Spk"), - SOC_DAPM_PIN_SWITCH("Right Spk"), -}; - -static int spk_init(struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_card *card = rtd->card; - int ret; - - card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s spk:mx8373", - card->components); - if (!card->components) - return -ENOMEM; - - ret = snd_soc_add_card_controls(card, mx8373_controls, - ARRAY_SIZE(mx8373_controls)); - if (ret) { - dev_err(card->dev, "mx8373 ctrls addition failed: %d\n", ret); - return ret; - } - - ret = snd_soc_dapm_new_controls(&card->dapm, mx8373_widgets, - ARRAY_SIZE(mx8373_widgets)); - if (ret) { - dev_err(card->dev, "mx8373 widgets addition failed: %d\n", ret); - return ret; - } - - ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes, 2); - if (ret) - dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret); - - return ret; -} - -static int mx8373_enable_spk_pin(struct snd_pcm_substream *substream, bool enable) -{ - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai; - struct snd_soc_dai *cpu_dai; - int ret; - int j; - - /* set spk pin by playback only */ - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) - return 0; - - cpu_dai = asoc_rtd_to_cpu(rtd, 0); - for_each_rtd_codec_dais(rtd, j, codec_dai) { - struct snd_soc_dapm_context *dapm = - snd_soc_component_get_dapm(cpu_dai->component); - char pin_name[16]; - - snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk", - codec_dai->component->name_prefix); - - if (enable) - ret = snd_soc_dapm_enable_pin(dapm, pin_name); - else - ret = snd_soc_dapm_disable_pin(dapm, pin_name); - - if (!ret) - snd_soc_dapm_sync(dapm); - } - - return 0; -} - -static int mx8373_sdw_prepare(struct snd_pcm_substream *substream) -{ - int ret; - - /* according to soc_pcm_prepare dai link prepare is called first */ - ret = sdw_prepare(substream); - if (ret < 0) - return ret; - - return mx8373_enable_spk_pin(substream, true); -} - -static int mx8373_sdw_hw_free(struct snd_pcm_substream *substream) -{ - int ret; - - /* according to soc_pcm_hw_free dai link free is called first */ - ret = sdw_hw_free(substream); - if (ret < 0) - return ret; - - return mx8373_enable_spk_pin(substream, false); -} - -static const struct snd_soc_ops max_98373_sdw_ops = { - .startup = sdw_startup, - .prepare = mx8373_sdw_prepare, - .trigger = sdw_trigger, - .hw_free = mx8373_sdw_hw_free, - .shutdown = sdw_shutdown, -}; - -static int mx8373_sdw_late_probe(struct snd_soc_card *card) -{ - struct snd_soc_dapm_context *dapm = &card->dapm; - - /* Disable Left and Right Spk pin after boot */ - snd_soc_dapm_disable_pin(dapm, "Left Spk"); - snd_soc_dapm_disable_pin(dapm, "Right Spk"); - return snd_soc_dapm_sync(dapm); -} - -int sof_sdw_mx8373_init(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback) -{ - info->amp_num++; - if (info->amp_num == 2) - dai_links->init = spk_init; - - info->codec_card_late_probe = mx8373_sdw_late_probe; - - dai_links->ops = &max_98373_sdw_ops; - - return 0; -} diff --git a/sound/soc/intel/boards/sof_sdw_maxim.c b/sound/soc/intel/boards/sof_sdw_maxim.c new file mode 100644 index 000000000000..3d7df58c0f1d --- /dev/null +++ b/sound/soc/intel/boards/sof_sdw_maxim.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2020 Intel Corporation +// +// sof_sdw_max98373 - Helpers to handle 2x MAX98373 +// codec devices from generic machine driver + +#include +#include +#include +#include +#include +#include +#include "sof_sdw_common.h" +#include "sof_maxim_common.h" + +static const struct snd_soc_dapm_widget mx8373_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_kcontrol_new mx8373_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static int spk_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_card *card = rtd->card; + int ret; + + card->components = devm_kasprintf(card->dev, GFP_KERNEL, + "%s spk:mx8373", + card->components); + if (!card->components) + return -ENOMEM; + + ret = snd_soc_add_card_controls(card, mx8373_controls, + ARRAY_SIZE(mx8373_controls)); + if (ret) { + dev_err(card->dev, "mx8373 ctrls addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_new_controls(&card->dapm, mx8373_widgets, + ARRAY_SIZE(mx8373_widgets)); + if (ret) { + dev_err(card->dev, "mx8373 widgets addition failed: %d\n", ret); + return ret; + } + + ret = snd_soc_dapm_add_routes(&card->dapm, max_98373_dapm_routes, 2); + if (ret) + dev_err(rtd->dev, "failed to add first SPK map: %d\n", ret); + + return ret; +} + +static int mx8373_enable_spk_pin(struct snd_pcm_substream *substream, bool enable) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai; + int ret; + int j; + + /* set spk pin by playback only */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + cpu_dai = asoc_rtd_to_cpu(rtd, 0); + for_each_rtd_codec_dais(rtd, j, codec_dai) { + struct snd_soc_dapm_context *dapm = + snd_soc_component_get_dapm(cpu_dai->component); + char pin_name[16]; + + snprintf(pin_name, ARRAY_SIZE(pin_name), "%s Spk", + codec_dai->component->name_prefix); + + if (enable) + ret = snd_soc_dapm_enable_pin(dapm, pin_name); + else + ret = snd_soc_dapm_disable_pin(dapm, pin_name); + + if (!ret) + snd_soc_dapm_sync(dapm); + } + + return 0; +} + +static int mx8373_sdw_prepare(struct snd_pcm_substream *substream) +{ + int ret; + + /* according to soc_pcm_prepare dai link prepare is called first */ + ret = sdw_prepare(substream); + if (ret < 0) + return ret; + + return mx8373_enable_spk_pin(substream, true); +} + +static int mx8373_sdw_hw_free(struct snd_pcm_substream *substream) +{ + int ret; + + /* according to soc_pcm_hw_free dai link free is called first */ + ret = sdw_hw_free(substream); + if (ret < 0) + return ret; + + return mx8373_enable_spk_pin(substream, false); +} + +static const struct snd_soc_ops max_98373_sdw_ops = { + .startup = sdw_startup, + .prepare = mx8373_sdw_prepare, + .trigger = sdw_trigger, + .hw_free = mx8373_sdw_hw_free, + .shutdown = sdw_shutdown, +}; + +static int mx8373_sdw_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_dapm_context *dapm = &card->dapm; + + /* Disable Left and Right Spk pin after boot */ + snd_soc_dapm_disable_pin(dapm, "Left Spk"); + snd_soc_dapm_disable_pin(dapm, "Right Spk"); + return snd_soc_dapm_sync(dapm); +} + +int sof_sdw_mx8373_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) +{ + info->amp_num++; + if (info->amp_num == 2) + dai_links->init = spk_init; + + info->codec_card_late_probe = mx8373_sdw_late_probe; + + dai_links->ops = &max_98373_sdw_ops; + + return 0; +} -- cgit v1.2.3 From fcb3f0fb4c7255b7617d3d0e98414ab36ddcbee3 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:22 -0500 Subject: ASoC: Intel: sof_sdw: Modify maxim helper functions and structure names Init function and structure names are modified to use maxim instead of max98373. Card components and speaker names are updated based on part id. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-26-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 2 +- sound/soc/intel/boards/sof_sdw_common.h | 12 ++++---- sound/soc/intel/boards/sof_sdw_maxim.c | 52 ++++++++++++++++++++------------- 3 files changed, 39 insertions(+), 27 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 60ce8100e1dc..04d050eac00d 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -805,7 +805,7 @@ static struct sof_sdw_codec_info codec_info_list[] = { .dai_name = "max98373-aif1", .dai_type = SOF_SDW_DAI_TYPE_AMP, .dailink = {SDW_AMP_OUT_DAI_ID, SDW_AMP_IN_DAI_ID}, - .init = sof_sdw_mx8373_init, + .init = sof_sdw_maxim_init, }, }, .dai_num = 1, diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index f98d1ded5b1a..64cfa5d1aceb 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -182,12 +182,12 @@ int sof_sdw_rt715_sdca_init(struct snd_soc_card *card, struct sof_sdw_codec_info *info, bool playback); -/* MAX98373 support */ -int sof_sdw_mx8373_init(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback); +/* MAXIM codec support */ +int sof_sdw_maxim_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback); /* RT5682 support */ int sof_sdw_rt5682_init(struct snd_soc_card *card, diff --git a/sound/soc/intel/boards/sof_sdw_maxim.c b/sound/soc/intel/boards/sof_sdw_maxim.c index 3d7df58c0f1d..3cc47ae98c5e 100644 --- a/sound/soc/intel/boards/sof_sdw_maxim.c +++ b/sound/soc/intel/boards/sof_sdw_maxim.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright (c) 2020 Intel Corporation // -// sof_sdw_max98373 - Helpers to handle 2x MAX98373 +// sof_sdw_maxim - Helpers to handle maxim codecs // codec devices from generic machine driver #include @@ -13,12 +13,15 @@ #include "sof_sdw_common.h" #include "sof_maxim_common.h" -static const struct snd_soc_dapm_widget mx8373_widgets[] = { +static int maxim_part_id; +#define SOF_SDW_PART_ID_MAX98373 0x8373 + +static const struct snd_soc_dapm_widget maxim_widgets[] = { SND_SOC_DAPM_SPK("Left Spk", NULL), SND_SOC_DAPM_SPK("Right Spk", NULL), }; -static const struct snd_kcontrol_new mx8373_controls[] = { +static const struct snd_kcontrol_new maxim_controls[] = { SOC_DAPM_PIN_SWITCH("Left Spk"), SOC_DAPM_PIN_SWITCH("Right Spk"), }; @@ -29,22 +32,25 @@ static int spk_init(struct snd_soc_pcm_runtime *rtd) int ret; card->components = devm_kasprintf(card->dev, GFP_KERNEL, - "%s spk:mx8373", - card->components); + "%s spk:mx%04x", + card->components, maxim_part_id); if (!card->components) return -ENOMEM; - ret = snd_soc_add_card_controls(card, mx8373_controls, - ARRAY_SIZE(mx8373_controls)); + dev_dbg(card->dev, "soundwire maxim card components assigned : %s\n", + card->components); + + ret = snd_soc_add_card_controls(card, maxim_controls, + ARRAY_SIZE(maxim_controls)); if (ret) { - dev_err(card->dev, "mx8373 ctrls addition failed: %d\n", ret); + dev_err(card->dev, "mx%04x ctrls addition failed: %d\n", maxim_part_id, ret); return ret; } - ret = snd_soc_dapm_new_controls(&card->dapm, mx8373_widgets, - ARRAY_SIZE(mx8373_widgets)); + ret = snd_soc_dapm_new_controls(&card->dapm, maxim_widgets, + ARRAY_SIZE(maxim_widgets)); if (ret) { - dev_err(card->dev, "mx8373 widgets addition failed: %d\n", ret); + dev_err(card->dev, "mx%04x widgets addition failed: %d\n", maxim_part_id, ret); return ret; } @@ -130,19 +136,25 @@ static int mx8373_sdw_late_probe(struct snd_soc_card *card) return snd_soc_dapm_sync(dapm); } -int sof_sdw_mx8373_init(struct snd_soc_card *card, - const struct snd_soc_acpi_link_adr *link, - struct snd_soc_dai_link *dai_links, - struct sof_sdw_codec_info *info, - bool playback) +int sof_sdw_maxim_init(struct snd_soc_card *card, + const struct snd_soc_acpi_link_adr *link, + struct snd_soc_dai_link *dai_links, + struct sof_sdw_codec_info *info, + bool playback) { info->amp_num++; if (info->amp_num == 2) dai_links->init = spk_init; - info->codec_card_late_probe = mx8373_sdw_late_probe; - - dai_links->ops = &max_98373_sdw_ops; - + maxim_part_id = info->part_id; + switch (maxim_part_id) { + case SOF_SDW_PART_ID_MAX98373: + info->codec_card_late_probe = mx8373_sdw_late_probe; + dai_links->ops = &max_98373_sdw_ops; + break; + default: + dev_err(card->dev, "Invalid maxim_part_id %#x\n", maxim_part_id); + return -EINVAL; + } return 0; } -- cgit v1.2.3 From dea4138d7794f3041f6969bff637b7e5ed89ae90 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:23 -0500 Subject: ASoC: Intel: sof_sdw: Add support for MAX98363 codec Add support for MAX98363 soundwire codec. Update build configuration to include this codec. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-27-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/Kconfig | 1 + sound/soc/intel/boards/sof_sdw.c | 13 +++++++++++++ sound/soc/intel/boards/sof_sdw_maxim.c | 6 ++++++ 3 files changed, 20 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index 799a51f23b84..f472f603ab75 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -662,6 +662,7 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH depends on MFD_INTEL_LPSS || COMPILE_TEST depends on SND_SOC_INTEL_USER_FRIENDLY_LONG_NAMES || COMPILE_TEST depends on SOUNDWIRE + select SND_SOC_MAX98363 select SND_SOC_MAX98373_I2C select SND_SOC_MAX98373_SDW select SND_SOC_RT700_SDW diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 04d050eac00d..6caf598c7aeb 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -810,6 +810,19 @@ static struct sof_sdw_codec_info codec_info_list[] = { }, .dai_num = 1, }, + { + .part_id = 0x8363, + .dais = { + { + .direction = {true, false}, + .dai_name = "max98363-aif1", + .dai_type = SOF_SDW_DAI_TYPE_AMP, + .dailink = {SDW_AMP_OUT_DAI_ID, SDW_UNUSED_DAI_ID}, + .init = sof_sdw_maxim_init, + }, + }, + .dai_num = 1, + }, { .part_id = 0x5682, .dais = { diff --git a/sound/soc/intel/boards/sof_sdw_maxim.c b/sound/soc/intel/boards/sof_sdw_maxim.c index 3cc47ae98c5e..8d40a83ad98e 100644 --- a/sound/soc/intel/boards/sof_sdw_maxim.c +++ b/sound/soc/intel/boards/sof_sdw_maxim.c @@ -14,6 +14,7 @@ #include "sof_maxim_common.h" static int maxim_part_id; +#define SOF_SDW_PART_ID_MAX98363 0x8363 #define SOF_SDW_PART_ID_MAX98373 0x8373 static const struct snd_soc_dapm_widget maxim_widgets[] = { @@ -148,6 +149,11 @@ int sof_sdw_maxim_init(struct snd_soc_card *card, maxim_part_id = info->part_id; switch (maxim_part_id) { + case SOF_SDW_PART_ID_MAX98363: + /* Default ops are set in function init_dai_link. + * called as part of function create_sdw_dailink + */ + break; case SOF_SDW_PART_ID_MAX98373: info->codec_card_late_probe = mx8373_sdw_late_probe; dai_links->ops = &max_98373_sdw_ops; -- cgit v1.2.3 From 164e5dc17525181c05563f0a06796f1a363801d5 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:24 -0500 Subject: ASoC: Intel: sof_sdw: Add support for Rex soundwire Add rex entry in the soundwire quirk table Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-28-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index 6caf598c7aeb..d942696b36cd 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -461,6 +461,14 @@ static const struct dmi_system_id sof_sdw_quirk_table[] = { }, .driver_data = (void *)(RT711_JD2_100K), }, + { + .callback = sof_sdw_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Google"), + DMI_MATCH(DMI_PRODUCT_NAME, "Rex"), + }, + .driver_data = (void *)(SOF_SDW_PCH_DMIC), + }, /* LunarLake devices */ { .callback = sof_sdw_quirk_cb, -- cgit v1.2.3 From a0503817c0be5ea15164f64e06350e3363466021 Mon Sep 17 00:00:00 2001 From: Uday M Bhat Date: Fri, 2 Jun 2023 15:22:25 -0500 Subject: ASoC: Intel: soc-acpi: add Rex CS42l42 and MAX98363 SoundWire entries Add support to the following daughter card for rex: SDW0: CS42l42 Headset SDW2: MX98363 Speaker Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Yong Zhi Signed-off-by: Uday M Bhat Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230602202225.249209-29-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/common/soc-acpi-intel-mtl-match.c | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c index 2c2bece6cd77..3d5cf8867926 100644 --- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -220,6 +220,45 @@ static const struct snd_soc_acpi_link_adr mtl_3_in_1_sdca[] = { {} }; +static const struct snd_soc_acpi_adr_device mx8363_2_adr[] = { + { + .adr = 0x000230019F836300ull, + .num_endpoints = 1, + .endpoints = &spk_l_endpoint, + .name_prefix = "Left" + }, + { + .adr = 0x000231019F836300ull, + .num_endpoints = 1, + .endpoints = &spk_r_endpoint, + .name_prefix = "Right" + } +}; + +static const struct snd_soc_acpi_adr_device cs42l42_0_adr[] = { + { + .adr = 0x00001001FA424200ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "cs42l42" + } +}; + +static const struct snd_soc_acpi_link_adr cs42l42_link0_max98363_link2[] = { + /* Expected order: jack -> amp */ + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(cs42l42_0_adr), + .adr_d = cs42l42_0_adr, + }, + { + .mask = BIT(2), + .num_adr = ARRAY_SIZE(mx8363_2_adr), + .adr_d = mx8363_2_adr, + }, + {} +}; + /* this table is used when there is no I2S codec present */ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { /* mockup tests need to be first */ @@ -265,6 +304,12 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_sdw_machines[] = { .drv_name = "sof_sdw", .sof_tplg_filename = "sof-mtl-sdw-rt5682-l2-max98373-l0.tplg", }, + { + .link_mask = BIT(0) | BIT(2), + .links = cs42l42_link0_max98363_link2, + .drv_name = "sof_sdw", + .sof_tplg_filename = "sof-mtl-sdw-cs42l42-l0-max98363-l2.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_sdw_machines); -- cgit v1.2.3 From c4be6024d51d3930459a61d4c91990f20264c60b Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:12 -0500 Subject: ASoC: SOF: Intel: hda-dai: add error checks to prevent static analysis warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make KCFLAGS='-fanalyzer' sound/soc/sof/intel/ reports several NULL pointer dereference paths. Example log: ops = hda_dai_get_ops(substream, cpu_dai); | | ^~~~~ | | | | | (14) return of NULL to ‘non_hda_dai_hw_params’ from ‘hda_dai_get_ops’ | 353 | sdev = widget_to_sdev(w); | 354 | hext_stream = ops->get_hext_stream(sdev, cpu_dai, substream); | | ~~~~~~~~~~~~~~~~~~~~ | | | | | (15) dereference of NULL ‘ops’ The function hda_dai_get_ops() can return NULL, but the return value is not checked across the board. It's not a problem today, since we do check in the first use of the function, but static analysis tools are not aware of the different ALSA stages. Rather than argue forever, let's just add the error checks consistently and make sure this tool can be added to the CI checks. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Bard Liao Reviewed-by: Péter Ujfalusi Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230602205620.310879-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-dai.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 09d8ee98581d..3d89c1923b03 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -120,6 +120,11 @@ static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev; int stream_tag; + if (!ops) { + dev_err(cpu_dai->dev, "DAI widget ops not set\n"); + return -EINVAL; + } + sdev = dai_to_sdev(substream, cpu_dai); hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); @@ -158,6 +163,11 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, unsigned int link_bps; int stream_tag; + if (!ops) { + dev_err(cpu_dai->dev, "DAI widget ops not set\n"); + return -EINVAL; + } + sdev = dai_to_sdev(substream, cpu_dai); bus = sof_to_bus(sdev); @@ -216,7 +226,7 @@ static int __maybe_unused hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai); if (!ops) { - dev_err(sdev->dev, "DAI widget ops not set\n"); + dev_err(cpu_dai->dev, "DAI widget ops not set\n"); return -EINVAL; } @@ -274,6 +284,11 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i struct snd_sof_dev *sdev; int ret; + if (!ops) { + dev_err(dai->dev, "DAI widget ops not set\n"); + return -EINVAL; + } + dev_dbg(dai->dev, "cmd=%d dai %s direction %d\n", cmd, dai->name, substream->stream); -- cgit v1.2.3 From 2205c63d8d216b44127560cc564b2098843553e2 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:13 -0500 Subject: ASoC: SOF: Intel: hda-dai: add codec_dai_set_stream callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing code for HDAudio DAIs cannot be extended to other types of DAIs, specific programming sequences need to be abstracted away. Start here with hiding the stream_tag needed by the HDAudio codec_dai. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230602205620.310879-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-dai-ops.c | 17 ++++++++++++++++- sound/soc/sof/intel/hda-dai.c | 3 ++- sound/soc/sof/intel/hda.h | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index 1e58256c8003..2d2953cee1d8 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -175,6 +175,17 @@ static void hda_reset_hext_stream(struct snd_sof_dev *sdev, struct hdac_ext_stre snd_hdac_ext_stream_reset(hext_stream); } +static void hda_codec_dai_set_stream(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct hdac_stream *hstream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + /* set the hdac_stream in the codec dai */ + snd_soc_dai_set_stream(codec_dai, hstream, substream->stream); +} + static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd) { @@ -307,7 +318,8 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { .reset_hext_stream = hda_reset_hext_stream, .pre_trigger = hda_ipc4_pre_trigger, .trigger = hda_trigger, - .post_trigger = hda_ipc4_post_trigger + .post_trigger = hda_ipc4_post_trigger, + .codec_dai_set_stream = hda_codec_dai_set_stream, }; static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { @@ -317,6 +329,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { .setup_hext_stream = hda_setup_hext_stream, .reset_hext_stream = hda_reset_hext_stream, .trigger = hda_trigger, + .codec_dai_set_stream = hda_codec_dai_set_stream, }; static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, @@ -350,6 +363,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = { .reset_hext_stream = hda_reset_hext_stream, .trigger = hda_trigger, .post_trigger = hda_ipc3_post_trigger, + .codec_dai_set_stream = hda_codec_dai_set_stream, }; static struct hdac_ext_stream * @@ -376,6 +390,7 @@ static void hda_dspless_setup_hext_stream(struct snd_sof_dev *sdev, static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = { .get_hext_stream = hda_dspless_get_hext_stream, .setup_hext_stream = hda_dspless_setup_hext_stream, + .codec_dai_set_stream = hda_codec_dai_set_stream, }; #endif diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 3d89c1923b03..0c018644347e 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -192,7 +192,8 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag); /* set the hdac_stream in the codec dai */ - snd_soc_dai_set_stream(codec_dai, hdac_stream(hext_stream), substream->stream); + if (ops->codec_dai_set_stream) + ops->codec_dai_set_stream(sdev, substream, hstream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) link_bps = codec_dai->driver->playback.sig_bits; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 5b3dad2dadf4..02d935daab28 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -919,6 +919,7 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, * @pre_trigger: Function pointer for DAI DMA pre-trigger actions * @trigger: Function pointer for DAI DMA trigger actions * @post_trigger: Function pointer for DAI DMA post-trigger actions + * @codec_dai_set_stream: Function pointer to set codec-side stream information */ struct hda_dai_widget_dma_ops { struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev, @@ -938,6 +939,9 @@ struct hda_dai_widget_dma_ops { struct snd_pcm_substream *substream, int cmd); int (*post_trigger)(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd); + void (*codec_dai_set_stream)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct hdac_stream *hstream); }; const struct hda_dai_widget_dma_ops * -- cgit v1.2.3 From 767cda3fdac0faec84dc3fd654bd9d09b55eef40 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:14 -0500 Subject: ASoC: SOF: Intel: hda-dai: add calc_stream_format callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing code for HDAudio DAIs cannot be extended to other types of DAIs, specific programming sequences need to be abstracted away. This patch hides the stream format setup which is currently completely related to the HDaudio codec setup - not something that will work for other types of DAIs. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230602205620.310879-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-dai-ops.c | 27 +++++++++++++++++++++++++++ sound/soc/sof/intel/hda-dai.c | 16 +++------------- sound/soc/sof/intel/hda.h | 5 +++++ 3 files changed, 35 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index 2d2953cee1d8..88fee1e256b0 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -186,6 +186,29 @@ static void hda_codec_dai_set_stream(struct snd_sof_dev *sdev, snd_soc_dai_set_stream(codec_dai, hstream, substream->stream); } +static unsigned int hda_calc_stream_format(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + unsigned int link_bps; + unsigned int format_val; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + link_bps = codec_dai->driver->playback.sig_bits; + else + link_bps = codec_dai->driver->capture.sig_bits; + + format_val = snd_hdac_calc_stream_format(params_rate(params), params_channels(params), + params_format(params), link_bps, 0); + + dev_dbg(sdev->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val, + params_rate(params), params_channels(params), params_format(params)); + + return format_val; +} + static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd) { @@ -320,6 +343,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { .trigger = hda_trigger, .post_trigger = hda_ipc4_post_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, + .calc_stream_format = hda_calc_stream_format, }; static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { @@ -330,6 +354,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { .reset_hext_stream = hda_reset_hext_stream, .trigger = hda_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, + .calc_stream_format = hda_calc_stream_format, }; static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, @@ -364,6 +389,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = { .trigger = hda_trigger, .post_trigger = hda_ipc3_post_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, + .calc_stream_format = hda_calc_stream_format, }; static struct hdac_ext_stream * @@ -391,6 +417,7 @@ static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = { .get_hext_stream = hda_dspless_get_hext_stream, .setup_hext_stream = hda_dspless_setup_hext_stream, .codec_dai_set_stream = hda_codec_dai_set_stream, + .calc_stream_format = hda_calc_stream_format, }; #endif diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index 0c018644347e..d9a77a253350 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -159,8 +159,6 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, struct hdac_ext_link *hlink; struct snd_sof_dev *sdev; struct hdac_bus *bus; - unsigned int format_val; - unsigned int link_bps; int stream_tag; if (!ops) { @@ -195,22 +193,14 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, if (ops->codec_dai_set_stream) ops->codec_dai_set_stream(sdev, substream, hstream); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - link_bps = codec_dai->driver->playback.sig_bits; - else - link_bps = codec_dai->driver->capture.sig_bits; - if (ops->reset_hext_stream) ops->reset_hext_stream(sdev, hext_stream); - format_val = snd_hdac_calc_stream_format(params_rate(params), params_channels(params), - params_format(params), link_bps, 0); - - dev_dbg(bus->dev, "format_val=%#x, rate=%d, ch=%d, format=%d\n", format_val, - params_rate(params), params_channels(params), params_format(params)); + if (ops->calc_stream_format && ops->setup_hext_stream) { + unsigned int format_val = ops->calc_stream_format(sdev, substream, params); - if (ops->setup_hext_stream) ops->setup_hext_stream(sdev, hext_stream, format_val); + } hext_stream->link_prepared = 1; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 02d935daab28..7a3d202f970e 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -920,6 +920,8 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, * @trigger: Function pointer for DAI DMA trigger actions * @post_trigger: Function pointer for DAI DMA post-trigger actions * @codec_dai_set_stream: Function pointer to set codec-side stream information + * @calc_stream_format: Function pointer to determine stream format from hw_params and + * for HDaudio codec DAI from the .sig bits */ struct hda_dai_widget_dma_ops { struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev, @@ -942,6 +944,9 @@ struct hda_dai_widget_dma_ops { void (*codec_dai_set_stream)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct hdac_stream *hstream); + unsigned int (*calc_stream_format)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); }; const struct hda_dai_widget_dma_ops * -- cgit v1.2.3 From d1bf58474d17a77a26bc27ff85a4e5c4fefc0934 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:15 -0500 Subject: ASoC: SOF: Intel: hda-dai: add get_hlink callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing code for HDAudio DAIs cannot be extended to other types of DAIs, specific programming sequences need to be abstracted away. This patch hides the mechanism to determine the multi-link structure related to the DAI and program the LOSIDV register. An added benefit is that we can remove all references to the codec DAI from what should be a CPU dai configuration only. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230602205620.310879-5-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-dai-ops.c | 14 ++++++++++++++ sound/soc/sof/intel/hda-dai.c | 28 ++++++---------------------- sound/soc/sof/intel/hda.h | 4 ++++ 3 files changed, 24 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-dai-ops.c b/sound/soc/sof/intel/hda-dai-ops.c index 88fee1e256b0..f3513796c189 100644 --- a/sound/soc/sof/intel/hda-dai-ops.c +++ b/sound/soc/sof/intel/hda-dai-ops.c @@ -209,6 +209,16 @@ static unsigned int hda_calc_stream_format(struct snd_sof_dev *sdev, return format_val; } +static struct hdac_ext_link *hda_get_hlink(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct hdac_bus *bus = sof_to_bus(sdev); + + return snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); +} + static int hda_ipc4_pre_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, struct snd_pcm_substream *substream, int cmd) { @@ -344,6 +354,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_dma_ops = { .post_trigger = hda_ipc4_post_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, .calc_stream_format = hda_calc_stream_format, + .get_hlink = hda_get_hlink, }; static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { @@ -355,6 +366,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc4_chain_dma_ops = { .trigger = hda_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, .calc_stream_format = hda_calc_stream_format, + .get_hlink = hda_get_hlink, }; static int hda_ipc3_post_trigger(struct snd_sof_dev *sdev, struct snd_soc_dai *cpu_dai, @@ -390,6 +402,7 @@ static const struct hda_dai_widget_dma_ops hda_ipc3_dma_ops = { .post_trigger = hda_ipc3_post_trigger, .codec_dai_set_stream = hda_codec_dai_set_stream, .calc_stream_format = hda_calc_stream_format, + .get_hlink = hda_get_hlink, }; static struct hdac_ext_stream * @@ -418,6 +431,7 @@ static const struct hda_dai_widget_dma_ops hda_dspless_dma_ops = { .setup_hext_stream = hda_dspless_setup_hext_stream, .codec_dai_set_stream = hda_codec_dai_set_stream, .calc_stream_format = hda_calc_stream_format, + .get_hlink = hda_get_hlink, }; #endif diff --git a/sound/soc/sof/intel/hda-dai.c b/sound/soc/sof/intel/hda-dai.c index d9a77a253350..3297dea493aa 100644 --- a/sound/soc/sof/intel/hda-dai.c +++ b/sound/soc/sof/intel/hda-dai.c @@ -109,12 +109,9 @@ hda_dai_get_ops(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, struct hdac_ext_stream *hext_stream, - struct snd_soc_dai *cpu_dai, - struct snd_soc_dai *codec_dai) + struct snd_soc_dai *cpu_dai) { const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); - struct hdac_stream *hstream = &hext_stream->hstream; - struct hdac_bus *bus = hstream->bus; struct sof_intel_hda_stream *hda_stream; struct hdac_ext_link *hlink; struct snd_sof_dev *sdev; @@ -127,7 +124,7 @@ static int hda_link_dma_cleanup(struct snd_pcm_substream *substream, sdev = dai_to_sdev(substream, cpu_dai); - hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); + hlink = ops->get_hlink(sdev, substream); if (!hlink) return -EINVAL; @@ -152,13 +149,10 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *cpu_dai) { const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct hdac_ext_stream *hext_stream; struct hdac_stream *hstream; struct hdac_ext_link *hlink; struct snd_sof_dev *sdev; - struct hdac_bus *bus; int stream_tag; if (!ops) { @@ -167,9 +161,8 @@ static int hda_link_dma_hw_params(struct snd_pcm_substream *substream, } sdev = dai_to_sdev(substream, cpu_dai); - bus = sof_to_bus(sdev); - hlink = snd_hdac_ext_bus_get_hlink_by_name(bus, codec_dai->component->name); + hlink = ops->get_hlink(sdev, substream); if (!hlink) return -EINVAL; @@ -211,8 +204,6 @@ static int __maybe_unused hda_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai) { const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, cpu_dai); - struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); - struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); struct hdac_ext_stream *hext_stream; struct snd_sof_dev *sdev = dai_to_sdev(substream, cpu_dai); @@ -225,7 +216,7 @@ static int __maybe_unused hda_dai_hw_free(struct snd_pcm_substream *substream, if (!hext_stream) return 0; - return hda_link_dma_cleanup(substream, hext_stream, cpu_dai, codec_dai); + return hda_link_dma_cleanup(substream, hext_stream, cpu_dai); } static int __maybe_unused hda_dai_hw_params(struct snd_pcm_substream *substream, @@ -270,8 +261,6 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i { const struct hda_dai_widget_dma_ops *ops = hda_dai_get_ops(substream, dai); struct hdac_ext_stream *hext_stream; - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_dai *codec_dai; struct snd_sof_dev *sdev; int ret; @@ -289,9 +278,6 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i if (!hext_stream) return -EINVAL; - rtd = asoc_substream_to_rtd(substream); - codec_dai = asoc_rtd_to_codec(rtd, 0); - if (ops->pre_trigger) { ret = ops->pre_trigger(sdev, dai, substream, cmd); if (ret < 0) @@ -312,7 +298,7 @@ static int __maybe_unused hda_dai_trigger(struct snd_pcm_substream *substream, i switch (cmd) { case SNDRV_PCM_TRIGGER_SUSPEND: - ret = hda_link_dma_cleanup(substream, hext_stream, dai, codec_dai); + ret = hda_link_dma_cleanup(substream, hext_stream, dai); if (ret < 0) { dev_err(sdev->dev, "%s: failed to clean up link DMA\n", __func__); return ret; @@ -366,14 +352,12 @@ static int hda_dai_suspend(struct hdac_bus *bus) const struct hda_dai_widget_dma_ops *ops; struct snd_sof_widget *swidget; struct snd_soc_dapm_widget *w; - struct snd_soc_dai *codec_dai; struct snd_soc_dai *cpu_dai; struct snd_sof_dev *sdev; struct snd_sof_dai *sdai; rtd = asoc_substream_to_rtd(hext_stream->link_substream); cpu_dai = asoc_rtd_to_cpu(rtd, 0); - codec_dai = asoc_rtd_to_codec(rtd, 0); w = snd_soc_dai_get_widget(cpu_dai, hdac_stream(hext_stream)->direction); swidget = w->dobj.private; sdev = widget_to_sdev(w); @@ -382,7 +366,7 @@ static int hda_dai_suspend(struct hdac_bus *bus) ret = hda_link_dma_cleanup(hext_stream->link_substream, hext_stream, - cpu_dai, codec_dai); + cpu_dai); if (ret < 0) return ret; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 7a3d202f970e..8ca43303d97f 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -922,6 +922,8 @@ int hda_dsp_ipc4_load_library(struct snd_sof_dev *sdev, * @codec_dai_set_stream: Function pointer to set codec-side stream information * @calc_stream_format: Function pointer to determine stream format from hw_params and * for HDaudio codec DAI from the .sig bits + * @get_hlink: Mandatory function pointer to retrieve hlink, mainly to program LOSIDV + * for legacy HDaudio links or program HDaudio Extended Link registers. */ struct hda_dai_widget_dma_ops { struct hdac_ext_stream *(*get_hext_stream)(struct snd_sof_dev *sdev, @@ -947,6 +949,8 @@ struct hda_dai_widget_dma_ops { unsigned int (*calc_stream_format)(struct snd_sof_dev *sdev, struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params); + struct hdac_ext_link * (*get_hlink)(struct snd_sof_dev *sdev, + struct snd_pcm_substream *substream); }; const struct hda_dai_widget_dma_ops * -- cgit v1.2.3 From e186e1f237c1e2447a83059d48439ffcefbf5a93 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:16 -0500 Subject: ASoC: SOF: ipc4-topology: extend ALH-specific data structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LunarLake introduces a new TLV blob passed to the firmware for DMA configuration. This TLV structure is directly inspired by the ALH multi-gateway structure used so far. This patch suggest a transition to the more abstract structure with no references to ALH. This is an iso-functionality redefinition of structure, the TLV will be added in a follow-up patch. 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/20230602205620.310879-6-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 10 +++++----- sound/soc/sof/ipc4-topology.h | 34 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index db64e0cb8663..31a97a4248f4 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -559,7 +559,7 @@ static int sof_ipc4_widget_setup_comp_dai(struct snd_sof_widget *swidget) strcmp(w->widget->sname, swidget->widget->sname)) continue; - blob->alh_cfg.count++; + blob->alh_cfg.device_count++; } ipc4_copier->copier_config = (uint32_t *)blob; @@ -1225,7 +1225,7 @@ static void sof_ipc4_unprepare_copier_module(struct snd_sof_widget *swidget) unsigned int group_id; blob = (struct sof_ipc4_alh_configuration_blob *)ipc4_copier->copier_config; - if (blob->alh_cfg.count > 1) { + if (blob->alh_cfg.device_count > 1) { group_id = SOF_IPC4_NODE_INDEX(ipc4_copier->data.gtw_cfg.node_id) - ALH_MULTI_GTW_BASE; ida_free(&alh_group_ida, group_id); @@ -1609,7 +1609,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ch_map >>= 4; } - step = ch_count / blob->alh_cfg.count; + step = ch_count / blob->alh_cfg.device_count; mask = GENMASK(step - 1, 0); /* * Set each gtw_cfg.node_id to blob->alh_cfg.mapping[] @@ -1624,7 +1624,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, dai = w->private; alh_copier = (struct sof_ipc4_copier *)dai->private; alh_data = &alh_copier->data; - blob->alh_cfg.mapping[i].alh_id = alh_data->gtw_cfg.node_id; + blob->alh_cfg.mapping[i].device = alh_data->gtw_cfg.node_id; /* * Set the same channel mask for playback as the audio data is * duplicated for all speakers. For capture, split the channels @@ -1643,7 +1643,7 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, i++; } - if (blob->alh_cfg.count > 1) { + if (blob->alh_cfg.device_count > 1) { int group_id; group_id = ida_alloc_max(&alh_group_ida, ALH_MULTI_GTW_COUNT - 1, diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index cf007282867b..6b59434fbd60 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -55,7 +55,7 @@ #define SOF_IPC4_GAIN_ALL_CHANNELS_MASK 0xffffffff #define SOF_IPC4_VOL_ZERO_DB 0x7fffffff -#define ALH_MAX_NUMBER_OF_GTW 16 +#define SOF_IPC4_DMA_DEVICE_MAX_COUNT 16 #define SOF_IPC4_INVALID_NODE_ID 0xffffffff @@ -220,18 +220,26 @@ struct sof_ipc4_gtw_attributes { uint32_t rsvd : 30; }; -/** struct sof_ipc4_alh_multi_gtw_cfg: ALH gateway cfg data - * @count: Number of streams (valid items in mapping array) - * @alh_id: ALH stream id of a single ALH stream aggregated - * @channel_mask: Channel mask - * @mapping: ALH streams +/** + * struct sof_ipc4_dma_device_stream_ch_map: abstract representation of + * channel mapping to DMAs + * @device: representation of hardware device address or FIFO + * @channel_mask: channels handled by @device. Channels are expected to be + * contiguous + */ +struct sof_ipc4_dma_device_stream_ch_map { + uint32_t device; + uint32_t channel_mask; +}; + +/** + * struct sof_ipc4_dma_stream_ch_map: DMA configuration data + * @device_count: Number valid items in mapping array + * @mapping: device address and channel mask */ -struct sof_ipc4_alh_multi_gtw_cfg { - uint32_t count; - struct { - uint32_t alh_id; - uint32_t channel_mask; - } mapping[ALH_MAX_NUMBER_OF_GTW]; +struct sof_ipc4_dma_stream_ch_map { + uint32_t device_count; + struct sof_ipc4_dma_device_stream_ch_map mapping[SOF_IPC4_DMA_DEVICE_MAX_COUNT]; } __packed; /** struct sof_ipc4_alh_configuration_blob: ALH blob @@ -240,7 +248,7 @@ struct sof_ipc4_alh_multi_gtw_cfg { */ struct sof_ipc4_alh_configuration_blob { struct sof_ipc4_gtw_attributes gw_attr; - struct sof_ipc4_alh_multi_gtw_cfg alh_cfg; + struct sof_ipc4_dma_stream_ch_map alh_cfg; }; /** -- cgit v1.2.3 From 116bc1503652c72541d63f67c74b2ff1e4532f03 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:17 -0500 Subject: ASoC: SOF: ipc4-topology: introduce DMA config TLV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting with LunarLake, the DMIC/SSP/SoundWire audio interfaces will use the HDaudio DMA. This patch adds the DMA configuration structure to be passed as a TLV appended at the end of each gateway configuration. This patch only provides the definitions for now, the TLV will be added in the actual blobs separately for each interface. 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/20230602205620.310879-7-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.h | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index 6b59434fbd60..f1d26b5c21d7 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -242,6 +242,44 @@ struct sof_ipc4_dma_stream_ch_map { struct sof_ipc4_dma_device_stream_ch_map mapping[SOF_IPC4_DMA_DEVICE_MAX_COUNT]; } __packed; +#define SOF_IPC4_DMA_METHOD_HDA 1 +#define SOF_IPC4_DMA_METHOD_GPDMA 2 /* defined for consistency but not used */ + +/** + * struct sof_ipc4_dma_config: DMA configuration + * @dma_method: HDAudio or GPDMA + * @pre_allocated_by_host: 1 if host driver allocates DMA channels, 0 otherwise + * @dma_channel_id: for HDaudio defined as @stream_id - 1 + * @stream_id: HDaudio stream tag + * @dma_stream_channel_map: array of device/channel mappings + * @dma_priv_config_size: currently not used + * @dma_priv_config: currently not used + */ +struct sof_ipc4_dma_config { + uint8_t dma_method; + uint8_t pre_allocated_by_host; + uint16_t rsvd; + uint32_t dma_channel_id; + uint32_t stream_id; + struct sof_ipc4_dma_stream_ch_map dma_stream_channel_map; + uint32_t dma_priv_config_size; + uint8_t dma_priv_config[]; +} __packed; + +#define SOF_IPC4_GTW_DMA_CONFIG_ID 0x1000 + +/** + * struct sof_ipc4_dma_config: DMA configuration + * @type: set to SOF_IPC4_GTW_DMA_CONFIG_ID + * @length: sizeof(struct sof_ipc4_dma_config) + dma_config.dma_priv_config_size + * @dma_config: actual DMA configuration + */ +struct sof_ipc4_dma_config_tlv { + uint32_t type; + uint32_t length; + struct sof_ipc4_dma_config dma_config; +} __packed; + /** struct sof_ipc4_alh_configuration_blob: ALH blob * @gw_attr: Gateway attributes * @alh_cfg: ALH configuration data -- cgit v1.2.3 From a0659f81c348946383526b764ad66d9900ea2cb9 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:18 -0500 Subject: ASoC: SOF: ipc4-topology: add DMA config TLV to IPC data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a DMA config TLV structure and the relevant code to copy this TLV after the gateway configuration. For now this is an iso-functionality change, the TLVs are not configured just yet. Additional patches will be needed for DMIC/SSP/ALH (aka SoundWire). 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/20230602205620.310879-8-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4-topology.c | 34 +++++++++++++++++++++++++++++++--- sound/soc/sof/ipc4-topology.h | 2 ++ 2 files changed, 33 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4-topology.c b/sound/soc/sof/ipc4-topology.c index 31a97a4248f4..a4e1a70b607d 100644 --- a/sound/soc/sof/ipc4-topology.c +++ b/sound/soc/sof/ipc4-topology.c @@ -1383,6 +1383,8 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, struct snd_sof_dai *dai; struct snd_mask *fmt; int out_sample_valid_bits; + u32 gtw_cfg_config_length; + u32 dma_config_tlv_size = 0; void **ipc_config_data; int *ipc_config_size; u32 **data; @@ -1699,7 +1701,27 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, ipc_config_data = &ipc4_copier->ipc_config_data; /* config_length is DWORD based */ - ipc_size = sizeof(*copier_data) + copier_data->gtw_cfg.config_length * 4; + gtw_cfg_config_length = copier_data->gtw_cfg.config_length * 4; + ipc_size = sizeof(*copier_data) + gtw_cfg_config_length; + + if (ipc4_copier->dma_config_tlv.type == SOF_IPC4_GTW_DMA_CONFIG_ID && + ipc4_copier->dma_config_tlv.length) { + dma_config_tlv_size = sizeof(ipc4_copier->dma_config_tlv) + + ipc4_copier->dma_config_tlv.dma_config.dma_priv_config_size; + + /* paranoia check on TLV size/length */ + if (dma_config_tlv_size != ipc4_copier->dma_config_tlv.length + + sizeof(uint32_t) * 2) { + dev_err(sdev->dev, "Invalid configuration, TLV size %d length %d\n", + dma_config_tlv_size, ipc4_copier->dma_config_tlv.length); + return -EINVAL; + } + + ipc_size += dma_config_tlv_size; + + /* we also need to increase the size at the gtw level */ + copier_data->gtw_cfg.config_length += dma_config_tlv_size / 4; + } dev_dbg(sdev->dev, "copier %s, IPC size is %d", swidget->widget->name, ipc_size); @@ -1711,9 +1733,15 @@ sof_ipc4_prepare_copier_module(struct snd_sof_widget *swidget, /* copy IPC data */ memcpy(*ipc_config_data, (void *)copier_data, sizeof(*copier_data)); - if (copier_data->gtw_cfg.config_length) + if (gtw_cfg_config_length) memcpy(*ipc_config_data + sizeof(*copier_data), - *data, copier_data->gtw_cfg.config_length * 4); + *data, gtw_cfg_config_length); + + /* add DMA Config TLV, if configured */ + if (dma_config_tlv_size) + memcpy(*ipc_config_data + sizeof(*copier_data) + + gtw_cfg_config_length, + &ipc4_copier->dma_config_tlv, dma_config_tlv_size); /* update pipeline memory usage */ sof_ipc4_update_resource_usage(sdev, swidget, &copier_data->base_config); diff --git a/sound/soc/sof/ipc4-topology.h b/sound/soc/sof/ipc4-topology.h index f1d26b5c21d7..6dcf14886e85 100644 --- a/sound/soc/sof/ipc4-topology.h +++ b/sound/soc/sof/ipc4-topology.h @@ -300,6 +300,7 @@ struct sof_ipc4_alh_configuration_blob { * @gtw_attr: Gateway attributes for copier blob * @dai_type: DAI type * @dai_index: DAI index + * @dma_config_tlv: DMA configuration */ struct sof_ipc4_copier { struct sof_ipc4_copier_data data; @@ -312,6 +313,7 @@ struct sof_ipc4_copier { struct sof_ipc4_gtw_attributes *gtw_attr; u32 dai_type; int dai_index; + struct sof_ipc4_dma_config_tlv dma_config_tlv; }; /** -- cgit v1.2.3 From 730025cffedc6b8887d72031795796ac6d9947c3 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:19 -0500 Subject: ASoC: SOF: Intel: mtl: prepare for code reuse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some functions can be used for newer LNL hardware. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230602205620.310879-9-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/mtl.c | 12 ++++++------ sound/soc/sof/intel/mtl.h | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 93dc2c9d8448..8ae331faca4e 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -55,7 +55,7 @@ static void mtl_ipc_dsp_done(struct snd_sof_dev *sdev) } /* Check if an IPC IRQ occurred */ -static bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev) +bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev) { u32 irq_status; u32 hfintipptr; @@ -118,7 +118,7 @@ static int mtl_ipc_send_msg(struct snd_sof_dev *sdev, struct snd_sof_ipc_msg *ms return 0; } -static void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev) +void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; @@ -132,7 +132,7 @@ static void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev) MTL_DSP_REG_HFIPCXCTL_BUSY | MTL_DSP_REG_HFIPCXCTL_DONE); } -static void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev) +void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; @@ -173,7 +173,7 @@ static void mtl_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable) enable ? "enable" : "disable"); } -static int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) +int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable) { u32 hfintipptr; u32 irqinten; @@ -394,7 +394,7 @@ static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core) return ret; } -static int mtl_power_down_dsp(struct snd_sof_dev *sdev) +int mtl_power_down_dsp(struct snd_sof_dev *sdev) { u32 dsphfdsscs, cpa; int ret; @@ -421,7 +421,7 @@ static int mtl_power_down_dsp(struct snd_sof_dev *sdev) HDA_DSP_RESET_TIMEOUT_US); } -static int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot) +int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot) { struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata; const struct sof_intel_dsp_desc *chip = hda->desc; diff --git a/sound/soc/sof/intel/mtl.h b/sound/soc/sof/intel/mtl.h index 26418fb08807..2794fe6e8139 100644 --- a/sound/soc/sof/intel/mtl.h +++ b/sound/soc/sof/intel/mtl.h @@ -82,3 +82,10 @@ #define MTL_DSP_REG_HfIMRIS1 0x162088 #define MTL_DSP_REG_HfIMRIS1_IU_MASK BIT(0) +void mtl_enable_ipc_interrupts(struct snd_sof_dev *sdev); +void mtl_disable_ipc_interrupts(struct snd_sof_dev *sdev); +bool mtl_dsp_check_ipc_irq(struct snd_sof_dev *sdev); + +int mtl_enable_interrupts(struct snd_sof_dev *sdev, bool enable); +int mtl_dsp_cl_init(struct snd_sof_dev *sdev, int stream_tag, bool imr_boot); +int mtl_power_down_dsp(struct snd_sof_dev *sdev); -- cgit v1.2.3 From d3e7c32b7d5c7132edca6d84499ec8ac2f060aa7 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Fri, 2 Jun 2023 15:56:20 -0500 Subject: ASoC: SOF: Intel: hda: add helper to extract SoundWire link count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The register changed with the HDaudio integration, the information is present in the extended link descriptor and not in the SHIM. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Péter Ujfalusi Reviewed-by: Bard Liao Link: https://lore.kernel.org/r/20230602205620.310879-10-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda.c | 25 +++++++++++++++++++++++++ sound/soc/sof/intel/hda.h | 6 ++++++ 2 files changed, 31 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 835c2568dd60..066f27e3402f 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -222,6 +222,31 @@ int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev) return 0; } +int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev) +{ + struct sof_intel_hda_dev *hdev; + struct sdw_intel_ctx *ctx; + struct hdac_bus *bus; + u32 slcount; + + bus = sof_to_bus(sdev); + + hdev = sdev->pdata->hw_pdata; + ctx = hdev->sdw; + + slcount = hdac_bus_eml_get_count(bus, true, AZX_REG_ML_LEPTR_ID_SDW); + + /* Check HW supported vs property value */ + if (slcount < ctx->count) { + dev_err(sdev->dev, + "%s: BIOS master count %d is larger than hardware capabilities %d\n", + __func__, ctx->count, slcount); + return -EINVAL; + } + + return 0; +} + static int hda_sdw_check_lcount(struct snd_sof_dev *sdev) { const struct sof_intel_dsp_desc *chip; diff --git a/sound/soc/sof/intel/hda.h b/sound/soc/sof/intel/hda.h index 8ca43303d97f..3f7c6fb05e5d 100644 --- a/sound/soc/sof/intel/hda.h +++ b/sound/soc/sof/intel/hda.h @@ -781,6 +781,7 @@ int hda_dsp_trace_trigger(struct snd_sof_dev *sdev, int cmd); #if IS_ENABLED(CONFIG_SND_SOC_SOF_INTEL_SOUNDWIRE) int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev); +int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev); int hda_sdw_startup(struct snd_sof_dev *sdev); void hda_common_enable_sdw_irq(struct snd_sof_dev *sdev, bool enable); void hda_sdw_int_enable(struct snd_sof_dev *sdev, bool enable); @@ -794,6 +795,11 @@ static inline int hda_sdw_check_lcount_common(struct snd_sof_dev *sdev) return 0; } +static inline int hda_sdw_check_lcount_ext(struct snd_sof_dev *sdev) +{ + return 0; +} + static inline int hda_sdw_startup(struct snd_sof_dev *sdev) { return 0; -- cgit v1.2.3 From 8c15a18331191b67bdce54d21af068baec044baf Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 5 Jun 2023 16:47:58 +0200 Subject: ALSA: seq: Avoid confusion of aligned read size Currently the read event packet size in snd_seq_read() is defined by client->midi_version value that is guaranteed to be zero if UMP isn't enabled. But the static analyzer doesn't know of the fact, and it still suspects as if it were leading to a potential overflow. Add the more explicit check of CONFIG_SND_SEQ_UMP to determine the aligned_size value for avoiding the confusion. Fixes: 46397622a3fa ("ALSA: seq: Add UMP support") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202305261415.NY0vapZK-lkp@intel.com/ Link: https://lore.kernel.org/r/20230605144758.6677-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 948ae45e0cc3..e3f9ea67d019 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -451,7 +451,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, err = 0; snd_seq_fifo_lock(fifo); - if (client->midi_version > 0) + if (IS_ENABLED(CONFIG_SND_SEQ_UMP) && client->midi_version > 0) aligned_size = sizeof(struct snd_seq_ump_event); else aligned_size = sizeof(struct snd_seq_event); -- cgit v1.2.3 From 448425f05b16f124290ad82b836c04da63b63035 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:34 +0100 Subject: ALSA: hda: cs35l41: Clean up Firmware Load Controls Ensure Firmware Load control and Firmware Type control returns 1 when the value changes. Remove fw_mutex from firmware load control put, since it is unnecessary, and prevents any possibility of mutex inversion. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-2-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l41_hda.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index b5210abb5141..d100189e15b8 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -835,34 +835,26 @@ static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); - unsigned int ret = 0; - - mutex_lock(&cs35l41->fw_mutex); if (cs35l41->request_fw_load == ucontrol->value.integer.value[0]) - goto err; + return 0; if (cs35l41->fw_request_ongoing) { dev_dbg(cs35l41->dev, "Existing request not complete\n"); - ret = -EBUSY; - goto err; + return -EBUSY; } /* Check if playback is ongoing when initial request is made */ if (cs35l41->playback_started) { dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); - ret = -EBUSY; - goto err; + return -EBUSY; } cs35l41->fw_request_ongoing = true; cs35l41->request_fw_load = ucontrol->value.integer.value[0]; schedule_work(&cs35l41->fw_load_work); -err: - mutex_unlock(&cs35l41->fw_mutex); - - return ret; + return 1; } static int cs35l41_fw_type_ctl_get(struct snd_kcontrol *kcontrol, @@ -881,8 +873,12 @@ static int cs35l41_fw_type_ctl_put(struct snd_kcontrol *kcontrol, struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); if (ucontrol->value.enumerated.item[0] < HDA_CS_DSP_NUM_FW) { - cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; - return 0; + if (cs35l41->firmware_type != ucontrol->value.enumerated.item[0]) { + cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; + return 1; + } else { + return 0; + } } return -EINVAL; -- cgit v1.2.3 From 31dbb503f07a059419f16773e47df4a31093ba31 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:35 +0100 Subject: ALSA: hda: cs35l41: Fix endian conversions Found during static analysis, ensure variables are correct types before endian conversion. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-3-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l41_hda.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index d100189e15b8..ce5faa620517 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -308,8 +308,8 @@ out: } #if IS_ENABLED(CONFIG_EFI) -static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient, - unsigned int r0, unsigned int status, unsigned int checksum) +static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, __be32 ambient, __be32 r0, + __be32 status, __be32 checksum) { int ret; @@ -745,7 +745,7 @@ err: static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) { - int halo_sts; + __be32 halo_sts; int ret; ret = cs35l41_init_dsp(cs35l41); @@ -773,7 +773,7 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) &halo_sts, sizeof(halo_sts)); if (ret) { - dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %u\n", halo_sts); goto clean_dsp; } -- cgit v1.2.3 From ebcbfd846367c980e105c787d372c4239e9ed922 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:36 +0100 Subject: ALSA: hda/realtek: Delete cs35l41 component master during free This ensures that the driver is properly cleaned up when freed. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-4-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 172ffc2c332b..ce9a9cb41e4b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6757,6 +6757,9 @@ static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char else spec->gen.pcm_playback_hook = comp_generic_playback_hook; break; + case HDA_FIXUP_ACT_FREE: + component_master_del(dev, &comp_master_ops); + break; } } -- cgit v1.2.3 From 306f3f78a5ff578bdfd97c658a862cb2c2419fb6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 6 Jun 2023 11:40:35 +0200 Subject: ALSA: control: Keep the previous numid at snd_ctl_rename_id() We don't need to change the numid at each time snd_ctl_rename_id() is called, as the control element size itself doesn't change. Let's keep the previous numid value. Along with it, add a note about calling this function only in the card init phase. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230606094035.14808-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/control.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/core/control.c b/sound/core/control.c index 82aa1af1d1d8..8386b53acdcd 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -730,12 +730,20 @@ EXPORT_SYMBOL_GPL(snd_ctl_activate_id); * Finds the control with the old id from the card, and replaces the * id with the new one. * + * The function tries to keep the already assigned numid while replacing + * the rest. + * + * Note that this function should be used only in the card initialization + * phase. Calling after the card instantiation may cause issues with + * user-space expecting persistent numids. + * * Return: Zero if successful, or a negative error code on failure. */ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, struct snd_ctl_elem_id *dst_id) { struct snd_kcontrol *kctl; + int saved_numid; down_write(&card->controls_rwsem); kctl = snd_ctl_find_id(card, src_id); @@ -743,10 +751,10 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, up_write(&card->controls_rwsem); return -ENOENT; } + saved_numid = kctl->id.numid; remove_hash_entries(card, kctl); kctl->id = *dst_id; - kctl->id.numid = card->last_numid + 1; - card->last_numid += kctl->count; + kctl->id.numid = saved_numid; add_hash_entries(card, kctl); up_write(&card->controls_rwsem); return 0; -- cgit v1.2.3 From 1c943f60e830d0b959c765df09d4c4b254de0481 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 1 Jun 2023 00:42:49 +0000 Subject: ASoC: add snd_soc_get_stream_cpu() We are using get_stream_cpu() to get CPU stream which cares Codec2Codec. But it is static function for now, and we want to use it from other files. This patch makes it global function. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87fs7cj9mf.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 1 + sound/soc/soc-core.c | 34 ++++++++++++++++++++++++++++++++++ sound/soc/soc-dapm.c | 35 +---------------------------------- sound/soc/soc-pcm.c | 6 ++---- 4 files changed, 38 insertions(+), 38 deletions(-) (limited to 'sound') diff --git a/include/sound/soc.h b/include/sound/soc.h index 888b23237840..10e4ea0664af 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1291,6 +1291,7 @@ unsigned int snd_soc_daifmt_parse_clock_provider_raw(struct device_node *np, snd_soc_daifmt_clock_provider_from_bitmap( \ snd_soc_daifmt_parse_clock_provider_as_bitmap(np, prefix)) +int snd_soc_get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream); int snd_soc_get_dai_id(struct device_node *ep); int snd_soc_get_dai_name(const struct of_phandle_args *args, const char **dai_name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index b48efc3a08d2..e8308926bd98 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3196,6 +3196,40 @@ unsigned int snd_soc_daifmt_parse_clock_provider_raw(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_daifmt_parse_clock_provider_raw); +int snd_soc_get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream) +{ + /* + * [Normal] + * + * Playback + * CPU : SNDRV_PCM_STREAM_PLAYBACK + * Codec: SNDRV_PCM_STREAM_PLAYBACK + * + * Capture + * CPU : SNDRV_PCM_STREAM_CAPTURE + * Codec: SNDRV_PCM_STREAM_CAPTURE + */ + if (!dai_link->c2c_params) + return stream; + + /* + * [Codec2Codec] + * + * Playback + * CPU : SNDRV_PCM_STREAM_CAPTURE + * Codec: SNDRV_PCM_STREAM_PLAYBACK + * + * Capture + * CPU : SNDRV_PCM_STREAM_PLAYBACK + * Codec: SNDRV_PCM_STREAM_CAPTURE + */ + if (stream == SNDRV_PCM_STREAM_CAPTURE) + return SNDRV_PCM_STREAM_PLAYBACK; + + return SNDRV_PCM_STREAM_CAPTURE; +} +EXPORT_SYMBOL_GPL(snd_soc_get_stream_cpu); + int snd_soc_get_dai_id(struct device_node *ep) { struct snd_soc_component *component; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index c65cc374bb3f..b7b31d4e8ae8 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -4338,39 +4338,6 @@ static void dapm_connect_dai_routes(struct snd_soc_dapm_context *dapm, snd_soc_dapm_add_path(dapm, src, sink, NULL, NULL); } -static int get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream) -{ - /* - * [Normal] - * - * Playback - * CPU : SNDRV_PCM_STREAM_PLAYBACK - * Codec: SNDRV_PCM_STREAM_PLAYBACK - * - * Playback - * CPU : SNDRV_PCM_STREAM_CAPTURE - * Codec: SNDRV_PCM_STREAM_CAPTURE - */ - if (!dai_link->c2c_params) - return stream; - - /* - * [Codec2Codec] - * - * Playback - * CPU : SNDRV_PCM_STREAM_CAPTURE - * Codec: SNDRV_PCM_STREAM_PLAYBACK - * - * Capture - * CPU : SNDRV_PCM_STREAM_PLAYBACK - * Codec: SNDRV_PCM_STREAM_CAPTURE - */ - if (stream == SNDRV_PCM_STREAM_CAPTURE) - return SNDRV_PCM_STREAM_PLAYBACK; - - return SNDRV_PCM_STREAM_CAPTURE; -} - static void dapm_connect_dai_pair(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *codec_dai, @@ -4388,7 +4355,7 @@ static void dapm_connect_dai_pair(struct snd_soc_card *card, for_each_pcm_streams(stream) { int stream_cpu, stream_codec; - stream_cpu = get_stream_cpu(dai_link, stream); + stream_cpu = snd_soc_get_stream_cpu(dai_link, stream); stream_codec = stream; /* connect BE DAI playback if widgets are valid */ diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index fc0817dd0d83..799865a6eb56 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -2781,10 +2781,8 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *codec_dai; /* Adapt stream for codec2codec links */ - int cpu_capture = dai_link->c2c_params ? - SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; - int cpu_playback = dai_link->c2c_params ? - SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; + int cpu_capture = snd_soc_get_stream_cpu(dai_link, SNDRV_PCM_STREAM_CAPTURE); + int cpu_playback = snd_soc_get_stream_cpu(dai_link, SNDRV_PCM_STREAM_PLAYBACK); for_each_rtd_codec_dais(rtd, i, codec_dai) { if (dai_link->num_cpus == 1) { -- cgit v1.2.3 From 28bd137a3c8e105587ba8c55b68ef43b519b270f Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:49 +0800 Subject: ALSA: hda: Add Loongson LS7A HD-Audio support Add the new PCI ID 0x0014 0x7a07 and the new PCI ID 0x0014 0x7a37 Loongson HDA controller. Signed-off-by: Yanteng Si Acked-by: Huacai Chen Link: https://lore.kernel.org/r/993587483b9509796b29a416f257fcfb4b15c6ea.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/linux/pci_ids.h | 3 +++ sound/hda/hdac_device.c | 1 + sound/pci/hda/hda_intel.c | 7 +++++++ sound/pci/hda/patch_hdmi.c | 1 + 4 files changed, 12 insertions(+) (limited to 'sound') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 95f33dadb2be..c0c4ca8e2851 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -158,6 +158,9 @@ #define PCI_VENDOR_ID_LOONGSON 0x0014 +#define PCI_DEVICE_ID_LOONGSON_HDA 0x7a07 +#define PCI_DEVICE_ID_LOONGSON_HDMI 0x7a37 + #define PCI_VENDOR_ID_TTTECH 0x0357 #define PCI_DEVICE_ID_TTTECH_MC322 0x000a diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index accc9d279ce5..89bed32b5379 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -645,6 +645,7 @@ struct hda_vendor_id { }; static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x0014, "Loongson" }, { 0x1002, "ATI" }, { 0x1013, "Cirrus Logic" }, { 0x1057, "Motorola" }, diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 3226691ac923..9c353dc7740c 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -237,6 +237,7 @@ enum { AZX_DRIVER_CTHDA, AZX_DRIVER_CMEDIA, AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_LOONGSON, AZX_DRIVER_GENERIC, AZX_NUM_DRIVERS, /* keep this as last entry */ }; @@ -360,6 +361,7 @@ static const char * const driver_short_names[] = { [AZX_DRIVER_CTHDA] = "HDA Creative", [AZX_DRIVER_CMEDIA] = "HDA C-Media", [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", [AZX_DRIVER_GENERIC] = "HD-Audio Generic", }; @@ -2809,6 +2811,11 @@ static const struct pci_device_id azx_ids[] = { .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI }, /* Zhaoxin */ { PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN }, + /* Loongson HDAudio*/ + {PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_HDA), + .driver_data = AZX_DRIVER_LOONGSON }, + {PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 64a944016c78..44b55ba38e45 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -4505,6 +4505,7 @@ static int patch_gf_hdmi(struct hda_codec *codec) * patch entries */ static const struct hda_device_id snd_hda_id_hdmi[] = { +HDA_CODEC_ENTRY(0x00147a47, "Loongson HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x1002793c, "RS600 HDMI", patch_atihdmi), HDA_CODEC_ENTRY(0x10027919, "RS600 HDMI", patch_atihdmi), HDA_CODEC_ENTRY(0x1002791a, "RS690/780 HDMI", patch_atihdmi), -- cgit v1.2.3 From cbc3e98acf802c8939e14103a059db60499d69eb Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:50 +0800 Subject: ALSA: hda: Using polling mode for loongson controller by default On loongson controller, RIRBSTS.RINTFL cannot be cleared, azx_interrupt() is called all the time. We disable RIRB interrupt, and use polling mode by default. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/d309a75424d438b958d90d797b4f1ba45468e090.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 1 + sound/hda/hdac_controller.c | 5 ++++- sound/pci/hda/hda_intel.c | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 97f09acae302..a0bb40a4b721 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -347,6 +347,7 @@ struct hdac_bus { bool corbrp_self_clear:1; /* CORBRP clears itself after reset */ bool polling_mode:1; bool needs_damn_long_delay:1; + bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ int poll_count; diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 3c7af6558249..7f3a000fab0c 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -79,7 +79,10 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus) /* set N=1, get RIRB response interrupt for new entry */ snd_hdac_chip_writew(bus, RINTCNT, 1); /* enable rirb dma and response irq */ - snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); + if (bus->not_use_interrupts) + snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN); + else + snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); /* Accept unsolicited responses */ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); spin_unlock_irq(&bus->reg_lock); diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 9c353dc7740c..b7a7a92d03ef 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1875,6 +1875,11 @@ static int azx_first_init(struct azx *chip) if (chip->driver_type == AZX_DRIVER_GFHDMI) bus->polling_mode = 1; + if (chip->driver_type == AZX_DRIVER_LOONGSON) { + bus->polling_mode = 1; + bus->not_use_interrupts = 1; + } + err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio"); if (err < 0) return err; -- cgit v1.2.3 From 942ccdd834f43b498abc3f022b73fb831d78f5f7 Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:51 +0800 Subject: ALSA: hda: Workaround for SDnCTL register on loongson On loongson controller, after calling snd_hdac_stream_updateb() to enable DMA engine, the SDnCTL.STRM will become to zero. We need to access SDnCTL in dword to keep SDnCTL.STRM is not changed. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/27aeddf5ebbe7c69631cec0e489c1b264be94990.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 1 + sound/hda/hdac_stream.c | 6 +++++- sound/pci/hda/hda_intel.c | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index a0bb40a4b721..2ffdf58bd6d4 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -348,6 +348,7 @@ struct hdac_bus { bool polling_mode:1; bool needs_damn_long_delay:1; bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ + bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ int poll_count; diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index 1f56fd33b9af..2633a4bb1d85 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -150,7 +150,11 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) stripe_ctl); } /* set DMA start and interrupt mask */ - snd_hdac_stream_updateb(azx_dev, SD_CTL, + if (bus->access_sdnctl_in_dword) + snd_hdac_stream_updatel(azx_dev, SD_CTL, + 0, SD_CTL_DMA_START | SD_INT_MASK); + else + snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_DMA_START | SD_INT_MASK); azx_dev->running = true; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index b7a7a92d03ef..fc4787c7782a 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1878,6 +1878,7 @@ static int azx_first_init(struct azx *chip) if (chip->driver_type == AZX_DRIVER_LOONGSON) { bus->polling_mode = 1; bus->not_use_interrupts = 1; + bus->access_sdnctl_in_dword = 1; } err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio"); -- cgit v1.2.3 From a4d2b8537845c9a4f4b16dd31793af9c08548341 Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:52 +0800 Subject: ALSA: hda/intel: Workaround for WALLCLK register for loongson controller On loongson controller, the value of WALLCLK register is always 0, which is meaningless, so we return directly. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/185df71ef413ab190460eb377703214ee7288aeb.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index fc4787c7782a..ef831770ca7d 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -655,6 +655,13 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) unsigned int pos; snd_pcm_uframes_t hwptr, target; + /* + * The value of the WALLCLK register is always 0 + * on the Loongson controller, so we return directly. + */ + if (chip->driver_type == AZX_DRIVER_LOONGSON) + return 1; + wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk; if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) return -1; /* bogus (too early) interrupt */ -- cgit v1.2.3 From 315a3d57c64c55901d6fe5a5eef3b3e51d215381 Mon Sep 17 00:00:00 2001 From: Ivan Orlov Date: Tue, 6 Jun 2023 23:32:53 +0400 Subject: ALSA: Implement the new Virtual PCM Test Driver We have a lot of different virtual media drivers, which can be used for testing of the userspace applications and media subsystem middle layer. However, all of them are aimed at testing the video functionality and simulating the video devices. For audio devices we have only snd-dummy module, which is good in simulating the correct behavior of an ALSA device. I decided to write a tool, which would help to test the userspace ALSA programs (and the PCM middle layer as well) under unusual circumstances to figure out how they would behave. So I came up with this Virtual PCM Test Driver. This new Virtual PCM Test Driver has several features which can be useful during the userspace ALSA applications testing/fuzzing, or testing/fuzzing of the PCM middle layer. Not all of them can be implemented using the existing virtual drivers (like dummy or loopback). Here is what can this driver do: - Simulate both capture and playback processes - Generate random or pattern-based capture data - Inject delays into the playback and capturing processes - Inject errors during the PCM callbacks Also, this driver can check the playback stream for containing the predefined pattern, which is used in the corresponding selftest to check the PCM middle layer data transferring functionality. Additionally, this driver redefines the default RESET ioctl, and the selftest covers this PCM API functionality as well. The driver supports both interleaved and non-interleaved access modes, and have separate pattern buffers for each channel. The driver supports up to 4 channels and up to 8 substreams. Signed-off-by: Ivan Orlov Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230606193254.20791-2-ivan.orlov0322@gmail.com Signed-off-by: Takashi Iwai --- MAINTAINERS | 8 + sound/drivers/Kconfig | 16 ++ sound/drivers/Makefile | 2 + sound/drivers/pcmtest.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 753 insertions(+) create mode 100644 sound/drivers/pcmtest.c (limited to 'sound') diff --git a/MAINTAINERS b/MAINTAINERS index e0ad886d3163..eba84c63d849 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22423,6 +22423,14 @@ L: linux-fsdevel@vger.kernel.org S: Maintained F: fs/vboxsf/* +VIRTUAL PCM TEST DRIVER +M: Ivan Orlov +L: alsa-devel@alsa-project.org +S: Maintained +F: Documentation/sound/cards/pcmtest.rst +F: sound/drivers/pcmtest.c +F: tools/testing/selftests/alsa/test-pcmtest-driver.c + VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul S: Maintained diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 864991d8776d..41c171468c1e 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -109,6 +109,22 @@ config SND_ALOOP To compile this driver as a module, choose M here: the module will be called snd-aloop. +config SND_PCMTEST + tristate "Virtual PCM test driver" + select SND_PCM + help + Say 'Y' or 'M' to include support for the Virtual PCM test driver. + This driver is aimed at extended testing of the userspace applications + which use the ALSA API, as well as the PCM middle layer testing. + + It can generate random or pattern-based data into the capture stream, + check the playback stream for containing the selected pattern, inject + time delays during capture/playback, redefine the RESET ioctl operation + to perform the PCM middle layer testing and inject errors during the + PCM callbacks. It supports both interleaved and non-interleaved access + modes. You can find the corresponding selftest in the 'alsa' + selftests folder. + config SND_VIRMIDI tristate "Virtual MIDI soundcard" depends on SND_SEQUENCER diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index b60303180a1b..2c0c7092d396 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -8,6 +8,7 @@ snd-dummy-objs := dummy.o snd-aloop-objs := aloop.o snd-mtpav-objs := mtpav.o snd-mts64-objs := mts64.o +snd-pcmtest-objs := pcmtest.o snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o snd-serial-generic-objs := serial-generic.o @@ -17,6 +18,7 @@ snd-virmidi-objs := virmidi.o obj-$(CONFIG_SND_DUMMY) += snd-dummy.o obj-$(CONFIG_SND_ALOOP) += snd-aloop.o obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o +obj-$(CONFIG_SND_PCMTEST) += snd-pcmtest.o obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c new file mode 100644 index 000000000000..2ae912a64d16 --- /dev/null +++ b/sound/drivers/pcmtest.c @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtual ALSA driver for PCM testing/fuzzing + * + * Copyright 2023 Ivan Orlov + * + * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer + * testing or fuzzing. + * It can: + * - Simulate 'playback' and 'capture' actions + * - Generate random or pattern-based capture data + * - Check playback buffer for containing looped template, and notify about the results + * through the debugfs entry + * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. + * - Inject errors during the PCM callbacks. + * - Register custom RESET ioctl and notify when it is called through the debugfs entry + * - Work in interleaved and non-interleaved modes + * - Support up to 8 substreams + * - Support up to 4 channels + * - Support framerates from 8 kHz to 48 kHz + * + * When driver works in the capture mode with multiple channels, it duplicates the looped + * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved + * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for + * each channel will contain abacabaabacaba... Same for the non-interleaved mode. + * + * However, it may break the capturing on the higher framerates with small period size, so it is + * better to choose larger period sizes. + * + * You can find the corresponding selftest in the 'alsa' selftests folder. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "pcmtestd" +#define CARD_NAME "pcm-test-card" +#define TIMER_PER_SEC 5 +#define TIMER_INTERVAL (HZ / TIMER_PER_SEC) +#define DELAY_JIFFIES HZ +#define PLAYBACK_SUBSTREAM_CNT 8 +#define CAPTURE_SUBSTREAM_CNT 8 +#define MAX_CHANNELS_NUM 4 + +#define DEFAULT_PATTERN "abacaba" +#define DEFAULT_PATTERN_LEN 7 + +#define FILL_MODE_RAND 0 +#define FILL_MODE_PAT 1 + +#define MAX_PATTERN_LEN 4096 + +static int index = -1; +static char *id = "pcmtest"; +static bool enable = true; +static int inject_delay; +static bool inject_hwpars_err; +static bool inject_prepare_err; +static bool inject_trigger_err; + +static short fill_mode = FILL_MODE_PAT; + +static u8 playback_capture_test; +static u8 ioctl_reset_test; +static struct dentry *driver_debug_dir; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard"); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard"); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param(fill_mode, short, 0600); +MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)"); +module_param(inject_delay, int, 0600); +MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)"); +module_param(inject_hwpars_err, bool, 0600); +MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback"); +module_param(inject_prepare_err, bool, 0600); +MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback"); +module_param(inject_trigger_err, bool, 0600); +MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback"); + +struct pcmtst { + struct snd_pcm *pcm; + struct snd_card *card; + struct platform_device *pdev; +}; + +struct pcmtst_buf_iter { + size_t buf_pos; // position in the DMA buffer + size_t period_pos; // period-relative position + size_t b_rw; // Bytes to write on every timer tick + size_t s_rw_ch; // Samples to write to one channel on every tick + unsigned int sample_bytes; // sample_bits / 8 + bool is_buf_corrupted; // playback test result indicator + size_t period_bytes; // bytes in a one period + bool interleaved; // Interleaved/Non-interleaved mode + size_t total_bytes; // Total bytes read/written + size_t chan_block; // Bytes in one channel buffer when non-interleaved + struct snd_pcm_substream *substream; + struct timer_list timer_instance; +}; + +static struct pcmtst *pcmtst; + +static struct snd_pcm_hardware snd_pcmtst_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = MAX_CHANNELS_NUM, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +struct pattern_buf { + char *buf; + u32 len; +}; + +static int buf_allocated; +static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM]; + +static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) +{ + v_iter->total_bytes += by; + v_iter->buf_pos += by; + v_iter->buf_pos %= bytes; +} + +/* + * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos + * every time we write a byte to any channel, so the position in the current channel buffer is + * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel + */ +static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, + unsigned int chan_num) +{ + return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; +} + +/* + * Get the count of bytes written for the current channel in the interleaved mode. + * This is (count of samples written for the current channel) * bytes_in_sample + + * (relative position in the current sample) + */ +static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) +{ + return b_total / channels / b_sample * b_sample + (b_total % b_sample); +} + +static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[v_iter->buf_pos]; + if (!current_byte) + break; + ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels; + if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(v_iter->total_bytes, + runtime->channels, + v_iter->sample_bytes) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + // If we broke during the loop, add remaining bytes to the buffer position. + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)]; + if (!current_byte) + break; + ch_num = i % channels; + if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +/* + * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is + * necessary because we need to detect when the reading/writing ends, so we assume that the pattern + * doesn't contain zeros. + */ +static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + check_buf_block_i(v_iter, runtime); + else + check_buf_block_ni(v_iter, runtime); +} + +/* + * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... + * The channel buffers lay in the DMA buffer continuously (see default copy_user and copy_kernel + * handlers in the pcm_lib.c file). + * + * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. + * We need this to simulate the correct hardware pointer moving. + */ +static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + unsigned int channels = runtime->channels; + short ch_num; + + for (i = 0; i < v_iter->b_rw; i++) { + ch_num = i % channels; + runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)] = + patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } +} + +// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... +static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t sample; + size_t pos_in_ch, pos_pattern; + short ch, pos_sample; + + pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, v_iter->sample_bytes); + + for (sample = 0; sample < v_iter->s_rw_ch; sample++) { + for (ch = 0; ch < runtime->channels; ch++) { + for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) { + pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes + + pos_sample) % patt_bufs[ch].len; + runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + } + } +} + +static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_pattern_i(v_iter, runtime); + else + fill_block_pattern_n(v_iter, runtime); +} + +static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + // Remaining space in all channel buffers + size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + unsigned int i; + + for (i = 0; i < channels; i++) { + if (v_iter->b_rw <= bytes_remain) { + //b_rw - count of bytes must be written for all channels at each timer tick + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + v_iter->b_rw / channels); + } else { + // Write to the end of buffer and start from the beginning of it + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + bytes_remain / channels); + get_random_bytes(runtime->dma_area + v_iter->chan_block * i, + (v_iter->b_rw - bytes_remain) / channels); + } + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; + + if (v_iter->b_rw <= in_cur_block) { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); + } else { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); + get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_rand_i(v_iter, runtime); + else + fill_block_rand_n(v_iter, runtime); +} + +static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + switch (fill_mode) { + case FILL_MODE_RAND: + fill_block_random(v_iter, runtime); + break; + case FILL_MODE_PAT: + fill_block_pattern(v_iter, runtime); + break; + } +} + +/* + * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. + * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer + * about period elapsed. + */ +static void timer_timeout(struct timer_list *data) +{ + struct pcmtst_buf_iter *v_iter; + struct snd_pcm_substream *substream; + + v_iter = from_timer(v_iter, data, timer_instance); + substream = v_iter->substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) + check_buf_block(v_iter, substream->runtime); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + fill_block(v_iter, substream->runtime); + else + inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes); + + v_iter->period_pos += v_iter->b_rw; + if (v_iter->period_pos >= v_iter->period_bytes) { + v_iter->period_pos %= v_iter->period_bytes; + snd_pcm_period_elapsed(substream); + } + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay); +} + +static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter; + + v_iter = kzalloc(sizeof(*v_iter), GFP_KERNEL); + if (!v_iter) + return -ENOMEM; + + runtime->hw = snd_pcmtst_hw; + runtime->private_data = v_iter; + v_iter->substream = substream; + v_iter->buf_pos = 0; + v_iter->is_buf_corrupted = false; + v_iter->period_pos = 0; + v_iter->total_bytes = 0; + + playback_capture_test = 0; + ioctl_reset_test = 0; + + timer_setup(&v_iter->timer_instance, timer_timeout, 0); + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL); + return 0; +} + +static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + timer_shutdown_sync(&v_iter->timer_instance); + v_iter->substream = NULL; + playback_capture_test = !v_iter->is_buf_corrupted; + kfree(v_iter); + return 0; +} + +static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter = runtime->private_data; + + if (inject_trigger_err) + return -EINVAL; + + v_iter->sample_bytes = runtime->sample_bits / 8; + v_iter->period_bytes = frames_to_bytes(runtime, runtime->period_size); + if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { + v_iter->chan_block = runtime->dma_bytes / runtime->channels; + v_iter->interleaved = false; + } else { + v_iter->interleaved = true; + } + // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes + v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC; + v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels; + + return 0; +} + +static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, v_iter->buf_pos); +} + +static int snd_pcmtst_free(struct pcmtst *pcmtst) +{ + if (!pcmtst) + return 0; + kfree(pcmtst); + return 0; +} + +// These callbacks are required, but empty - all freeing occurs in pdev_remove +static int snd_pcmtst_dev_free(struct snd_device *device) +{ + return 0; +} + +static void pcmtst_pdev_release(struct device *dev) +{ +} + +static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) +{ + if (inject_prepare_err) + return -EINVAL; + return 0; +} + +static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + if (inject_hwpars_err) + return -EBUSY; + return 0; +} + +static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + ioctl_reset_test = 1; + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static const struct snd_pcm_ops snd_pcmtst_playback_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .ioctl = snd_pcmtst_ioctl, + .hw_free = snd_pcmtst_pcm_hw_free, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_pcmtst_capture_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .hw_free = snd_pcmtst_pcm_hw_free, + .ioctl = snd_pcmtst_ioctl, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT, + CAPTURE_SUBSTREAM_CNT, &pcm); + if (err < 0) + return err; + pcm->private_data = pcmtst; + strcpy(pcm->name, "PCMTest"); + pcmtst->pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops); + + err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev, + 0, 128 * 1024); + return err; +} + +static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, + struct pcmtst **r_pcmtst) +{ + struct pcmtst *pcmtst; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_pcmtst_dev_free, + }; + + pcmtst = kzalloc(sizeof(*pcmtst), GFP_KERNEL); + if (!pcmtst) + return -ENOMEM; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); + if (err < 0) + goto _err_free_chip; + + err = snd_pcmtst_new_pcm(pcmtst); + if (err < 0) + goto _err_free_chip; + + *r_pcmtst = pcmtst; + return 0; + +_err_free_chip: + snd_pcmtst_free(pcmtst); + return err; +} + +static int pcmtst_probe(struct platform_device *pdev) +{ + struct snd_card *card; + int err; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + err = snd_pcmtst_create(card, pdev, &pcmtst); + if (err < 0) + return err; + + strcpy(card->driver, "PCM-TEST Driver"); + strcpy(card->shortname, "PCM-Test"); + strcpy(card->longname, "PCM-Test virtual driver"); + + err = snd_card_register(card); + if (err < 0) + return err; + + return 0; +} + +static int pdev_remove(struct platform_device *dev) +{ + snd_pcmtst_free(pcmtst); + return 0; +} + +static struct platform_device pcmtst_pdev = { + .name = "pcmtest", + .dev.release = pcmtst_pdev_release, +}; + +static struct platform_driver pcmtst_pdrv = { + .probe = pcmtst_probe, + .remove = pdev_remove, + .driver = { + .name = "pcmtest", + }, +}; + +static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_write = len; + + if (*off + to_write > MAX_PATTERN_LEN) + to_write = MAX_PATTERN_LEN - *off; + + // Crop silently everything over the buffer + if (to_write <= 0) + return len; + + if (copy_from_user(patt_buf->buf + *off, u_buff, to_write)) + return -EFAULT; + + patt_buf->len = *off + to_write; + *off += to_write; + + return to_write; +} + +static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_read = len; + + if (*off + to_read >= MAX_PATTERN_LEN) + to_read = MAX_PATTERN_LEN - *off; + if (to_read <= 0) + return 0; + + if (copy_to_user(u_buff, patt_buf->buf + *off, to_read)) + to_read = 0; + else + *off += to_read; + + return to_read; +} + +static const struct file_operations fill_pattern_fops = { + .read = pattern_read, + .write = pattern_write, +}; + +static int setup_patt_bufs(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) { + patt_bufs[i].buf = kzalloc(MAX_PATTERN_LEN, GFP_KERNEL); + if (!patt_bufs[i].buf) + break; + strcpy(patt_bufs[i].buf, DEFAULT_PATTERN); + patt_bufs[i].len = DEFAULT_PATTERN_LEN; + } + + return i; +} + +static const char * const pattern_files[] = { "fill_pattern0", "fill_pattern1", + "fill_pattern2", "fill_pattern3"}; +static int init_debug_files(int buf_count) +{ + size_t i; + char len_file_name[32]; + + driver_debug_dir = debugfs_create_dir("pcmtest", NULL); + if (IS_ERR(driver_debug_dir)) + return PTR_ERR(driver_debug_dir); + debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test); + debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test); + + for (i = 0; i < buf_count; i++) { + debugfs_create_file(pattern_files[i], 0600, driver_debug_dir, + &patt_bufs[i], &fill_pattern_fops); + snprintf(len_file_name, sizeof(len_file_name), "%s_len", pattern_files[i]); + debugfs_create_u32(len_file_name, 0444, driver_debug_dir, &patt_bufs[i].len); + } + + return 0; +} + +static void free_pattern_buffers(void) +{ + int i; + + for (i = 0; i < buf_allocated; i++) + kfree(patt_bufs[i].buf); +} + +static void clear_debug_files(void) +{ + debugfs_remove_recursive(driver_debug_dir); +} + +static int __init mod_init(void) +{ + int err = 0; + + buf_allocated = setup_patt_bufs(); + if (!buf_allocated) + return -ENOMEM; + + snd_pcmtst_hw.channels_max = buf_allocated; + + err = init_debug_files(buf_allocated); + if (err) + return err; + err = platform_device_register(&pcmtst_pdev); + if (err) + return err; + err = platform_driver_register(&pcmtst_pdrv); + if (err) + platform_device_unregister(&pcmtst_pdev); + return err; +} + +static void __exit mod_exit(void) +{ + clear_debug_files(); + free_pattern_buffers(); + + platform_driver_unregister(&pcmtst_pdrv); + platform_device_unregister(&pcmtst_pdev); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Orlov"); +module_init(mod_init); +module_exit(mod_exit); -- cgit v1.2.3 From f751b99255cacd9ffe8c4bbf99767ad670cee1f7 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Tue, 6 Jun 2023 17:25:28 -0500 Subject: ASoC: SOF: Intel: fix SoundWire/HDaudio mutual exclusion The functionality described in Commit 61bef9e68dca ("ASoC: SOF: Intel: hda: enforce exclusion between HDaudio and SoundWire") does not seem to be properly implemented with two issues that need to be corrected. a) The test used is incorrect when DisplayAudio codecs are not supported. b) Conversely when only Display Audio codecs can be found, we do want to start the SoundWire links, if any. That will help add the relevant topologies and machine descriptors, and identify cases where the SoundWire information in ACPI needs to be modified with a quirk. Signed-off-by: Pierre-Louis Bossart Reviewed-by: Bard Liao Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230606222529.57156-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda.c b/sound/soc/sof/intel/hda.c index 066f27e3402f..ec31a5762ddf 100644 --- a/sound/soc/sof/intel/hda.c +++ b/sound/soc/sof/intel/hda.c @@ -1368,12 +1368,22 @@ static void hda_generic_machine_select(struct snd_sof_dev *sdev, hda_mach->mach_params.dmic_num = dmic_num; pdata->tplg_filename = tplg_filename; - if (codec_num == 2) { + if (codec_num == 2 || + (codec_num == 1 && !HDA_IDISP_CODEC(bus->codec_mask))) { /* * Prevent SoundWire links from starting when an external * HDaudio codec is used */ hda_mach->mach_params.link_mask = 0; + } else { + /* + * Allow SoundWire links to start when no external HDaudio codec + * was detected. This will not create a SoundWire card but + * will help detect if any SoundWire codec reports as ATTACHED. + */ + struct sof_intel_hda_dev *hdev = sdev->pdata->hw_pdata; + + hda_mach->mach_params.link_mask = hdev->info.link_mask; } *mach = hda_mach; -- cgit v1.2.3 From 3bd45b8dea73eabc9bbfdcdc69675e3ef8ca8920 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Tue, 6 Jun 2023 17:25:29 -0500 Subject: ASoC: SOF: Intel: hda-pcm: remove kernel parameter init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'hda_disable_rewinds' kernel parameter is initialized with a non-existent CONFIG_SND_SOC_SOF_HDA_DISABLE_REWINDS. We probably forgot to clean this up when this Kconfig option was removed when upstreaming in 2021. Reported-by: Ranjani Sridharan 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/20230606222529.57156-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/hda-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/hda-pcm.c b/sound/soc/sof/intel/hda-pcm.c index 981e7b699bdb..f23c72cdff48 100644 --- a/sound/soc/sof/intel/hda-pcm.c +++ b/sound/soc/sof/intel/hda-pcm.c @@ -33,7 +33,7 @@ static bool hda_always_enable_dmi_l1; module_param_named(always_enable_dmi_l1, hda_always_enable_dmi_l1, bool, 0444); MODULE_PARM_DESC(always_enable_dmi_l1, "SOF HDA always enable DMI l1"); -static bool hda_disable_rewinds = IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_DISABLE_REWINDS); +static bool hda_disable_rewinds; module_param_named(disable_rewinds, hda_disable_rewinds, bool, 0444); MODULE_PARM_DESC(disable_rewinds, "SOF HDA disable rewinds"); -- cgit v1.2.3 From 484ede9bcb031a98880817480b685cac0ec96f2b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 7 Jun 2023 14:08:15 +0200 Subject: ASoC: mediatek: mt8188-mt6359: add i2c dependency The newly added driver is missing this dependency, causing a possible build failure: WARNING: unmet direct dependencies detected for SND_SOC_MAX98390 WARNING: unmet direct dependencies detected for SND_SOC_NAU8825 Depends on [m]: SOUND [=y] && !UML && SND [=y] && SND_SOC [=y] && I2C [=m] Selected by [y]: - SND_SOC_MT8188_MT6359 [=y] && SOUND [=y] && !UML && SND [=y] && SND_SOC [=y] && SND_SOC_MT8188 [=y] && MTK_PMIC_WRAP [=y] aarch64-linux-ld: sound/soc/codecs/max98390.o: in function `max98390_i2c_probe': max98390.c:(.text+0x514): undefined reference to `__devm_regmap_init_i2c' Fixes: 9f08dcbddeb30 ("ASoC: mediatek: mt8188-mt6359: support new board with nau88255") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230607120831.3587379-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/mediatek/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/mediatek/Kconfig b/sound/soc/mediatek/Kconfig index 4ea012342b52..90db67e0ce4f 100644 --- a/sound/soc/mediatek/Kconfig +++ b/sound/soc/mediatek/Kconfig @@ -223,6 +223,7 @@ config SND_SOC_MT8188 config SND_SOC_MT8188_MT6359 tristate "ASoC Audio driver for MT8188 with MT6359 and I2S codecs" depends on SND_SOC_MT8188 && MTK_PMIC_WRAP + depends on I2C select SND_SOC_MT6359 select SND_SOC_HDMI_CODEC select SND_SOC_DMIC -- cgit v1.2.3 From 99f3e7de7a100eddcf92af55a7e23000afeed35c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 7 Jun 2023 19:13:26 +0200 Subject: ASoC: codecs: wsa883x: use existing define instead of raw value Use existing define for WSA883X_GLOBAL_PA_ENABLE instead of hard-coded value, just like in other places in this driver. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230607171326.179527-1-krzysztof.kozlowski@linaro.org Signed-off-by: Mark Brown --- sound/soc/codecs/wsa883x.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/wsa883x.c b/sound/soc/codecs/wsa883x.c index c609cb63dae6..5c1cfceb2956 100644 --- a/sound/soc/codecs/wsa883x.c +++ b/sound/soc/codecs/wsa883x.c @@ -1334,7 +1334,8 @@ static int wsa883x_digital_mute(struct snd_soc_dai *dai, int mute, int stream) WSA883X_DRE_GAIN_EN_MASK, WSA883X_DRE_GAIN_FROM_CSR); snd_soc_component_write_field(component, WSA883X_PA_FSM_CTL, - WSA883X_GLOBAL_PA_EN_MASK, 1); + WSA883X_GLOBAL_PA_EN_MASK, + WSA883X_GLOBAL_PA_ENABLE); } -- cgit v1.2.3 From 22628e92d76a403181916f7bac7848dd2326d750 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 8 Jun 2023 10:47:23 +0200 Subject: ASoC: mediatek: mt8188-mt6359: Compress of_device_id entries Those entries fit in one line: compress them to reduce line count. While at it, also add the sentinel comment to the last entry. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230608084727.74403-2-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index bc4b74970a46..643a7a12a96b 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -1117,15 +1117,9 @@ static struct mt8188_card_data mt8188_nau8825_card = { }; static const struct of_device_id mt8188_mt6359_dt_match[] = { - { - .compatible = "mediatek,mt8188-mt6359-evb", - .data = &mt8188_evb_card, - }, - { - .compatible = "mediatek,mt8188-nau8825", - .data = &mt8188_nau8825_card, - }, - {}, + { .compatible = "mediatek,mt8188-mt6359-evb", .data = &mt8188_evb_card, }, + { .compatible = "mediatek,mt8188-nau8825", .data = &mt8188_nau8825_card, }, + { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, mt8188_mt6359_dt_match); -- cgit v1.2.3 From 1148b42257e2bf30093708398db2c4570ae9fe97 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 8 Jun 2023 10:47:24 +0200 Subject: ASoC: mediatek: mt8188-mt6359: clean up a return in codec_init This code triggers a Smatch static checker warning and does sort of look like an error path. sound/soc/mediatek/mt8188/mt8188-mt6359.c:597 mt8188_max98390_codec_init() warn: missing error code? 'ret' However, returning 0 is intentional. Make that explicit. Signed-off-by: Dan Carpenter Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230608084727.74403-3-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 643a7a12a96b..b2735496d140 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -594,7 +594,7 @@ static int mt8188_max98390_codec_init(struct snd_soc_pcm_runtime *rtd) } if (rtd->dai_link->num_codecs <= 2) - return ret; + return 0; /* add widgets/controls/dapm for rear speakers */ ret = snd_soc_dapm_new_controls(&card->dapm, mt8188_rear_spk_widgets, -- cgit v1.2.3 From 4882ef44f51bbb759b8a738b747fdbcbad38e51b Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 8 Jun 2023 10:47:25 +0200 Subject: ASoC: mediatek: mt8188-mt6359: Cleanup return 0 disguised as return ret Change all instances of `return ret` to `return 0` at the end of functions where ret is always zero and also change functions mt8188_{hdmi,dptx}_codec_init to be consistent with how other functions are returning errors Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230608084727.74403-4-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index b2735496d140..260cace408b9 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -491,11 +491,13 @@ static int mt8188_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd) } ret = snd_soc_component_set_jack(component, &priv->hdmi_jack, NULL); - if (ret) + if (ret) { dev_info(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", __func__, component->name, ret); + return ret; + } - return ret; + return 0; } static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) @@ -513,11 +515,13 @@ static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) } ret = snd_soc_component_set_jack(component, &priv->dp_jack, NULL); - if (ret) + if (ret) { dev_info(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", __func__, component->name, ret); + return ret; + } - return ret; + return 0; } static int mt8188_dumb_amp_init(struct snd_soc_pcm_runtime *rtd) @@ -539,7 +543,7 @@ static int mt8188_dumb_amp_init(struct snd_soc_pcm_runtime *rtd) return ret; } - return ret; + return 0; } static int mt8188_max98390_hw_params(struct snd_pcm_substream *substream, @@ -612,7 +616,7 @@ static int mt8188_max98390_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; } - return ret; + return 0; } static int mt8188_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) @@ -660,7 +664,7 @@ static int mt8188_nau8825_codec_init(struct snd_soc_pcm_runtime *rtd) return ret; } - return ret; + return 0; }; static void mt8188_nau8825_codec_exit(struct snd_soc_pcm_runtime *rtd) @@ -697,7 +701,7 @@ static int mt8188_nau8825_hw_params(struct snd_pcm_substream *substream, return ret; } - return ret; + return 0; } static const struct snd_soc_ops mt8188_nau8825_ops = { -- cgit v1.2.3 From acb43baf8b7e75acdb14920de29881e3f70c6819 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 8 Jun 2023 10:47:26 +0200 Subject: ASoC: mediatek: mt8188-mt6359: Clean up log levels Change some dev_info prints to dev_err() and some to dev_dbg(), depending on the actual severity of them. Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230608084727.74403-5-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 260cace408b9..5b2660139421 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -337,9 +337,8 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) /* handle if never test done */ if (++counter > 10000) { - dev_info(afe->dev, "%s(), test fail, cycle_1 %d, cycle_2 %d, monitor 0x%x\n", - __func__, - cycle_1, cycle_2, monitor); + dev_err(afe->dev, "%s(), test fail, cycle_1 %d, cycle_2 %d, monitor 0x%x\n", + __func__, cycle_1, cycle_2, monitor); mtkaif_calibration_ok = false; break; } @@ -398,8 +397,8 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) for (i = 0; i < MT8188_MTKAIF_MISO_NUM; i++) param->mtkaif_phase_cycle[i] = mtkaif_phase_cycle[i]; - dev_info(afe->dev, "%s(), end, calibration ok %d\n", - __func__, param->mtkaif_calibration_ok); + dev_dbg(afe->dev, "%s(), end, calibration ok %d\n", + __func__, param->mtkaif_calibration_ok); return 0; } @@ -486,14 +485,14 @@ static int mt8188_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd) mt8188_hdmi_jack_pins, ARRAY_SIZE(mt8188_hdmi_jack_pins)); if (ret) { - dev_info(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); + dev_err(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); return ret; } ret = snd_soc_component_set_jack(component, &priv->hdmi_jack, NULL); if (ret) { - dev_info(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", - __func__, component->name, ret); + dev_err(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", + __func__, component->name, ret); return ret; } @@ -510,14 +509,14 @@ static int mt8188_dptx_codec_init(struct snd_soc_pcm_runtime *rtd) &priv->dp_jack, mt8188_dp_jack_pins, ARRAY_SIZE(mt8188_dp_jack_pins)); if (ret) { - dev_info(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); + dev_err(rtd->dev, "%s, new jack failed: %d\n", __func__, ret); return ret; } ret = snd_soc_component_set_jack(component, &priv->dp_jack, NULL); if (ret) { - dev_info(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", - __func__, component->name, ret); + dev_err(rtd->dev, "%s, set jack failed on %s (ret=%d)\n", + __func__, component->name, ret); return ret; } -- cgit v1.2.3 From b0e2e4fb8a5467f4f64bcf64d1454d18cb665cc8 Mon Sep 17 00:00:00 2001 From: AngeloGioacchino Del Regno Date: Thu, 8 Jun 2023 10:47:27 +0200 Subject: ASoC: mediatek: mt8188-mt6359: Use bitfield macros for registers Replace open coded instances of FIELD_GET() with it, move register definitions at the top of the file and also replace magic numbers with register definitions. While at it, also change a regmap_update_bits() call to regmap_write() because the top 29 bits of AUD_TOP_CFG (31:3) are reserved (unused). Signed-off-by: AngeloGioacchino Del Regno Reviewed-by: Alexandre Mergnat Link: https://lore.kernel.org/r/20230608084727.74403-6-angelogioacchino.delregno@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8188/mt8188-mt6359.c | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c index 5b2660139421..ac69c23e0da1 100644 --- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c +++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c @@ -6,6 +6,7 @@ * Author: Trevor Wu */ +#include #include #include #include @@ -19,6 +20,15 @@ #include "../common/mtk-afe-platform-driver.h" #include "../common/mtk-soundcard-driver.h" +#define CKSYS_AUD_TOP_CFG 0x032c + #define RG_TEST_ON BIT(0) + #define RG_TEST_TYPE BIT(2) +#define CKSYS_AUD_TOP_MON 0x0330 + #define TEST_MISO_COUNT_1 GENMASK(3, 0) + #define TEST_MISO_COUNT_2 GENMASK(7, 4) + #define TEST_MISO_DONE_1 BIT(28) + #define TEST_MISO_DONE_2 BIT(29) + #define NAU8825_HS_PRESENT BIT(0) /* @@ -251,9 +261,6 @@ static const struct snd_kcontrol_new mt8188_nau8825_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone Jack"), }; -#define CKSYS_AUD_TOP_CFG 0x032c -#define CKSYS_AUD_TOP_MON 0x0330 - static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_component *cmpnt_afe = @@ -265,13 +272,13 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) struct mtkaif_param *param; int chosen_phase_1, chosen_phase_2; int prev_cycle_1, prev_cycle_2; - int test_done_1, test_done_2; + u8 test_done_1, test_done_2; int cycle_1, cycle_2; int mtkaif_chosen_phase[MT8188_MTKAIF_MISO_NUM]; int mtkaif_phase_cycle[MT8188_MTKAIF_MISO_NUM]; int mtkaif_calibration_num_phase; bool mtkaif_calibration_ok; - unsigned int monitor = 0; + u32 monitor = 0; int counter; int phase; int i; @@ -303,8 +310,7 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mt6359_mtkaif_calibration_enable(cmpnt_codec); /* set test type to synchronizer pulse */ - regmap_update_bits(afe_priv->topckgen, - CKSYS_AUD_TOP_CFG, 0xffff, 0x4); + regmap_write(afe_priv->topckgen, CKSYS_AUD_TOP_CFG, RG_TEST_TYPE); mtkaif_calibration_num_phase = 42; /* mt6359: 0 ~ 42 */ mtkaif_calibration_ok = true; @@ -314,7 +320,7 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mt6359_set_mtkaif_calibration_phase(cmpnt_codec, phase, phase, phase); - regmap_set_bits(afe_priv->topckgen, CKSYS_AUD_TOP_CFG, 0x1); + regmap_set_bits(afe_priv->topckgen, CKSYS_AUD_TOP_CFG, RG_TEST_ON); test_done_1 = 0; test_done_2 = 0; @@ -326,14 +332,14 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) while (!(test_done_1 & test_done_2)) { regmap_read(afe_priv->topckgen, CKSYS_AUD_TOP_MON, &monitor); - test_done_1 = (monitor >> 28) & 0x1; - test_done_2 = (monitor >> 29) & 0x1; + test_done_1 = FIELD_GET(TEST_MISO_DONE_1, monitor); + test_done_2 = FIELD_GET(TEST_MISO_DONE_2, monitor); if (test_done_1 == 1) - cycle_1 = monitor & 0xf; + cycle_1 = FIELD_GET(TEST_MISO_COUNT_1, monitor); if (test_done_2 == 1) - cycle_2 = (monitor >> 4) & 0xf; + cycle_2 = FIELD_GET(TEST_MISO_COUNT_2, monitor); /* handle if never test done */ if (++counter > 10000) { @@ -361,7 +367,7 @@ static int mt8188_mt6359_mtkaif_calibration(struct snd_soc_pcm_runtime *rtd) mtkaif_phase_cycle[MT8188_MTKAIF_MISO_1] = prev_cycle_2; } - regmap_clear_bits(afe_priv->topckgen, CKSYS_AUD_TOP_CFG, 0x1); + regmap_clear_bits(afe_priv->topckgen, CKSYS_AUD_TOP_CFG, RG_TEST_ON); if (mtkaif_chosen_phase[MT8188_MTKAIF_MISO_0] >= 0 && mtkaif_chosen_phase[MT8188_MTKAIF_MISO_1] >= 0) -- cgit v1.2.3 From 3b3a8d6d34a3ace4d49beb6f69ebb0d3cfaf0479 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Thu, 8 Jun 2023 15:55:40 +0800 Subject: ASoC: max98088: clean up some inconsistent indenting No functional modification involved. sound/soc/codecs/max98088.c:316 m98088_eq_band() warn: inconsistent indenting. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=5461 Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/20230608075540.61575-1-jiapeng.chong@linux.alibaba.com Signed-off-by: Mark Brown --- sound/soc/codecs/max98088.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index fb136e2b849b..8b56ee550c09 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -310,24 +310,24 @@ static const struct regmap_config max98088_regmap = { static void m98088_eq_band(struct snd_soc_component *component, unsigned int dai, unsigned int band, u16 *coefs) { - unsigned int eq_reg; - unsigned int i; + unsigned int eq_reg; + unsigned int i; if (WARN_ON(band > 4) || WARN_ON(dai > 1)) return; - /* Load the base register address */ - eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE; + /* Load the base register address */ + eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE; - /* Add the band address offset, note adjustment for word address */ - eq_reg += band * (M98088_COEFS_PER_BAND << 1); + /* Add the band address offset, note adjustment for word address */ + eq_reg += band * (M98088_COEFS_PER_BAND << 1); - /* Step through the registers and coefs */ - for (i = 0; i < M98088_COEFS_PER_BAND; i++) { - snd_soc_component_write(component, eq_reg++, M98088_BYTE1(coefs[i])); - snd_soc_component_write(component, eq_reg++, M98088_BYTE0(coefs[i])); - } + /* Step through the registers and coefs */ + for (i = 0; i < M98088_COEFS_PER_BAND; i++) { + snd_soc_component_write(component, eq_reg++, M98088_BYTE1(coefs[i])); + snd_soc_component_write(component, eq_reg++, M98088_BYTE0(coefs[i])); + } } /* -- cgit v1.2.3 From 41a343cd6b7f8d0f70dd90c236086ccf8a84a7de Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 8 Jun 2023 07:27:22 +0200 Subject: ASoC: tegra: Simplify code around clk_get_rate() handling clk_get_rate() returns an unsigned long, so there is no point in storing it in a long, and test for negative values. So, turn 'parent_rate' into an unsigned long, simplify the sanity check, the error message and the return value, in case of error (i.e. 0). Doing so also turns 'i' and 'valid_rates' into unsigned long, but it is fine and harmless. Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/53f928290f08f50ff43031e17fe1d88443c2c441.1686202022.git.christophe.jaillet@wanadoo.fr Signed-off-by: Mark Brown --- sound/soc/tegra/tegra20_i2s.c | 9 ++++----- sound/soc/tegra/tegra20_spdif.c | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c index e1a0f50969c1..d38b58305c6b 100644 --- a/sound/soc/tegra/tegra20_i2s.c +++ b/sound/soc/tegra/tegra20_i2s.c @@ -273,13 +273,12 @@ static int tegra20_i2s_filter_rates(struct snd_pcm_hw_params *params, struct snd_soc_dai *dai = rule->private; struct tegra20_i2s *i2s = dev_get_drvdata(dai->dev); struct clk *parent = clk_get_parent(i2s->clk_i2s); - long i, parent_rate, valid_rates = 0; + unsigned long i, parent_rate, valid_rates = 0; parent_rate = clk_get_rate(parent); - if (parent_rate <= 0) { - dev_err(dai->dev, "Can't get parent clock rate: %ld\n", - parent_rate); - return parent_rate ?: -EINVAL; + if (!parent_rate) { + dev_err(dai->dev, "Can't get parent clock rate\n"); + return -EINVAL; } for (i = 0; i < ARRAY_SIZE(tegra20_i2s_rates); i++) { diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c index 86bef54dfdf2..d034803695a0 100644 --- a/sound/soc/tegra/tegra20_spdif.c +++ b/sound/soc/tegra/tegra20_spdif.c @@ -187,13 +187,12 @@ static int tegra20_spdif_filter_rates(struct snd_pcm_hw_params *params, struct tegra20_spdif *spdif = dev_get_drvdata(dai->dev); struct clk *parent = clk_get_parent(spdif->clk_spdif_out); static const unsigned int rates[] = { 32000, 44100, 48000 }; - long i, parent_rate, valid_rates = 0; + unsigned long i, parent_rate, valid_rates = 0; parent_rate = clk_get_rate(parent); - if (parent_rate <= 0) { - dev_err(dai->dev, "Can't get parent clock rate: %ld\n", - parent_rate); - return parent_rate ?: -EINVAL; + if (!parent_rate) { + dev_err(dai->dev, "Can't get parent clock rate\n"); + return -EINVAL; } for (i = 0; i < ARRAY_SIZE(rates); i++) { -- cgit v1.2.3 From 3582cf94ff49469ffe78e96014550f7d4e466fbd Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Thu, 8 Jun 2023 21:57:49 +0800 Subject: ASoC: starfive: Fix an error check in jh7110_tdm_clk_reset_get() Fix the check for devm_reset_control_array_get_exclusive() return value. The devm_reset_control_array_get_exclusive() function may return NULL if it's an optional request. If optional is intended then NULL should not be treated as an error case, but as a special kind of success case. So here the IS_ERR() is used to check better. Signed-off-by: Walker Chen Reviewed-by: Claudiu Beznea Link: https://lore.kernel.org/r/20230608135750.11041-2-walker.chen@starfivetech.com Signed-off-by: Mark Brown --- sound/soc/starfive/jh7110_tdm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c index 973b910d2d3e..a9a3d52bdd2a 100644 --- a/sound/soc/starfive/jh7110_tdm.c +++ b/sound/soc/starfive/jh7110_tdm.c @@ -580,10 +580,9 @@ static int jh7110_tdm_clk_reset_get(struct platform_device *pdev, } tdm->resets = devm_reset_control_array_get_exclusive(&pdev->dev); - if (IS_ERR_OR_NULL(tdm->resets)) { - ret = PTR_ERR(tdm->resets); - dev_err(&pdev->dev, "Failed to get tdm resets"); - return ret; + if (IS_ERR(tdm->resets)) { + dev_err(&pdev->dev, "Failed to get tdm resets\n"); + return PTR_ERR(tdm->resets); } return 0; -- cgit v1.2.3 From 8bd81864533bd02d6922deadeed643c813dfe142 Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Thu, 8 Jun 2023 21:57:50 +0800 Subject: ASoC: starfive: Remove some unused macros These macros are unused and can be dropped. Signed-off-by: Walker Chen Reviewed-by: Claudiu Beznea Link: https://lore.kernel.org/r/20230608135750.11041-3-walker.chen@starfivetech.com Signed-off-by: Mark Brown --- sound/soc/starfive/jh7110_tdm.c | 8 -------- 1 file changed, 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c index a9a3d52bdd2a..e4bdba20c499 100644 --- a/sound/soc/starfive/jh7110_tdm.c +++ b/sound/soc/starfive/jh7110_tdm.c @@ -25,11 +25,8 @@ #include #define TDM_PCMGBCR 0x00 - #define PCMGBCR_MASK 0x1e #define PCMGBCR_ENABLE BIT(0) - #define PCMGBCR_TRITXEN BIT(4) #define CLKPOL_BIT 5 - #define TRITXEN_BIT 4 #define ELM_BIT 3 #define SYNCM_BIT 2 #define MS_BIT 1 @@ -42,11 +39,6 @@ #define LRJ_BIT 1 #define TDM_PCMRXCR 0x08 #define PCMRXCR_RXEN BIT(0) - #define PCMRXCR_RXSL_MASK 0xc - #define PCMRXCR_RXSL_16BIT 0x4 - #define PCMRXCR_RXSL_32BIT 0x8 - #define PCMRXCR_SCALE_MASK 0xf0 - #define PCMRXCR_SCALE_1CH 0x10 #define TDM_PCMDIV 0x0c #define JH7110_TDM_FIFO 0x170c0000 -- cgit v1.2.3 From e352f31a863f47adfa54c76b633a21b1ed562387 Mon Sep 17 00:00:00 2001 From: "Nícolas F. R. A. Prado" Date: Thu, 8 Jun 2023 18:10:48 -0400 Subject: ASoC: mediatek: mt8192-mt6359: Go back to old headphone pin name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cbbc0ec6dea09c ("ASoC: mediatek: mt8192-mt6359: Remove " Jack" from Headphone pin name"). That commit removed the " Jack" suffix with the reasoning that it is automatically added to the name of the kcontrol created, which is true, but this name is also used to look for the DAPM widget that will be toggled when the jack status is updated. Since the widget is still called "Headphone Jack" the jack can't link to the widget and the following error is shown: mt8192_mt6359 sound: ASoC: DAPM unknown pin Headphone It is not possible to also rename the headphone DAPM widget because its name is used by a switch kcontrol, "Headphone Jack Switch", both to link to the headphone widget and to assemble its name. This switch's name is referenced in the upstream UCM file, so renaming it would break userspace. Since the original commit didn't bring any benefit, besides sparing a few CPU cycles, simply revert it. Signed-off-by: Nícolas F. R. A. Prado Link: https://lore.kernel.org/r/20230608221050.217968-1-nfraprado@collabora.com Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c index 4e0d5bf12b47..5e163e23a207 100644 --- a/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c +++ b/sound/soc/mediatek/mt8192/mt8192-mt6359-rt1015-rt5682.c @@ -46,7 +46,7 @@ struct mt8192_mt6359_priv { /* Headset jack detection DAPM pins */ static struct snd_soc_jack_pin mt8192_jack_pins[] = { { - .pin = "Headphone", + .pin = "Headphone Jack", .mask = SND_JACK_HEADPHONE, }, { -- cgit v1.2.3 From 12c41c779fad54714ce4901757374f6006a88644 Mon Sep 17 00:00:00 2001 From: Curtis Malainey Date: Thu, 8 Jun 2023 15:18:15 -0700 Subject: ASoC: SOF: Refactor rx function for fuzzing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the function so reading the data is done outside the work function so fuzzing can pass data directly into the work callbacks. Also expose the inner function outside the module so we can call it from the injector. Signed-off-by: Curtis Malainey Reviewed-by: Ranjani Sridharan Reviewed-by: Pierre-Louis Bossart Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20230608221822.2825786-1-cujomalainey@chromium.org Signed-off-by: Mark Brown --- sound/soc/sof/ipc3-priv.h | 2 ++ sound/soc/sof/ipc3.c | 63 +++++++++++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 24 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc3-priv.h b/sound/soc/sof/ipc3-priv.h index f5044202f3c5..0bbca418e67e 100644 --- a/sound/soc/sof/ipc3-priv.h +++ b/sound/soc/sof/ipc3-priv.h @@ -28,6 +28,8 @@ int sof_ipc3_validate_fw_version(struct snd_sof_dev *sdev); /* dtrace position update */ int ipc3_dtrace_posn_update(struct snd_sof_dev *sdev, struct sof_ipc_dma_trace_posn *posn); +/* RX handler backend */ +void sof_ipc3_do_rx_work(struct snd_sof_dev *sdev, struct sof_ipc_cmd_hdr *hdr, void *msg_buf); /* dtrace platform callback wrappers */ static inline int sof_dtrace_host_init(struct snd_sof_dev *sdev, diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index c67767742093..ec1ac0fb2d9f 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -954,31 +954,21 @@ static void ipc3_trace_message(struct snd_sof_dev *sdev, void *msg_buf) } } -/* DSP firmware has sent host a message */ -static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) +void sof_ipc3_do_rx_work(struct snd_sof_dev *sdev, struct sof_ipc_cmd_hdr *hdr, void *msg_buf) { ipc3_rx_callback rx_callback = NULL; - struct sof_ipc_cmd_hdr hdr; - void *msg_buf; u32 cmd; int err; - /* read back header */ - err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); - if (err < 0) { - dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); - return; - } + ipc3_log_header(sdev->dev, "ipc rx", hdr->cmd); - if (hdr.size < sizeof(hdr) || hdr.size > SOF_IPC_MSG_MAX_SIZE) { + if (hdr->size < sizeof(hdr) || hdr->size > SOF_IPC_MSG_MAX_SIZE) { dev_err(sdev->dev, "The received message size is invalid: %u\n", - hdr.size); + hdr->size); return; } - ipc3_log_header(sdev->dev, "ipc rx", hdr.cmd); - - cmd = hdr.cmd & SOF_GLB_TYPE_MASK; + cmd = hdr->cmd & SOF_GLB_TYPE_MASK; /* check message type */ switch (cmd) { @@ -1016,6 +1006,36 @@ static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) break; } + /* Call local handler for the message */ + if (rx_callback) + rx_callback(sdev, msg_buf); + + /* Notify registered clients */ + sof_client_ipc_rx_dispatcher(sdev, msg_buf); + + ipc3_log_header(sdev->dev, "ipc rx done", hdr->cmd); +} +EXPORT_SYMBOL(sof_ipc3_do_rx_work); + +/* DSP firmware has sent host a message */ +static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) +{ + struct sof_ipc_cmd_hdr hdr; + void *msg_buf; + int err; + + /* read back header */ + err = snd_sof_ipc_msg_data(sdev, NULL, &hdr, sizeof(hdr)); + if (err < 0) { + dev_warn(sdev->dev, "failed to read IPC header: %d\n", err); + return; + } + + if (hdr.size < sizeof(hdr)) { + dev_err(sdev->dev, "The received message size is invalid\n"); + return; + } + /* read the full message */ msg_buf = kmalloc(hdr.size, GFP_KERNEL); if (!msg_buf) @@ -1024,18 +1044,13 @@ static void sof_ipc3_rx_msg(struct snd_sof_dev *sdev) err = snd_sof_ipc_msg_data(sdev, NULL, msg_buf, hdr.size); if (err < 0) { dev_err(sdev->dev, "%s: Failed to read message: %d\n", __func__, err); - } else { - /* Call local handler for the message */ - if (rx_callback) - rx_callback(sdev, msg_buf); - - /* Notify registered clients */ - sof_client_ipc_rx_dispatcher(sdev, msg_buf); + kfree(msg_buf); + return; } - kfree(msg_buf); + sof_ipc3_do_rx_work(sdev, &hdr, msg_buf); - ipc3_log_header(sdev->dev, "ipc rx done", hdr.cmd); + kfree(msg_buf); } static int sof_ipc3_set_core_state(struct snd_sof_dev *sdev, int core_idx, bool on) -- cgit v1.2.3 From 70dad53ddff0778c4920a1ee9eb1cfea539d4e91 Mon Sep 17 00:00:00 2001 From: Curtis Malainey Date: Thu, 8 Jun 2023 15:18:16 -0700 Subject: ASoC: SOF: Add IPC3 Kernel Injector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add debugfs path to fake a malicious firmware message for fuzzing purposes. Skip IPC4 for initial integration Signed-off-by: Curtis Malainey Reviewed-by: Ranjani Sridharan Reviewed-by: Pierre-Louis Bossart Reviewed-by: Péter Ujfalusi Link: https://lore.kernel.org/r/20230608221822.2825786-2-cujomalainey@chromium.org Signed-off-by: Mark Brown --- sound/soc/sof/Kconfig | 11 ++ sound/soc/sof/Makefile | 2 + sound/soc/sof/sof-client-ipc-kernel-injector.c | 162 +++++++++++++++++++++++++ sound/soc/sof/sof-client.c | 52 ++++++++ sound/soc/sof/sof-client.h | 1 + 5 files changed, 228 insertions(+) create mode 100644 sound/soc/sof/sof-client-ipc-kernel-injector.c (limited to 'sound') diff --git a/sound/soc/sof/Kconfig b/sound/soc/sof/Kconfig index a2725188f4ce..80361139a49a 100644 --- a/sound/soc/sof/Kconfig +++ b/sound/soc/sof/Kconfig @@ -236,6 +236,17 @@ config SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR Say Y if you want to enable the IPC message injector. If unsure, select "N". +config SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR + tristate "SOF enable IPC kernel injector" + depends on SND_SOC_SOF + select SND_SOC_SOF_CLIENT + help + This option enables the IPC kernel injector which can be used to send + crafted IPC messages to the kernel to test its robustness against + DSP messages. + Say Y if you want to enable the IPC kernel injector. + If unsure, select "N". + config SND_SOC_SOF_DEBUG_RETAIN_DSP_CONTEXT bool "SOF retain DSP context on any FW exceptions" help diff --git a/sound/soc/sof/Makefile b/sound/soc/sof/Makefile index 308d87639916..744d40bd8c8b 100644 --- a/sound/soc/sof/Makefile +++ b/sound/soc/sof/Makefile @@ -26,6 +26,7 @@ snd-sof-of-objs := sof-of-dev.o snd-sof-ipc-flood-test-objs := sof-client-ipc-flood-test.o snd-sof-ipc-msg-injector-objs := sof-client-ipc-msg-injector.o +snd-sof-ipc-kernel-injector-objs := sof-client-ipc-kernel-injector.o snd-sof-probes-objs := sof-client-probes.o ifneq ($(CONFIG_SND_SOC_SOF_IPC3),) snd-sof-probes-objs += sof-client-probes-ipc3.o @@ -49,6 +50,7 @@ obj-$(CONFIG_SND_SOC_SOF_PCI_DEV) += snd-sof-pci.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_FLOOD_TEST) += snd-sof-ipc-flood-test.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR) += snd-sof-ipc-msg-injector.o +obj-$(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) += snd-sof-ipc-kernel-injector.o obj-$(CONFIG_SND_SOC_SOF_DEBUG_PROBES) += snd-sof-probes.o obj-$(CONFIG_SND_SOC_SOF_INTEL_TOPLEVEL) += intel/ diff --git a/sound/soc/sof/sof-client-ipc-kernel-injector.c b/sound/soc/sof/sof-client-ipc-kernel-injector.c new file mode 100644 index 000000000000..ad0ed2d570a9 --- /dev/null +++ b/sound/soc/sof/sof-client-ipc-kernel-injector.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2023 Google Inc. All rights reserved. +// +// Author: Curtis Malainey +// + +#include +#include +#include +#include +#include +#include + +#include "sof-client.h" + +#define SOF_IPC_CLIENT_SUSPEND_DELAY_MS 3000 + +struct sof_msg_inject_priv { + struct dentry *kernel_dfs_file; + size_t max_msg_size; + + void *kernel_buffer; +}; + +static int sof_msg_inject_dfs_open(struct inode *inode, struct file *file) +{ + int ret = debugfs_file_get(file->f_path.dentry); + + if (unlikely(ret)) + return ret; + + ret = simple_open(inode, file); + if (ret) + debugfs_file_put(file->f_path.dentry); + + return ret; +} + +static ssize_t sof_kernel_msg_inject_dfs_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct sof_client_dev *cdev = file->private_data; + struct sof_msg_inject_priv *priv = cdev->data; + struct sof_ipc_cmd_hdr *hdr = priv->kernel_buffer; + struct device *dev = &cdev->auxdev.dev; + ssize_t size; + int ret; + + if (*ppos) + return 0; + + size = simple_write_to_buffer(priv->kernel_buffer, priv->max_msg_size, + ppos, buffer, count); + if (size < 0) + return size; + if (size != count) + return -EFAULT; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(dev, "debugfs write failed to resume %d\n", ret); + return ret; + } + + sof_client_ipc_rx_message(cdev, hdr, priv->kernel_buffer); + + pm_runtime_mark_last_busy(dev); + ret = pm_runtime_put_autosuspend(dev); + if (ret < 0) + dev_err_ratelimited(dev, "debugfs write failed to idle %d\n", ret); + + return count; +}; + +static int sof_msg_inject_dfs_release(struct inode *inode, struct file *file) +{ + debugfs_file_put(file->f_path.dentry); + + return 0; +} + +static const struct file_operations sof_kernel_msg_inject_fops = { + .open = sof_msg_inject_dfs_open, + .write = sof_kernel_msg_inject_dfs_write, + .release = sof_msg_inject_dfs_release, + + .owner = THIS_MODULE, +}; + +static int sof_msg_inject_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct dentry *debugfs_root = sof_client_get_debugfs_root(cdev); + struct device *dev = &auxdev->dev; + struct sof_msg_inject_priv *priv; + size_t alloc_size; + + /* allocate memory for client data */ + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->max_msg_size = sof_client_get_ipc_max_payload_size(cdev); + alloc_size = priv->max_msg_size; + priv->kernel_buffer = devm_kmalloc(dev, alloc_size, GFP_KERNEL); + + if (!priv->kernel_buffer) + return -ENOMEM; + + cdev->data = priv; + + priv->kernel_dfs_file = debugfs_create_file("kernel_ipc_msg_inject", 0644, + debugfs_root, cdev, + &sof_kernel_msg_inject_fops); + + /* enable runtime PM */ + pm_runtime_set_autosuspend_delay(dev, SOF_IPC_CLIENT_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_idle(dev); + + return 0; +} + +static void sof_msg_inject_remove(struct auxiliary_device *auxdev) +{ + struct sof_client_dev *cdev = auxiliary_dev_to_sof_client_dev(auxdev); + struct sof_msg_inject_priv *priv = cdev->data; + + pm_runtime_disable(&auxdev->dev); + + debugfs_remove(priv->kernel_dfs_file); +} + +static const struct auxiliary_device_id sof_msg_inject_client_id_table[] = { + { .name = "snd_sof.kernel_injector" }, + {}, +}; +MODULE_DEVICE_TABLE(auxiliary, sof_msg_inject_client_id_table); + +/* + * No need for driver pm_ops as the generic pm callbacks in the auxiliary bus + * type are enough to ensure that the parent SOF device resumes to bring the DSP + * back to D0. + * Driver name will be set based on KBUILD_MODNAME. + */ +static struct auxiliary_driver sof_msg_inject_client_drv = { + .probe = sof_msg_inject_probe, + .remove = sof_msg_inject_remove, + + .id_table = sof_msg_inject_client_id_table, +}; + +module_auxiliary_driver(sof_msg_inject_client_drv); + +MODULE_DESCRIPTION("SOF IPC Kernel Injector Client Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_SOF_CLIENT); diff --git a/sound/soc/sof/sof-client.c b/sound/soc/sof/sof-client.c index d6b7caa0cf03..284de96e779c 100644 --- a/sound/soc/sof/sof-client.c +++ b/sound/soc/sof/sof-client.c @@ -16,6 +16,7 @@ #include "ops.h" #include "sof-client.h" #include "sof-priv.h" +#include "ipc3-priv.h" #include "ipc4-priv.h" /** @@ -126,6 +127,29 @@ static inline int sof_register_ipc_msg_injector(struct snd_sof_dev *sdev) static inline void sof_unregister_ipc_msg_injector(struct snd_sof_dev *sdev) {} #endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_MSG_INJECTOR */ +#if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR) +static int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev) +{ + /* Only IPC3 supported right now */ + if (sdev->pdata->ipc_type != SOF_IPC) + return 0; + + return sof_client_dev_register(sdev, "kernel_injector", 0, NULL, 0); +} + +static void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) +{ + sof_client_dev_unregister(sdev, "kernel_injector", 0); +} +#else +static inline int sof_register_ipc_kernel_injector(struct snd_sof_dev *sdev) +{ + return 0; +} + +static inline void sof_unregister_ipc_kernel_injector(struct snd_sof_dev *sdev) {} +#endif /* CONFIG_SND_SOC_SOF_DEBUG_IPC_KERNEL_INJECTOR */ + int sof_register_clients(struct snd_sof_dev *sdev) { int ret; @@ -146,6 +170,12 @@ int sof_register_clients(struct snd_sof_dev *sdev) goto err_msg_injector; } + ret = sof_register_ipc_kernel_injector(sdev); + if (ret) { + dev_err(sdev->dev, "IPC kernel injector client registration failed\n"); + goto err_kernel_injector; + } + /* Platform depndent client device registration */ if (sof_ops(sdev) && sof_ops(sdev)->register_ipc_clients) @@ -154,6 +184,9 @@ int sof_register_clients(struct snd_sof_dev *sdev) if (!ret) return 0; + sof_unregister_ipc_kernel_injector(sdev); + +err_kernel_injector: sof_unregister_ipc_msg_injector(sdev); err_msg_injector: @@ -167,6 +200,7 @@ void sof_unregister_clients(struct snd_sof_dev *sdev) if (sof_ops(sdev) && sof_ops(sdev)->unregister_ipc_clients) sof_ops(sdev)->unregister_ipc_clients(sdev); + sof_unregister_ipc_kernel_injector(sdev); sof_unregister_ipc_msg_injector(sdev); sof_unregister_ipc_flood_test(sdev); } @@ -269,6 +303,24 @@ int sof_client_ipc_tx_message(struct sof_client_dev *cdev, void *ipc_msg, } EXPORT_SYMBOL_NS_GPL(sof_client_ipc_tx_message, SND_SOC_SOF_CLIENT); +int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf) +{ + if (cdev->sdev->pdata->ipc_type == SOF_IPC) { + struct sof_ipc_cmd_hdr *hdr = ipc_msg; + + if (hdr->size < sizeof(hdr)) { + dev_err(cdev->sdev->dev, "The received message size is invalid\n"); + return -EINVAL; + } + + sof_ipc3_do_rx_work(cdev->sdev, ipc_msg, msg_buf); + return 0; + } + + return -EOPNOTSUPP; +} +EXPORT_SYMBOL_NS_GPL(sof_client_ipc_rx_message, SND_SOC_SOF_CLIENT); + int sof_client_ipc_set_get_data(struct sof_client_dev *cdev, void *ipc_msg, bool set) { diff --git a/sound/soc/sof/sof-client.h b/sound/soc/sof/sof-client.h index 10571d1ea9a7..b6ccc2cd69e5 100644 --- a/sound/soc/sof/sof-client.h +++ b/sound/soc/sof/sof-client.h @@ -75,5 +75,6 @@ int sof_client_register_fw_state_handler(struct sof_client_dev *cdev, sof_client_fw_state_callback callback); void sof_client_unregister_fw_state_handler(struct sof_client_dev *cdev); enum sof_fw_state sof_client_get_fw_state(struct sof_client_dev *cdev); +int sof_client_ipc_rx_message(struct sof_client_dev *cdev, void *ipc_msg, void *msg_buf); #endif /* __SOC_SOF_CLIENT_H */ -- cgit v1.2.3 From ca27441efe696a8990858156c6c332e786c30b4a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 9 Jun 2023 01:40:41 +0000 Subject: ASoC: audio-graph-card2-custom-sample: add missing CPU:Codec = 1:N sample It has CPU:Codec = 1:1 and N:N samples, but missing 1:N settings. This patch adds it. One note here is that because of registering timing, probing and CPU/Codec numbering are mismatching. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87ilbx1kh3.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- .../generic/audio-graph-card2-custom-sample.dtsi | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'sound') diff --git a/sound/soc/generic/audio-graph-card2-custom-sample.dtsi b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi index 994db61a26b3..f5e7d669da45 100644 --- a/sound/soc/generic/audio-graph-card2-custom-sample.dtsi +++ b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi @@ -42,6 +42,15 @@ * [Normal] * cpu0 <-@-----------------> codec0 * + * [Semi-Multi] + * + * CPU:Codec = 1:N + * + * +-+ + * cpu7 <-@------->| |-> codec12 + * | |-> codec13 + * +-+ + * * [Multi-CPU/Codec] * +-+ +-+ * cpu1 <--| |<-@--------->| |-> codec1 @@ -128,6 +137,9 @@ */ &cpu0 + /* [Semi-Multi] */ + &sm0 + /* * [Multi-CPU/Codec]: cpu side only * cpu1/cpu2/codec1/codec2 @@ -194,6 +206,13 @@ port@1 { mc2c10_ep: endpoint { remote-endpoint = <&codec10_ep>; }; }; port@2 { mc2c11_ep: endpoint { remote-endpoint = <&codec11_ep>; }; }; }; + + /* [Semi-Multi] */ + ports@5 { + port@0 { smcodec0_ep: endpoint { remote-endpoint = <&cpu7_ep>; }; }; + port@1 { smcodec1_ep: endpoint { remote-endpoint = <&codec12_ep>; }; }; + port@2 { smcodec2_ep: endpoint { remote-endpoint = <&codec13_ep>; }; }; + }; }; dpcm { @@ -261,6 +280,9 @@ /* [DPCM-Multi]::FE */ port@5 { cpu5_ep: endpoint { remote-endpoint = <&fe10_ep>; }; }; port@6 { cpu6_ep: endpoint { remote-endpoint = <&fe11_ep>; }; }; + + /* [Semi-Multi] */ + sm0: port@7 { cpu7_ep: endpoint { remote-endpoint = <&smcodec0_ep>; }; }; }; }; @@ -311,6 +333,10 @@ port@9 { codec9_ep: endpoint { remote-endpoint = <&mc2c01_ep>; }; }; port@10 { codec10_ep: endpoint { remote-endpoint = <&mc2c10_ep>; }; }; port@11 { codec11_ep: endpoint { remote-endpoint = <&mc2c11_ep>; }; }; + + /* [Semi-Multi] */ + port@12 { codec12_ep: endpoint { remote-endpoint = <&smcodec1_ep>; }; }; + port@13 { codec13_ep: endpoint { remote-endpoint = <&smcodec2_ep>; }; }; }; }; }; -- cgit v1.2.3 From 15253079ca300160c92c9c0ee2541836463043f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:26:37 +0100 Subject: ALSA: hda: Use maple tree register cache HDA can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-alsa-hda-maple-v1-1-a2b725c8b8f5@kernel.org Signed-off-by: Takashi Iwai --- sound/hda/hdac_regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index fe3587547cfe..2caa1f9b858e 100644 --- a/sound/hda/hdac_regmap.c +++ b/sound/hda/hdac_regmap.c @@ -358,7 +358,7 @@ static const struct regmap_config hda_regmap_cfg = { .writeable_reg = hda_writeable_reg, .readable_reg = hda_readable_reg, .volatile_reg = hda_volatile_reg, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_read = hda_reg_read, .reg_write = hda_reg_write, .use_single_read = true, -- cgit v1.2.3 From 81c29435073355b8194986a2193d3e7b9d449225 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 11 Jun 2023 23:44:44 +0900 Subject: ALSA: firewire: use 'GPL' string for module license contributed by Takashi Sakamoto In MODULE_LICENSE macro, "GPL" string obsoletes "GPL v2" string by a commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity"). This commit uses the preferable expression. Signed-off-by: Takashi Sakamoto Link: https://lore.kernel.org/r/20230611144445.221529-2-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai --- sound/firewire/bebob/bebob.c | 2 +- sound/firewire/digi00x/digi00x.c | 2 +- sound/firewire/fireface/ff.c | 2 +- sound/firewire/fireworks/fireworks.c | 2 +- sound/firewire/motu/motu.c | 2 +- sound/firewire/tascam/tascam.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 06a7ced218e2..2ba5962beb30 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -15,7 +15,7 @@ MODULE_DESCRIPTION("BridgeCo BeBoB driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 995302808c27..704ae2a5316b 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); #define VENDOR_DIGIDESIGN 0x00a07e #define MODEL_CONSOLE 0x000001 diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 448e972028d9..82241058ea14 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -11,7 +11,7 @@ MODULE_DESCRIPTION("RME Fireface series Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static void name_card(struct snd_ff *ff) { diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index ffb6dd796243..dd4298876ac0 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -18,7 +18,7 @@ MODULE_DESCRIPTION("Echo Fireworks driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index f8b7fe38751c..d73599eb7d5a 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -11,7 +11,7 @@ MODULE_DESCRIPTION("MOTU FireWire driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { /* mode 0 */ diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index eb58d3fcf087..86880089de28 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("TASCAM FireWire series Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static const struct snd_tscm_spec model_specs[] = { { -- cgit v1.2.3 From 9b4469410cf9a0fcbccc92c480fd42f7c815a745 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 11 Jun 2023 23:44:45 +0900 Subject: ALSA: firewire: use 'GPL' string for module license contributed by Clemens Ladisch In MODULE_LICENSE macro, "GPL" string obsoletes "GPL v2" string by a commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity"). This commit uses the preferable expression. Signed-off-by: Takashi Sakamoto Link: https://lore.kernel.org/r/20230611144445.221529-3-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice.c | 2 +- sound/firewire/isight.c | 2 +- sound/firewire/lib.c | 2 +- sound/firewire/oxfw/oxfw.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 6036a5edbcb8..6c93e6e4982c 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("DICE driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); #define OUI_WEISS 0x001c6a #define OUI_LOUD 0x000ff2 diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 6655af53b367..806f82c9ceee 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -77,7 +77,7 @@ struct audio_payload { MODULE_DESCRIPTION("iSight audio driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static struct fw_iso_packet audio_packet = { .payload_length = sizeof(struct audio_payload), diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index e0a2337e8f27..654e1a6050a9 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -69,4 +69,4 @@ EXPORT_SYMBOL(snd_fw_transaction); MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index b496f87841ae..9523479fa94a 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -32,7 +32,7 @@ MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_ALIAS("snd-firewire-speakers"); MODULE_ALIAS("snd-scs1x"); -- cgit v1.2.3 From 530ca0a7ed04230408775b495034941346ea5db1 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:47 +0800 Subject: ASoC: Intel: avs-da7219: remove redundant dapm routes Two routes "Playback<-sspX Tx" and "sspX Rx<-Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-2-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/da7219.c | 45 +++---------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c index 1a1d572cc1d0..964a763732ab 100644 --- a/sound/soc/intel/avs/boards/da7219.c +++ b/sound/soc/intel/avs/boards/da7219.c @@ -181,38 +181,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, DA7219_DAI_NAME); @@ -230,14 +198,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_da7219_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -249,12 +216,6 @@ static int avs_da7219_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -271,8 +232,8 @@ static int avs_da7219_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From c2076f4fa4f15559ed7e568186c4089479a62154 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:48 +0800 Subject: ASoC: Intel: avs-dmic: remove redundant dapm routes Two routes "DMIC Rx<-Capture" and "DMIC WoV Rx<-Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-3-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/dmic.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c index 90a921638572..c270646faf86 100644 --- a/sound/soc/intel/avs/boards/dmic.c +++ b/sound/soc/intel/avs/boards/dmic.c @@ -44,8 +44,6 @@ static const struct snd_soc_dapm_widget card_widgets[] = { static const struct snd_soc_dapm_route card_routes[] = { {"DMic", NULL, "SoC DMIC"}, - {"DMIC Rx", NULL, "Capture"}, - {"DMIC WoV Rx", NULL, "Capture"}, }; static int avs_dmic_probe(struct platform_device *pdev) -- cgit v1.2.3 From 12ea56d73c548929ef1a498848905b04bfe85f90 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:49 +0800 Subject: ASoC: Intel: avs-hdaudio: remove redundant dapm routes Three routes "HDMI 0 Playback<-hdaudioB0D2-cpu0 Tx", "HDMI 1 Playback<-hdaudioB0D2-cpu1 Tx" and "HDMI 2 Playback<-hdaudioB0D2-cpu2 Tx" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-4-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/hdaudio.c | 65 +----------------------------------- 1 file changed, 1 insertion(+), 64 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c index a542a67e21d0..cb00bc86ac94 100644 --- a/sound/soc/intel/avs/boards/hdaudio.c +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -64,56 +64,6 @@ static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int return 0; } -static int avs_create_dapm_routes(struct device *dev, struct hda_codec *codec, int pcm_count, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - struct hda_pcm *pcm; - const char *cname = dev_name(&codec->core.dev); - int i, n = 0; - - /* at max twice the number of pcms */ - dr = devm_kcalloc(dev, pcm_count * 2, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - pcm = list_first_entry(&codec->pcm_list_head, struct hda_pcm, list); - - for (i = 0; i < pcm_count; i++, pcm = list_next_entry(pcm, list)) { - struct hda_pcm_stream *stream; - int dir; - - dir = SNDRV_PCM_STREAM_PLAYBACK; - stream = &pcm->stream[dir]; - if (!stream->substreams) - goto capture_routes; - - dr[n].sink = devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, - snd_pcm_direction_name(dir)); - dr[n].source = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d Tx", cname, i); - if (!dr[n].sink || !dr[n].source) - return -ENOMEM; - n++; - -capture_routes: - dir = SNDRV_PCM_STREAM_CAPTURE; - stream = &pcm->stream[dir]; - if (!stream->substreams) - continue; - - dr[n].sink = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d Rx", cname, i); - dr[n].source = devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, - snd_pcm_direction_name(dir)); - if (!dr[n].sink || !dr[n].source) - return -ENOMEM; - n++; - } - - *routes = dr; - *num_routes = n; - return 0; -} - /* Should be aligned with SectionPCM's name from topology */ #define FEDAI_NAME_PREFIX "HDMI" @@ -172,13 +122,12 @@ static int avs_card_late_probe(struct snd_soc_card *card) static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) { - struct snd_soc_dapm_route *routes; struct snd_soc_acpi_mach *mach; struct snd_soc_dai_link *links = NULL; struct snd_soc_card *card = rtm->card; struct hda_codec *codec; struct hda_pcm *pcm; - int ret, n, pcm_count = 0; + int ret, pcm_count = 0; mach = dev_get_platdata(card->dev); codec = mach->pdata; @@ -200,18 +149,6 @@ static int avs_probing_link_init(struct snd_soc_pcm_runtime *rtm) return ret; } - ret = avs_create_dapm_routes(card->dev, codec, pcm_count, &routes, &n); - if (ret < 0) { - dev_err(card->dev, "create routes failed: %d\n", ret); - return ret; - } - - ret = snd_soc_dapm_add_routes(&card->dapm, routes, n); - if (ret < 0) { - dev_err(card->dev, "add routes failed: %d\n", ret); - return ret; - } - return 0; } -- cgit v1.2.3 From d48e3cd5aaecd7769b073f65bb95004a54bc76e6 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:50 +0800 Subject: ASoC: Intel: avs-max98357a: remove redundant dapm routes The route "HiFi Playback<-sspX Tx" is created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-5-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/max98357a.c | 39 +++------------------------------- 1 file changed, 3 insertions(+), 36 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c index 183123d08c5a..b9b20562c691 100644 --- a/sound/soc/intel/avs/boards/max98357a.c +++ b/sound/soc/intel/avs/boards/max98357a.c @@ -86,41 +86,14 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 1; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "HiFi Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_max98357a_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -132,12 +105,6 @@ static int avs_max98357a_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; @@ -151,8 +118,8 @@ static int avs_max98357a_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; ret = snd_soc_fixup_dai_links_platform_name(card, pname); -- cgit v1.2.3 From b4df7ce9905b1e8cb84ee247ca7db6ae004bc508 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:51 +0800 Subject: ASoC: Intel: avs-max98373: remove redundant dapm routes Two routes "Left HiFi Playback<-sspX Tx" and "Right HiFi Playback<-sspX Tx" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-6-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/max98373.c | 45 +++-------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c index 8e221ecd34b0..3833251ade26 100644 --- a/sound/soc/intel/avs/boards/max98373.c +++ b/sound/soc/intel/avs/boards/max98373.c @@ -141,47 +141,14 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Left HiFi Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Right HiFi Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_max98373_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -193,12 +160,6 @@ static int avs_max98373_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; @@ -214,8 +175,8 @@ static int avs_max98373_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; ret = snd_soc_fixup_dai_links_platform_name(card, pname); -- cgit v1.2.3 From 9868ca64fd7a87cf997040452519b07e47a8d008 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:52 +0800 Subject: ASoC: Intel: avs-max98927: remove redundant dapm routes Two routes "Left HiFi Playback<-sspX Tx" and "Right HiFi Playback<-sspX Tx" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-7-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/max98927.c | 45 +++-------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/max98927.c b/sound/soc/intel/avs/boards/max98927.c index 7cccce99f92e..09b231bf4e6d 100644 --- a/sound/soc/intel/avs/boards/max98927.c +++ b/sound/soc/intel/avs/boards/max98927.c @@ -138,47 +138,14 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Left HiFi Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Right HiFi Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_max98927_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -190,12 +157,6 @@ static int avs_max98927_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; @@ -211,8 +172,8 @@ static int avs_max98927_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; ret = snd_soc_fixup_dai_links_platform_name(card, pname); -- cgit v1.2.3 From ae7d66822de5aeaf991eda96c823ee9dffebfe46 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:53 +0800 Subject: ASoC: Intel: avs-nau8825: remove redundant dapm routes Two routes "Playback<-sspX Tx" and "sspX Rx<-Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-8-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/nau8825.c | 45 +++--------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c index b69fc5567135..38c5087d98e9 100644 --- a/sound/soc/intel/avs/boards/nau8825.c +++ b/sound/soc/intel/avs/boards/nau8825.c @@ -215,38 +215,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); @@ -274,14 +242,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_nau8825_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -293,12 +260,6 @@ static int avs_nau8825_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -315,8 +276,8 @@ static int avs_nau8825_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From 6227269fb375af2ff239a68499856abfd6a2bceb Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:54 +0800 Subject: ASoC: Intel: avs-rt274: remove redundant dapm routes Two routes "AIF1 Playback<-sspX Tx" and "sspX Rx<-AIF1 Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-9-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/rt274.c | 45 +++----------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c index 6a1e121f082f..ebfee54814ce 100644 --- a/sound/soc/intel/avs/boards/rt274.c +++ b/sound/soc/intel/avs/boards/rt274.c @@ -188,38 +188,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); @@ -237,14 +205,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_rt274_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -256,12 +223,6 @@ static int avs_rt274_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -278,8 +239,8 @@ static int avs_rt274_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From cca1ac1f097afa7ad6e587d6f1e86fd738ede508 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:55 +0800 Subject: ASoC: Intel: avs-rt286: remove redundant dapm routes Two routes "AIF1 Playback<-sspX Tx" and "sspX Rx<-AIF1 Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-10-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/rt286.c | 45 +++----------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c index 3551a05bd599..84cf9a0c8dfe 100644 --- a/sound/soc/intel/avs/boards/rt286.c +++ b/sound/soc/intel/avs/boards/rt286.c @@ -158,38 +158,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT286_CODEC_DAI); @@ -207,14 +175,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_rt286_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -226,12 +193,6 @@ static int avs_rt286_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -248,8 +209,8 @@ static int avs_rt286_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From eae0655316a5d741ab27c7b3a67a55b0af970e19 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:56 +0800 Subject: ASoC: Intel: avs-rt298: remove redundant dapm routes Two routes "AIF1 Playback<-sspX Tx" and "sspX Rx<-AIF1 Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-11-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/rt298.c | 45 +++----------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c index 2923f3805bbe..3b0e2b1a3251 100644 --- a/sound/soc/intel/avs/boards/rt298.c +++ b/sound/soc/intel/avs/boards/rt298.c @@ -178,38 +178,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, RT298_CODEC_DAI); @@ -227,14 +195,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_rt298_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -246,12 +213,6 @@ static int avs_rt298_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -268,8 +229,8 @@ static int avs_rt298_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From 96b5452fe43c23451050b3efa5197bd10dce9bc6 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:57 +0800 Subject: ASoC: Intel: avs-rt5682: remove redundant dapm routes Two routes "AIF1 Playback<-sspX Tx" and "sspX Rx<-AIF1 Capture" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-12-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/rt5682.c | 45 +++---------------------------------- 1 file changed, 3 insertions(+), 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c index b2c2ba93dcb5..7142a67900bf 100644 --- a/sound/soc/intel/avs/boards/rt5682.c +++ b/sound/soc/intel/avs/boards/rt5682.c @@ -234,38 +234,6 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 2; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "AIF1 Capture"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_card_suspend_pre(struct snd_soc_card *card) { struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, AVS_RT5682_CODEC_DAI_NAME); @@ -283,14 +251,13 @@ static int avs_card_resume_post(struct snd_soc_card *card) static int avs_rt5682_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct snd_soc_jack *jack; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; if (pdev->id_entry && pdev->id_entry->driver_data) avs_rt5682_quirk = (unsigned long)pdev->id_entry->driver_data; @@ -308,12 +275,6 @@ static int avs_rt5682_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - jack = devm_kzalloc(dev, sizeof(*jack), GFP_KERNEL); card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!jack || !card) @@ -330,8 +291,8 @@ static int avs_rt5682_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; snd_soc_card_set_drvdata(card, jack); -- cgit v1.2.3 From 51bdf6ebe5b7da8d1b86cf66fe7e21de353e5218 Mon Sep 17 00:00:00 2001 From: Brent Lu Date: Mon, 12 Jun 2023 19:09:58 +0800 Subject: ASoC: Intel: avs-ssm4567: remove redundant dapm routes Four routes "Left Playback<-sspX Tx", "Right Playback<-sspX Tx", "sspX Rx<-Left Capture Sense", and "sspX Rx<-Right Capture Sense" are created by snd_soc_dapm_connect_dai_link_widgets() automatically. Remove the duplicate routes. Signed-off-by: Brent Lu Link: https://lore.kernel.org/r/20230612110958.592674-13-brent.lu@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/avs/boards/ssm4567.c | 57 ++---------------------------------- 1 file changed, 3 insertions(+), 54 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c index 2b7f5ad92aca..7324869d6132 100644 --- a/sound/soc/intel/avs/boards/ssm4567.c +++ b/sound/soc/intel/avs/boards/ssm4567.c @@ -129,59 +129,14 @@ static int avs_create_dai_link(struct device *dev, const char *platform_name, in return 0; } -static int avs_create_dapm_routes(struct device *dev, int ssp_port, - struct snd_soc_dapm_route **routes, int *num_routes) -{ - struct snd_soc_dapm_route *dr; - const int num_base = ARRAY_SIZE(card_base_routes); - const int num_dr = num_base + 4; - int idx; - - dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); - if (!dr) - return -ENOMEM; - - memcpy(dr, card_base_routes, num_base * sizeof(*dr)); - - idx = num_base; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Left Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "Right Playback"); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Left Capture Sense"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - idx++; - dr[idx].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); - dr[idx].source = devm_kasprintf(dev, GFP_KERNEL, "Right Capture Sense"); - if (!dr[idx].sink || !dr[idx].source) - return -ENOMEM; - - *routes = dr; - *num_routes = num_dr; - - return 0; -} - static int avs_ssm4567_probe(struct platform_device *pdev) { - struct snd_soc_dapm_route *routes; struct snd_soc_dai_link *dai_link; struct snd_soc_acpi_mach *mach; struct snd_soc_card *card; struct device *dev = &pdev->dev; const char *pname; - int num_routes, ssp_port, ret; + int ssp_port, ret; mach = dev_get_platdata(dev); pname = mach->mach_params.platform; @@ -193,12 +148,6 @@ static int avs_ssm4567_probe(struct platform_device *pdev) return ret; } - ret = avs_create_dapm_routes(dev, ssp_port, &routes, &num_routes); - if (ret) { - dev_err(dev, "Failed to create dapm routes: %d", ret); - return ret; - } - card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); if (!card) return -ENOMEM; @@ -214,8 +163,8 @@ static int avs_ssm4567_probe(struct platform_device *pdev) card->num_controls = ARRAY_SIZE(card_controls); card->dapm_widgets = card_widgets; card->num_dapm_widgets = ARRAY_SIZE(card_widgets); - card->dapm_routes = routes; - card->num_dapm_routes = num_routes; + card->dapm_routes = card_base_routes; + card->num_dapm_routes = ARRAY_SIZE(card_base_routes); card->fully_routed = true; card->disable_route_checks = true; -- cgit v1.2.3 From a46d37012a5be1737393b8f82fd35665e4556eee Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Mon, 12 Jun 2023 11:05:31 +0200 Subject: ASoC: mediatek: mt8173: Fix snd_soc_component_initialize error path If the second component fails to initialize, cleanup the first on. Reported-by: Dan Carpenter Cc: stable@kernel.org Fixes: f1b5bf07365d ("ASoC: mt2701/mt8173: replace platform to component") Signed-off-by: Ricardo Ribalda Delgado Reviewed-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20230612-mt8173-fixup-v2-1-432aa99ce24d@chromium.org Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index f93c2ec8beb7..ff25c44070a3 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1156,14 +1156,14 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) comp_hdmi = devm_kzalloc(&pdev->dev, sizeof(*comp_hdmi), GFP_KERNEL); if (!comp_hdmi) { ret = -ENOMEM; - goto err_pm_disable; + goto err_cleanup_components; } ret = snd_soc_component_initialize(comp_hdmi, &mt8173_afe_hdmi_dai_component, &pdev->dev); if (ret) - goto err_pm_disable; + goto err_cleanup_components; #ifdef CONFIG_DEBUG_FS comp_hdmi->debugfs_prefix = "hdmi"; -- cgit v1.2.3 From f9c058d14f4fe23ef523a7ff73734d51c151683c Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Delgado Date: Mon, 12 Jun 2023 11:05:32 +0200 Subject: ASoC: mediatek: mt8173: Fix irq error path After reordering the irq probe, the error path was not properly done. Lets fix it. Reported-by: Dan Carpenter Cc: stable@kernel.org Fixes: 4cbb264d4e91 ("ASoC: mediatek: mt8173: Enable IRQ when pdata is ready") Signed-off-by: Ricardo Ribalda Delgado Reviewed-by: AngeloGioacchino Del Regno Link: https://lore.kernel.org/r/20230612-mt8173-fixup-v2-2-432aa99ce24d@chromium.org Signed-off-by: Mark Brown --- sound/soc/mediatek/mt8173/mt8173-afe-pcm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c index ff25c44070a3..06269f7e3756 100644 --- a/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c +++ b/sound/soc/mediatek/mt8173/mt8173-afe-pcm.c @@ -1070,6 +1070,10 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) afe->dev = &pdev->dev; + irq_id = platform_get_irq(pdev, 0); + if (irq_id <= 0) + return irq_id < 0 ? irq_id : -ENXIO; + afe->base_addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(afe->base_addr)) return PTR_ERR(afe->base_addr); @@ -1175,14 +1179,11 @@ static int mt8173_afe_pcm_dev_probe(struct platform_device *pdev) if (ret) goto err_cleanup_components; - irq_id = platform_get_irq(pdev, 0); - if (irq_id <= 0) - return irq_id < 0 ? irq_id : -ENXIO; ret = devm_request_irq(afe->dev, irq_id, mt8173_afe_irq_handler, 0, "Afe_ISR_Handle", (void *)afe); if (ret) { dev_err(afe->dev, "could not request_irq\n"); - goto err_pm_disable; + goto err_cleanup_components; } dev_info(&pdev->dev, "MT8173 AFE driver initialized.\n"); -- cgit v1.2.3 From 947e3960a72aa51d8643098851534baa5cc6538b Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 12 Jun 2023 09:06:08 +0200 Subject: ASoC: Switch two more i2c drivers back to use .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous conversion back to .probe() applied in commit 9abcd24002bf ("ASoC: Switch i2c drivers back to use .probe()") was created based on v6.3. Since then two more drivers were added which need to be convert back in the same way before eventually .probe_new() can be dropped from struct i2c_driver. Signed-off-by: Uwe Kleine-König Link: https://lore.kernel.org/r/20230612070608.836186-1-u.kleine-koenig@pengutronix.de Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l56-i2c.c | 2 +- sound/soc/codecs/ssm3515.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l56-i2c.c b/sound/soc/codecs/cs35l56-i2c.c index 295caad26224..ed2a41943d97 100644 --- a/sound/soc/codecs/cs35l56-i2c.c +++ b/sound/soc/codecs/cs35l56-i2c.c @@ -68,7 +68,7 @@ static struct i2c_driver cs35l56_i2c_driver = { .pm = &cs35l56_pm_ops_i2c_spi, }, .id_table = cs35l56_id_i2c, - .probe_new = cs35l56_i2c_probe, + .probe = cs35l56_i2c_probe, .remove = cs35l56_i2c_remove, }; diff --git a/sound/soc/codecs/ssm3515.c b/sound/soc/codecs/ssm3515.c index 784e890031a4..008cb3eb5758 100644 --- a/sound/soc/codecs/ssm3515.c +++ b/sound/soc/codecs/ssm3515.c @@ -439,7 +439,7 @@ static struct i2c_driver ssm3515_i2c_driver = { .name = "ssm3515", .of_match_table = of_match_ptr(ssm3515_of_match), }, - .probe_new = ssm3515_i2c_probe, + .probe = ssm3515_i2c_probe, }; module_i2c_driver(ssm3515_i2c_driver); -- cgit v1.2.3 From fd6f223639b834d169218ff3f53e2f6e9f8c1790 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 12 Jun 2023 01:59:34 +0000 Subject: ASoC: audio-graph-card2-custom-sample.dtsi: remove DT warning Current audio-graph-card2-custom-sample.dtsi is missing address-cells / size-cells / reg. Thus it get too many DT warnings. This patch solved it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87a5x5qw3d.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- .../generic/audio-graph-card2-custom-sample.dtsi | 151 ++++++++++++++------- 1 file changed, 101 insertions(+), 50 deletions(-) (limited to 'sound') diff --git a/sound/soc/generic/audio-graph-card2-custom-sample.dtsi b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi index f5e7d669da45..2ac0de3c21da 100644 --- a/sound/soc/generic/audio-graph-card2-custom-sample.dtsi +++ b/sound/soc/generic/audio-graph-card2-custom-sample.dtsi @@ -172,83 +172,127 @@ >; multi { + #address-cells = <1>; + #size-cells = <0>; + ports@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; /* [Multi-CPU] */ - mcpu0: port@0 { mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; - port@1 { mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; - port@2 { mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; + mcpu0: port@0 { reg = <0>; mcpu0_ep: endpoint { remote-endpoint = <&mcodec0_ep>; }; }; + port@1 { reg = <1>; mcpu1_ep: endpoint { remote-endpoint = <&cpu1_ep>; }; }; + port@2 { reg = <2>; mcpu2_ep: endpoint { remote-endpoint = <&cpu2_ep>; }; }; }; /* [Multi-Codec] */ ports@1 { - port@0 { mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; - port@1 { mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; - port@2 { mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + port@0 { reg = <0>; mcodec0_ep: endpoint { remote-endpoint = <&mcpu0_ep>; }; }; + port@1 { reg = <1>; mcodec1_ep: endpoint { remote-endpoint = <&codec1_ep>; }; }; + port@2 { reg = <2>; mcodec2_ep: endpoint { remote-endpoint = <&codec2_ep>; }; }; }; /* [DPCM-Multi]::BE */ ports@2 { - port@0 { mbe_ep: endpoint { remote-endpoint = <&be10_ep>; }; }; - port@1 { mbe1_ep: endpoint { remote-endpoint = <&codec4_ep>; }; }; - port@2 { mbe2_ep: endpoint { remote-endpoint = <&codec5_ep>; }; }; + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + port@0 { reg = <0>; mbe_ep: endpoint { remote-endpoint = <&be10_ep>; }; }; + port@1 { reg = <1>; mbe1_ep: endpoint { remote-endpoint = <&codec4_ep>; }; }; + port@2 { reg = <2>; mbe2_ep: endpoint { remote-endpoint = <&codec5_ep>; }; }; }; /* [Codec2Codec-Multi]::CPU */ ports@3 { - port@0 { mc2c0_ep: endpoint { remote-endpoint = <&c2cmf_ep>; }; }; - port@1 { mc2c00_ep: endpoint { remote-endpoint = <&codec8_ep>; }; }; - port@2 { mc2c01_ep: endpoint { remote-endpoint = <&codec9_ep>; }; }; + reg = <3>; + #address-cells = <1>; + #size-cells = <0>; + port@0 { reg = <0>; mc2c0_ep: endpoint { remote-endpoint = <&c2cmf_ep>; }; }; + port@1 { reg = <1>; mc2c00_ep: endpoint { remote-endpoint = <&codec8_ep>; }; }; + port@2 { reg = <2>; mc2c01_ep: endpoint { remote-endpoint = <&codec9_ep>; }; }; }; /* [Codec2Codec-Multi]::Codec */ ports@4 { - port@0 { mc2c1_ep: endpoint { remote-endpoint = <&c2cmb_ep>; }; }; - port@1 { mc2c10_ep: endpoint { remote-endpoint = <&codec10_ep>; }; }; - port@2 { mc2c11_ep: endpoint { remote-endpoint = <&codec11_ep>; }; }; + reg = <4>; + #address-cells = <1>; + #size-cells = <0>; + port@0 { reg = <0>; mc2c1_ep: endpoint { remote-endpoint = <&c2cmb_ep>; }; }; + port@1 { reg = <1>; mc2c10_ep: endpoint { remote-endpoint = <&codec10_ep>; }; }; + port@2 { reg = <2>; mc2c11_ep: endpoint { remote-endpoint = <&codec11_ep>; }; }; }; /* [Semi-Multi] */ ports@5 { - port@0 { smcodec0_ep: endpoint { remote-endpoint = <&cpu7_ep>; }; }; - port@1 { smcodec1_ep: endpoint { remote-endpoint = <&codec12_ep>; }; }; - port@2 { smcodec2_ep: endpoint { remote-endpoint = <&codec13_ep>; }; }; + reg = <5>; + #address-cells = <1>; + #size-cells = <0>; + port@0 { reg = <0>; smcodec0_ep: endpoint { remote-endpoint = <&cpu7_ep>; }; }; + port@1 { reg = <1>; smcodec1_ep: endpoint { remote-endpoint = <&codec12_ep>; }; }; + port@2 { reg = <2>; smcodec2_ep: endpoint { remote-endpoint = <&codec13_ep>; }; }; }; }; dpcm { + #address-cells = <1>; + #size-cells = <0>; + ports@0 { + reg = <0>; + + #address-cells = <1>; + #size-cells = <0>; /* [DPCM]::FE */ - fe00: port@0 { fe00_ep: endpoint { remote-endpoint = <&cpu3_ep>; }; }; - fe01: port@1 { fe01_ep: endpoint { remote-endpoint = <&cpu4_ep>; }; }; + fe00: port@0 { reg = <0>; fe00_ep: endpoint { remote-endpoint = <&cpu3_ep>; }; }; + fe01: port@1 { reg = <1>; fe01_ep: endpoint { remote-endpoint = <&cpu4_ep>; }; }; /* [DPCM-Multi]::FE */ - fe10: port@2 { fe10_ep: endpoint { remote-endpoint = <&cpu5_ep>; }; }; - fe11: port@3 { fe11_ep: endpoint { remote-endpoint = <&cpu6_ep>; }; }; + fe10: port@2 { reg = <2>; fe10_ep: endpoint { remote-endpoint = <&cpu5_ep>; }; }; + fe11: port@3 { reg = <3>; fe11_ep: endpoint { remote-endpoint = <&cpu6_ep>; }; }; }; ports@1 { + reg = <1>; + + #address-cells = <1>; + #size-cells = <0>; /* [DPCM]::BE */ - be0: port@0 { be00_ep: endpoint { remote-endpoint = <&codec3_ep>; }; }; + be0: port@0 { reg = <0>; be00_ep: endpoint { remote-endpoint = <&codec3_ep>; }; }; /* [DPCM-Multi]::BE */ - be1: port@1 { be10_ep: endpoint { remote-endpoint = <&mbe_ep>; }; }; + be1: port@1 { reg = <1>; be10_ep: endpoint { remote-endpoint = <&mbe_ep>; }; }; }; }; codec2codec { + #address-cells = <1>; + #size-cells = <0>; /* [Codec2Codec] */ ports@0 { + reg = <0>; + + #address-cells = <1>; + #size-cells = <0>; + /* use default settings */ - c2c: port@0 { c2cf_ep: endpoint { remote-endpoint = <&codec6_ep>; }; }; - port@1 { c2cb_ep: endpoint { remote-endpoint = <&codec7_ep>; }; }; + c2c: port@0 { reg = <0>; c2cf_ep: endpoint { remote-endpoint = <&codec6_ep>; }; }; + port@1 { reg = <1>; c2cb_ep: endpoint { remote-endpoint = <&codec7_ep>; }; }; }; /* [Codec2Codec-Multi] */ ports@1 { + reg = <1>; + + #address-cells = <1>; + #size-cells = <0>; + /* use original settings */ rate = <48000>; - c2c_m: port@0 { c2cmf_ep: endpoint { remote-endpoint = <&mc2c0_ep>; }; }; - port@1 { c2cmb_ep: endpoint { remote-endpoint = <&mc2c1_ep>; }; }; + c2c_m: port@0 { reg = <0>; c2cmf_ep: endpoint { remote-endpoint = <&mc2c0_ep>; }; }; + port@1 { reg = <1>; c2cmb_ep: endpoint { remote-endpoint = <&mc2c1_ep>; }; }; }; }; }; @@ -264,25 +308,28 @@ */ compatible = "test-cpu"; ports { + #address-cells = <1>; + #size-cells = <0>; + bitclock-master; frame-master; /* [Normal] */ - cpu0: port@0 { cpu0_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; + cpu0: port@0 { reg = <0>; cpu0_ep: endpoint { remote-endpoint = <&codec0_ep>; }; }; /* [Multi-CPU] */ - port@1 { cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; }; - port@2 { cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; }; + port@1 { reg = <1>; cpu1_ep: endpoint { remote-endpoint = <&mcpu1_ep>; }; }; + port@2 { reg = <2>; cpu2_ep: endpoint { remote-endpoint = <&mcpu2_ep>; }; }; /* [DPCM]::FE */ - port@3 { cpu3_ep: endpoint { remote-endpoint = <&fe00_ep>; }; }; - port@4 { cpu4_ep: endpoint { remote-endpoint = <&fe01_ep>; }; }; + port@3 { reg = <3>; cpu3_ep: endpoint { remote-endpoint = <&fe00_ep>; }; }; + port@4 { reg = <4>; cpu4_ep: endpoint { remote-endpoint = <&fe01_ep>; }; }; /* [DPCM-Multi]::FE */ - port@5 { cpu5_ep: endpoint { remote-endpoint = <&fe10_ep>; }; }; - port@6 { cpu6_ep: endpoint { remote-endpoint = <&fe11_ep>; }; }; + port@5 { reg = <5>; cpu5_ep: endpoint { remote-endpoint = <&fe10_ep>; }; }; + port@6 { reg = <6>; cpu6_ep: endpoint { remote-endpoint = <&fe11_ep>; }; }; /* [Semi-Multi] */ - sm0: port@7 { cpu7_ep: endpoint { remote-endpoint = <&smcodec0_ep>; }; }; + sm0: port@7 { reg = <7>; cpu7_ep: endpoint { remote-endpoint = <&smcodec0_ep>; }; }; }; }; @@ -297,6 +344,9 @@ */ compatible = "test-codec"; ports { + #address-cells = <1>; + #size-cells = <0>; + /* * prefix can be added to *component*, * see audio-graph-card2::routing @@ -304,39 +354,40 @@ prefix = "TC"; /* [Normal] */ - port@0 { codec0_ep: endpoint { remote-endpoint = <&cpu0_ep>; }; }; + port@0 { reg = <0>; codec0_ep: endpoint { remote-endpoint = <&cpu0_ep>; }; }; /* [Multi-Codec] */ - port@1 { codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; }; - port@2 { codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; }; + port@1 { reg = <1>; codec1_ep: endpoint { remote-endpoint = <&mcodec1_ep>; }; }; + port@2 { reg = <2>; codec2_ep: endpoint { remote-endpoint = <&mcodec2_ep>; }; }; /* [DPCM]::BE */ port@3 { convert-rate = <44100>; - codec3_ep: endpoint { remote-endpoint = <&be00_ep>; }; + reg = <3>; codec3_ep: endpoint { remote-endpoint = <&be00_ep>; }; }; /* [DPCM-Multi]::BE */ - port@4 { codec4_ep: endpoint { remote-endpoint = <&mbe1_ep>; }; }; - port@5 { codec5_ep: endpoint { remote-endpoint = <&mbe2_ep>; }; }; + port@4 { reg = <4>; codec4_ep: endpoint { remote-endpoint = <&mbe1_ep>; }; }; + port@5 { reg = <5>; codec5_ep: endpoint { remote-endpoint = <&mbe2_ep>; }; }; /* [Codec2Codec] */ port@6 { bitclock-master; frame-master; - codec6_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; - port@7 { codec7_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; + reg = <6>; codec6_ep: endpoint { remote-endpoint = <&c2cf_ep>; }; }; + port@7 { reg = <7>; codec7_ep: endpoint { remote-endpoint = <&c2cb_ep>; }; }; /* [Codec2Codec-Multi] */ port@8 { bitclock-master; frame-master; - codec8_ep: endpoint { remote-endpoint = <&mc2c00_ep>; }; }; - port@9 { codec9_ep: endpoint { remote-endpoint = <&mc2c01_ep>; }; }; - port@10 { codec10_ep: endpoint { remote-endpoint = <&mc2c10_ep>; }; }; - port@11 { codec11_ep: endpoint { remote-endpoint = <&mc2c11_ep>; }; }; + reg = <8>; codec8_ep: endpoint { remote-endpoint = <&mc2c00_ep>; }; }; + port@9 { reg = <9>; codec9_ep: endpoint { remote-endpoint = <&mc2c01_ep>; }; }; + port@a { reg = <10>; codec10_ep: endpoint { remote-endpoint = <&mc2c10_ep>; }; }; + port@b { reg = <11>; codec11_ep: endpoint { remote-endpoint = <&mc2c11_ep>; }; }; /* [Semi-Multi] */ - port@12 { codec12_ep: endpoint { remote-endpoint = <&smcodec1_ep>; }; }; - port@13 { codec13_ep: endpoint { remote-endpoint = <&smcodec2_ep>; }; }; + port@c { reg = <12>; codec12_ep: endpoint { remote-endpoint = <&smcodec1_ep>; }; }; + port@d { reg = <13>; codec13_ep: endpoint { remote-endpoint = <&smcodec2_ep>; }; }; + }; }; }; -- cgit v1.2.3 From e375b8a045873cf5fb8bf61bf9a0ddfcd484243a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:45 +0200 Subject: ALSA: ump: Add more attributes to UMP EP and FB info Add a few more fields to snd_ump_endpoint_info and snd_ump_block_info that are added in the new v1.1 spec. Those are filled by the UMP Stream messages. The rawmidi protocol version is bumped to 2.0.4 to indicate those updates. Also, update the proc outputs to show the newly introduced fields. Link: https://lore.kernel.org/r/20230612081054.17200-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 18 +++++++++++++++--- sound/core/ump.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 5c5f41dd4001..79ee48b2ed6d 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -708,7 +708,7 @@ enum { * Raw MIDI section - /dev/snd/midi?? */ -#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3) +#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 4) enum { SNDRV_RAWMIDI_STREAM_OUTPUT = 0, @@ -797,7 +797,11 @@ struct snd_ump_endpoint_info { unsigned int protocol; /* current protocol */ unsigned int num_blocks; /* # of function blocks */ unsigned short version; /* UMP major/minor version */ - unsigned short padding[7]; + unsigned short family_id; /* MIDI device family ID */ + unsigned short model_id; /* MIDI family model ID */ + unsigned int manufacturer_id; /* MIDI manufacturer ID */ + unsigned char sw_revision[4]; /* software revision */ + unsigned short padding; unsigned char name[128]; /* endpoint name string */ unsigned char product_id[128]; /* unique product id string */ unsigned char reserved[32]; @@ -812,6 +816,12 @@ struct snd_ump_endpoint_info { #define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */ #define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */ +/* UMP block user-interface hint */ +#define SNDRV_UMP_BLOCK_UI_HINT_UNKNOWN 0x00 +#define SNDRV_UMP_BLOCK_UI_HINT_RECEIVER 0x01 +#define SNDRV_UMP_BLOCK_UI_HINT_SENDER 0x02 +#define SNDRV_UMP_BLOCK_UI_HINT_BOTH 0x03 + /* UMP groups and blocks */ #define SNDRV_UMP_MAX_GROUPS 16 #define SNDRV_UMP_MAX_BLOCKS 32 @@ -825,7 +835,9 @@ struct snd_ump_block_info { unsigned char active; /* Activeness */ unsigned char first_group; /* first group ID */ unsigned char num_groups; /* number of groups */ - unsigned char padding[3]; + unsigned char midi_ci_version; /* MIDI-CI support version */ + unsigned char sysex8_streams; /* max number of sysex8 streams */ + unsigned char ui_hint; /* user interface hint */ unsigned int flags; /* various info flags */ unsigned char name[128]; /* block name string */ unsigned char reserved[32]; diff --git a/sound/core/ump.c b/sound/core/ump.c index 69993cad6772..839873fb0f33 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -448,6 +448,20 @@ static const char *ump_direction_string(int dir) } } +static const char *ump_ui_hint_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER: + return "receiver"; + case SNDRV_UMP_BLOCK_UI_HINT_SENDER: + return "sender"; + case SNDRV_UMP_BLOCK_UI_HINT_BOTH: + return "both"; + default: + return "unknown"; + } +} + /* Additional proc file output */ static void snd_ump_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) @@ -461,6 +475,17 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); + if (ump->info.version) { + snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n", + ump->info.manufacturer_id); + snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id); + snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id); + snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n", + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + } snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); list_for_each_entry(fb, &ump->block_list, list) { @@ -476,6 +501,14 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, snd_iprintf(buffer, " Is MIDI1: %s%s\n", (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); + if (ump->info.version) { + snd_iprintf(buffer, " MIDI-CI Version: %d\n", + fb->info.midi_ci_version); + snd_iprintf(buffer, " Sysex8 Streams: %d\n", + fb->info.sysex8_streams); + snd_iprintf(buffer, " UI Hint: %s\n", + ump_ui_hint_string(fb->info.ui_hint)); + } snd_iprintf(buffer, "\n"); } } -- cgit v1.2.3 From 37e0e14128e0685267dc5c037bf655421a6ce2ea Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:46 +0200 Subject: ALSA: ump: Support UMP Endpoint and Function Block parsing This patch adds the basic support for UMP Endpoint and UMP Function Block parsing, which are extended in the new UMP v1.1 spec. The patch provides a new helper function to perform the query of the UMP Endpoint information and builds up the UMP blocks based on UMP Function Block information. For the communication over the UMP Endpoint, it opens the rawmidi device once internally, inquiries the UMP Endpoint and Function Block info by sending new UMP Stream messages, and waits for the response for each query. The new UMP spec allows to update the FB info and change its associated groups or its activeness on the fly, too. For catching it, the UMP core keeps watching the incoming UMP messages, and snd_ump_receive() handles the incoming UMP Stream messages to refresh the FB info. Link: https://lore.kernel.org/r/20230612081054.17200-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 83 +++++++++++ include/sound/ump_msg.h | 225 +++++++++++++++++++++++++++++ sound/core/ump.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 684 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index e4fdf7cccf12..aef4748842d0 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -24,6 +24,13 @@ struct snd_ump_endpoint { void *private_data; void (*private_free)(struct snd_ump_endpoint *ump); + /* UMP Stream message processing */ + u32 stream_wait_for; /* expected stream message status */ + bool stream_finished; /* set when message has been processed */ + bool parsed; /* UMP / FB parse finished? */ + wait_queue_head_t stream_wait; + struct snd_rawmidi_file stream_rfile; + struct list_head block_list; /* list of snd_ump_block objects */ /* intermediate buffer for UMP input */ @@ -80,6 +87,7 @@ struct snd_ump_block { int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, int output, int input, struct snd_ump_endpoint **ump_ret); +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump); int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, unsigned int direction, unsigned int first_group, unsigned int num_groups, struct snd_ump_block **blk_ret); @@ -109,6 +117,8 @@ enum { UMP_MSG_TYPE_DATA = 0x03, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04, UMP_MSG_TYPE_EXTENDED_DATA = 0x05, + UMP_MSG_TYPE_FLEX_DATA = 0x0d, + UMP_MSG_TYPE_STREAM = 0x0f, }; /* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */ @@ -119,6 +129,62 @@ enum { UMP_SYSEX_STATUS_END = 3, }; +/* UMP Utility Type Status (type 0x0) */ +enum { + UMP_UTILITY_MSG_STATUS_NOOP = 0x00, + UMP_UTILITY_MSG_STATUS_JR_CLOCK = 0x01, + UMP_UTILITY_MSG_STATUS_JR_TSTAMP = 0x02, + UMP_UTILITY_MSG_STATUS_DCTPQ = 0x03, + UMP_UTILITY_MSG_STATUS_DC = 0x04, +}; + +/* UMP Stream Message Status (type 0xf) */ +enum { + UMP_STREAM_MSG_STATUS_EP_DISCOVERY = 0x00, + UMP_STREAM_MSG_STATUS_EP_INFO = 0x01, + UMP_STREAM_MSG_STATUS_DEVICE_INFO = 0x02, + UMP_STREAM_MSG_STATUS_EP_NAME = 0x03, + UMP_STREAM_MSG_STATUS_PRODUCT_ID = 0x04, + UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST = 0x05, + UMP_STREAM_MSG_STATUS_STREAM_CFG = 0x06, + UMP_STREAM_MSG_STATUS_FB_DISCOVERY = 0x10, + UMP_STREAM_MSG_STATUS_FB_INFO = 0x11, + UMP_STREAM_MSG_STATUS_FB_NAME = 0x12, + UMP_STREAM_MSG_STATUS_START_CLIP = 0x20, + UMP_STREAM_MSG_STATUS_END_CLIP = 0x21, +}; + +/* UMP Endpoint Discovery filter bitmap */ +enum { + UMP_STREAM_MSG_REQUEST_EP_INFO = (1U << 0), + UMP_STREAM_MSG_REQUEST_DEVICE_INFO = (1U << 1), + UMP_STREAM_MSG_REQUEST_EP_NAME = (1U << 2), + UMP_STREAM_MSG_REQUEST_PRODUCT_ID = (1U << 3), + UMP_STREAM_MSG_REQUEST_STREAM_CFG = (1U << 4), +}; + +/* UMP Function Block Discovery filter bitmap */ +enum { + UMP_STREAM_MSG_REQUEST_FB_INFO = (1U << 0), + UMP_STREAM_MSG_REQUEST_FB_NAME = (1U << 1), +}; + +/* UMP Endpoint Info capability bits (used for protocol request/notify, too) */ +enum { + UMP_STREAM_MSG_EP_INFO_CAP_TXJR = (1U << 0), /* Sending JRTS */ + UMP_STREAM_MSG_EP_INFO_CAP_RXJR = (1U << 1), /* Receiving JRTS */ + UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 = (1U << 8), /* MIDI 1.0 */ + UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 = (1U << 9), /* MIDI 2.0 */ +}; + +/* UMP EP / FB name string format; same as SysEx string handling */ +enum { + UMP_STREAM_MSG_FORMAT_SINGLE = 0, + UMP_STREAM_MSG_FORMAT_START = 1, + UMP_STREAM_MSG_FORMAT_CONTINUE = 2, + UMP_STREAM_MSG_FORMAT_END = 3, +}; + /* * Helpers for retrieving / filling bits from UMP */ @@ -172,4 +238,21 @@ static inline unsigned char ump_sysex_message_length(u32 data) return (data >> 16) & 0xf; } +/* For Stream Messages */ +static inline unsigned char ump_stream_message_format(u32 data) +{ + return (data >> 26) & 0x03; +} + +static inline unsigned int ump_stream_message_status(u32 data) +{ + return (data >> 16) & 0x3ff; +} + +static inline u32 ump_stream_compose(unsigned char status, unsigned short form) +{ + return (UMP_MSG_TYPE_STREAM << 28) | ((u32)form << 26) | + ((u32)status << 16); +} + #endif /* __SOUND_UMP_H */ diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h index a594ef951b54..72f60ddfea75 100644 --- a/include/sound/ump_msg.h +++ b/include/sound/ump_msg.h @@ -537,4 +537,229 @@ union snd_ump_midi2_msg { u32 raw[2]; }; +/* UMP Stream Message: Endpoint Discovery (128bit) */ +struct snd_ump_stream_msg_ep_discovery { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 ump_version_major:8; + u32 ump_version_minor:8; + /* 1 */ + u32 reserved:24; + u32 filter_bitmap:8; + /* 2-3 */ + u32 reserved2[2]; +#else + /* 0 */ + u32 ump_version_minor:8; + u32 ump_version_major:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 filter_bitmap:8; + u32 reserved:24; + /* 2-3 */ + u32 reserved2[2]; +#endif +} __packed; + +/* UMP Stream Message: Endpoint Info Notification (128bit) */ +struct snd_ump_stream_msg_ep_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 ump_version_major:8; + u32 ump_version_minor:8; + /* 1 */ + u32 static_function_block:1; + u32 num_function_blocks:7; + u32 reserved:8; + u32 protocol:8; + u32 reserved2:6; + u32 jrts:2; + /* 2-3 */ + u32 reserved3[2]; +#else + /* 0 */ + u32 ump_version_minor:8; + u32 ump_version_major:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 jrts:2; + u32 reserved2:6; + u32 protocol:8; + u32 reserved:8; + u32 num_function_blocks:7; + u32 static_function_block:1; + /* 2-3 */ + u32 reserved3[2]; +#endif +} __packed; + +/* UMP Stream Message: Device Info Notification (128bit) */ +struct snd_ump_stream_msg_devince_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 reserved:16; + /* 1 */ + u32 manufacture_id; + /* 2 */ + u8 family_lsb; + u8 family_msb; + u8 model_lsb; + u8 model_msb; + /* 3 */ + u32 sw_revision; +#else + /* 0 */ + u32 reserved:16; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 manufacture_id; + /* 2 */ + u8 model_msb; + u8 model_lsb; + u8 family_msb; + u8 family_lsb; + /* 3 */ + u32 sw_revision; +#endif +} __packed; + +/* UMP Stream Message: Stream Config Request / Notification (128bit) */ +struct snd_ump_stream_msg_stream_cfg { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 protocol:8; + u32 reserved:6; + u32 jrts:2; + /* 1-3 */ + u32 reserved2[3]; +#else + /* 0 */ + u32 jrts:2; + u32 reserved:6; + u32 protocol:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1-3 */ + u32 reserved2[3]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Discovery (128bit) */ +struct snd_ump_stream_msg_fb_discovery { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 function_block_id:8; + u32 filter:8; + /* 1-3 */ + u32 reserved[3]; +#else + /* 0 */ + u32 filter:8; + u32 function_block_id:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1-3 */ + u32 reserved[3]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Info Notification (128bit) */ +struct snd_ump_stream_msg_fb_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 active:1; + u32 function_block_id:7; + u32 reserved:2; + u32 ui_hint:2; + u32 midi_10:2; + u32 direction:2; + /* 1 */ + u32 first_group:8; + u32 num_groups:8; + u32 midi_ci_version:8; + u32 sysex8_streams:8; + /* 2-3 */ + u32 reserved2[2]; +#else + /* 0 */ + u32 direction:2; + u32 midi_10:2; + u32 ui_hint:2; + u32 reserved:2; + u32 function_block_id:7; + u32 active:1; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 sysex8_streams:8; + u32 midi_ci_version:8; + u32 num_groups:8; + u32 first_group:8; + /* 2-3 */ + u32 reserved2[2]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Name Notification (128bit) */ +struct snd_ump_stream_msg_fb_name { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u16 type:4; + u16 format:2; + u16 status:10; + u8 function_block_id; + u8 name0; + /* 1-3 */ + u8 name[12]; +#else + /* 0 */ + u8 name0; + u8 function_block_id; + u16 status:10; + u16 format:2; + u16 type:4; + /* 1-3 */ + u8 name[12]; // FIXME: byte order +#endif +} __packed; + +/* MIDI 2.0 Stream Messages (128bit) */ +union snd_ump_stream_msg { + struct snd_ump_stream_msg_ep_discovery ep_discovery; + struct snd_ump_stream_msg_ep_info ep_info; + struct snd_ump_stream_msg_devince_info device_info; + struct snd_ump_stream_msg_stream_cfg stream_cfg; + struct snd_ump_stream_msg_fb_discovery fb_discovery; + struct snd_ump_stream_msg_fb_info fb_info; + struct snd_ump_stream_msg_fb_name fb_name; + u32 raw[4]; +}; + #endif /* __SOUND_UMP_MSG_H */ diff --git a/sound/core/ump.c b/sound/core/ump.c index 839873fb0f33..7df50f0affe9 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -30,6 +30,8 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, int up); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) static int process_legacy_output(struct snd_ump_endpoint *ump, u32 *buffer, int count); @@ -133,6 +135,7 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); mutex_init(&ump->open_mutex); + init_waitqueue_head(&ump->stream_wait); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spin_lock_init(&ump->legacy_locks[0]); spin_lock_init(&ump->legacy_locks[1]); @@ -302,6 +305,7 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) n = snd_ump_receive_ump_val(ump, *p++); if (!n) continue; + ump_handle_stream_msg(ump, ump->input_buf, n); #if IS_ENABLED(CONFIG_SND_SEQUENCER) if (ump->seq_ops) ump->seq_ops->input_receive(ump, ump->input_buf, n); @@ -513,6 +517,378 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, } } +/* + * UMP endpoint and function block handling + */ + +/* open / close UMP streams for the internal stream msg communication */ +static int ump_request_open(struct snd_ump_endpoint *ump) +{ + return snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &ump->stream_rfile); +} + +static void ump_request_close(struct snd_ump_endpoint *ump) +{ + snd_rawmidi_kernel_release(&ump->stream_rfile); +} + +/* request a command and wait for the given response; + * @req1 and @req2 are u32 commands + * @reply is the expected UMP stream status + */ +static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2, + u32 reply) +{ + u32 buf[4]; + + ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n", + __func__, req1, req2, reply); + memset(buf, 0, sizeof(buf)); + buf[0] = req1; + buf[1] = req2; + ump->stream_finished = 0; + ump->stream_wait_for = reply; + snd_rawmidi_kernel_write(ump->stream_rfile.output, + (unsigned char *)&buf, 16); + wait_event_timeout(ump->stream_wait, ump->stream_finished, + msecs_to_jiffies(500)); + if (!READ_ONCE(ump->stream_finished)) { + ump_dbg(ump, "%s: request timed out\n", __func__); + return -ETIMEDOUT; + } + ump->stream_finished = 0; + ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + return 0; +} + +/* append the received letters via UMP packet to the given string buffer; + * return 1 if the full string is received or 0 to continue + */ +static int ump_append_string(struct snd_ump_endpoint *ump, char *dest, + int maxsize, const u32 *buf, int offset) +{ + unsigned char format; + int c; + + format = ump_stream_message_format(buf[0]); + if (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_START) { + c = 0; + } else { + c = strlen(dest); + if (c >= maxsize - 1) + return 1; + } + + for (; offset < 16; offset++) { + dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8; + if (!dest[c]) + break; + if (++c >= maxsize - 1) + break; + } + dest[c] = 0; + return (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_END); +} + +/* handle EP info stream message; update the UMP attributes */ +static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.version = (buf->ep_info.ump_version_major << 8) | + buf->ep_info.ump_version_minor; + ump->info.num_blocks = buf->ep_info.num_function_blocks; + if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) { + ump_info(ump, "Invalid function blocks %d, fallback to 1\n", + ump->info.num_blocks); + ump->info.num_blocks = 1; + } + + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | + buf->ep_info.jrts; + + ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n", + ump->info.version, ump->info.num_blocks, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* handle EP device info stream message; update the UMP attributes */ +static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f; + ump->info.family_id = (buf->device_info.family_msb << 8) | + buf->device_info.family_lsb; + ump->info.model_id = (buf->device_info.model_msb << 8) | + buf->device_info.model_lsb; + ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f; + ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f; + ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f; + ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f; + ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n", + ump->info.manufacturer_id, + ump->info.family_id, + ump->info.model_id, + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + return 1; /* finished */ +} + +/* handle EP name stream message; update the UMP name string */ +static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.name, sizeof(ump->info.name), + buf->raw, 2); +} + +/* handle EP product id stream message; update the UMP product_id string */ +static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.product_id, + sizeof(ump->info.product_id), + buf->raw, 2); +} + +/* handle EP stream config message; update the UMP protocol */ +static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.protocol = + (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; + ump_dbg(ump, "Current protocol = %x (caps = %x)\n", + ump->info.protocol, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* Extract Function Block info from UMP packet */ +static void fill_fb_info(struct snd_ump_endpoint *ump, + struct snd_ump_block_info *info, + const union snd_ump_stream_msg *buf) +{ + info->direction = buf->fb_info.direction; + info->ui_hint = buf->fb_info.ui_hint; + info->first_group = buf->fb_info.first_group; + info->num_groups = buf->fb_info.num_groups; + info->flags = buf->fb_info.midi_10; + info->active = buf->fb_info.active; + info->midi_ci_version = buf->fb_info.midi_ci_version; + info->sysex8_streams = buf->fb_info.sysex8_streams; + + ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n", + info->block_id, info->direction, info->active, + info->first_group, info->num_groups, info->midi_ci_version, + info->sysex8_streams, info->flags); +} + +/* handle FB info message; update FB info if the block is present */ +static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_info.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (fb) { + fill_fb_info(ump, &fb->info, buf); + } else if (ump->parsed) { + /* complain only if updated after parsing */ + ump_info(ump, "Function Block Info Update for non-existing block %d\n", + blk); + return -ENODEV; + } + return 1; /* finished */ +} + +/* handle FB name message; update the FB name string */ +static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_name.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (!fb) + return -ENODEV; + + return ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); +} + +static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) +{ + struct snd_ump_block *fb; + unsigned char direction, first_group, num_groups; + const union snd_ump_stream_msg *buf = + (const union snd_ump_stream_msg *)ump->input_buf; + u32 msg; + int err; + + /* query the FB info once */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get FB info for block %d\n", blk); + return err; + } + + /* the last input must be the FB info */ + if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) { + ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw); + return -EINVAL; + } + + direction = buf->fb_info.direction; + first_group = buf->fb_info.first_group; + num_groups = buf->fb_info.num_groups; + + err = snd_ump_block_new(ump, blk, direction, first_group, num_groups, + &fb); + if (err < 0) + return err; + + fill_fb_info(ump, &fb->info, buf); + + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME); + if (err) + ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk); + + return 0; +} + +/* handle stream messages, called from snd_ump_receive() */ +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size) +{ + const union snd_ump_stream_msg *msg; + unsigned int status; + int ret; + + BUILD_BUG_ON(sizeof(*msg) != 16); + ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", + buf[0], buf[1], buf[2], buf[3]); + + if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM) + return; + + msg = (const union snd_ump_stream_msg *)buf; + status = ump_stream_message_status(*buf); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_INFO: + ret = ump_handle_ep_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_DEVICE_INFO: + ret = ump_handle_device_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_EP_NAME: + ret = ump_handle_ep_name_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_PRODUCT_ID: + ret = ump_handle_product_id_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_STREAM_CFG: + ret = ump_handle_stream_cfg_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_INFO: + ret = ump_handle_fb_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_NAME: + ret = ump_handle_fb_name_msg(ump, msg); + break; + default: + return; + } + + /* when the message has been processed fully, wake up */ + if (ret > 0 && ump->stream_wait_for == status) { + WRITE_ONCE(ump->stream_finished, 1); + wake_up(&ump->stream_wait); + } +} + +/** + * snd_ump_parse_endpoint - parse endpoint and create function blocks + * @ump: UMP object + * + * Returns 0 for successful parse, -ENODEV if device doesn't respond + * (or the query is unsupported), or other error code for serious errors. + */ +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump) +{ + int blk, err; + u32 msg; + + if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + return -ENODEV; + + err = ump_request_open(ump); + if (err < 0) { + ump_dbg(ump, "Unable to open rawmidi device: %d\n", err); + return err; + } + + /* Check Endpoint Information */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) | + 0x0101; /* UMP version 1.1 */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO, + UMP_STREAM_MSG_STATUS_EP_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get UMP EP info\n"); + goto error; + } + + /* Request Endpoint Device Info */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO, + UMP_STREAM_MSG_STATUS_DEVICE_INFO); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP device info\n"); + + /* Request Endpoint Name */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME, + UMP_STREAM_MSG_STATUS_EP_NAME); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP name string\n"); + + /* Request Endpoint Product ID */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID, + UMP_STREAM_MSG_STATUS_PRODUCT_ID); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP product ID string\n"); + + /* Get the current stream configuration */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG, + UMP_STREAM_MSG_STATUS_STREAM_CFG); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP stream config\n"); + + /* Query and create blocks from Function Blocks */ + for (blk = 0; blk < ump->info.num_blocks; blk++) { + err = create_block_from_fb_info(ump, blk); + if (err < 0) + continue; + } + + error: + ump->parsed = true; + ump_request_close(ump); + if (err == -ETIMEDOUT) + err = -ENODEV; + return err; +} +EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint); + #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) /* * Legacy rawmidi support -- cgit v1.2.3 From 54852e8f401a70b3a0197737122be523639dc62f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:47 +0200 Subject: ALSA: usb-audio: Parse UMP Endpoint and Function Blocks at first Try to parse the UMP Endpoint and UMP Function Blocks for building the topology at first. Only when those are missing (e.g. on an older USB MIDI 2.0 spec or a unidirectional endpoint), the driver still creates blocks based on USB group terminal block information as fallback. Link: https://lore.kernel.org/r/20230612081054.17200-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 341783418a6a..fad094e15999 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -80,6 +80,7 @@ struct snd_usb_midi2_ump { struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ int index; /* rawmidi device index */ unsigned char usb_block_id; /* USB GTB id used for finding a pair */ + bool ump_parsed; /* Parsed UMP 1.1 EP/FB info*/ struct list_head list; /* list to umidi->rawmidi_list */ }; @@ -786,6 +787,31 @@ static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, return 0; } +/* Call UMP helper to parse UMP endpoints; + * this needs to be called after starting the input streams for bi-directional + * communications + */ +static int parse_ump_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump || + !(rmidi->ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + continue; + err = snd_ump_parse_endpoint(rmidi->ump); + if (!err) { + rmidi->ump_parsed = true; + } else { + if (err == -ENOMEM) + return err; + /* fall back to GTB later */ + } + } + return 0; +} + /* create a UMP block from a GTB entry */ static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) { @@ -856,7 +882,7 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) if (!rmidi->ump) continue; /* Blocks have been already created? */ - if (rmidi->ump->info.num_blocks) + if (rmidi->ump_parsed || rmidi->ump->info.num_blocks) continue; /* loop over GTBs */ for (dir = 0; dir < 2; dir++) { @@ -1110,6 +1136,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } + err = parse_ump_endpoints(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse UMP endpoint\n"); + goto error; + } + err = create_blocks_from_gtb(umidi); if (err < 0) { usb_audio_err(chip, "Failed to create GTB blocks\n"); -- cgit v1.2.3 From 960a1149c8fa70c221c70eaa13903ff873ba1873 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:48 +0200 Subject: ALSA: usb-audio: Add midi2_ump_probe option Add a new option to enable/disable the UMP Endpoint probing. Some firmware seems screwed up when such a new command issued, and this option allows user to suppress it. Link: https://lore.kernel.org/r/20230612081054.17200-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index fad094e15999..13fa1978267a 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -27,6 +27,10 @@ static bool midi2_enable = true; module_param(midi2_enable, bool, 0444); MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); +static bool midi2_ump_probe = true; +module_param(midi2_ump_probe, bool, 0444); +MODULE_PARM_DESC(midi2_ump_probe, "Probe UMP v1.1 support at first."); + /* stream direction; just shorter names */ enum { STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, @@ -1136,10 +1140,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } - err = parse_ump_endpoints(umidi); - if (err < 0) { - usb_audio_err(chip, "Failed to parse UMP endpoint\n"); - goto error; + if (midi2_ump_probe) { + err = parse_ump_endpoints(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse UMP endpoint\n"); + goto error; + } } err = create_blocks_from_gtb(umidi); -- cgit v1.2.3 From 5437ac9bad639bb9112e1a749acbe4a143562cdc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:49 +0200 Subject: ALSA: seq: ump: Handle groupless messages The UMP Utility and Stream messages are "groupless", i.e. an incoming groupless packet should be sent only to the UMP EP port, and the event with the groupless message is sent to UMP EP as is without the group translation per port. Also, the former reserved bit 0 for the client group filter is now used for groupless events. When the bit 0 is set, the groupless events are filtered out and skipped. Link: https://lore.kernel.org/r/20230612081054.17200-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 3 +++ include/uapi/sound/asequencer.h | 5 ++++- sound/core/seq/seq_ump_client.c | 5 ++++- sound/core/seq/seq_ump_convert.c | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index aef4748842d0..5b50a2fc0d79 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -255,4 +255,7 @@ static inline u32 ump_stream_compose(unsigned char status, unsigned short form) ((u32)status << 16); } +#define ump_is_groupless_msg(type) \ + ((type) == UMP_MSG_TYPE_UTILITY || (type) == UMP_MSG_TYPE_STREAM) + #endif /* __SOUND_UMP_H */ diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 5e91243665d8..b5bc8604efe8 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -362,7 +362,10 @@ struct snd_seq_client_info { int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ unsigned int midi_version; /* MIDI version */ - unsigned int group_filter; /* UMP group filter bitmap (for 1-based Group indices) */ + unsigned int group_filter; /* UMP group filter bitmap + * (bit 0 = groupless messages, + * bit 1-16 = messages for groups 1-16) + */ char reserved[48]; /* for future use */ }; diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index e24833804094..7739fb3ebf34 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -73,7 +73,10 @@ static void seq_ump_input_receive(struct snd_ump_endpoint *ump, if (!client->opened[STR_IN]) return; - ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); + if (ump_is_groupless_msg(ump_message_type(*val))) + ev.source.port = 0; /* UMP EP port */ + else + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; ev.flags = SNDRV_SEQ_EVENT_UMP; memcpy(ev.ump, val, words << 2); diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index 14ba6fed9dd1..eb1d86ff6166 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -534,6 +534,8 @@ static bool ump_event_filtered(struct snd_seq_client *dest, unsigned char group; group = ump_message_group(ev->ump[0]); + if (ump_is_groupless_msg(ump_message_type(ev->ump[0]))) + return dest->group_filter & (1U << 0); /* check the bitmap for 1-based group number */ return dest->group_filter & (1U << (group + 1)); } @@ -565,6 +567,7 @@ int snd_seq_deliver_from_ump(struct snd_seq_client *source, event, atomic, hop); /* non-EP port and different group is set? */ if (dest_port->ump_group && + !ump_is_groupless_msg(type) && ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) return deliver_with_group_convert(dest, dest_port, ump_ev, atomic, hop); -- cgit v1.2.3 From 4a16a3af05712e7fd5a205f34e2908055bd9fb5e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:50 +0200 Subject: ALSA: seq: ump: Handle FB info update This patch implements the handling of the dynamic update of FB info. When the FB info update is received after the initial parsing, it means the dynamic FB info update. We compare the result, and if the actual update is detected, it's notified via a new ops, notify_fb_change, to the sequencer client, and the corresponding sequencer ports are updated accordingly. Link: https://lore.kernel.org/r/20230612081054.17200-7-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 2 ++ sound/core/seq/seq_ump_client.c | 61 +++++++++++++++++++++++++++++++++++++++++ sound/core/ump.c | 49 +++++++++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 5b50a2fc0d79..0e9c048346fa 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -70,6 +70,8 @@ struct snd_ump_ops { struct snd_seq_ump_ops { void (*input_receive)(struct snd_ump_endpoint *ump, const u32 *data, int words); + int (*notify_fb_change)(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb); }; struct snd_ump_block { diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 7739fb3ebf34..2f93d76b05ce 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -48,6 +48,7 @@ struct seq_ump_client { struct seq_ump_input_buffer input; /* input parser context */ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ }; /* number of 32bit words for each UMP message type */ @@ -244,6 +245,40 @@ static int seq_ump_group_init(struct seq_ump_client *client, int group_index) return err; } +/* update the sequencer ports; called from notify_fb_change callback */ +static void update_port_infos(struct seq_ump_client *client) +{ + struct snd_seq_port_info *old, *new; + int i, err; + + old = kzalloc(sizeof(*old), GFP_KERNEL); + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!old || !new) + goto error; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + old->addr.client = client->seq_client; + old->addr.port = i; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_GET_PORT_INFO, + old); + if (err < 0) + goto error; + fill_port_info(new, client, &client->groups[i]); + if (old->capability == new->capability && + !strcmp(old->name, new->name)) + continue; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_SET_PORT_INFO, + new); + if (err < 0) + goto error; + } + error: + kfree(new); + kfree(old); +} + /* update dir_bits and active flag for all groups in the client */ static void update_group_attrs(struct seq_ump_client *client) { @@ -353,6 +388,8 @@ static int create_ump_endpoint_port(struct seq_ump_client *client) /* release the client resources */ static void seq_ump_client_free(struct seq_ump_client *client) { + cancel_work_sync(&client->group_notify_work); + if (client->seq_client >= 0) snd_seq_delete_kernel_client(client->seq_client); @@ -377,8 +414,31 @@ static void setup_client_midi_version(struct seq_ump_client *client) snd_seq_kernel_client_put(cptr); } +/* UMP group change notification */ +static void handle_group_notify(struct work_struct *work) +{ + struct seq_ump_client *client = + container_of(work, struct seq_ump_client, group_notify_work); + + update_group_attrs(client); + update_port_infos(client); +} + +/* UMP FB change notification */ +static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ + struct seq_ump_client *client = ump->seq_client; + + if (!client) + return -ENODEV; + schedule_work(&client->group_notify_work); + return 0; +} + static const struct snd_seq_ump_ops seq_ump_ops = { .input_receive = seq_ump_input_receive, + .notify_fb_change = seq_ump_notify_fb_change, }; /* create a sequencer client and ports for the given UMP endpoint */ @@ -396,6 +456,7 @@ static int snd_seq_ump_probe(struct device *_dev) if (!client) return -ENOMEM; + INIT_WORK(&client->group_notify_work, handle_group_notify); client->ump = ump; client->seq_client = diff --git a/sound/core/ump.c b/sound/core/ump.c index 7df50f0affe9..c0cda12bce10 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -688,6 +688,28 @@ static void fill_fb_info(struct snd_ump_endpoint *ump, info->sysex8_streams, info->flags); } +/* check whether the FB info gets updated by the current message */ +static bool is_fb_info_updated(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb, + const union snd_ump_stream_msg *buf) +{ + char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); + fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); + return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; +} + +/* notify the FB info/name change to sequencer */ +static void seq_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->notify_fb_change) + ump->seq_ops->notify_fb_change(ump, fb); +#endif +} + /* handle FB info message; update FB info if the block is present */ static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) @@ -697,14 +719,24 @@ static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, blk = buf->fb_info.function_block_id; fb = snd_ump_get_block(ump, blk); - if (fb) { - fill_fb_info(ump, &fb->info, buf); - } else if (ump->parsed) { - /* complain only if updated after parsing */ + + /* complain only if updated after parsing */ + if (!fb && ump->parsed) { ump_info(ump, "Function Block Info Update for non-existing block %d\n", blk); return -ENODEV; } + + /* When updated after the initial parse, check the FB info update */ + if (ump->parsed && !is_fb_info_updated(ump, fb, buf)) + return 1; /* no content change */ + + if (fb) { + fill_fb_info(ump, &fb->info, buf); + if (ump->parsed) + seq_notify_fb_change(ump, fb); + } + return 1; /* finished */ } @@ -714,14 +746,19 @@ static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, { unsigned char blk; struct snd_ump_block *fb; + int ret; blk = buf->fb_name.function_block_id; fb = snd_ump_get_block(ump, blk); if (!fb) return -ENODEV; - return ump_append_string(ump, fb->info.name, sizeof(fb->info.name), - buf->raw, 3); + ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); + /* notify the FB name update to sequencer, too */ + if (ret > 0 && ump->parsed) + seq_notify_fb_change(ump, fb); + return ret; } static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) -- cgit v1.2.3 From 174a6dfbc17ee9defed4daeeea6061ef34644f02 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:51 +0200 Subject: ALSA: seq: ump: Notify port changes to system port For allowing applications to track the FB active changes, this patch adds the notification from the system port at each time a FB change is handled and the active flag or re-grouping happens. Link: https://lore.kernel.org/r/20230612081054.17200-8-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_system.c | 1 + sound/core/seq/seq_ump_client.c | 3 +++ 2 files changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c index 32c2d9b57751..80267290190d 100644 --- a/sound/core/seq/seq_system.c +++ b/sound/core/seq/seq_system.c @@ -85,6 +85,7 @@ void snd_seq_system_broadcast(int client, int port, int type) ev.type = type; snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); } +EXPORT_SYMBOL_GPL(snd_seq_system_broadcast); /* entry points for broadcasting system events */ int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 2f93d76b05ce..901a670dcb36 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -13,6 +13,7 @@ #include #include #include "seq_clientmgr.h" +#include "seq_system.h" struct seq_ump_client; struct seq_ump_group; @@ -273,6 +274,8 @@ static void update_port_infos(struct seq_ump_client *client) new); if (err < 0) goto error; + /* notify to system port */ + snd_seq_system_client_ev_port_change(client->seq_client, i); } error: kfree(new); -- cgit v1.2.3 From 6a8b4800ae54e9ceb8d017bcfb61d04ff0da90f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:52 +0200 Subject: ALSA: seq: ump: Notify UMP protocol change to sequencer UMP v1.1 supports the protocol switch via a UMP Stream message. When it's received, we need to take care of the midi_version field in the corresponding sequencer client, too. This patch introduces a new ops to notify the protocol change to snd_seq_ump_ops for handling it. Link: https://lore.kernel.org/r/20230612081054.17200-9-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/seq/seq_ump_client.c | 10 ++++++++++ sound/core/ump.c | 13 +++++++++++++ 3 files changed, 24 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 0e9c048346fa..68478e7be3b4 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -72,6 +72,7 @@ struct snd_seq_ump_ops { const u32 *data, int words); int (*notify_fb_change)(struct snd_ump_endpoint *ump, struct snd_ump_block *fb); + int (*switch_protocol)(struct snd_ump_endpoint *ump); }; struct snd_ump_block { diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 901a670dcb36..fe21c801af74 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -439,9 +439,19 @@ static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, return 0; } +/* UMP protocol change notification; just update the midi_version field */ +static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump) +{ + if (!ump->seq_client) + return -ENODEV; + setup_client_midi_version(ump->seq_client); + return 0; +} + static const struct snd_seq_ump_ops seq_ump_ops = { .input_receive = seq_ump_input_receive, .notify_fb_change = seq_ump_notify_fb_change, + .switch_protocol = seq_ump_switch_protocol, }; /* create a sequencer client and ports for the given UMP endpoint */ diff --git a/sound/core/ump.c b/sound/core/ump.c index c0cda12bce10..f364bb290d3a 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -657,14 +657,27 @@ static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, buf->raw, 2); } +/* notify the protocol change to sequencer */ +static void seq_notify_protocol(struct snd_ump_endpoint *ump) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->switch_protocol) + ump->seq_ops->switch_protocol(ump); +#endif /* CONFIG_SND_SEQUENCER */ +} + /* handle EP stream config message; update the UMP protocol */ static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) { + unsigned int old_protocol = ump->info.protocol; + ump->info.protocol = (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; ump_dbg(ump, "Current protocol = %x (caps = %x)\n", ump->info.protocol, ump->info.protocol_caps); + if (ump->parsed && ump->info.protocol != old_protocol) + seq_notify_protocol(ump); return 1; /* finished */ } -- cgit v1.2.3 From 01dfa8e969dbbc72fc4564e8d61c905c4f3a2352 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:53 +0200 Subject: ALSA: ump: Add info flag bit for static blocks UMP v1.1 spec allows to inform whether the function blocks are static and not dynamically updated. Add a new flag bit to snd_ump_endpoint_info to reflect that attribute, too. The flag is set when a USB MIDI device is still in the old MIDI 2.0 without UMP 1.1 support. Then the driver falls back to GTBs, and they are supposed to be static-only. Link: https://lore.kernel.org/r/20230612081054.17200-10-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 +++ sound/core/ump.c | 11 +++++++++++ sound/usb/midi2.c | 2 ++ 3 files changed, 16 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 79ee48b2ed6d..4d1ac0797d56 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -780,6 +780,9 @@ struct snd_rawmidi_status { }; #endif +/* UMP EP info flags */ +#define SNDRV_UMP_EP_INFO_STATIC_BLOCKS 0x01 + /* UMP EP Protocol / JRTS capability bits */ #define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300 #define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */ diff --git a/sound/core/ump.c b/sound/core/ump.c index f364bb290d3a..a64dc2d8a129 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -490,6 +490,8 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, ump->info.sw_revision[2], ump->info.sw_revision[3]); } + snd_iprintf(buffer, "Static Blocks: %s\n", + (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No"); snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); list_for_each_entry(fb, &ump->block_list, list) { @@ -608,6 +610,9 @@ static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, ump->info.num_blocks = 1; } + if (buf->ep_info.static_function_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | buf->ep_info.jrts; @@ -708,6 +713,12 @@ static bool is_fb_info_updated(struct snd_ump_endpoint *ump, { char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { + ump_info(ump, "Skipping static FB info update (blk#%d)\n", + fb->info.block_id); + return 0; + } + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 13fa1978267a..ee2835741479 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -888,6 +888,8 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) /* Blocks have been already created? */ if (rmidi->ump_parsed || rmidi->ump->info.num_blocks) continue; + /* GTB is static-only */ + rmidi->ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; /* loop over GTBs */ for (dir = 0; dir < 2; dir++) { if (!rmidi->eps[dir]) -- cgit v1.2.3 From 1359886227e52c27c0a230769f3be4c486e36299 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:17 +0200 Subject: ALSA: emu10k1: split off E-MU fallback clock from clock source So far, we set the fallback as a side effect of setting the source. But the fallback makes no sense at all when an internal clock is selected. Defaulting to 48k for S/PDIF & ADAT makes sense, but as that is the global default and we're not changing it automatically any more, it's just fine to leave it entirely to the explicit setting. This changes the name of the pre-existing control to something more appropriate (regardless of the split), so users will need to adjust their mixer settings. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +- sound/pci/emu10k1/emu10k1_main.c | 3 +- sound/pci/emu10k1/emumixer.c | 89 +++++++++++++++++++++++++++++----------- sound/pci/emu10k1/emupcm.c | 4 +- 4 files changed, 72 insertions(+), 27 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index cc0151e7c828..59e79ea1f75e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1668,7 +1668,8 @@ struct snd_emu1010 { unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ - unsigned int internal_clock; /* 44100 or 48000 */ + unsigned int clock_source; + unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ unsigned int optical_out; /* 0:SPDIF, 1:ADAT */ struct delayed_work firmware_work; diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 65207ef689cb..2aa11d70e285 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -900,7 +900,8 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* IRQ Enable: All off */ snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, 0x00); - emu->emu1010.internal_clock = 1; /* 48000 */ + emu->emu1010.clock_source = 1; /* 48000 */ + emu->emu1010.clock_fallback = 1; /* 48000 */ /* Default WCLK set to 48kHz. */ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K); /* Word Clock source, Internal 48kHz x1 */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 20a0b3afc8a5..5b50d9c07a60 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -888,7 +888,7 @@ static const struct snd_emu1010_pads_info emu1010_pads_info[] = { }; -static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char * const texts[4] = { @@ -898,16 +898,16 @@ static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, return snd_ctl_enum_info(uinfo, 1, 4, texts); } -static int snd_emu1010_internal_clock_get(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - ucontrol->value.enumerated.item[0] = emu->emu1010.internal_clock; + ucontrol->value.enumerated.item[0] = emu->emu1010.clock_source; return 0; } -static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); @@ -918,16 +918,14 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* Limit: uinfo->value.enumerated.items = 4; */ if (val >= 4) return -EINVAL; - change = (emu->emu1010.internal_clock != val); + change = (emu->emu1010.clock_source != val); if (change) { - emu->emu1010.internal_clock = val; + emu->emu1010.clock_source = val; switch (val) { case 0: /* 44100 */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 44.1kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_44_1K ); /* Word Clock source, Internal 44.1kHz x1 */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X ); @@ -943,8 +941,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* 48000 */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, Internal 48kHz x1 */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X ); @@ -960,8 +956,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, case 2: /* Take clock from S/PDIF IN */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, sync to S/PDIF input */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X ); @@ -979,8 +973,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* Take clock from ADAT IN */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, sync to ADAT input */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X ); @@ -999,15 +991,62 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static const struct snd_kcontrol_new snd_emu1010_internal_clock = +static const struct snd_kcontrol_new snd_emu1010_clock_source = { - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Clock Internal Rate", - .count = 1, - .info = snd_emu1010_internal_clock_info, - .get = snd_emu1010_internal_clock_get, - .put = snd_emu1010_internal_clock_put + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Source", + .count = 1, + .info = snd_emu1010_clock_source_info, + .get = snd_emu1010_clock_source_get, + .put = snd_emu1010_clock_source_put +}; + +static int snd_emu1010_clock_fallback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "44100", "48000" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_emu1010_clock_fallback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = emu->emu1010.clock_fallback; + return 0; +} + +static int snd_emu1010_clock_fallback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + unsigned int val = ucontrol->value.enumerated.item[0]; + int change; + + if (val >= 2) + return -EINVAL; + change = (emu->emu1010.clock_fallback != val); + if (change) { + emu->emu1010.clock_fallback = val; + snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 1 - val); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu1010_clock_fallback = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Fallback", + .count = 1, + .info = snd_emu1010_clock_fallback_info, + .get = snd_emu1010_clock_fallback_get, + .put = snd_emu1010_clock_fallback_put }; static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol, @@ -2297,7 +2336,11 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, snd_emu1010_apply_sources(emu); err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_internal_clock, emu)); + snd_ctl_new1(&snd_emu1010_clock_source, emu)); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_clock_fallback, emu)); if (err < 0) return err; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 550caefa0ce4..fab537788587 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1185,7 +1185,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) kfree(epcm); return err; } - if (emu->card_capabilities->emu_model && emu->emu1010.internal_clock == 0) + if (emu->card_capabilities->emu_model && emu->emu1010.clock_source == 0) sample_rate = 44100; else sample_rate = 48000; @@ -1335,7 +1335,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * but we don't exceed 16 channels anyway. */ #if 1 - switch (emu->emu1010.internal_clock) { + switch (emu->emu1010.clock_source) { case 0: /* For 44.1kHz */ runtime->hw.rates = SNDRV_PCM_RATE_44100; -- cgit v1.2.3 From 60985241bfc61c6d6d85a78e0f29c866c546c7f5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:18 +0200 Subject: ALSA: emu10k1: make available E-MU clock sources card-specific The actually available clock sources depend on the available audio input ports and dedicated clock input ports. This includes refactoring the code to be data-driven to remain manageable. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 + sound/pci/emu10k1/emu10k1_main.c | 4 +- sound/pci/emu10k1/emumixer.c | 153 ++++++++++++++++++++------------------- sound/pci/emu10k1/io.c | 23 ++++++ 4 files changed, 107 insertions(+), 75 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 59e79ea1f75e..703ef441bb2a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1668,6 +1668,7 @@ struct snd_emu1010 { unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ + unsigned int wclock; /* Cached register value */ unsigned int clock_source; unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ @@ -1824,6 +1825,7 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); +void snd_emu1010_update_clock(struct snd_emu10k1 *emu); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 2aa11d70e285..58ed72de6403 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -905,10 +905,10 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* Default WCLK set to 48kHz. */ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K); /* Word Clock source, Internal 48kHz x1 */ + emu->emu1010.wclock = EMU_HANA_WCLOCK_INT_48K; snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K); /* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */ - /* Audio Dock LEDs. */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_LOCK | EMU_HANA_DOCK_LEDS_2_48K); + snd_emu1010_update_clock(emu); // The routes are all set to EMU_SRC_SILENCE due to the reset, // so it is safe to simply enable the outputs. diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 5b50d9c07a60..f9500cd50a4b 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -887,15 +887,79 @@ static const struct snd_emu1010_pads_info emu1010_pads_info[] = { }, }; +static const char * const emu1010_clock_texts[] = { + "44100", "48000", "SPDIF", "ADAT", "Dock", "BNC" +}; + +static const u8 emu1010_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_HANA_ADAT_IN, + EMU_HANA_WCLOCK_2ND_HANA, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +static const char * const emu0404_clock_texts[] = { + "44100", "48000", "SPDIF", "BNC" +}; + +static const u8 emu0404_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +struct snd_emu1010_clock_info { + const char * const *texts; + const u8 *vals; + unsigned num; +}; + +static const struct snd_emu1010_clock_info emu1010_clock_info[] = { + { + // rev1 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // rev2 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals) - 1, + }, + { + // 1616(m) CardBus + .texts = emu1010_clock_texts, + // TODO: determine what is actually available. + // Pedantically, *every* source comes from the 2nd FPGA, as the + // card itself has no own (digital) audio ports. The user manual + // claims that ADAT and S/PDIF clock sources are separate, which + // can mean two things: either E-MU mapped the dock's sources to + // the primary ones, or they determine the meaning of the "Dock" + // source depending on how the ports are actually configured + // (which the 2nd FPGA must be doing anyway). + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // 0404 + .texts = emu0404_clock_texts, + .vals = emu0404_clock_vals, + .num = ARRAY_SIZE(emu0404_clock_vals), + }, +}; static int snd_emu1010_clock_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - static const char * const texts[4] = { - "44100", "48000", "SPDIF", "ADAT" - }; + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; - return snd_ctl_enum_info(uinfo, 1, 4, texts); + return snd_ctl_enum_info(uinfo, 1, emu_ci->num, emu_ci->texts); } static int snd_emu1010_clock_source_get(struct snd_kcontrol *kcontrol, @@ -911,84 +975,27 @@ static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; unsigned int val; int change = 0; val = ucontrol->value.enumerated.item[0] ; - /* Limit: uinfo->value.enumerated.items = 4; */ - if (val >= 4) + if (val >= emu_ci->num) return -EINVAL; change = (emu->emu1010.clock_source != val); if (change) { emu->emu1010.clock_source = val; - switch (val) { - case 0: - /* 44100 */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, Internal 44.1kHz x1 */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_44K | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - case 1: - /* 48000 */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, Internal 48kHz x1 */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_48K | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - - case 2: /* Take clock from S/PDIF IN */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, sync to S/PDIF input */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */ - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - - case 3: - /* Take clock from ADAT IN */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, sync to ADAT input */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */ - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - - - break; - } + emu->emu1010.wclock = emu_ci->vals[val]; + + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE); + snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock); + msleep(10); // Allow DLL to settle + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + + snd_emu1010_update_clock(emu); } - return change; + return change; } static const struct snd_kcontrol_new snd_emu1010_clock_source = diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index abe69ae40499..e7a44443023a 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -357,6 +357,29 @@ u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) return (hi << 8) | lo; } +void snd_emu1010_update_clock(struct snd_emu10k1 *emu) +{ + u32 leds; + + switch (emu->emu1010.wclock) { + case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X: + leds = EMU_HANA_DOCK_LEDS_2_44K; + break; + case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X: + leds = EMU_HANA_DOCK_LEDS_2_48K; + break; + default: + leds = EMU_HANA_DOCK_LEDS_2_EXT; + break; + } + + // FIXME: this should probably represent the AND of all currently + // used sources' lock status. But we don't know how to get that ... + leds |= EMU_HANA_DOCK_LEDS_2_LOCK; + + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, leds); +} + void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) { unsigned long flags; -- cgit v1.2.3 From e73b597e63ebad26d9dee5feb5d47251ed53b8a4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:19 +0200 Subject: ALSA: emu10k1: query rate of external clock sources on E-MU cards The value isn't used yet; the subsequent commits will do that. This ignores the existence of rates above 48 kHz, which is fine, as the hardware will just switch to the fallback clock source when fed with a rate which is incompatible with the base clock multiplier, which currently is always x1. The sample rate display in /proc spdif-in is adjusted to reflect our understanding of the input rates. This is tested only with an 0404b card without sync card, so there is a lot of room for improvement. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 5 +++++ sound/pci/emu10k1/emuproc.c | 43 ++++++++++++++++++++------------------ sound/pci/emu10k1/io.c | 51 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 703ef441bb2a..d64cf1697586 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1110,6 +1110,9 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM #define EMU_DOCK_BOARD_ID0 0x00 /* ID bit 0 */ #define EMU_DOCK_BOARD_ID1 0x03 /* ID bit 1 */ +// The actual code disagrees about the bit width of the registers - +// the formula used is freq = 0x1770000 / (((X_HI << 5) | X_LO) + 1) + #define EMU_HANA_WC_SPDIF_HI 0x28 /* 0xxxxxx 6 bit SPDIF IN Word clock, upper 6 bits */ #define EMU_HANA_WC_SPDIF_LO 0x29 /* 0xxxxxx 6 bit SPDIF IN Word clock, lower 6 bits */ @@ -1669,6 +1672,7 @@ struct snd_emu1010 { unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ unsigned int wclock; /* Cached register value */ + unsigned int word_clock; /* Cached effective value */ unsigned int clock_source; unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ @@ -1825,6 +1829,7 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); +int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src); void snd_emu1010_update_clock(struct snd_emu10k1 *emu); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index ca7b4dddbea8..993b35362499 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -168,29 +168,32 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, struct snd_emu10k1 *emu = entry->private_data; u32 value; u32 value2; - u32 rate; if (emu->card_capabilities->emu_model) { - if (!emu->card_capabilities->no_adat) { - snd_emu1010_fpga_read(emu, 0x38, &value); - if ((value & 0x1) == 0) { - snd_emu1010_fpga_read(emu, 0x2a, &value); - snd_emu1010_fpga_read(emu, 0x2b, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "ADAT Locked : %u\n", rate); - } else { - snd_iprintf(buffer, "ADAT Unlocked\n"); - } - } - snd_emu1010_fpga_read(emu, 0x20, &value); - if ((value & 0x4) == 0) { - snd_emu1010_fpga_read(emu, 0x28, &value); - snd_emu1010_fpga_read(emu, 0x29, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "SPDIF Locked : %d\n", rate); - } else { - snd_iprintf(buffer, "SPDIF Unlocked\n"); + // This represents the S/PDIF lock status on 0404b, which is + // kinda weird and unhelpful, because monitoring it via IRQ is + // impractical (one gets an IRQ flood as long as it is desynced). + snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &value); + snd_iprintf(buffer, "Lock status 1: %#x\n", value & 0x10); + + // Bit 0x1 in LO being 0 is supposedly for ADAT lock. + // The registers are always all zero on 0404b. + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_LO, &value); + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_HI, &value2); + snd_iprintf(buffer, "Lock status 2: %#x %#x\n", value, value2); + + snd_iprintf(buffer, "S/PDIF rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_SPDIF_IN)); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + snd_iprintf(buffer, "ADAT rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_ADAT_IN)); + snd_iprintf(buffer, "Dock rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_2ND_HANA)); } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU0404 || + emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) + snd_iprintf(buffer, "BNC rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_SYNC_BNC)); } else { snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS); snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS); diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index e7a44443023a..a0d66ce3ee83 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -357,21 +357,70 @@ u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) return (hi << 8) | lo; } +int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src) +{ + u32 reg_lo, reg_hi, value, value2; + + switch (src) { + case EMU_HANA_WCLOCK_HANA_SPDIF_IN: + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + return 0; + reg_lo = EMU_HANA_WC_SPDIF_LO; + reg_hi = EMU_HANA_WC_SPDIF_HI; + break; + case EMU_HANA_WCLOCK_HANA_ADAT_IN: + reg_lo = EMU_HANA_WC_ADAT_LO; + reg_hi = EMU_HANA_WC_ADAT_HI; + break; + case EMU_HANA_WCLOCK_SYNC_BNC: + reg_lo = EMU_HANA_WC_BNC_LO; + reg_hi = EMU_HANA_WC_BNC_HI; + break; + case EMU_HANA_WCLOCK_2ND_HANA: + reg_lo = EMU_HANA2_WC_SPDIF_LO; + reg_hi = EMU_HANA2_WC_SPDIF_HI; + break; + default: + return 0; + } + snd_emu1010_fpga_read(emu, reg_hi, &value); + snd_emu1010_fpga_read(emu, reg_lo, &value2); + // FIXME: The /4 is valid for 0404b, but contradicts all other info. + return 0x1770000 / 4 / (((value << 5) | value2) + 1); +} + void snd_emu1010_update_clock(struct snd_emu10k1 *emu) { + int clock; u32 leds; switch (emu->emu1010.wclock) { case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X: + clock = 44100; leds = EMU_HANA_DOCK_LEDS_2_44K; break; case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X: + clock = 48000; leds = EMU_HANA_DOCK_LEDS_2_48K; break; default: - leds = EMU_HANA_DOCK_LEDS_2_EXT; + clock = snd_emu1010_get_raw_rate( + emu, emu->emu1010.wclock & EMU_HANA_WCLOCK_SRC_MASK); + // The raw rate reading is rather coarse (it cannot accurately + // represent 44.1 kHz) and fluctuates slightly. Luckily, the + // clock comes from digital inputs, which use standardized rates. + // So we round to the closest standard rate and ignore discrepancies. + if (clock < 46000) { + clock = 44100; + leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_44K; + } else { + clock = 48000; + leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K; + } break; } + emu->emu1010.word_clock = clock; // FIXME: this should probably represent the AND of all currently // used sources' lock status. But we don't know how to get that ... -- cgit v1.2.3 From 19b89d15fa978c7e6327287f90d1dde15aff01c4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:20 +0200 Subject: ALSA: emu10k1: fix sample rates for E-MU cards at 44.1 kHz word clock Now that we know the actual word clock, we can: - Put the resulting rate into the hardware info - At 44.1 kHz word clock shift the rate for the pitch calculations, which presume a 48 kHz word clock Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emupcm.c | 112 ++++++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 57 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index d64cf1697586..386a5f3be3e0 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1495,6 +1495,7 @@ struct snd_emu10k1_pcm { unsigned short first_ptr; snd_pcm_uframes_t resume_pos; struct snd_util_memblk *memblk; + unsigned int pitch_target; unsigned int start_addr; unsigned int ccca_start_addr; unsigned int capture_ipr; /* interrupt acknowledge mask */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index fab537788587..3ef9130a9577 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -195,6 +195,33 @@ static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate) } } +static void snd_emu10k1_constrain_capture_rates(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) { + // This also sets the rate constraint by deleting SNDRV_PCM_RATE_KNOT + runtime->hw.rates = SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100; + runtime->hw.rate_min = 11025; + runtime->hw.rate_max = 44100; + return; + } + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_capture_rates); +} + +static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + int rate; + + rate = emu->emu1010.word_clock; + runtime->hw.rate_min = runtime->hw.rate_max = rate; + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); +} + static unsigned int emu10k1_calc_pitch_target(unsigned int rate) { unsigned int pitch_target; @@ -251,18 +278,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, const unsigned char *send_routing, const unsigned char *send_amount) { - struct snd_pcm_substream *substream = evoice->epcm->substream; - struct snd_pcm_runtime *runtime = substream->runtime; unsigned int silent_page; int voice; - unsigned int pitch_target; voice = evoice->number; - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); snd_emu10k1_ptr_write_multiple(emu, voice, @@ -273,7 +293,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, // Stereo slaves don't need to have the addresses set, but it doesn't hurt DSL, end_addr | (send_amount[3] << 24), PSST, start_addr | (send_amount[2] << 24), - CCCA, emu10k1_select_interprom(pitch_target) | + CCCA, emu10k1_select_interprom(evoice->epcm->pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT), // Clear filter delay memory Z1, 0, @@ -419,6 +439,13 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) bool w_16 = snd_pcm_format_width(runtime->format) == 16; bool stereo = runtime->channels == 2; unsigned int start_addr, end_addr; + unsigned int rate; + + rate = runtime->rate; + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + rate = rate * 480 / 441; + epcm->pitch_target = emu10k1_calc_pitch_target(rate); start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; @@ -443,6 +470,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) unsigned int extra_size, channel_size; unsigned int i; + epcm->pitch_target = PITCH_48000; + start_addr = epcm->start_addr >> 1; // 16-bit voices extra_size = runtime->period_size; @@ -526,12 +555,16 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) epcm->capture_bs_val++; } if (epcm->type == CAPTURE_AC97ADC) { + unsigned rate = runtime->rate; + if (!(runtime->hw.rates & SNDRV_PCM_RATE_48000)) + rate = rate * 480 / 441; + epcm->capture_cr_val = emu->audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE; if (runtime->channels > 1) epcm->capture_cr_val |= emu->audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE; epcm->capture_cr_val |= emu->audigy ? - snd_emu10k1_audigy_capture_rate_reg(runtime->rate) : - snd_emu10k1_capture_rate_reg(runtime->rate); + snd_emu10k1_audigy_capture_rate_reg(rate) : + snd_emu10k1_capture_rate_reg(rate); } return 0; } @@ -670,19 +703,10 @@ static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; - unsigned int voice, pitch_target; + unsigned int voice; - substream = evoice->epcm->substream; - runtime = substream->runtime; voice = evoice->number; - - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_playback_commit_pitch(emu, voice, pitch_target << 16); + snd_emu10k1_playback_commit_pitch(emu, voice, evoice->epcm->pitch_target << 16); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -1043,11 +1067,9 @@ static const struct snd_pcm_hardware snd_emu10k1_capture_efx = SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, - .rate_min = 44100, - .rate_max = 192000, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, .channels_min = 1, .channels_max = 16, .buffer_bytes_max = (64*1024), @@ -1144,6 +1166,8 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_efx_playback; + if (emu->card_capabilities->emu_model) + snd_emu1010_constrain_efx_rate(emu, runtime); err = snd_emu10k1_playback_set_constraints(runtime); if (err < 0) { kfree(epcm); @@ -1185,8 +1209,8 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) kfree(epcm); return err; } - if (emu->card_capabilities->emu_model && emu->emu1010.clock_source == 0) - sample_rate = 44100; + if (emu->card_capabilities->emu_model) + sample_rate = emu->emu1010.word_clock; else sample_rate = 48000; err = snd_pcm_hw_rule_noresample(runtime, sample_rate); @@ -1236,11 +1260,11 @@ static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture; + snd_emu10k1_constrain_capture_rates(emu, runtime); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt; emu->pcm_capture_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates); return 0; } @@ -1313,17 +1337,9 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) substream->runtime->private_data = epcm; substream->runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture_efx; - runtime->hw.rates = SNDRV_PCM_RATE_48000; - runtime->hw.rate_min = runtime->hw.rate_max = 48000; if (emu->card_capabilities->emu_model) { - /* TODO - * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - * SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 - * rate_min = 44100, - * rate_max = 192000, - * Need to add mixer control to fix sample rate - * + snd_emu1010_constrain_efx_rate(emu, runtime); + /* * There are 32 mono channels of 16bits each. * 24bit Audio uses 2x channels over 16bit, * 96kHz uses 2x channels over 48kHz, @@ -1334,30 +1350,12 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * 1010rev2 and 1616(m) cards have double that, * but we don't exceed 16 channels anyway. */ -#if 1 - switch (emu->emu1010.clock_source) { - case 0: - /* For 44.1kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_44100; - runtime->hw.rate_min = runtime->hw.rate_max = 44100; - break; - case 1: - /* For 48kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_48000; - runtime->hw.rate_min = runtime->hw.rate_max = 48000; - break; - } -#endif #if 0 /* For 96kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_96000; - runtime->hw.rate_min = runtime->hw.rate_max = 96000; runtime->hw.channels_min = runtime->hw.channels_max = 4; #endif #if 0 /* For 192kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_192000; - runtime->hw.rate_min = runtime->hw.rate_max = 192000; runtime->hw.channels_min = runtime->hw.channels_max = 2; #endif runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; -- cgit v1.2.3 From e68235c8aae9af08a868e4a4337daf2bcb4f6a92 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:21 +0200 Subject: ALSA: emu10k1: fix synthesizer pitch for E-MU cards at 44.1 kHz This is only a very partial fix - the frequency-dependent envelope & LFO register values aren't adjusted. But I'm not sure they were even correct at 48 kHz to start with, as most of them are precalculated by common code which assumes an EMU8K-specific 44.1 kHz word clock, and it seems somewhat unlikely that the hardware's register interpretation was adjusted to compensate for the different word clock. In any case I'm not going to spend time on fixing that, as this code is unlikely to be actually used by anyone today. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emux_synth.h | 2 +- sound/pci/emu10k1/emu10k1_callback.c | 10 ++++++++++ sound/pci/emu10k1/emu10k1_synth.c | 1 - sound/synth/emux/emux_synth.c | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/sound/emux_synth.h b/include/sound/emux_synth.h index d499b68122a3..1cc530434b97 100644 --- a/include/sound/emux_synth.h +++ b/include/sound/emux_synth.h @@ -54,6 +54,7 @@ struct snd_emux_operators { #if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) int (*oss_ioctl)(struct snd_emux *emu, int cmd, int p1, int p2); #endif + int (*get_pitch_shift)(struct snd_emux *emu); }; @@ -82,7 +83,6 @@ struct snd_emux { int max_voices; /* Number of voices */ int mem_size; /* memory size (in byte) */ int num_ports; /* number of ports to be created */ - int pitch_shift; /* pitch shift value (for Emu10k1) */ struct snd_emux_operators ops; /* operators */ void *hw; /* hardware */ unsigned long flags; /* other conditions */ diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index ad0dea0c2be9..d36234b88fb4 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -35,6 +35,7 @@ static void terminate_voice(struct snd_emux_voice *vp); static void free_voice(struct snd_emux_voice *vp); static u32 make_fmmod(struct snd_emux_voice *vp); static u32 make_fm2frq2(struct snd_emux_voice *vp); +static int get_pitch_shift(struct snd_emux *emu); /* * Ensure a value is between two points @@ -58,6 +59,7 @@ static const struct snd_emux_operators emu10k1_ops = { .free_voice = free_voice, .sample_new = snd_emu10k1_sample_new, .sample_free = snd_emu10k1_sample_free, + .get_pitch_shift = get_pitch_shift, }; void @@ -508,3 +510,11 @@ make_fm2frq2(struct snd_emux_voice *vp) LIMITVALUE(pitch, -128, 127); return ((unsigned char)pitch << 8) | freq; } + +static int get_pitch_shift(struct snd_emux *emu) +{ + struct snd_emu10k1 *hw = emu->hw; + + return (hw->card_capabilities->emu_model && + hw->emu1010.word_clock == 44100) ? 0 : -501; +} diff --git a/sound/pci/emu10k1/emu10k1_synth.c b/sound/pci/emu10k1/emu10k1_synth.c index 549013a4a80b..759e66e1105a 100644 --- a/sound/pci/emu10k1/emu10k1_synth.c +++ b/sound/pci/emu10k1/emu10k1_synth.c @@ -43,7 +43,6 @@ static int snd_emu10k1_synth_probe(struct device *_dev) emux->hw = hw; emux->max_voices = arg->max_voices; emux->num_ports = arg->seq_ports; - emux->pitch_shift = -501; emux->memhdr = hw->memhdr; /* maximum two ports */ emux->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2; diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c index a5385efcedb6..075358a533a0 100644 --- a/sound/synth/emux/emux_synth.c +++ b/sound/synth/emux/emux_synth.c @@ -845,7 +845,8 @@ calc_pitch(struct snd_emux_voice *vp) /* 0xe000: root pitch */ offset += 0xe000 + vp->reg.rate_offset; - offset += vp->emu->pitch_shift; + if (vp->emu->ops.get_pitch_shift) + offset += vp->emu->ops.get_pitch_shift(vp->emu); LIMITVALUE(offset, 0, 0xffff); if (offset == vp->apitch) return 0; /* unchanged */ -- cgit v1.2.3 From 6cc844504638b0d1429be729b2d66be92276e24b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:22 +0200 Subject: ALSA: timer: minimize open-coded access to hw.resolution Some info-querying code still used hw.resolution directly instead of calling snd_timer_hw_resolution(), thus missing a possible hw.c_resolution callback. This patch rectifies that. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index e08a37c23add..9d0d2a5c2e15 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1246,6 +1246,7 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, { struct snd_timer *timer; struct snd_timer_instance *ti; + unsigned long resolution; mutex_lock(®ister_mutex); list_for_each_entry(timer, &snd_timer_list, device_list) { @@ -1269,10 +1270,13 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, timer->tmr_device, timer->tmr_subdevice); } snd_iprintf(buffer, "%s :", timer->name); - if (timer->hw.resolution) + spin_lock_irq(&timer->lock); + resolution = snd_timer_hw_resolution(timer); + spin_unlock_irq(&timer->lock); + if (resolution) snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", - timer->hw.resolution / 1000, - timer->hw.resolution % 1000, + resolution / 1000, + resolution % 1000, timer->hw.ticks); if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) snd_iprintf(buffer, " SLAVE"); @@ -1662,7 +1666,9 @@ static int snd_timer_user_ginfo(struct file *file, ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(ginfo->id, t->id, sizeof(ginfo->id)); strscpy(ginfo->name, t->name, sizeof(ginfo->name)); - ginfo->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + ginfo->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (t->hw.resolution_min > 0) { ginfo->resolution_min = t->hw.resolution_min; ginfo->resolution_max = t->hw.resolution_max; @@ -1817,7 +1823,9 @@ static int snd_timer_user_info(struct file *file, info->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(info->id, t->id, sizeof(info->id)); strscpy(info->name, t->name, sizeof(info->name)); - info->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + info->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (copy_to_user(_info, info, sizeof(*_info))) err = -EFAULT; kfree(info); -- cgit v1.2.3 From ca533448a0936cd1c9f8e158af36f0657ed4d66e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:23 +0200 Subject: ALSA: emu10k1: fix timer for E-MU cards at 44.1 kHz word clock The timer was presuming a fixed 48 kHz word clock, like the rest of the code. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/timer.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c index 993663fef885..f3c78adf3248 100644 --- a/sound/pci/emu10k1/timer.c +++ b/sound/pci/emu10k1/timer.c @@ -38,20 +38,36 @@ static int snd_emu10k1_timer_stop(struct snd_timer *timer) return 0; } +static unsigned long snd_emu10k1_timer_c_resolution(struct snd_timer *timer) +{ + struct snd_emu10k1 *emu = snd_timer_chip(timer); + + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + return 22676; // 1 sample @ 44.1 kHz = 22.675736...us + else + return 20833; // 1 sample @ 48 kHz = 20.833...us +} + static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer, unsigned long *num, unsigned long *den) { + struct snd_emu10k1 *emu = snd_timer_chip(timer); + *num = 1; - *den = 48000; + if (emu->card_capabilities->emu_model) + *den = emu->emu1010.word_clock; + else + *den = 48000; return 0; } static const struct snd_timer_hardware snd_emu10k1_timer_hw = { .flags = SNDRV_TIMER_HW_AUTO, - .resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */ .ticks = 1024, .start = snd_emu10k1_timer_start, .stop = snd_emu10k1_timer_stop, + .c_resolution = snd_emu10k1_timer_c_resolution, .precise_resolution = snd_emu10k1_timer_precise_resolution, }; -- cgit v1.2.3 From 3ac251420be2bbc9f5e83281661dbfaf05983f69 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:24 +0200 Subject: ALSA: emu10k1: add support for 12 kHz capture on Audigy Fixes a tentative FIXME. Because we can. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 3ef9130a9577..387288d623d7 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -177,12 +177,22 @@ static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate) } } +static const unsigned int audigy_capture_rates[9] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_audigy_capture_rates = { + .count = 9, + .list = audigy_capture_rates, + .mask = 0 +}; + static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate) { switch (rate) { case 8000: return A_ADCCR_SAMPLERATE_8; case 11025: return A_ADCCR_SAMPLERATE_11; - case 12000: return A_ADCCR_SAMPLERATE_12; /* really supported? */ + case 12000: return A_ADCCR_SAMPLERATE_12; case 16000: return ADCCR_SAMPLERATE_16; case 22050: return ADCCR_SAMPLERATE_22; case 24000: return ADCCR_SAMPLERATE_24; @@ -209,7 +219,8 @@ static void snd_emu10k1_constrain_capture_rates(struct snd_emu10k1 *emu, return; } snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - &hw_constraints_capture_rates); + emu->audigy ? &hw_constraints_audigy_capture_rates : + &hw_constraints_capture_rates); } static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu, -- cgit v1.2.3 From 58cc6133cc27496c1f041f302e13c33f0867be8a Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:25 +0200 Subject: ALSA: emu10k1: actually show some S/PDIF status in /proc for E-MU cards The file is called spdif-in, but we abused it to show only sample rates from various sources. Rectify it as far as possible (the FPGA doesn't give us a lot of information). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-10-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 993b35362499..7e2cc532471f 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -194,6 +194,14 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) snd_iprintf(buffer, "BNC rate: %dHz\n", snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_SYNC_BNC)); + + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + snd_iprintf(buffer, "\nS/PDIF input invalid\n"); + else + snd_iprintf(buffer, "\nS/PDIF mode: %s%s\n", + value & EMU_HANA_SPDIF_MODE_RX_PRO ? "professional" : "consumer", + value & EMU_HANA_SPDIF_MODE_RX_NOCOPY ? ", no copy" : ""); } else { snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS); snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS); -- cgit v1.2.3 From 176bb179f190682d496220be469ea20527bb5f43 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:21 +0100 Subject: ASoC: cs35l32: Use maple tree register cache The cs35l32 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-1-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l32.c b/sound/soc/codecs/cs35l32.c index 88828714c5d6..6e658bb16fb0 100644 --- a/sound/soc/codecs/cs35l32.c +++ b/sound/soc/codecs/cs35l32.c @@ -260,7 +260,7 @@ static const struct regmap_config cs35l32_regmap = { .volatile_reg = cs35l32_volatile_register, .readable_reg = cs35l32_readable_register, .precious_reg = cs35l32_precious_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From 7a230512d335793fdc43bb85a7d5cff9dd171c26 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:22 +0100 Subject: ASoC: cs35l33: Use maple tree register cache The cs35l33 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-2-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l33.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l33.c b/sound/soc/codecs/cs35l33.c index 33552414e9f1..9968c2e189e6 100644 --- a/sound/soc/codecs/cs35l33.c +++ b/sound/soc/codecs/cs35l33.c @@ -852,7 +852,7 @@ static const struct regmap_config cs35l33_regmap = { .volatile_reg = cs35l33_volatile_register, .readable_reg = cs35l33_readable_register, .writeable_reg = cs35l33_writeable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From e7795f2d29e08e15fbc5ad88b94cf1899915a7c3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:23 +0100 Subject: ASoC: cs35l34: Use maple tree register cache The cs35l34 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-3-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l34.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l34.c b/sound/soc/codecs/cs35l34.c index 5ef3fc453391..6974dd461410 100644 --- a/sound/soc/codecs/cs35l34.c +++ b/sound/soc/codecs/cs35l34.c @@ -799,7 +799,7 @@ static struct regmap_config cs35l34_regmap = { .volatile_reg = cs35l34_volatile_register, .readable_reg = cs35l34_readable_register, .precious_reg = cs35l34_precious_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From 28f851babc484c86bc8e1919ad0bbe11f4fd9210 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:24 +0100 Subject: ASoC: cs35l35: Use maple tree register cache The cs35l35 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-4-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 2e3bb61a2bf4..0a4b5aa78185 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1099,7 +1099,7 @@ static struct regmap_config cs35l35_regmap = { .volatile_reg = cs35l35_volatile_register, .readable_reg = cs35l35_readable_register, .precious_reg = cs35l35_precious_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From bb1bd25ad79cf21b8fa4c0eae474307b2d24b268 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:25 +0100 Subject: ASoC: cs4234: Use maple tree register cache The cs4234 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-5-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs4234.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs4234.c b/sound/soc/codecs/cs4234.c index 2d53a440a107..69287ba7e955 100644 --- a/sound/soc/codecs/cs4234.c +++ b/sound/soc/codecs/cs4234.c @@ -675,7 +675,7 @@ static const struct regmap_config cs4234_regmap = { .writeable_reg = cs4234_writeable_register, .reg_defaults = cs4234_default_reg, .num_reg_defaults = ARRAY_SIZE(cs4234_default_reg), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 6b7fed83c9455f64a1509a9e1d512a92edaaf44e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:26 +0100 Subject: ASoC: cs42l42: Use maple tree register cache The cs42l42 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-6-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l42.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c index 8aa6af21e52c..a0de0329406a 100644 --- a/sound/soc/codecs/cs42l42.c +++ b/sound/soc/codecs/cs42l42.c @@ -393,7 +393,7 @@ const struct regmap_config cs42l42_regmap = { .max_register = CS42L42_MAX_REGISTER, .reg_defaults = cs42l42_reg_defaults, .num_reg_defaults = ARRAY_SIZE(cs42l42_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From 7e39a71876244639774c71144e4b5dee7799e1cf Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:27 +0100 Subject: ASoC: cs42l73: Use maple tree register cache The cs42l73 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-7-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l73.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs42l73.c b/sound/soc/codecs/cs42l73.c index 378a4400d234..6ab67d196d10 100644 --- a/sound/soc/codecs/cs42l73.c +++ b/sound/soc/codecs/cs42l73.c @@ -1267,7 +1267,7 @@ static const struct regmap_config cs42l73_regmap = { .num_reg_defaults = ARRAY_SIZE(cs42l73_reg_defaults), .volatile_reg = cs42l73_volatile_register, .readable_reg = cs42l73_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From 62145b0a537410d7ce237945c339635f9a86a895 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:28 +0100 Subject: ASoC: cs42l83: Use maple tree register cache The cs42l83 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-8-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs42l83-i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs42l83-i2c.c b/sound/soc/codecs/cs42l83-i2c.c index d3aa1edf60bb..f482b6a4f5c3 100644 --- a/sound/soc/codecs/cs42l83-i2c.c +++ b/sound/soc/codecs/cs42l83-i2c.c @@ -158,7 +158,7 @@ static const struct regmap_config cs42l83_regmap = { .max_register = CS42L42_MAX_REGISTER, .reg_defaults = cs42l83_reg_defaults, .num_reg_defaults = ARRAY_SIZE(cs42l83_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From ce598b2f83608f3818f8a4079662d3844679b16f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:29 +0100 Subject: ASoC: cs43130: Use maple tree register cache The cs43130 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-9-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs43130.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs43130.c b/sound/soc/codecs/cs43130.c index 523ca54ebf64..3292405024bc 100644 --- a/sound/soc/codecs/cs43130.c +++ b/sound/soc/codecs/cs43130.c @@ -2357,7 +2357,7 @@ static const struct regmap_config cs43130_regmap = { .readable_reg = cs43130_readable_register, .precious_reg = cs43130_precious_register, .volatile_reg = cs43130_volatile_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, /* needed for regcache_sync */ .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From 0eff26b13da4eb5a71a59280c3483273ccb5b9cb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:56:30 +0100 Subject: ASoC: cs35l30: Use maple tree register cache The cs35l30 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Acked-by: David Rhodes Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-cirrus-maple-v1-10-b806c4cbd1d4@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/cs53l30.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index 21962b828ab1..f4065555c36e 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -911,7 +911,7 @@ static struct regmap_config cs53l30_regmap = { .volatile_reg = cs53l30_volatile_register, .writeable_reg = cs53l30_writeable_register, .readable_reg = cs53l30_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, -- cgit v1.2.3 From ac950278b0872c87bcef6153fd9c119265c8ba83 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 7 Jun 2023 11:12:41 +0800 Subject: ASoC: add N cpus to M codecs dai link support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, ASoC supports dailinks with the following mappings: 1 cpu DAI to N codec DAIs N cpu DAIs to N codec DAIs But the mapping between N cpu DAIs and M codec DAIs is not supported. The reason is that we didn't have a mechanism to map cpu and codec DAIs This patch suggests a new snd_soc_dai_link_codec_ch_map struct in struct snd_soc_dai_link{} which provides codec DAI to cpu DAI mapping information used to implement N cpu DAIs to M codec DAIs support. When a dailink contains two or more cpu DAIs, we should set channel number of cpus based on its channel mask. The new struct also provides channel mask information for each codec and we can construct the cpu channel mask by combining all codec channel masks which map to the cpu. The N:M mapping is however restricted to the N <= M case due to physical restrictions on a time-multiplexed bus such as I2S/TDM, AC97, SoundWire and HDaudio. Signed-off-by: Bard Liao Reviewed-by: Péter Ujfalusi Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230607031242.1032060-2-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- include/sound/soc.h | 6 ++++++ sound/soc/soc-dapm.c | 24 +++++++++++++++++++++++- sound/soc/soc-pcm.c | 44 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/include/sound/soc.h b/include/sound/soc.h index 10e4ea0664af..1e48a1135844 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -645,6 +645,11 @@ struct snd_soc_dai_link_component { const char *dai_name; }; +struct snd_soc_dai_link_codec_ch_map { + unsigned int connected_cpu_id; + unsigned int ch_mask; +}; + struct snd_soc_dai_link { /* config - must be set by machine driver */ const char *name; /* Codec name */ @@ -673,6 +678,7 @@ struct snd_soc_dai_link { struct snd_soc_dai_link_component *codecs; unsigned int num_codecs; + struct snd_soc_dai_link_codec_ch_map *codec_ch_maps; /* * You MAY specify the link's platform/PCM/DMA driver, either by * device name, or by DT/OF node, but not both. Some forms of link diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b7b31d4e8ae8..3091e8160bad 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -4444,9 +4444,31 @@ void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card) for_each_rtd_codec_dais(rtd, i, codec_dai) dapm_connect_dai_pair(card, rtd, codec_dai, asoc_rtd_to_cpu(rtd, i)); + } else if (rtd->dai_link->num_codecs > rtd->dai_link->num_cpus) { + int cpu_id; + + if (!rtd->dai_link->codec_ch_maps) { + dev_err(card->dev, "%s: no codec channel mapping table provided\n", + __func__); + continue; + } + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + cpu_id = rtd->dai_link->codec_ch_maps[i].connected_cpu_id; + if (cpu_id >= rtd->dai_link->num_cpus) { + dev_err(card->dev, + "%s: dai_link %s cpu_id %d too large, num_cpus is %d\n", + __func__, rtd->dai_link->name, cpu_id, + rtd->dai_link->num_cpus); + continue; + } + dapm_connect_dai_pair(card, rtd, codec_dai, + asoc_rtd_to_cpu(rtd, cpu_id)); + } } else { dev_err(card->dev, - "N cpus to M codecs link is not supported yet\n"); + "%s: codec number %d < cpu number %d is not supported\n", + __func__, rtd->dai_link->num_codecs, rtd->dai_link->num_cpus); } } } diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 799865a6eb56..60cfbe565759 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1034,6 +1034,10 @@ static int __soc_pcm_hw_params(struct snd_soc_pcm_runtime *rtd, } for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + struct snd_pcm_hw_params cpu_params; + unsigned int ch_mask = 0; + int j; + /* * Skip CPUs which don't support the current stream * type. See soc_pcm_init_runtime_hw() for more details @@ -1041,13 +1045,32 @@ static int __soc_pcm_hw_params(struct snd_soc_pcm_runtime *rtd, if (!snd_soc_dai_stream_valid(cpu_dai, substream->stream)) continue; - ret = snd_soc_dai_hw_params(cpu_dai, substream, params); + /* copy params for each cpu */ + cpu_params = *params; + + if (!rtd->dai_link->codec_ch_maps) + goto hw_params; + /* + * construct cpu channel mask by combining ch_mask of each + * codec which maps to the cpu. + */ + for_each_rtd_codec_dais(rtd, j, codec_dai) { + if (rtd->dai_link->codec_ch_maps[j].connected_cpu_id == i) + ch_mask |= rtd->dai_link->codec_ch_maps[j].ch_mask; + } + + /* fixup cpu channel number */ + if (ch_mask) + soc_pcm_codec_params_fixup(&cpu_params, ch_mask); + +hw_params: + ret = snd_soc_dai_hw_params(cpu_dai, substream, &cpu_params); if (ret < 0) goto out; /* store the parameters for each DAI */ - soc_pcm_set_dai_params(cpu_dai, params); - snd_soc_dapm_update_dai(substream, params, cpu_dai); + soc_pcm_set_dai_params(cpu_dai, &cpu_params); + snd_soc_dapm_update_dai(substream, &cpu_params, cpu_dai); } ret = snd_soc_pcm_component_hw_params(substream, params); @@ -2789,9 +2812,22 @@ static int soc_get_playback_capture(struct snd_soc_pcm_runtime *rtd, cpu_dai = asoc_rtd_to_cpu(rtd, 0); } else if (dai_link->num_cpus == dai_link->num_codecs) { cpu_dai = asoc_rtd_to_cpu(rtd, i); + } else if (rtd->dai_link->num_codecs > rtd->dai_link->num_cpus) { + int cpu_id; + + if (!rtd->dai_link->codec_ch_maps) { + dev_err(rtd->card->dev, "%s: no codec channel mapping table provided\n", + __func__); + return -EINVAL; + } + + cpu_id = rtd->dai_link->codec_ch_maps[i].connected_cpu_id; + cpu_dai = asoc_rtd_to_cpu(rtd, cpu_id); } else { dev_err(rtd->card->dev, - "N cpus to M codecs link is not supported yet\n"); + "%s codec number %d < cpu number %d is not supported\n", + __func__, rtd->dai_link->num_codecs, + rtd->dai_link->num_cpus); return -EINVAL; } -- cgit v1.2.3 From 0281b02e1913a9443ce891dcc13613829e4dc3c5 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Wed, 7 Jun 2023 11:12:42 +0800 Subject: ASoC: Intel: sof_sdw: add dai_link_codec_ch_map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The captured data will be combined from each cpu DAI if the dai link has more than one cpu DAIs. We can set channel number indirectly by adding sdw_codec_ch_maps. Signed-off-by: Bard Liao Reviewed-by: Péter Ujfalusi Reviewed-by: Pierre-Louis Bossart Reviewed-by: Ranjani Sridharan Link: https://lore.kernel.org/r/20230607031242.1032060-3-yung-chuan.liao@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_sdw.c | 69 +++++++++++++++++++++++++++++++++ sound/soc/intel/boards/sof_sdw_common.h | 2 + sound/soc/intel/boards/sof_sdw_maxim.c | 1 + 3 files changed, 72 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_sdw.c b/sound/soc/intel/boards/sof_sdw.c index d942696b36cd..f2f56150e1da 100644 --- a/sound/soc/intel/boards/sof_sdw.c +++ b/sound/soc/intel/boards/sof_sdw.c @@ -560,6 +560,55 @@ int sdw_trigger(struct snd_pcm_substream *substream, int cmd) return ret; } +int sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + int ch = params_channels(params); + struct snd_soc_dai *codec_dai; + struct snd_soc_dai *cpu_dai; + unsigned int ch_mask; + int num_codecs; + int step; + int i; + int j; + + if (!rtd->dai_link->codec_ch_maps) + return 0; + + /* Identical data will be sent to all codecs in playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ch_mask = GENMASK(ch - 1, 0); + step = 0; + } else { + num_codecs = rtd->dai_link->num_codecs; + + if (ch < num_codecs || ch % num_codecs != 0) { + dev_err(rtd->dev, "Channels number %d is invalid when codec number = %d\n", + ch, num_codecs); + return -EINVAL; + } + + ch_mask = GENMASK(ch / num_codecs - 1, 0); + step = hweight_long(ch_mask); + + } + + /* + * The captured data will be combined from each cpu DAI if the dai + * link has more than one codec DAIs. Set codec channel mask and + * ASoC will set the corresponding channel numbers for each cpu dai. + */ + for_each_rtd_cpu_dais(rtd, i, cpu_dai) { + for_each_rtd_codec_dais(rtd, j, codec_dai) { + if (rtd->dai_link->codec_ch_maps[j].connected_cpu_id != i) + continue; + rtd->dai_link->codec_ch_maps[j].ch_mask = ch_mask << (j * step); + } + } + return 0; +} + int sdw_hw_free(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); @@ -588,6 +637,7 @@ static const struct snd_soc_ops sdw_ops = { .startup = sdw_startup, .prepare = sdw_prepare, .trigger = sdw_trigger, + .hw_params = sdw_hw_params, .hw_free = sdw_hw_free, .shutdown = sdw_shutdown, }; @@ -1281,6 +1331,17 @@ static int get_slave_info(const struct snd_soc_acpi_link_adr *adr_link, return 0; } +static void set_dailink_map(struct snd_soc_dai_link_codec_ch_map *sdw_codec_ch_maps, + int codec_num, int cpu_num) +{ + int step; + int i; + + step = codec_num / cpu_num; + for (i = 0; i < codec_num; i++) + sdw_codec_ch_maps[i].connected_cpu_id = i / step; +} + static const char * const type_strings[] = {"SimpleJack", "SmartAmp", "SmartMic"}; static int create_sdw_dailink(struct snd_soc_card *card, @@ -1357,6 +1418,7 @@ static int create_sdw_dailink(struct snd_soc_card *card, cpu_dai_index = *cpu_id; for_each_pcm_streams(stream) { + struct snd_soc_dai_link_codec_ch_map *sdw_codec_ch_maps; char *name, *cpu_name; int playback, capture; static const char * const sdw_stream_name[] = { @@ -1375,6 +1437,11 @@ static int create_sdw_dailink(struct snd_soc_card *card, return -EINVAL; } + sdw_codec_ch_maps = devm_kcalloc(dev, codec_num, + sizeof(*sdw_codec_ch_maps), GFP_KERNEL); + if (!sdw_codec_ch_maps) + return -ENOMEM; + /* create stream name according to first link id */ if (append_dai_type) { name = devm_kasprintf(dev, GFP_KERNEL, @@ -1435,6 +1502,8 @@ static int create_sdw_dailink(struct snd_soc_card *card, */ dai_links[*link_index].nonatomic = true; + set_dailink_map(sdw_codec_ch_maps, codec_num, cpu_dai_num); + dai_links[*link_index].codec_ch_maps = sdw_codec_ch_maps; ret = set_codec_init_func(card, link, dai_links + (*link_index)++, playback, group_id, adr_index, dai_index); if (ret < 0) { diff --git a/sound/soc/intel/boards/sof_sdw_common.h b/sound/soc/intel/boards/sof_sdw_common.h index 64cfa5d1aceb..37402170d5f9 100644 --- a/sound/soc/intel/boards/sof_sdw_common.h +++ b/sound/soc/intel/boards/sof_sdw_common.h @@ -103,6 +103,8 @@ extern unsigned long sof_sdw_quirk; int sdw_startup(struct snd_pcm_substream *substream); int sdw_prepare(struct snd_pcm_substream *substream); int sdw_trigger(struct snd_pcm_substream *substream, int cmd); +int sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params); int sdw_hw_free(struct snd_pcm_substream *substream); void sdw_shutdown(struct snd_pcm_substream *substream); diff --git a/sound/soc/intel/boards/sof_sdw_maxim.c b/sound/soc/intel/boards/sof_sdw_maxim.c index 8d40a83ad98e..414c4d8dac77 100644 --- a/sound/soc/intel/boards/sof_sdw_maxim.c +++ b/sound/soc/intel/boards/sof_sdw_maxim.c @@ -123,6 +123,7 @@ static const struct snd_soc_ops max_98373_sdw_ops = { .startup = sdw_startup, .prepare = mx8373_sdw_prepare, .trigger = sdw_trigger, + .hw_params = sdw_hw_params, .hw_free = mx8373_sdw_hw_free, .shutdown = sdw_shutdown, }; -- cgit v1.2.3 From 356caf663deee8dc46ff3168ec0b24bcbeb00b28 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 8 Jun 2023 06:48:36 +0000 Subject: ASoC: add new trigger ordering method Current ASoC is assuming that trigger starting order is Link -> Component -> DAI as default, and its reverse order for stopping. But some Driver / Card want to reorder it for some reasons. We have such flags, but is unbalance like below. struct snd_soc_component_driver :: start_dma_last struct snd_soc_dai_link :: stop_dma_first We want to have more flexible, and more generic method. This patch adds new snd_soc_trigger_order for start/stop at component / DAI-link. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87r0qmfnzx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 9 ++++ include/sound/soc.h | 17 +++++++ sound/soc/soc-pcm.c | 114 ++++++++++++++++++++++++------------------ 3 files changed, 90 insertions(+), 50 deletions(-) (limited to 'sound') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index 0b47603c9db2..c7733382757b 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -158,6 +158,15 @@ struct snd_soc_component_driver { int probe_order; int remove_order; + /* + * soc_pcm_trigger() start/stop sequence. + * see also + * snd_soc_dai_link + * soc_pcm_trigger() + */ + enum snd_soc_trigger_order trigger_start; + enum snd_soc_trigger_order trigger_stop; + /* * signal if the module handling the component should not be removed * if a pcm is open. Setting this would prevent the module diff --git a/include/sound/soc.h b/include/sound/soc.h index 10e4ea0664af..49442583d46d 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -607,6 +607,14 @@ int snd_soc_get_strobe(struct snd_kcontrol *kcontrol, int snd_soc_put_strobe(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); +enum snd_soc_trigger_order { + /* start stop */ + SND_SOC_TRIGGER_ORDER_DEFAULT = 0, /* Link->Component->DAI DAI->Component->Link */ + SND_SOC_TRIGGER_ORDER_LDC, /* Link->DAI->Component Component->DAI->Link */ + + SND_SOC_TRIGGER_ORDER_MAX, +}; + /* SoC PCM stream information */ struct snd_soc_pcm_stream { const char *stream_name; @@ -707,6 +715,15 @@ struct snd_soc_dai_link { const struct snd_soc_ops *ops; const struct snd_soc_compr_ops *compr_ops; + /* + * soc_pcm_trigger() start/stop sequence. + * see also + * snd_soc_component_driver + * soc_pcm_trigger() + */ + enum snd_soc_trigger_order trigger_start; + enum snd_soc_trigger_order trigger_stop; + /* Mark this pcm with non atomic ops */ unsigned int nonatomic:1; diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 799865a6eb56..a10c928debe3 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1071,49 +1071,77 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, return ret; } +#define TRIGGER_MAX 3 +static int (* const trigger[][TRIGGER_MAX])(struct snd_pcm_substream *substream, int cmd, int rollback) = { + [SND_SOC_TRIGGER_ORDER_DEFAULT] = { + snd_soc_link_trigger, + snd_soc_pcm_component_trigger, + snd_soc_pcm_dai_trigger, + }, + [SND_SOC_TRIGGER_ORDER_LDC] = { + snd_soc_link_trigger, + snd_soc_pcm_dai_trigger, + snd_soc_pcm_component_trigger, + }, +}; + static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_component *component; - int ret = -EINVAL, _ret = 0, start_dma_last = 0, i; + int ret = 0, r = 0, i; int rollback = 0; + int start = 0, stop = 0; + /* + * select START/STOP sequence + */ + for_each_rtd_components(rtd, i, component) { + if (component->driver->trigger_start) + start = component->driver->trigger_start; + if (component->driver->trigger_stop) + stop = component->driver->trigger_stop; + } + if (rtd->dai_link->trigger_start) + start = rtd->dai_link->trigger_start; + if (rtd->dai_link->trigger_stop) + stop = rtd->dai_link->trigger_stop; + + if (start < 0 || start >= SND_SOC_TRIGGER_ORDER_MAX || + stop < 0 || stop >= SND_SOC_TRIGGER_ORDER_MAX) + return -EINVAL; + + /* REMOVE ME */ + for_each_rtd_components(rtd, i, component) { + if (component->driver->start_dma_last) { + start = SND_SOC_TRIGGER_ORDER_LDC; + break; + } + } + if (rtd->dai_link->stop_dma_first) + stop = SND_SOC_TRIGGER_ORDER_LDC; + + /* + * START + */ switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - /* Do we need to start dma last? */ - for_each_rtd_components(rtd, i, component) { - if (component->driver->start_dma_last) { - start_dma_last = 1; + for (i = 0; i < TRIGGER_MAX; i++) { + r = trigger[start][i](substream, cmd, 0); + if (r < 0) break; - } - } - - ret = snd_soc_link_trigger(substream, cmd, 0); - if (ret < 0) - goto start_err; - - if (start_dma_last) { - ret = snd_soc_pcm_dai_trigger(substream, cmd, 0); - if (ret < 0) - goto start_err; - - ret = snd_soc_pcm_component_trigger(substream, cmd, 0); - } else { - ret = snd_soc_pcm_component_trigger(substream, cmd, 0); - if (ret < 0) - goto start_err; - - ret = snd_soc_pcm_dai_trigger(substream, cmd, 0); } -start_err: - if (ret < 0) - rollback = 1; } - if (rollback) { - _ret = ret; + /* + * Rollback if START failed + * find correspond STOP command + */ + if (r < 0) { + rollback = 1; + ret = r; switch (cmd) { case SNDRV_PCM_TRIGGER_START: cmd = SNDRV_PCM_TRIGGER_STOP; @@ -1127,34 +1155,20 @@ start_err: } } + /* + * STOP + */ switch (cmd) { case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (rtd->dai_link->stop_dma_first) { - ret = snd_soc_pcm_component_trigger(substream, cmd, rollback); - if (ret < 0) - break; - - ret = snd_soc_pcm_dai_trigger(substream, cmd, rollback); - if (ret < 0) - break; - } else { - ret = snd_soc_pcm_dai_trigger(substream, cmd, rollback); - if (ret < 0) - break; - - ret = snd_soc_pcm_component_trigger(substream, cmd, rollback); - if (ret < 0) - break; + for (i = TRIGGER_MAX; i > 0; i--) { + r = trigger[stop][i - 1](substream, cmd, rollback); + if (r < 0) + ret = r; } - ret = snd_soc_link_trigger(substream, cmd, rollback); - break; } - if (_ret) - ret = _ret; - return ret; } -- cgit v1.2.3 From 4a6aeaebbe3b5ef2ae637c00840de171a6c93478 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 8 Jun 2023 06:48:51 +0000 Subject: ASoC: amd: use use new trigger ordering method ASoC is now supporting generic trigger ordering method. This patch switch to use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87pm66fnzi.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/amd/acp-da7219-max98357a.c | 20 ++++++++++---------- sound/soc/amd/acp-es8336.c | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/acp-da7219-max98357a.c b/sound/soc/amd/acp-da7219-max98357a.c index 375417bd7d6e..7464ca2b596c 100644 --- a/sound/soc/amd/acp-da7219-max98357a.c +++ b/sound/soc/amd/acp-da7219-max98357a.c @@ -524,7 +524,7 @@ static struct snd_soc_dai_link cz_dai_7219_98357[] = { | SND_SOC_DAIFMT_CBP_CFP, .init = cz_da7219_init, .dpcm_playback = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_da7219_play_ops, SND_SOC_DAILINK_REG(designware1, dlgs, platform), }, @@ -534,7 +534,7 @@ static struct snd_soc_dai_link cz_dai_7219_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_da7219_cap_ops, SND_SOC_DAILINK_REG(designware2, dlgs, platform), }, @@ -544,7 +544,7 @@ static struct snd_soc_dai_link cz_dai_7219_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_playback = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_max_play_ops, SND_SOC_DAILINK_REG(designware3, mx, platform), }, @@ -555,7 +555,7 @@ static struct snd_soc_dai_link cz_dai_7219_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_dmic0_cap_ops, SND_SOC_DAILINK_REG(designware3, adau, platform), }, @@ -566,7 +566,7 @@ static struct snd_soc_dai_link cz_dai_7219_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_dmic1_cap_ops, SND_SOC_DAILINK_REG(designware2, adau, platform), }, @@ -580,7 +580,7 @@ static struct snd_soc_dai_link cz_dai_5682_98357[] = { | SND_SOC_DAIFMT_CBP_CFP, .init = cz_rt5682_init, .dpcm_playback = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_rt5682_play_ops, SND_SOC_DAILINK_REG(designware1, rt5682, platform), }, @@ -590,7 +590,7 @@ static struct snd_soc_dai_link cz_dai_5682_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_rt5682_cap_ops, SND_SOC_DAILINK_REG(designware2, rt5682, platform), }, @@ -600,7 +600,7 @@ static struct snd_soc_dai_link cz_dai_5682_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_playback = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_rt5682_max_play_ops, SND_SOC_DAILINK_REG(designware3, mx, platform), }, @@ -611,7 +611,7 @@ static struct snd_soc_dai_link cz_dai_5682_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_rt5682_dmic0_cap_ops, SND_SOC_DAILINK_REG(designware3, adau, platform), }, @@ -622,7 +622,7 @@ static struct snd_soc_dai_link cz_dai_5682_98357[] = { .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, .dpcm_capture = 1, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .ops = &cz_rt5682_dmic1_cap_ops, SND_SOC_DAILINK_REG(designware2, adau, platform), }, diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 89499542c803..5e56d3a53be7 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -149,7 +149,7 @@ static struct snd_soc_dai_link st_dai_es8336[] = { .stream_name = "ES8336 HiFi Play", .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBP_CFP, - .stop_dma_first = 1, + .trigger_stop = SND_SOC_TRIGGER_ORDER_LDC, .dpcm_capture = 1, .dpcm_playback = 1, .init = st_es8336_init, -- cgit v1.2.3 From 38cb2a362d070dcabfbfb2466ca409751c426c30 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 8 Jun 2023 06:48:58 +0000 Subject: ASoC: atmel: use use new trigger ordering method ASoC is now supporting generic trigger ordering method. This patch switch to use it. Signed-off-by: Kuninori Morimoto Reviewed-by: Claudiu Beznea Tested-by: Claudiu Beznea Link: https://lore.kernel.org/r/87o7lqfnzb.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/atmel/mchp-pdmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/atmel/mchp-pdmc.c b/sound/soc/atmel/mchp-pdmc.c index da23855a0e40..c79c73e6791e 100644 --- a/sound/soc/atmel/mchp-pdmc.c +++ b/sound/soc/atmel/mchp-pdmc.c @@ -423,7 +423,7 @@ static const struct snd_soc_component_driver mchp_pdmc_dai_component = { .open = &mchp_pdmc_open, .close = &mchp_pdmc_close, .legacy_dai_naming = 1, - .start_dma_last = 1, + .trigger_start = SND_SOC_TRIGGER_ORDER_LDC, }; static const unsigned int mchp_pdmc_1mic[] = {1}; -- cgit v1.2.3 From 0a67a14f74ace85cbd5bd4f49595850db2ebe53c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 8 Jun 2023 06:49:04 +0000 Subject: ASoC: starfive: use use new trigger ordering method ASoC is now supporting generic trigger ordering method. This patch switch to use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87mt1afnz5.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/starfive/jh7110_tdm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/starfive/jh7110_tdm.c b/sound/soc/starfive/jh7110_tdm.c index e4bdba20c499..5f5a6ca7dbda 100644 --- a/sound/soc/starfive/jh7110_tdm.c +++ b/sound/soc/starfive/jh7110_tdm.c @@ -328,7 +328,7 @@ static int jh7110_tdm_startup(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai_link *dai_link = rtd->dai_link; - dai_link->stop_dma_first = 1; + dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC; return 0; } -- cgit v1.2.3 From 099770e2dae04579670947aaf8b5c70ef6a4cb6a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 8 Jun 2023 06:49:11 +0000 Subject: ASoC: remove old trigger ordering method All drivers switch to use generic trigger ordering method. Let's remove old method. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87legufnyy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-component.h | 2 -- include/sound/soc.h | 6 ------ sound/soc/soc-pcm.c | 10 ---------- 3 files changed, 18 deletions(-) (limited to 'sound') diff --git a/include/sound/soc-component.h b/include/sound/soc-component.h index c7733382757b..87f248a06271 100644 --- a/include/sound/soc-component.h +++ b/include/sound/soc-component.h @@ -199,8 +199,6 @@ struct snd_soc_component_driver { bool use_dai_pcm_id; /* use DAI link PCM ID as PCM device number */ int be_pcm_base; /* base device ID for all BE PCMs */ - unsigned int start_dma_last; - #ifdef CONFIG_DEBUG_FS const char *debugfs_prefix; #endif diff --git a/include/sound/soc.h b/include/sound/soc.h index 49442583d46d..52bb64d427f5 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -762,12 +762,6 @@ struct snd_soc_dai_link { /* Do not create a PCM for this DAI link (Backend link) */ unsigned int ignore:1; - /* This flag will reorder stop sequence. By enabling this flag - * DMA controller stop sequence will be invoked first followed by - * CPU DAI driver stop sequence - */ - unsigned int stop_dma_first:1; - #ifdef CONFIG_SND_SOC_TOPOLOGY struct snd_soc_dobj dobj; /* For topology */ #endif diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index a10c928debe3..fd45a7433c24 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -1111,16 +1111,6 @@ static int soc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) stop < 0 || stop >= SND_SOC_TRIGGER_ORDER_MAX) return -EINVAL; - /* REMOVE ME */ - for_each_rtd_components(rtd, i, component) { - if (component->driver->start_dma_last) { - start = SND_SOC_TRIGGER_ORDER_LDC; - break; - } - } - if (rtd->dai_link->stop_dma_first) - stop = SND_SOC_TRIGGER_ORDER_LDC; - /* * START */ -- cgit v1.2.3 From 82a28d5aa582a98f40ab527af08c66556dd3d310 Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Tue, 13 Jun 2023 11:54:54 +0200 Subject: ASoC: siu: Add MODULE_FIRMWARE macro The module loads firmware so add a MODULE_FIRMWARE macro to provide that information via modinfo. Signed-off-by: Juerg Haefliger Link: https://lore.kernel.org/r/20230613095454.38696-1-juerg.haefliger@canonical.com Signed-off-by: Mark Brown --- sound/soc/sh/siu_dai.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/soc/sh/siu_dai.c b/sound/soc/sh/siu_dai.c index 84e1b14e68e4..d0b5c543fd2f 100644 --- a/sound/soc/sh/siu_dai.c +++ b/sound/soc/sh/siu_dai.c @@ -796,3 +796,5 @@ module_platform_driver(siu_driver); MODULE_AUTHOR("Carlos Munoz "); MODULE_DESCRIPTION("ALSA SoC SH7722 SIU driver"); MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("siu_spb.bin"); -- cgit v1.2.3 From 049a78048e15ab276052d846c9692ea272699644 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:12 +0100 Subject: ASoC: rt700: Use maple tree register cache The rt700 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-1-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt700-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt700-sdw.c b/sound/soc/codecs/rt700-sdw.c index ba7767bee07c..8b28e47775cc 100644 --- a/sound/soc/codecs/rt700-sdw.c +++ b/sound/soc/codecs/rt700-sdw.c @@ -292,7 +292,7 @@ static const struct regmap_config rt700_regmap = { .max_register = 0x755800, .reg_defaults = rt700_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt700_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, .reg_read = rt700_sdw_read, -- cgit v1.2.3 From 0a5757293339fbbbb627baa7d2da65fd083c7cce Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:13 +0100 Subject: ASoC: rt711: Use maple tree register cache The rt711 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-2-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt711-sdca-sdw.c | 4 ++-- sound/soc/codecs/rt711-sdw.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c index 2c5eb28259dc..119e1f9605d7 100644 --- a/sound/soc/codecs/rt711-sdca-sdw.c +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -119,7 +119,7 @@ static const struct regmap_config rt711_sdca_regmap = { .max_register = 0x44ffffff, .reg_defaults = rt711_sdca_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt711_sdca_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; @@ -133,7 +133,7 @@ static const struct regmap_config rt711_sdca_mbq_regmap = { .max_register = 0x40800f12, .reg_defaults = rt711_sdca_mbq_defaults, .num_reg_defaults = ARRAY_SIZE(rt711_sdca_mbq_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c index b8ed3c6236d8..87dafcb4545d 100644 --- a/sound/soc/codecs/rt711-sdw.c +++ b/sound/soc/codecs/rt711-sdw.c @@ -296,7 +296,7 @@ static const struct regmap_config rt711_regmap = { .max_register = 0x755800, .reg_defaults = rt711_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt711_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, .reg_read = rt711_sdw_read, -- cgit v1.2.3 From f438c799aa934fcd9b956083043b6f691bcc8492 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:14 +0100 Subject: ASoC: rt712: Use maple tree register cache The rt712 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-3-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt712-sdca-dmic.c | 4 ++-- sound/soc/codecs/rt712-sdca-sdw.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt712-sdca-dmic.c b/sound/soc/codecs/rt712-sdca-dmic.c index 847198e6c07e..869cc7bfd178 100644 --- a/sound/soc/codecs/rt712-sdca-dmic.c +++ b/sound/soc/codecs/rt712-sdca-dmic.c @@ -110,7 +110,7 @@ static const struct regmap_config rt712_sdca_dmic_regmap = { .max_register = 0x40981300, .reg_defaults = rt712_sdca_dmic_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt712_sdca_dmic_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; @@ -124,7 +124,7 @@ static const struct regmap_config rt712_sdca_dmic_mbq_regmap = { .max_register = 0x40800f14, .reg_defaults = rt712_sdca_dmic_mbq_defaults, .num_reg_defaults = ARRAY_SIZE(rt712_sdca_dmic_mbq_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; diff --git a/sound/soc/codecs/rt712-sdca-sdw.c b/sound/soc/codecs/rt712-sdca-sdw.c index 8f65516e7562..ad06267b0ea0 100644 --- a/sound/soc/codecs/rt712-sdca-sdw.c +++ b/sound/soc/codecs/rt712-sdca-sdw.c @@ -116,7 +116,7 @@ static const struct regmap_config rt712_sdca_regmap = { .max_register = 0x44ffffff, .reg_defaults = rt712_sdca_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt712_sdca_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; @@ -130,7 +130,7 @@ static const struct regmap_config rt712_sdca_mbq_regmap = { .max_register = 0x41000312, .reg_defaults = rt712_sdca_mbq_defaults, .num_reg_defaults = ARRAY_SIZE(rt712_sdca_mbq_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From dd08b6ddcb319375b4ee69cd02ce3298ca7608aa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:15 +0100 Subject: ASoC: rt715: Use maple tree register cache The rt715 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-4-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt715-sdca-sdw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt715-sdca-sdw.c b/sound/soc/codecs/rt715-sdca-sdw.c index 7e5ddce8097d..df10916bab46 100644 --- a/sound/soc/codecs/rt715-sdca-sdw.c +++ b/sound/soc/codecs/rt715-sdca-sdw.c @@ -97,7 +97,7 @@ static const struct regmap_config rt715_sdca_regmap = { .max_register = 0x43ffffff, .reg_defaults = rt715_reg_defaults_sdca, .num_reg_defaults = ARRAY_SIZE(rt715_reg_defaults_sdca), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; @@ -111,7 +111,7 @@ static const struct regmap_config rt715_sdca_mbq_regmap = { .max_register = 0x43ffffff, .reg_defaults = rt715_mbq_reg_defaults_sdca, .num_reg_defaults = ARRAY_SIZE(rt715_mbq_reg_defaults_sdca), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 799457a3200b0451ca9859c77dd4e863f70ba608 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:16 +0100 Subject: ASoC: rt722: Use maple tree register cache The rt722 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-5-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt722-sdca-sdw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt722-sdca-sdw.c b/sound/soc/codecs/rt722-sdca-sdw.c index bfb2dac6bfee..cc57e4e27805 100644 --- a/sound/soc/codecs/rt722-sdca-sdw.c +++ b/sound/soc/codecs/rt722-sdca-sdw.c @@ -139,7 +139,7 @@ static const struct regmap_config rt722_sdca_regmap = { .max_register = 0x44ffffff, .reg_defaults = rt722_sdca_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt722_sdca_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; @@ -153,7 +153,7 @@ static const struct regmap_config rt722_sdca_mbq_regmap = { .max_register = 0x41000312, .reg_defaults = rt722_sdca_mbq_defaults, .num_reg_defaults = ARRAY_SIZE(rt722_sdca_mbq_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 4f69e29ace9dce5f8226bfc99b77b8497d3d3d79 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:17 +0100 Subject: ASoC: rt1308: Use maple tree register cache The rt1308 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-6-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1308-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1308-sdw.c b/sound/soc/codecs/rt1308-sdw.c index 313e97c94532..f43520ca3187 100644 --- a/sound/soc/codecs/rt1308-sdw.c +++ b/sound/soc/codecs/rt1308-sdw.c @@ -68,7 +68,7 @@ static const struct regmap_config rt1308_sdw_regmap = { .max_register = 0xcfff, .reg_defaults = rt1308_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt1308_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 6179a2e84f0b0b353079fe965d321ed25251c996 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:18 +0100 Subject: ASoC: rt1316: Use maple tree register cache The rt1316 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-7-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1316-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c index 601b76320124..721821d9e9af 100644 --- a/sound/soc/codecs/rt1316-sdw.c +++ b/sound/soc/codecs/rt1316-sdw.c @@ -188,7 +188,7 @@ static const struct regmap_config rt1316_sdw_regmap = { .max_register = 0x4108ffff, .reg_defaults = rt1316_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt1316_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 22691a051377763e6a4e149b7362944253fb434a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:27:19 +0100 Subject: ASoC: rt1318: Use maple tree register cache The rt1318 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Tested-by: Bard Liao Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-sdw-maple-v1-8-85ee50c93905@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1318-sdw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1318-sdw.c b/sound/soc/codecs/rt1318-sdw.c index 3751d923611c..16d750102c8c 100644 --- a/sound/soc/codecs/rt1318-sdw.c +++ b/sound/soc/codecs/rt1318-sdw.c @@ -337,7 +337,7 @@ static const struct regmap_config rt1318_sdw_regmap = { .max_register = 0x41081488, .reg_defaults = rt1318_reg_defaults, .num_reg_defaults = ARRAY_SIZE(rt1318_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From fd01a15164a15328fd96f9ce820f0fc9f700f00b Mon Sep 17 00:00:00 2001 From: Venkata Prasad Potturu Date: Wed, 14 Jun 2023 16:07:05 +0530 Subject: ASoC: SOF: amd: Add support for IPC with a reply_size set to zero Add support for IPC tx_message with a reply_size set to zero, return zero when message reply_size is zero at acp_dsp_ipc_get_reply(). Signed-off-by: Venkata Prasad Potturu Link: https://lore.kernel.org/r/20230614103707.2246296-1-venkataprasad.potturu@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp-ipc.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/amd/acp-ipc.c b/sound/soc/sof/amd/acp-ipc.c index 749e856dc601..8a0fc635a997 100644 --- a/sound/soc/sof/amd/acp-ipc.c +++ b/sound/soc/sof/amd/acp-ipc.c @@ -130,6 +130,13 @@ static void acp_dsp_ipc_get_reply(struct snd_sof_dev *sdev) memcpy(msg->reply_data, &reply, sizeof(reply)); ret = reply.error; } else { + /* + * To support an IPC tx_message with a + * reply_size set to zero. + */ + if (!msg->reply_size) + goto out; + /* reply correct size ? */ if (reply.hdr.size != msg->reply_size && !(reply.hdr.cmd & SOF_IPC_GLB_PROBE)) { -- cgit v1.2.3 From fed4be313a55e9a19fdabe99d1ec373e25889e2c Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 14 Jun 2023 00:02:57 +0000 Subject: ASoC: simple-card-utils.c: share asoc_graph_parse_dai() Current Audio Graph Card/Card2 implements asoc_simple_parse_dai() on each driver, but these are same function. This patch share it as asoc_graph_parse_dai(). Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87o7lihpvy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 3 + sound/soc/generic/audio-graph-card.c | 107 +--------------------------------- sound/soc/generic/audio-graph-card2.c | 107 +--------------------------------- sound/soc/generic/simple-card-utils.c | 105 +++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 212 deletions(-) (limited to 'sound') diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 0e46f985eeda..9daef37fe9a8 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -195,6 +195,9 @@ int asoc_simple_remove(struct platform_device *pdev); int asoc_graph_card_probe(struct snd_soc_card *card); int asoc_graph_is_ports0(struct device_node *port); +int asoc_graph_parse_dai(struct device_node *ep, + struct snd_soc_dai_link_component *dlc, + int *is_single_link); #ifdef DEBUG static inline void asoc_simple_debug_dai(struct asoc_simple_priv *priv, diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c index 4e85536a1b26..c6e0f9132193 100644 --- a/sound/soc/generic/audio-graph-card.c +++ b/sound/soc/generic/audio-graph-card.c @@ -55,60 +55,6 @@ static const struct snd_soc_ops graph_ops = { .hw_params = asoc_simple_hw_params, }; -static int graph_get_dai_id(struct device_node *ep) -{ - struct device_node *node; - struct device_node *endpoint; - struct of_endpoint info; - int i, id; - const u32 *reg; - int ret; - - /* use driver specified DAI ID if exist */ - ret = snd_soc_get_dai_id(ep); - if (ret != -ENOTSUPP) - return ret; - - /* use endpoint/port reg if exist */ - ret = of_graph_parse_endpoint(ep, &info); - if (ret == 0) { - /* - * Because it will count port/endpoint if it doesn't have "reg". - * But, we can't judge whether it has "no reg", or "reg = <0>" - * only of_graph_parse_endpoint(). - * We need to check "reg" property - */ - if (of_property_present(ep, "reg")) - return info.id; - - node = of_get_parent(ep); - reg = of_get_property(node, "reg", NULL); - of_node_put(node); - if (reg) - return info.port; - } - node = of_graph_get_port_parent(ep); - - /* - * Non HDMI sound case, counting port/endpoint on its DT - * is enough. Let's count it. - */ - i = 0; - id = -1; - for_each_endpoint_of_node(node, endpoint) { - if (endpoint == ep) - id = i; - i++; - } - - of_node_put(node); - - if (id < 0) - return -ENODEV; - - return id; -} - static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc) { struct snd_soc_dai *dai = snd_soc_find_dai_with_mutex(dlc); @@ -120,57 +66,6 @@ static bool soc_component_is_pcm(struct snd_soc_dai_link_component *dlc) return false; } -static int asoc_simple_parse_dai(struct device_node *ep, - struct snd_soc_dai_link_component *dlc, - int *is_single_link) -{ - struct device_node *node; - struct of_phandle_args args; - int ret; - - if (!ep) - return 0; - - node = of_graph_get_port_parent(ep); - - /* Get dai->name */ - args.np = node; - args.args[0] = graph_get_dai_id(ep); - args.args_count = (of_graph_get_endpoint_count(node) > 1); - - /* - * FIXME - * - * Here, dlc->dai_name is pointer to CPU/Codec DAI name. - * If user unbinded CPU or Codec driver, but not for Sound Card, - * dlc->dai_name is keeping unbinded CPU or Codec - * driver's pointer. - * - * If user re-bind CPU or Codec driver again, ALSA SoC will try - * to rebind Card via snd_soc_try_rebind_card(), but because of - * above reason, it might can't bind Sound Card. - * Because Sound Card is pointing to released dai_name pointer. - * - * To avoid this rebind Card issue, - * 1) It needs to alloc memory to keep dai_name eventhough - * CPU or Codec driver was unbinded, or - * 2) user need to rebind Sound Card everytime - * if he unbinded CPU or Codec. - */ - ret = snd_soc_get_dai_name(&args, &dlc->dai_name); - if (ret < 0) { - of_node_put(node); - return ret; - } - - dlc->of_node = node; - - if (is_single_link) - *is_single_link = of_graph_get_endpoint_count(node) == 1; - - return 0; -} - static void graph_parse_convert(struct device *dev, struct device_node *ep, struct asoc_simple_data *adata) @@ -231,7 +126,7 @@ static int graph_parse_node(struct asoc_simple_priv *priv, graph_parse_mclk_fs(top, ep, dai_props); - ret = asoc_simple_parse_dai(ep, dlc, cpu); + ret = asoc_graph_parse_dai(ep, dlc, cpu); if (ret < 0) return ret; diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 25aa79dd55b3..542c4a114940 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -353,111 +353,6 @@ static const struct snd_soc_ops graph_ops = { .hw_params = asoc_simple_hw_params, }; -static int graph_get_dai_id(struct device_node *ep) -{ - struct device_node *node; - struct device_node *endpoint; - struct of_endpoint info; - int i, id; - const u32 *reg; - int ret; - - /* use driver specified DAI ID if exist */ - ret = snd_soc_get_dai_id(ep); - if (ret != -ENOTSUPP) - return ret; - - /* use endpoint/port reg if exist */ - ret = of_graph_parse_endpoint(ep, &info); - if (ret == 0) { - /* - * Because it will count port/endpoint if it doesn't have "reg". - * But, we can't judge whether it has "no reg", or "reg = <0>" - * only of_graph_parse_endpoint(). - * We need to check "reg" property - */ - if (of_property_present(ep, "reg")) - return info.id; - - node = of_get_parent(ep); - reg = of_get_property(node, "reg", NULL); - of_node_put(node); - if (reg) - return info.port; - } - node = of_graph_get_port_parent(ep); - - /* - * Non HDMI sound case, counting port/endpoint on its DT - * is enough. Let's count it. - */ - i = 0; - id = -1; - for_each_endpoint_of_node(node, endpoint) { - if (endpoint == ep) - id = i; - i++; - } - - of_node_put(node); - - if (id < 0) - return -ENODEV; - - return id; -} - -static int asoc_simple_parse_dai(struct device_node *ep, - struct snd_soc_dai_link_component *dlc, - int *is_single_link) -{ - struct device_node *node; - struct of_phandle_args args; - int ret; - - if (!ep) - return 0; - - node = of_graph_get_port_parent(ep); - - /* Get dai->name */ - args.np = node; - args.args[0] = graph_get_dai_id(ep); - args.args_count = (of_graph_get_endpoint_count(node) > 1); - - /* - * FIXME - * - * Here, dlc->dai_name is pointer to CPU/Codec DAI name. - * If user unbinded CPU or Codec driver, but not for Sound Card, - * dlc->dai_name is keeping unbinded CPU or Codec - * driver's pointer. - * - * If user re-bind CPU or Codec driver again, ALSA SoC will try - * to rebind Card via snd_soc_try_rebind_card(), but because of - * above reason, it might can't bind Sound Card. - * Because Sound Card is pointing to released dai_name pointer. - * - * To avoid this rebind Card issue, - * 1) It needs to alloc memory to keep dai_name eventhough - * CPU or Codec driver was unbinded, or - * 2) user need to rebind Sound Card everytime - * if he unbinded CPU or Codec. - */ - ret = snd_soc_get_dai_name(&args, &dlc->dai_name); - if (ret < 0) { - of_node_put(node); - return ret; - } - - dlc->of_node = node; - - if (is_single_link) - *is_single_link = of_graph_get_endpoint_count(node) == 1; - - return 0; -} - static void graph_parse_convert(struct device_node *ep, struct simple_dai_props *props) { @@ -512,7 +407,7 @@ static int __graph_parse_node(struct asoc_simple_priv *priv, graph_parse_mclk_fs(ep, dai_props); - ret = asoc_simple_parse_dai(ep, dlc, &is_single_links); + ret = asoc_graph_parse_dai(ep, dlc, &is_single_links); if (ret < 0) return ret; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index b5ac0f0d5e8e..6a3c9e4e1cfe 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1019,6 +1019,111 @@ int asoc_graph_is_ports0(struct device_node *np) } EXPORT_SYMBOL_GPL(asoc_graph_is_ports0); +static int graph_get_dai_id(struct device_node *ep) +{ + struct device_node *node; + struct device_node *endpoint; + struct of_endpoint info; + int i, id; + int ret; + + /* use driver specified DAI ID if exist */ + ret = snd_soc_get_dai_id(ep); + if (ret != -ENOTSUPP) + return ret; + + /* use endpoint/port reg if exist */ + ret = of_graph_parse_endpoint(ep, &info); + if (ret == 0) { + /* + * Because it will count port/endpoint if it doesn't have "reg". + * But, we can't judge whether it has "no reg", or "reg = <0>" + * only of_graph_parse_endpoint(). + * We need to check "reg" property + */ + if (of_property_present(ep, "reg")) + return info.id; + + node = of_get_parent(ep); + ret = of_property_present(node, "reg"); + of_node_put(node); + if (ret) + return info.port; + } + node = of_graph_get_port_parent(ep); + + /* + * Non HDMI sound case, counting port/endpoint on its DT + * is enough. Let's count it. + */ + i = 0; + id = -1; + for_each_endpoint_of_node(node, endpoint) { + if (endpoint == ep) + id = i; + i++; + } + + of_node_put(node); + + if (id < 0) + return -ENODEV; + + return id; +} + +int asoc_graph_parse_dai(struct device_node *ep, + struct snd_soc_dai_link_component *dlc, + int *is_single_link) +{ + struct device_node *node; + struct of_phandle_args args = {}; + int ret; + + if (!ep) + return 0; + + node = of_graph_get_port_parent(ep); + + /* Get dai->name */ + args.np = node; + args.args[0] = graph_get_dai_id(ep); + args.args_count = (of_graph_get_endpoint_count(node) > 1); + + /* + * FIXME + * + * Here, dlc->dai_name is pointer to CPU/Codec DAI name. + * If user unbinded CPU or Codec driver, but not for Sound Card, + * dlc->dai_name is keeping unbinded CPU or Codec + * driver's pointer. + * + * If user re-bind CPU or Codec driver again, ALSA SoC will try + * to rebind Card via snd_soc_try_rebind_card(), but because of + * above reason, it might can't bind Sound Card. + * Because Sound Card is pointing to released dai_name pointer. + * + * To avoid this rebind Card issue, + * 1) It needs to alloc memory to keep dai_name eventhough + * CPU or Codec driver was unbinded, or + * 2) user need to rebind Sound Card everytime + * if he unbinded CPU or Codec. + */ + ret = snd_soc_get_dai_name(&args, &dlc->dai_name); + if (ret < 0) { + of_node_put(node); + return ret; + } + + dlc->of_node = node; + + if (is_single_link) + *is_single_link = of_graph_get_endpoint_count(node) == 1; + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_graph_parse_dai); + /* Module information */ MODULE_AUTHOR("Kuninori Morimoto "); MODULE_DESCRIPTION("ALSA SoC Simple Card Utils"); -- cgit v1.2.3 From 79597c8bf64ca99eab385115743131d260339da5 Mon Sep 17 00:00:00 2001 From: Su Hui Date: Thu, 15 Jun 2023 10:17:32 +0800 Subject: ALSA: ac97: Fix possible NULL dereference in snd_ac97_mixer smatch error: sound/pci/ac97/ac97_codec.c:2354 snd_ac97_mixer() error: we previously assumed 'rac97' could be null (see line 2072) remove redundant assignment, return error if rac97 is NULL. Fixes: da3cec35dd3c ("ALSA: Kill snd_assert() in sound/pci/*") Signed-off-by: Su Hui Link: https://lore.kernel.org/r/20230615021732.1972194-1-suhui@nfschina.com Signed-off-by: Takashi Iwai --- sound/pci/ac97/ac97_codec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 9afc5906d662..80a65b8ad7b9 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2069,8 +2069,8 @@ int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, .dev_disconnect = snd_ac97_dev_disconnect, }; - if (rac97) - *rac97 = NULL; + if (!rac97) + return -EINVAL; if (snd_BUG_ON(!bus || !template)) return -EINVAL; if (snd_BUG_ON(template->num >= 4)) -- cgit v1.2.3 From 45b4ad53d4840d92681060c11fcd4f55b1c2f246 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 15 Jun 2023 05:32:42 +0000 Subject: ASoC: simple_card_utils: remove unused cpus/codecs/platforms from props simple_dai_props has cpus/codecs/platforms. These pointer were used for dai_link before, but are allocated today since commit 050c7950fd70 ("ASoC: simple-card-utils: alloc dai_link information for CPU/Codec/Platform"). We don't need to keep it anymore. This patch removes these. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87bkhhxpc6.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/simple_card_utils.h | 3 --- sound/soc/generic/simple-card-utils.c | 6 ------ 2 files changed, 9 deletions(-) (limited to 'sound') diff --git a/include/sound/simple_card_utils.h b/include/sound/simple_card_utils.h index 9daef37fe9a8..b450d5873227 100644 --- a/include/sound/simple_card_utils.h +++ b/include/sound/simple_card_utils.h @@ -59,9 +59,6 @@ struct asoc_simple_priv { struct simple_dai_props { struct asoc_simple_dai *cpu_dai; struct asoc_simple_dai *codec_dai; - struct snd_soc_dai_link_component *cpus; - struct snd_soc_dai_link_component *codecs; - struct snd_soc_dai_link_component *platforms; struct asoc_simple_data adata; struct snd_soc_codec_conf *codec_conf; struct prop_nums num; diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 6a3c9e4e1cfe..f94c48aa126c 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -903,7 +903,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, for (i = 0; i < li->link; i++) { if (li->num[i].cpus) { /* Normal CPU */ - dai_props[i].cpus = dai_link[i].cpus = dlcs; dai_props[i].num.cpus = dai_link[i].num_cpus = li->num[i].cpus; @@ -913,7 +912,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, dais += li->num[i].cpus; } else { /* DPCM Be's CPU = dummy */ - dai_props[i].cpus = dai_link[i].cpus = &asoc_dummy_dlc; dai_props[i].num.cpus = dai_link[i].num_cpus = 1; @@ -921,7 +919,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, if (li->num[i].codecs) { /* Normal Codec */ - dai_props[i].codecs = dai_link[i].codecs = dlcs; dai_props[i].num.codecs = dai_link[i].num_codecs = li->num[i].codecs; @@ -937,7 +934,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, } } else { /* DPCM Fe's Codec = dummy */ - dai_props[i].codecs = dai_link[i].codecs = &asoc_dummy_dlc; dai_props[i].num.codecs = dai_link[i].num_codecs = 1; @@ -945,7 +941,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, if (li->num[i].platforms) { /* Have Platform */ - dai_props[i].platforms = dai_link[i].platforms = dlcs; dai_props[i].num.platforms = dai_link[i].num_platforms = li->num[i].platforms; @@ -953,7 +948,6 @@ int asoc_simple_init_priv(struct asoc_simple_priv *priv, dlcs += li->num[i].platforms; } else { /* Doesn't have Platform */ - dai_props[i].platforms = dai_link[i].platforms = NULL; dai_props[i].num.platforms = dai_link[i].num_platforms = 0; -- cgit v1.2.3 From d84881e06836dc1655777a592b4279be76ad7324 Mon Sep 17 00:00:00 2001 From: Yingkun Meng Date: Thu, 15 Jun 2023 20:27:18 +0800 Subject: ASoC: Add support for Loongson I2S controller Loongson I2S controller is found on 7axxx/2kxxx chips from loongson, it is a PCI device with two private DMA controllers, one for playback, the other for capture. The driver supports the use of DTS or ACPI to describe device resources. Signed-off-by: Yingkun Meng Link: https://lore.kernel.org/r/20230615122718.3412942-1-mengyingkun@loongson.cn Signed-off-by: Mark Brown --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/loongson/Kconfig | 16 ++ sound/soc/loongson/Makefile | 4 + sound/soc/loongson/loongson_dma.c | 350 ++++++++++++++++++++++++++++++++++ sound/soc/loongson/loongson_dma.h | 16 ++ sound/soc/loongson/loongson_i2s.c | 269 ++++++++++++++++++++++++++ sound/soc/loongson/loongson_i2s.h | 71 +++++++ sound/soc/loongson/loongson_i2s_pci.c | 171 +++++++++++++++++ 9 files changed, 899 insertions(+) create mode 100644 sound/soc/loongson/Kconfig create mode 100644 sound/soc/loongson/Makefile create mode 100644 sound/soc/loongson/loongson_dma.c create mode 100644 sound/soc/loongson/loongson_dma.h create mode 100644 sound/soc/loongson/loongson_i2s.c create mode 100644 sound/soc/loongson/loongson_i2s.h create mode 100644 sound/soc/loongson/loongson_i2s_pci.c (limited to 'sound') diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 4b6e5a802880..bfa9622e1ab1 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -79,6 +79,7 @@ source "sound/soc/google/Kconfig" source "sound/soc/hisilicon/Kconfig" source "sound/soc/jz4740/Kconfig" source "sound/soc/kirkwood/Kconfig" +source "sound/soc/loongson/Kconfig" source "sound/soc/img/Kconfig" source "sound/soc/intel/Kconfig" source "sound/soc/mediatek/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 9d9b228e4508..8376fdb217ed 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -46,6 +46,7 @@ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += google/ obj-$(CONFIG_SND_SOC) += hisilicon/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += loongson/ obj-$(CONFIG_SND_SOC) += img/ obj-$(CONFIG_SND_SOC) += intel/ obj-$(CONFIG_SND_SOC) += mediatek/ diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig new file mode 100644 index 000000000000..4478ac91e402 --- /dev/null +++ b/sound/soc/loongson/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "SoC Audio for Loongson CPUs" + depends on LOONGARCH || COMPILE_TEST + +config SND_SOC_LOONGSON_I2S_PCI + tristate "Loongson I2S-PCI Device Driver" + select REGMAP_MMIO + depends on PCI + help + Say Y or M if you want to add support for I2S driver for + Loongson I2S controller. + + The controller is found in loongson bridge chips or SoCs, + and work as a PCI device. + +endmenu diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile new file mode 100644 index 000000000000..099af7989103 --- /dev/null +++ b/sound/soc/loongson/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +#Platform Support +snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o +obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o diff --git a/sound/soc/loongson/loongson_dma.c b/sound/soc/loongson/loongson_dma.c new file mode 100644 index 000000000000..65b6719e61c5 --- /dev/null +++ b/sound/soc/loongson/loongson_dma.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Loongson ALSA SoC Platform (DMA) driver +// +// Copyright (C) 2023 Loongson Technology Corporation Limited +// Author: Yingkun Meng +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "loongson_i2s.h" + +/* DMA dma_order Register */ +#define DMA_ORDER_STOP (1 << 4) /* DMA stop */ +#define DMA_ORDER_START (1 << 3) /* DMA start */ +#define DMA_ORDER_ASK_VALID (1 << 2) /* DMA ask valid flag */ +#define DMA_ORDER_AXI_UNCO (1 << 1) /* Uncache access */ +#define DMA_ORDER_ADDR_64 (1 << 0) /* 64bits address support */ + +#define DMA_ORDER_ASK_MASK (~0x1fUL) /* Ask addr mask */ +#define DMA_ORDER_CTRL_MASK (0x0fUL) /* Control mask */ + +/* + * DMA registers descriptor. + */ +struct loongson_dma_desc { + u32 order; /* Next descriptor address register */ + u32 saddr; /* Source address register */ + u32 daddr; /* Device address register */ + u32 length; /* Total length register */ + u32 step_length; /* Memory stride register */ + u32 step_times; /* Repeat time register */ + u32 cmd; /* Command register */ + u32 stats; /* Status register */ + u32 order_hi; /* Next descriptor high address register */ + u32 saddr_hi; /* High source address register */ + u32 res[6]; /* Reserved */ +} __packed; + +struct loongson_runtime_data { + struct loongson_dma_data *dma_data; + + struct loongson_dma_desc *dma_desc_arr; + dma_addr_t dma_desc_arr_phy; + int dma_desc_arr_size; + + struct loongson_dma_desc *dma_pos_desc; + dma_addr_t dma_pos_desc_phy; +}; + +static const struct snd_pcm_hardware ls_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_PAUSE, + .formats = (SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE), + .period_bytes_min = 128, + .period_bytes_max = 128 * 1024, + .periods_min = 1, + .periods_max = PAGE_SIZE / sizeof(struct loongson_dma_desc), + .buffer_bytes_max = 1024 * 1024, +}; + +static struct +loongson_dma_desc *dma_desc_save(struct loongson_runtime_data *prtd) +{ + void __iomem *order_reg = prtd->dma_data->order_addr; + u64 val; + + val = (u64)prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); + val |= DMA_ORDER_ASK_VALID; + writeq(val, order_reg); + + while (readl(order_reg) & DMA_ORDER_ASK_VALID) + udelay(2); + + return prtd->dma_pos_desc; +} + +static int loongson_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct loongson_runtime_data *prtd = substream->runtime->private_data; + struct device *dev = substream->pcm->card->dev; + void __iomem *order_reg = prtd->dma_data->order_addr; + u64 val; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + val = prtd->dma_pos_desc_phy & DMA_ORDER_ASK_MASK; + if (dev->coherent_dma_mask == DMA_BIT_MASK(64)) + val |= DMA_ORDER_ADDR_64; + else + val &= ~DMA_ORDER_ADDR_64; + val |= (readq(order_reg) & DMA_ORDER_CTRL_MASK); + val |= DMA_ORDER_START; + writeq(val, order_reg); + + while ((readl(order_reg) & DMA_ORDER_START)) + udelay(2); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dma_desc_save(prtd); + + /* dma stop */ + val = readq(order_reg) | DMA_ORDER_STOP; + writeq(val, order_reg); + udelay(1000); + + break; + default: + dev_err(dev, "Invalid pcm trigger operation\n"); + return -EINVAL; + } + + return ret; +} + +static int loongson_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = substream->pcm->card->dev; + struct loongson_runtime_data *prtd = runtime->private_data; + size_t buf_len = params_buffer_bytes(params); + size_t period_len = params_period_bytes(params); + dma_addr_t order_addr, mem_addr; + struct loongson_dma_desc *desc; + u32 num_periods; + int i; + + if (buf_len % period_len) { + dev_err(dev, "buf len not multiply of period len\n"); + return -EINVAL; + } + + num_periods = buf_len / period_len; + if (!num_periods || num_periods > prtd->dma_desc_arr_size) { + dev_err(dev, "dma data too small or too big\n"); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = buf_len; + + /* initialize dma descriptor array */ + mem_addr = runtime->dma_addr; + order_addr = prtd->dma_desc_arr_phy; + for (i = 0; i < num_periods; i++) { + desc = &prtd->dma_desc_arr[i]; + + /* next descriptor physical address */ + order_addr += sizeof(*desc); + desc->order = lower_32_bits(order_addr | BIT(0)); + desc->order_hi = upper_32_bits(order_addr); + + desc->saddr = lower_32_bits(mem_addr); + desc->saddr_hi = upper_32_bits(mem_addr); + desc->daddr = prtd->dma_data->dev_addr; + + desc->cmd = BIT(0); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + desc->cmd |= BIT(12); + + desc->length = period_len >> 2; + desc->step_length = 0; + desc->step_times = 1; + + mem_addr += period_len; + } + desc = &prtd->dma_desc_arr[num_periods - 1]; + desc->order = lower_32_bits(prtd->dma_desc_arr_phy | BIT(0)); + desc->order_hi = upper_32_bits(prtd->dma_desc_arr_phy); + + /* init position descriptor */ + *prtd->dma_pos_desc = *prtd->dma_desc_arr; + + return 0; +} + +static snd_pcm_uframes_t +loongson_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loongson_runtime_data *prtd = runtime->private_data; + struct loongson_dma_desc *desc; + snd_pcm_uframes_t x; + u64 addr; + + desc = dma_desc_save(prtd); + addr = ((u64)desc->saddr_hi << 32) | desc->saddr; + + x = bytes_to_frames(runtime, addr - runtime->dma_addr); + if (x == runtime->buffer_size) + x = 0; + return x; +} + +static irqreturn_t loongson_pcm_dma_irq(int irq, void *devid) +{ + struct snd_pcm_substream *substream = devid; + + snd_pcm_period_elapsed(substream); + return IRQ_HANDLED; +} + +static int loongson_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_card *card = substream->pcm->card; + struct loongson_runtime_data *prtd; + struct loongson_dma_data *dma_data; + int ret; + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 128); + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 128); + snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + snd_soc_set_runtime_hwparams(substream, &ls_pcm_hardware); + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + prtd->dma_desc_arr = dma_alloc_coherent(card->dev, PAGE_SIZE, + &prtd->dma_desc_arr_phy, + GFP_KERNEL); + if (!prtd->dma_desc_arr) { + ret = -ENOMEM; + goto desc_err; + } + prtd->dma_desc_arr_size = PAGE_SIZE / sizeof(*prtd->dma_desc_arr); + + prtd->dma_pos_desc = dma_alloc_coherent(card->dev, + sizeof(*prtd->dma_pos_desc), + &prtd->dma_pos_desc_phy, + GFP_KERNEL); + if (!prtd->dma_pos_desc) { + ret = -ENOMEM; + goto pos_err; + } + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + prtd->dma_data = dma_data; + + substream->runtime->private_data = prtd; + + return 0; +pos_err: + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, + prtd->dma_desc_arr_phy); +desc_err: + kfree(prtd); + + return ret; +} + +static int loongson_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_card *card = substream->pcm->card; + struct loongson_runtime_data *prtd = substream->runtime->private_data; + + dma_free_coherent(card->dev, PAGE_SIZE, prtd->dma_desc_arr, + prtd->dma_desc_arr_phy); + + dma_free_coherent(card->dev, sizeof(*prtd->dma_pos_desc), + prtd->dma_pos_desc, prtd->dma_pos_desc_phy); + + kfree(prtd); + return 0; +} + +static int loongson_pcm_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + return remap_pfn_range(vma, vma->vm_start, + substream->dma_buffer.addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot); +} + +static int loongson_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm_substream *substream; + struct loongson_dma_data *dma_data; + unsigned int i; + int ret; + + for_each_pcm_streams(i) { + substream = rtd->pcm->streams[i].substream; + if (!substream) + continue; + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), + substream); + ret = devm_request_irq(card->dev, dma_data->irq, + loongson_pcm_dma_irq, + IRQF_TRIGGER_HIGH, LS_I2S_DRVNAME, + substream); + if (ret < 0) { + dev_err(card->dev, "request irq for DMA failed\n"); + return ret; + } + } + + return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + card->dev, + ls_pcm_hardware.buffer_bytes_max); +} + +const struct snd_soc_component_driver loongson_i2s_component = { + .name = LS_I2S_DRVNAME, + .open = loongson_pcm_open, + .close = loongson_pcm_close, + .hw_params = loongson_pcm_hw_params, + .trigger = loongson_pcm_trigger, + .pointer = loongson_pcm_pointer, + .mmap = loongson_pcm_mmap, + .pcm_construct = loongson_pcm_new, +}; diff --git a/sound/soc/loongson/loongson_dma.h b/sound/soc/loongson/loongson_dma.h new file mode 100644 index 000000000000..073ee8c0c046 --- /dev/null +++ b/sound/soc/loongson/loongson_dma.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA ASoC interface for the Loongson platform + * + * Copyright (C) 2023 Loongson Technology Corporation Limited + * Author: Yingkun Meng + */ + +#ifndef _LOONGSON_DMA_H +#define _LOONGSON_DMA_H + +#include + +extern const struct snd_soc_component_driver loongson_i2s_component; + +#endif diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c new file mode 100644 index 000000000000..35d34568be79 --- /dev/null +++ b/sound/soc/loongson/loongson_i2s.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Common functions for loongson I2S controller driver +// +// Copyright (C) 2023 Loongson Technology Corporation Limited. +// Author: Yingkun Meng +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "loongson_i2s.h" + +#define LOONGSON_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static int loongson_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN); + else + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_TX_EN | I2S_CTRL_TX_DMA_EN, 0); + else + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_RX_EN | I2S_CTRL_RX_DMA_EN, 0); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int loongson_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 clk_rate = i2s->clk_rate; + u32 sysclk = i2s->sysclk; + u32 bits = params_width(params); + u32 chans = params_channels(params); + u32 fs = params_rate(params); + u32 bclk_ratio, mclk_ratio; + u32 mclk_ratio_frac; + u32 val = 0; + + switch (i2s->rev_id) { + case 0: + bclk_ratio = DIV_ROUND_CLOSEST(clk_rate, + (bits * chans * fs * 2)) - 1; + mclk_ratio = DIV_ROUND_CLOSEST(clk_rate, (sysclk * 2)) - 1; + + /* According to 2k1000LA user manual, set bits == depth */ + val |= (bits << 24); + val |= (bits << 16); + val |= (bclk_ratio << 8); + val |= mclk_ratio; + regmap_write(i2s->regmap, LS_I2S_CFG, val); + + break; + case 1: + bclk_ratio = DIV_ROUND_CLOSEST(sysclk, + (bits * chans * fs * 2)) - 1; + mclk_ratio = clk_rate / sysclk; + mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16), + sysclk) - (mclk_ratio << 16); + + regmap_read(i2s->regmap, LS_I2S_CFG, &val); + val |= (bits << 24); + val |= (bclk_ratio << 8); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val |= (bits << 16); + else + val |= bits; + regmap_write(i2s->regmap, LS_I2S_CFG, val); + + val = (mclk_ratio_frac << 16) | mclk_ratio; + regmap_write(i2s->regmap, LS_I2S_CFG1, val); + + break; + default: + dev_err(i2s->dev, "I2S revision invalid\n"); + return -EINVAL; + } + + return 0; +} + +static int loongson_i2s_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + i2s->sysclk = freq; + + return 0; +} + +static int loongson_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct loongson_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 val; + int ret; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_RIGHT_J: + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MSB, + I2S_CTRL_MSB); + break; + default: + return -EINVAL; + } + + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BC_FC: + break; + case SND_SOC_DAIFMT_BP_FC: + /* Enable master mode */ + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, + I2S_CTRL_MASTER); + if (i2s->rev_id == 1) { + ret = regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + val & I2S_CTRL_CLK_READY, + 10, 500000); + if (ret < 0) + dev_warn(dai->dev, "wait BCLK ready timeout\n"); + } + break; + case SND_SOC_DAIFMT_BC_FP: + /* Enable MCLK */ + if (i2s->rev_id == 1) { + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_MCLK_EN, + I2S_CTRL_MCLK_EN); + ret = regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + val & I2S_CTRL_MCLK_READY, + 10, 500000); + if (ret < 0) + dev_warn(dai->dev, "wait MCLK ready timeout\n"); + } + break; + case SND_SOC_DAIFMT_BP_FP: + /* Enable MCLK */ + if (i2s->rev_id == 1) { + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, + I2S_CTRL_MCLK_EN, + I2S_CTRL_MCLK_EN); + ret = regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + val & I2S_CTRL_MCLK_READY, + 10, 500000); + if (ret < 0) + dev_warn(dai->dev, "wait MCLK ready timeout\n"); + } + + /* Enable master mode */ + regmap_update_bits(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_MASTER, + I2S_CTRL_MASTER); + if (i2s->rev_id == 1) { + ret = regmap_read_poll_timeout_atomic(i2s->regmap, + LS_I2S_CTRL, val, + val & I2S_CTRL_CLK_READY, + 10, 500000); + if (ret < 0) + dev_warn(dai->dev, "wait BCLK ready timeout\n"); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops loongson_i2s_dai_ops = { + .trigger = loongson_i2s_trigger, + .hw_params = loongson_i2s_hw_params, + .set_sysclk = loongson_i2s_set_dai_sysclk, + .set_fmt = loongson_i2s_set_fmt, +}; + +static int loongson_i2s_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct loongson_i2s *i2s = dev_get_drvdata(cpu_dai->dev); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s->playback_dma_data, + &i2s->capture_dma_data); + snd_soc_dai_set_drvdata(cpu_dai, i2s); + + return 0; +} + +struct snd_soc_dai_driver loongson_i2s_dai = { + .name = "loongson-i2s", + .probe = loongson_i2s_dai_probe, + .playback = { + .stream_name = "CPU-Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = LOONGSON_I2S_FORMATS, + }, + .capture = { + .stream_name = "CPU-Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = LOONGSON_I2S_FORMATS, + }, + .ops = &loongson_i2s_dai_ops, + .symmetric_rate = 1, +}; + +static int i2s_suspend(struct device *dev) +{ + struct loongson_i2s *i2s = dev_get_drvdata(dev); + + regcache_cache_only(i2s->regmap, true); + + return 0; +} + +static int i2s_resume(struct device *dev) +{ + struct loongson_i2s *i2s = dev_get_drvdata(dev); + int ret; + + regcache_cache_only(i2s->regmap, false); + regcache_mark_dirty(i2s->regmap); + ret = regcache_sync(i2s->regmap); + + return ret; +} + +const struct dev_pm_ops loongson_i2s_pm = { + SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) +}; diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h new file mode 100644 index 000000000000..52788f6a94ad --- /dev/null +++ b/sound/soc/loongson/loongson_i2s.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ALSA I2S interface for the Loongson platform + * + * Copyright (C) 2023 Loongson Technology Corporation Limited + * Author: Yingkun Meng + */ + +#ifndef _LOONGSON_I2S_H +#define _LOONGSON_I2S_H + +#include +#include + +/* I2S Common Registers */ +#define LS_I2S_VER 0x00 /* I2S Version */ +#define LS_I2S_CFG 0x04 /* I2S Config */ +#define LS_I2S_CTRL 0x08 /* I2S Control */ +#define LS_I2S_RX_DATA 0x0C /* I2S DMA RX Address */ +#define LS_I2S_TX_DATA 0x10 /* I2S DMA TX Address */ + +/* 2K2000 I2S Specify Registers */ +#define LS_I2S_CFG1 0x14 /* I2S Config1 */ + +/* 7A2000 I2S Specify Registers */ +#define LS_I2S_TX_ORDER 0x100 /* TX DMA Order */ +#define LS_I2S_RX_ORDER 0x110 /* RX DMA Order */ + +/* Loongson I2S Control Register */ +#define I2S_CTRL_MCLK_READY (1 << 16) /* MCLK ready */ +#define I2S_CTRL_MASTER (1 << 15) /* Master mode */ +#define I2S_CTRL_MSB (1 << 14) /* MSB bit order */ +#define I2S_CTRL_RX_EN (1 << 13) /* RX enable */ +#define I2S_CTRL_TX_EN (1 << 12) /* TX enable */ +#define I2S_CTRL_RX_DMA_EN (1 << 11) /* DMA RX enable */ +#define I2S_CTRL_CLK_READY (1 << 8) /* BCLK ready */ +#define I2S_CTRL_TX_DMA_EN (1 << 7) /* DMA TX enable */ +#define I2S_CTRL_RESET (1 << 4) /* Controller soft reset */ +#define I2S_CTRL_MCLK_EN (1 << 3) /* Enable MCLK */ +#define I2S_CTRL_RX_INT_EN (1 << 1) /* RX interrupt enable */ +#define I2S_CTRL_TX_INT_EN (1 << 0) /* TX interrupt enable */ + +#define LS_I2S_DRVNAME "loongson-i2s" + +struct loongson_dma_data { + dma_addr_t dev_addr; /* device physical address for DMA */ + void __iomem *order_addr; /* DMA order register */ + u32 irq; /* DMA irq */ +}; + +struct loongson_i2s { + struct device *dev; + union { + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct loongson_dma_data tx_dma_data; + }; + union { + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct loongson_dma_data rx_dma_data; + }; + struct regmap *regmap; + void __iomem *reg_base; + u32 rev_id; + u32 clk_rate; + u32 sysclk; +}; + +extern const struct dev_pm_ops loongson_i2s_pm; +extern struct snd_soc_dai_driver loongson_i2s_dai; + +#endif diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c new file mode 100644 index 000000000000..6dcfb17d3276 --- /dev/null +++ b/sound/soc/loongson/loongson_i2s_pci.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// loongson_i2s_pci.c -- Loongson I2S controller driver +// +// Copyright (C) 2023 Loongson Technology Corporation Limited +// Author: Yingkun Meng +// + +#include +#include +#include +#include +#include +#include +#include +#include "loongson_i2s.h" +#include "loongson_dma.h" + +static bool loongson_i2s_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LS_I2S_CFG: + case LS_I2S_CTRL: + case LS_I2S_RX_DATA: + case LS_I2S_TX_DATA: + case LS_I2S_CFG1: + return true; + default: + return false; + }; +} + +static bool loongson_i2s_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LS_I2S_VER: + case LS_I2S_CFG: + case LS_I2S_CTRL: + case LS_I2S_RX_DATA: + case LS_I2S_TX_DATA: + case LS_I2S_CFG1: + return true; + default: + return false; + }; +} + +static bool loongson_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case LS_I2S_CFG: + case LS_I2S_CTRL: + case LS_I2S_RX_DATA: + case LS_I2S_TX_DATA: + case LS_I2S_CFG1: + return true; + default: + return false; + }; +} + +static const struct regmap_config loongson_i2s_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = LS_I2S_CFG1, + .writeable_reg = loongson_i2s_wr_reg, + .readable_reg = loongson_i2s_rd_reg, + .volatile_reg = loongson_i2s_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int loongson_i2s_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *pid) +{ + const struct fwnode_handle *fwnode = pdev->dev.fwnode; + struct loongson_dma_data *tx_data, *rx_data; + struct loongson_i2s *i2s; + int ret; + + if (pcim_enable_device(pdev)) { + dev_err(&pdev->dev, "pci_enable_device failed\n"); + return -ENODEV; + } + + i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); + if (!i2s) + return -ENOMEM; + + i2s->rev_id = pdev->revision; + i2s->dev = &pdev->dev; + pci_set_drvdata(pdev, i2s); + + ret = pcim_iomap_regions(pdev, 1 << 0, dev_name(&pdev->dev)); + if (ret < 0) { + dev_err(&pdev->dev, "iomap_regions failed\n"); + return ret; + } + i2s->reg_base = pcim_iomap_table(pdev)[0]; + i2s->regmap = devm_regmap_init_mmio(&pdev->dev, i2s->reg_base, + &loongson_i2s_regmap_config); + if (IS_ERR(i2s->regmap)) { + dev_err(&pdev->dev, "regmap_init_mmio failed\n"); + return PTR_ERR(i2s->regmap); + } + + tx_data = &i2s->tx_dma_data; + rx_data = &i2s->rx_dma_data; + + tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA; + tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; + + rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA; + rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; + + tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); + if (tx_data->irq < 0) { + dev_err(&pdev->dev, "dma tx irq invalid\n"); + return tx_data->irq; + } + + rx_data->irq = fwnode_irq_get_byname(fwnode, "rx"); + if (rx_data->irq < 0) { + dev_err(&pdev->dev, "dma rx irq invalid\n"); + return rx_data->irq; + } + + device_property_read_u32(&pdev->dev, "clock-frequency", &i2s->clk_rate); + if (!i2s->clk_rate) { + dev_err(&pdev->dev, "clock-frequency property invalid\n"); + return -EINVAL; + } + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + + if (i2s->rev_id == 1) { + regmap_write(i2s->regmap, LS_I2S_CTRL, I2S_CTRL_RESET); + udelay(200); + } + + ret = devm_snd_soc_register_component(&pdev->dev, + &loongson_i2s_component, + &loongson_i2s_dai, 1); + if (ret) { + dev_err(&pdev->dev, "register DAI failed %d\n", ret); + return ret; + } + + return 0; +} + +static const struct pci_device_id loongson_i2s_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x7a27) }, + { }, +}; +MODULE_DEVICE_TABLE(pci, loongson_i2s_ids); + +static struct pci_driver loongson_i2s_driver = { + .name = "loongson-i2s-pci", + .id_table = loongson_i2s_ids, + .probe = loongson_i2s_pci_probe, + .driver = { + .owner = THIS_MODULE, + .pm = pm_sleep_ptr(&loongson_i2s_pm), + }, +}; +module_pci_driver(loongson_i2s_driver); + +MODULE_DESCRIPTION("Loongson I2S Master Mode ASoC Driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6a8e1d46f0621c15d2993c5e847f4f264102f93d Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 12 Jun 2023 23:09:45 -0700 Subject: ASoC: max98388: add amplifier driver Added Analog Devices MAX98388 amplifier driver. MAX98388 provides a PCM interface for audio data and a standard I2C interface for control data communication. Signed-off-by: Ryan Lee Link: https://lore.kernel.org/r/20230613060945.183128-2-ryan.lee.analog@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 10 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/max98388.c | 1013 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/max98388.h | 234 ++++++++++ 4 files changed, 1259 insertions(+) create mode 100644 sound/soc/codecs/max98388.c create mode 100644 sound/soc/codecs/max98388.h (limited to 'sound') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 8f487d1ba2f9..7422cd10c1da 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -137,6 +137,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_MAX98363 imply SND_SOC_MAX98373_I2C imply SND_SOC_MAX98373_SDW + imply SND_SOC_MAX98388 imply SND_SOC_MAX98390 imply SND_SOC_MAX98396 imply SND_SOC_MAX9850 @@ -1175,6 +1176,15 @@ config SND_SOC_MAX98373_SDW interface for control data. Select this if MAX98373 is connected via soundwire. +config SND_SOC_MAX98388 + tristate "Analog Devices MAX98388 Speaker Amplifier" + depends on I2C + help + Enable support for Analog Devices MAX98388 audio + amplifier. The device provides a PCM interface for + audio data and a standard I2C interface for control + data communication. + config SND_SOC_MAX98390 tristate "Maxim Integrated MAX98390 Speaker Amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 67f336a12d74..0fd003d432e5 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -153,6 +153,7 @@ snd-soc-max98363-objs := max98363.o snd-soc-max98373-objs := max98373.o snd-soc-max98373-i2c-objs := max98373-i2c.o snd-soc-max98373-sdw-objs := max98373-sdw.o +snd-soc-max98388-objs := max98388.o snd-soc-max98390-objs := max98390.o snd-soc-max98396-objs := max98396.o snd-soc-max9850-objs := max9850.o @@ -525,6 +526,7 @@ obj-$(CONFIG_SND_SOC_MAX98363) += snd-soc-max98363.o obj-$(CONFIG_SND_SOC_MAX98373) += snd-soc-max98373.o obj-$(CONFIG_SND_SOC_MAX98373_I2C) += snd-soc-max98373-i2c.o obj-$(CONFIG_SND_SOC_MAX98373_SDW) += snd-soc-max98373-sdw.o +obj-$(CONFIG_SND_SOC_MAX98388) += snd-soc-max98388.o obj-$(CONFIG_SND_SOC_MAX98390) += snd-soc-max98390.o obj-$(CONFIG_SND_SOC_MAX98396) += snd-soc-max98396.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c new file mode 100644 index 000000000000..8062a7115007 --- /dev/null +++ b/sound/soc/codecs/max98388.c @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2022, Analog Devices Inc. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "max98388.h" + +static struct reg_default max98388_reg[] = { + {MAX98388_R2000_SW_RESET, 0x00}, + {MAX98388_R2001_INT_RAW1, 0x00}, + {MAX98388_R2002_INT_RAW2, 0x00}, + {MAX98388_R2004_INT_STATE1, 0x00}, + {MAX98388_R2005_INT_STATE2, 0x00}, + {MAX98388_R2020_THERM_WARN_THRESH, 0x0A}, + {MAX98388_R2031_SPK_MON_THRESH, 0x58}, + {MAX98388_R2032_SPK_MON_LD_SEL, 0x08}, + {MAX98388_R2033_SPK_MON_DURATION, 0x02}, + {MAX98388_R2037_ERR_MON_CTRL, 0x01}, + {MAX98388_R2040_PCM_MODE_CFG, 0xC0}, + {MAX98388_R2041_PCM_CLK_SETUP, 0x04}, + {MAX98388_R2042_PCM_SR_SETUP, 0x88}, + {MAX98388_R2044_PCM_TX_CTRL1, 0x00}, + {MAX98388_R2045_PCM_TX_CTRL2, 0x00}, + {MAX98388_R2050_PCM_TX_HIZ_CTRL1, 0xFF}, + {MAX98388_R2051_PCM_TX_HIZ_CTRL2, 0xFF}, + {MAX98388_R2052_PCM_TX_HIZ_CTRL3, 0xFF}, + {MAX98388_R2053_PCM_TX_HIZ_CTRL4, 0xFF}, + {MAX98388_R2054_PCM_TX_HIZ_CTRL5, 0xFF}, + {MAX98388_R2055_PCM_TX_HIZ_CTRL6, 0xFF}, + {MAX98388_R2056_PCM_TX_HIZ_CTRL7, 0xFF}, + {MAX98388_R2057_PCM_TX_HIZ_CTRL8, 0xFF}, + {MAX98388_R2058_PCM_RX_SRC1, 0x00}, + {MAX98388_R2059_PCM_RX_SRC2, 0x01}, + {MAX98388_R205C_PCM_TX_DRIVE_STRENGTH, 0x00}, + {MAX98388_R205D_PCM_TX_SRC_EN, 0x00}, + {MAX98388_R205E_PCM_RX_EN, 0x00}, + {MAX98388_R205F_PCM_TX_EN, 0x00}, + {MAX98388_R2090_SPK_CH_VOL_CTRL, 0x00}, + {MAX98388_R2091_SPK_CH_CFG, 0x02}, + {MAX98388_R2092_SPK_AMP_OUT_CFG, 0x03}, + {MAX98388_R2093_SPK_AMP_SSM_CFG, 0x01}, + {MAX98388_R2094_SPK_AMP_ER_CTRL, 0x00}, + {MAX98388_R209E_SPK_CH_PINK_NOISE_EN, 0x00}, + {MAX98388_R209F_SPK_CH_AMP_EN, 0x00}, + {MAX98388_R20A0_IV_DATA_DSP_CTRL, 0x10}, + {MAX98388_R20A7_IV_DATA_EN, 0x00}, + {MAX98388_R20E0_BP_ALC_THRESH, 0x04}, + {MAX98388_R20E1_BP_ALC_RATES, 0x20}, + {MAX98388_R20E2_BP_ALC_ATTEN, 0x06}, + {MAX98388_R20E3_BP_ALC_REL, 0x02}, + {MAX98388_R20E4_BP_ALC_MUTE, 0x33}, + {MAX98388_R20EE_BP_INF_HOLD_REL, 0x00}, + {MAX98388_R20EF_BP_ALC_EN, 0x00}, + {MAX98388_R210E_AUTO_RESTART, 0x00}, + {MAX98388_R210F_GLOBAL_EN, 0x00}, + {MAX98388_R22FF_REV_ID, 0x00}, +}; + +static int max98388_dac_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(max98388->regmap, + MAX98388_R210F_GLOBAL_EN, 1); + usleep_range(30000, 31000); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(max98388->regmap, + MAX98388_R210F_GLOBAL_EN, 0); + usleep_range(30000, 31000); + max98388->tdm_mode = false; + break; + default: + return 0; + } + return 0; +} + +static const char * const max98388_monomix_switch_text[] = { + "Left", "Right", "LeftRight"}; + +static const struct soc_enum dai_sel_enum = + SOC_ENUM_SINGLE(MAX98388_R2058_PCM_RX_SRC1, + MAX98388_PCM_TO_SPK_MONOMIX_CFG_SHIFT, + 3, max98388_monomix_switch_text); + +static const struct snd_kcontrol_new max98388_dai_controls = + SOC_DAPM_ENUM("DAI Sel", dai_sel_enum); + +static const struct snd_kcontrol_new max98388_vi_control = + SOC_DAPM_SINGLE("Switch", MAX98388_R205F_PCM_TX_EN, 0, 1, 0); + +static const struct snd_soc_dapm_widget max98388_dapm_widgets[] = { + SND_SOC_DAPM_DAC_E("Amp Enable", "HiFi Playback", + MAX98388_R205E_PCM_RX_EN, 0, 0, max98388_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("DAI Sel Mux", SND_SOC_NOPM, 0, 0, + &max98388_dai_controls), + SND_SOC_DAPM_OUTPUT("BE_OUT"), + SND_SOC_DAPM_AIF_OUT("Voltage Sense", "HiFi Capture", 0, + MAX98388_R20A7_IV_DATA_EN, 0, 0), + SND_SOC_DAPM_AIF_OUT("Current Sense", "HiFi Capture", 0, + MAX98388_R20A7_IV_DATA_EN, 1, 0), + SND_SOC_DAPM_ADC("ADC Voltage", NULL, + MAX98388_R205D_PCM_TX_SRC_EN, 0, 0), + SND_SOC_DAPM_ADC("ADC Current", NULL, + MAX98388_R205D_PCM_TX_SRC_EN, 1, 0), + SND_SOC_DAPM_SWITCH("VI Sense", SND_SOC_NOPM, 0, 0, + &max98388_vi_control), + SND_SOC_DAPM_SIGGEN("VMON"), + SND_SOC_DAPM_SIGGEN("IMON"), +}; + +static DECLARE_TLV_DB_SCALE(max98388_digital_tlv, -6350, 50, 1); +static DECLARE_TLV_DB_SCALE(max98388_amp_gain_tlv, -300, 300, 0); + +static const char * const max98388_alc_max_atten_text[] = { + "0dBFS", "-1dBFS", "-2dBFS", "-3dBFS", "-4dBFS", "-5dBFS", + "-6dBFS", "-7dBFS", "-8dBFS", "-9dBFS", "-10dBFS", "-11dBFS", + "-12dBFS", "-13dBFS", "-14dBFS", "-15dBFS" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_max_atten_enum, + MAX98388_R20E2_BP_ALC_ATTEN, + MAX98388_ALC_MAX_ATTEN_SHIFT, + max98388_alc_max_atten_text); + +static const char * const max98388_thermal_warn_text[] = { + "95C", "105C", "115C", "125C" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_thermal_warning_thresh_enum, + MAX98388_R2020_THERM_WARN_THRESH, + MAX98388_THERM_WARN_THRESH_SHIFT, + max98388_thermal_warn_text); + +static const char * const max98388_thermal_shutdown_text[] = { + "135C", "145C", "155C", "165C" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_thermal_shutdown_thresh_enum, + MAX98388_R2020_THERM_WARN_THRESH, + MAX98388_THERM_SHDN_THRESH_SHIFT, + max98388_thermal_shutdown_text); + +static const char * const max98388_alc_thresh_single_text[] = { + "3.625V", "3.550V", "3.475V", "3.400V", "3.325V", "3.250V", + "3.175V", "3.100V", "3.025V", "2.950V", "2.875V", "2.800V", + "2.725V", "2.650V", "2.575V", "2.500V" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_thresh_single_enum, + MAX98388_R20E0_BP_ALC_THRESH, + MAX98388_ALC_THRESH_SHIFT, + max98388_alc_thresh_single_text); + +static const char * const max98388_alc_attack_rate_text[] = { + "0", "10us", "20us", "40us", "80us", "160us", + "320us", "640us", "1.28ms", "2.56ms", "5.12ms", "10.24ms", + "20.48ms", "40.96ms", "81.92ms", "163.84ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_attack_rate_enum, + MAX98388_R20E1_BP_ALC_RATES, + MAX98388_ALC_ATTACK_RATE_SHIFT, + max98388_alc_attack_rate_text); + +static const char * const max98388_alc_release_rate_text[] = { + "20us", "40us", "80us", "160us", "320us", "640us", + "1.28ms", "2.56ms", "5.12ms", "10.24ms", "20.48ms", "40.96ms", + "81.92ms", "163.84ms", "327.68ms", "655.36ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_release_rate_enum, + MAX98388_R20E1_BP_ALC_RATES, + MAX98388_ALC_RELEASE_RATE_SHIFT, + max98388_alc_release_rate_text); + +static const char * const max98388_alc_debounce_text[] = { + "0.01ms", "0.1ms", "1ms", "10ms", "100ms", "250ms", "500ms", "hold" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_debouce_enum, + MAX98388_R20E3_BP_ALC_REL, + MAX98388_ALC_DEBOUNCE_TIME_SHIFT, + max98388_alc_debounce_text); + +static const char * const max98388_alc_mute_delay_text[] = { + "0.01ms", "0.05ms", "0.1ms", "0.5ms", "1ms", "5ms", "25ms", "250ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_alc_mute_delay_enum, + MAX98388_R20E4_BP_ALC_MUTE, + MAX98388_ALC_MUTE_DELAY_SHIFT, + max98388_alc_mute_delay_text); + +static const char * const max98388_spkmon_duration_text[] = { + "10ms", "25ms", "50ms", "75ms", "100ms", "200ms", "300ms", "400ms", + "500ms", "600ms", "700ms", "800ms", "900ms", "1000ms", "1100ms", "1200ms" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_spkmon_duration_enum, + MAX98388_R2033_SPK_MON_DURATION, + MAX98388_SPKMON_DURATION_SHIFT, + max98388_spkmon_duration_text); + +static const char * const max98388_spkmon_thresh_text[] = { + "0.03V", "0.06V", "0.09V", "0.12V", "0.15V", "0.18V", "0.20V", "0.23V", + "0.26V", "0.29V", "0.32V", "0.35V", "0.38V", "0.41V", "0.44V", "0.47V", + "0.50V", "0.53V", "0.56V", "0.58V", "0.61V", "0.64V", "0.67V", "0.70V", + "0.73V", "0.76V", "0.79V", "0.82V", "0.85V", "0.88V", "0.91V", "0.94V", + "0.96V", "0.99V", "1.02V", "1.05V", "1.08V", "1.11V", "1.14V", "1.17V", + "1.20V", "1.23V", "1.26V", "1.29V", "1.32V", "1.35V", "1.37V", "1.40V", + "1.43V", "1.46V", "1.49V", "1.52V", "1.55V", "1.58V", "1.61V", "1.64V", + "1.67V", "1.70V", "1.73V", "1.75V", "1.78V", "1.81V", "1.84V", "1.87V", + "1.90V", "1.93V", "1.96V", "1.99V", "2.02V", "2.05V", "2.08V", "2.11V", + "2.13V", "2.16V", "2.19V", "2.22V", "2.25V", "2.28V", "2.31V", "2.34V", + "2.37V", "2.40V", "2.43V", "2.46V", "2.49V", "2.51V", "2.54V", "2.57V", + "2.60V", "2.63V", "2.66V", "2.69V", "2.72V", "2.75V", "2.78V", "2.81V", + "2.84V", "2.87V", "2.89V", "2.92V", "2.95V", "2.98V", "3.01V", "3.04V", + "3.07V", "3.10V", "3.13V", "3.16V", "3.19V", "3.22V", "3.25V", "3.27V", + "3.30V", "3.33V", "3.36V", "3.39V", "3.42V", "3.45V", "3.48V", "3.51V", + "3.54V", "3.57V", "3.60V", "3.63V", "3.66V", "3.68V", "3.71V", "3.74V" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_spkmon_thresh_enum, + MAX98388_R2031_SPK_MON_THRESH, + MAX98388_SPKMON_THRESH_SHIFT, + max98388_spkmon_thresh_text); + +static const char * const max98388_spkmon_load_text[] = { + "2.00ohm", "2.25ohm", "2.50ohm", "2.75ohm", "3.00ohm", "3.25ohm", + "3.50ohm", "3.75ohm", "4.00ohm", "4.25ohm", "4.50ohm", "4.75ohm", + "5.00ohm", "5.25ohm", "5.50ohm", "5.75ohm", "6.00ohm", "6.25ohm", + "6.50ohm", "6.75ohm", "7.00ohm", "7.25ohm", "7.50ohm", "7.75ohm", + "8.00ohm", "8.25ohm", "8.50ohm", "8.75ohm", "9.00ohm", "9.25ohm", + "9.50ohm", "9.75ohm", "10.00ohm", "10.25ohm", "10.50ohm", "10.75ohm", + "11.00ohm", "11.25ohm", "11.50ohm", "11.75ohm", "12.00ohm", "12.25ohm", + "12.50ohm", "12.75ohm", "13.00ohm", "13.25ohm", "13.50ohm", "13.75ohm", + "14.00ohm", "14.25ohm", "14.50ohm", "14.75ohm", "15.00ohm", "15.25ohm", + "15.50ohm", "15.75ohm", "16.00ohm", "16.25ohm", "16.50ohm", "16.75ohm", + "17.00ohm", "17.25ohm", "17.50ohm", "17.75ohm", "18.00ohm", "18.25ohm", + "18.50ohm", "18.75ohm", "19.00ohm", "19.25ohm", "19.50ohm", "19.75ohm", + "20.00ohm", "20.25ohm", "20.50ohm", "20.75ohm", "21.00ohm", "21.25ohm", + "21.50ohm", "21.75ohm", "22.00ohm", "22.25ohm", "22.50ohm", "22.75ohm", + "23.00ohm", "23.25ohm", "23.50ohm", "23.75ohm", "24.00ohm", "24.25ohm", + "24.50ohm", "24.75ohm", "25.00ohm", "25.25ohm", "25.50ohm", "25.75ohm", + "26.00ohm", "26.25ohm", "26.50ohm", "26.75ohm", "27.00ohm", "27.25ohm", + "27.50ohm", "27.75ohm", "28.00ohm", "28.25ohm", "28.50ohm", "28.75ohm", + "29.00ohm", "29.25ohm", "29.50ohm", "29.75ohm", "30.00ohm", "30.25ohm", + "30.50ohm", "30.75ohm", "31.00ohm", "31.25ohm", "31.50ohm", "31.75ohm", + "32.00ohm", "32.25ohm", "32.50ohm", "32.75ohm", "33.00ohm", "33.25ohm", + "33.50ohm", "33.75ohm" +}; + +static SOC_ENUM_SINGLE_DECL(max98388_spkmon_load_enum, + MAX98388_R2032_SPK_MON_LD_SEL, + MAX98388_SPKMON_LOAD_SHIFT, + max98388_spkmon_load_text); + +static const char * const max98388_edge_rate_text[] = { + "Normal", "Reduced", "Maximum", "Increased", +}; + +static SOC_ENUM_SINGLE_DECL(max98388_edge_rate_falling_enum, + MAX98388_R2094_SPK_AMP_ER_CTRL, + MAX98388_EDGE_RATE_FALL_SHIFT, + max98388_edge_rate_text); + +static SOC_ENUM_SINGLE_DECL(max98388_edge_rate_rising_enum, + MAX98388_R2094_SPK_AMP_ER_CTRL, + MAX98388_EDGE_RATE_RISE_SHIFT, + max98388_edge_rate_text); + +static const char * const max98388_ssm_mod_text[] = { + "1.5%", "3.0%", "4.5%", "6.0%", +}; + +static SOC_ENUM_SINGLE_DECL(max98388_ssm_mod_enum, + MAX98388_R2093_SPK_AMP_SSM_CFG, + MAX98388_SPK_AMP_SSM_MOD_SHIFT, + max98388_ssm_mod_text); + +static const struct snd_kcontrol_new max98388_snd_controls[] = { + SOC_SINGLE("Ramp Up Switch", MAX98388_R2091_SPK_CH_CFG, + MAX98388_SPK_CFG_VOL_RMPUP_SHIFT, 1, 0), + SOC_SINGLE("Ramp Down Switch", MAX98388_R2091_SPK_CH_CFG, + MAX98388_SPK_CFG_VOL_RMPDN_SHIFT, 1, 0), + /* Two Cell Mode Enable */ + SOC_SINGLE("OP Mode Switch", MAX98388_R2092_SPK_AMP_OUT_CFG, + MAX98388_SPK_AMP_OUT_MODE_SHIFT, 1, 0), + /* Speaker Amplifier Overcurrent Automatic Restart Enable */ + SOC_SINGLE("OVC Autorestart Switch", MAX98388_R210E_AUTO_RESTART, + MAX98388_OVC_AUTORESTART_SHIFT, 1, 0), + /* Thermal Shutdown Automatic Restart Enable */ + SOC_SINGLE("THERM Autorestart Switch", MAX98388_R210E_AUTO_RESTART, + MAX98388_THERM_AUTORESTART_SHIFT, 1, 0), + /* PVDD UVLO Auto Restart */ + SOC_SINGLE("UVLO Autorestart Switch", MAX98388_R210E_AUTO_RESTART, + MAX98388_PVDD_UVLO_AUTORESTART_SHIFT, 1, 0), + /* Clock Monitor Automatic Restart Enable */ + SOC_SINGLE("CMON Autorestart Switch", MAX98388_R210E_AUTO_RESTART, + MAX98388_CMON_AUTORESTART_SHIFT, 1, 0), + SOC_SINGLE("CLK Monitor Switch", MAX98388_R2037_ERR_MON_CTRL, + MAX98388_CLOCK_MON_SHIFT, 1, 0), + /* Pinknoise Generator Enable */ + SOC_SINGLE("Pinknoise Gen Switch", MAX98388_R209E_SPK_CH_PINK_NOISE_EN, + MAX98388_PINK_NOISE_GEN_SHIFT, 1, 0), + /* Dither Enable */ + SOC_SINGLE("Dither Switch", MAX98388_R2091_SPK_CH_CFG, + MAX98388_SPK_CFG_DITH_EN_SHIFT, 1, 0), + SOC_SINGLE("VI Dither Switch", MAX98388_R20A0_IV_DATA_DSP_CTRL, + MAX98388_AMP_DSP_CTRL_DITH_SHIFT, 1, 0), + /* DC Blocker Enable */ + SOC_SINGLE("DC Blocker Switch", MAX98388_R2091_SPK_CH_CFG, + MAX98388_SPK_CFG_DCBLK_SHIFT, 1, 0), + SOC_SINGLE("Voltage DC Blocker Switch", MAX98388_R20A0_IV_DATA_DSP_CTRL, + MAX98388_AMP_DSP_CTRL_VOL_DCBLK_SHIFT, 1, 0), + SOC_SINGLE("Current DC Blocker Switch", MAX98388_R20A0_IV_DATA_DSP_CTRL, + MAX98388_AMP_DSP_CTRL_CUR_DCBLK_SHIFT, 1, 0), + /* Digital Volume */ + SOC_SINGLE_TLV("Digital Volume", MAX98388_R2090_SPK_CH_VOL_CTRL, + 0, 0x7F, 1, max98388_digital_tlv), + /* Speaker Volume */ + SOC_SINGLE_TLV("Speaker Volume", MAX98388_R2092_SPK_AMP_OUT_CFG, + 0, 5, 0, max98388_amp_gain_tlv), + SOC_ENUM("Thermal Warn Thresh", max98388_thermal_warning_thresh_enum), + SOC_ENUM("Thermal SHDN Thresh", max98388_thermal_shutdown_thresh_enum), + /* Brownout Protection Automatic Level Control */ + SOC_SINGLE("ALC Switch", MAX98388_R20EF_BP_ALC_EN, 0, 1, 0), + SOC_ENUM("ALC Thresh", max98388_alc_thresh_single_enum), + SOC_ENUM("ALC Attack Rate", max98388_alc_attack_rate_enum), + SOC_ENUM("ALC Release Rate", max98388_alc_release_rate_enum), + SOC_ENUM("ALC Max Atten", max98388_alc_max_atten_enum), + SOC_ENUM("ALC Debounce Time", max98388_alc_debouce_enum), + SOC_SINGLE("ALC Unmute Ramp Switch", MAX98388_R20E4_BP_ALC_MUTE, + MAX98388_ALC_UNMUTE_RAMP_EN_SHIFT, 1, 0), + SOC_SINGLE("ALC Mute Ramp Switch", MAX98388_R20E4_BP_ALC_MUTE, + MAX98388_ALC_MUTE_RAMP_EN_SHIFT, 1, 0), + SOC_SINGLE("ALC Mute Switch", MAX98388_R20E4_BP_ALC_MUTE, + MAX98388_ALC_MUTE_EN_SHIFT, 1, 0), + SOC_ENUM("ALC Mute Delay", max98388_alc_mute_delay_enum), + /* Speaker Monitor */ + SOC_SINGLE("SPKMON Switch", MAX98388_R2037_ERR_MON_CTRL, + MAX98388_SPK_MON_SHIFT, 1, 0), + SOC_ENUM("SPKMON Thresh", max98388_spkmon_thresh_enum), + SOC_ENUM("SPKMON Load", max98388_spkmon_load_enum), + SOC_ENUM("SPKMON Duration", max98388_spkmon_duration_enum), + /* General Parameters */ + SOC_ENUM("Fall Slew Rate", max98388_edge_rate_falling_enum), + SOC_ENUM("Rise Slew Rate", max98388_edge_rate_rising_enum), + SOC_SINGLE("AMP SSM Switch", MAX98388_R2093_SPK_AMP_SSM_CFG, + MAX98388_SPK_AMP_SSM_EN_SHIFT, 1, 0), + SOC_ENUM("AMP SSM Mod", max98388_ssm_mod_enum), +}; + +static const struct snd_soc_dapm_route max98388_audio_map[] = { + /* Plabyack */ + {"DAI Sel Mux", "Left", "Amp Enable"}, + {"DAI Sel Mux", "Right", "Amp Enable"}, + {"DAI Sel Mux", "LeftRight", "Amp Enable"}, + {"BE_OUT", NULL, "DAI Sel Mux"}, + /* Capture */ + { "ADC Voltage", NULL, "VMON"}, + { "ADC Current", NULL, "IMON"}, + { "VI Sense", "Switch", "ADC Voltage"}, + { "VI Sense", "Switch", "ADC Current"}, + { "Voltage Sense", NULL, "VI Sense"}, + { "Current Sense", NULL, "VI Sense"}, +}; + +static void max98388_reset(struct max98388_priv *max98388, struct device *dev) +{ + int ret, reg, count; + + /* Software Reset */ + ret = regmap_update_bits(max98388->regmap, + MAX98388_R2000_SW_RESET, + MAX98388_SOFT_RESET, + MAX98388_SOFT_RESET); + if (ret) + dev_err(dev, "Reset command failed. (ret:%d)\n", ret); + + count = 0; + while (count < 3) { + usleep_range(10000, 11000); + /* Software Reset Verification */ + ret = regmap_read(max98388->regmap, + MAX98388_R22FF_REV_ID, ®); + if (!ret) { + dev_info(dev, "Reset completed (retry:%d)\n", count); + return; + } + count++; + } + dev_err(dev, "Reset failed. (ret:%d)\n", ret); +} + +static int max98388_probe(struct snd_soc_component *component) +{ + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + + /* Software Reset */ + max98388_reset(max98388, component->dev); + + /* General channel source configuration */ + regmap_write(max98388->regmap, + MAX98388_R2059_PCM_RX_SRC2, + 0x10); + + /* Enable DC blocker */ + regmap_write(max98388->regmap, + MAX98388_R2091_SPK_CH_CFG, + 0x1); + /* Enable IMON VMON DC blocker */ + regmap_write(max98388->regmap, + MAX98388_R20A0_IV_DATA_DSP_CTRL, + 0x3); + /* TX slot configuration */ + regmap_write(max98388->regmap, + MAX98388_R2044_PCM_TX_CTRL1, + max98388->v_slot); + + regmap_write(max98388->regmap, + MAX98388_R2045_PCM_TX_CTRL2, + max98388->i_slot); + /* Enable Auto-restart behavior by default */ + regmap_write(max98388->regmap, + MAX98388_R210E_AUTO_RESTART, 0xF); + /* Set interleave mode */ + if (max98388->interleave_mode) + regmap_update_bits(max98388->regmap, + MAX98388_R2040_PCM_MODE_CFG, + MAX98388_PCM_TX_CH_INTERLEAVE_MASK, + MAX98388_PCM_TX_CH_INTERLEAVE_MASK); + + /* Speaker Amplifier Channel Enable */ + regmap_update_bits(max98388->regmap, + MAX98388_R209F_SPK_CH_AMP_EN, + MAX98388_SPK_EN_MASK, 1); + + return 0; +} + +static int max98388_dai_set_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_component *component = codec_dai->component; + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + unsigned int format = 0; + unsigned int invert = 0; + + dev_dbg(component->dev, "%s: fmt 0x%08X\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + invert = MAX98388_PCM_MODE_CFG_PCM_BCLKEDGE; + break; + default: + dev_err(component->dev, "DAI invert mode unsupported\n"); + return -EINVAL; + } + + regmap_update_bits(max98388->regmap, + MAX98388_R2041_PCM_CLK_SETUP, + MAX98388_PCM_MODE_CFG_PCM_BCLKEDGE, + invert); + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format = MAX98388_PCM_FORMAT_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + format = MAX98388_PCM_FORMAT_LJ; + break; + case SND_SOC_DAIFMT_DSP_A: + format = MAX98388_PCM_FORMAT_TDM_MODE1; + break; + case SND_SOC_DAIFMT_DSP_B: + format = MAX98388_PCM_FORMAT_TDM_MODE0; + break; + default: + return -EINVAL; + } + + regmap_update_bits(max98388->regmap, + MAX98388_R2040_PCM_MODE_CFG, + MAX98388_PCM_MODE_CFG_FORMAT_MASK, + format << MAX98388_PCM_MODE_CFG_FORMAT_SHIFT); + + return 0; +} + +/* BCLKs per LRCLK */ +static const int bclk_sel_table[] = { + 32, 48, 64, 96, 128, 192, 256, 384, 512, 320, +}; + +static int max98388_get_bclk_sel(int bclk) +{ + int i; + /* match BCLKs per LRCLK */ + for (i = 0; i < ARRAY_SIZE(bclk_sel_table); i++) { + if (bclk_sel_table[i] == bclk) + return i + 2; + } + return 0; +} + +static int max98388_set_clock(struct snd_soc_component *component, + struct snd_pcm_hw_params *params) +{ + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + /* BCLK/LRCLK ratio calculation */ + int blr_clk_ratio = params_channels(params) * max98388->ch_size; + int value; + + if (!max98388->tdm_mode) { + /* BCLK configuration */ + value = max98388_get_bclk_sel(blr_clk_ratio); + if (!value) { + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + return -EINVAL; + } + + regmap_update_bits(max98388->regmap, + MAX98388_R2041_PCM_CLK_SETUP, + MAX98388_PCM_CLK_SETUP_BSEL_MASK, + value); + } + return 0; +} + +static int max98388_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + unsigned int sampling_rate = 0; + unsigned int chan_sz = 0; + int ret, reg; + int status = 0; + + /* pcm mode configuration */ + switch (snd_pcm_format_width(params_format(params))) { + case 16: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + params_format(params)); + goto err; + } + + max98388->ch_size = snd_pcm_format_width(params_format(params)); + + ret = regmap_read(max98388->regmap, + MAX98388_R2040_PCM_MODE_CFG, ®); + if (ret < 0) + goto err; + + /* GLOBAL_EN OFF prior to the channel size re-configure */ + if (chan_sz != (reg & MAX98388_PCM_MODE_CFG_CHANSZ_MASK)) { + ret = regmap_read(max98388->regmap, + MAX98388_R210F_GLOBAL_EN, &status); + if (ret < 0) + goto err; + + if (status) { + regmap_write(max98388->regmap, + MAX98388_R210F_GLOBAL_EN, 0); + usleep_range(30000, 31000); + } + regmap_update_bits(max98388->regmap, + MAX98388_R2040_PCM_MODE_CFG, + MAX98388_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + } + dev_dbg(component->dev, "format supported %d", + params_format(params)); + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 8000: + sampling_rate = MAX98388_PCM_SR_8000; + break; + case 11025: + sampling_rate = MAX98388_PCM_SR_11025; + break; + case 12000: + sampling_rate = MAX98388_PCM_SR_12000; + break; + case 16000: + sampling_rate = MAX98388_PCM_SR_16000; + break; + case 22050: + sampling_rate = MAX98388_PCM_SR_22050; + break; + case 24000: + sampling_rate = MAX98388_PCM_SR_24000; + break; + case 32000: + sampling_rate = MAX98388_PCM_SR_32000; + break; + case 44100: + sampling_rate = MAX98388_PCM_SR_44100; + break; + case 48000: + sampling_rate = MAX98388_PCM_SR_48000; + break; + case 88200: + sampling_rate = MAX98388_PCM_SR_88200; + break; + case 96000: + sampling_rate = MAX98388_PCM_SR_96000; + break; + default: + dev_err(component->dev, "rate %d not supported\n", + params_rate(params)); + goto err; + } + + /* set DAI_SR to correct LRCLK frequency */ + regmap_update_bits(max98388->regmap, + MAX98388_R2042_PCM_SR_SETUP, + MAX98388_PCM_SR_MASK, + sampling_rate); + + /* set sampling rate of IV */ + if (max98388->interleave_mode && + sampling_rate > MAX98388_PCM_SR_16000) + regmap_update_bits(max98388->regmap, + MAX98388_R2042_PCM_SR_SETUP, + MAX98388_PCM_SR_IV_MASK, + (sampling_rate - 3) << MAX98388_PCM_SR_IV_SHIFT); + else + regmap_update_bits(max98388->regmap, + MAX98388_R2042_PCM_SR_SETUP, + MAX98388_PCM_SR_IV_MASK, + sampling_rate << MAX98388_PCM_SR_IV_SHIFT); + + ret = max98388_set_clock(component, params); + + if (status) { + regmap_write(max98388->regmap, + MAX98388_R210F_GLOBAL_EN, 1); + usleep_range(30000, 31000); + } + + return ret; + +err: + return -EINVAL; +} + +#define MAX_NUM_SLOTS 16 +#define MAX_NUM_CH 2 + +static int max98388_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + int bsel = 0; + unsigned int chan_sz = 0; + unsigned int mask; + int cnt, slot_found; + int addr, bits; + + if (!tx_mask && !rx_mask && !slots && !slot_width) + max98388->tdm_mode = false; + else + max98388->tdm_mode = true; + + /* BCLK configuration */ + bsel = max98388_get_bclk_sel(slots * slot_width); + if (bsel == 0) { + dev_err(component->dev, "BCLK %d not supported\n", + slots * slot_width); + return -EINVAL; + } + + regmap_update_bits(max98388->regmap, + MAX98388_R2041_PCM_CLK_SETUP, + MAX98388_PCM_CLK_SETUP_BSEL_MASK, + bsel); + + /* Channel size configuration */ + switch (slot_width) { + case 16: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_16; + break; + case 24: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_24; + break; + case 32: + chan_sz = MAX98388_PCM_MODE_CFG_CHANSZ_32; + break; + default: + dev_err(component->dev, "format unsupported %d\n", + slot_width); + return -EINVAL; + } + + regmap_update_bits(max98388->regmap, + MAX98388_R2040_PCM_MODE_CFG, + MAX98388_PCM_MODE_CFG_CHANSZ_MASK, chan_sz); + + /* Rx slot configuration */ + slot_found = 0; + mask = rx_mask; + for (cnt = 0 ; cnt < MAX_NUM_SLOTS ; cnt++, mask >>= 1) { + if (mask & 0x1) { + if (slot_found == 0) + regmap_update_bits(max98388->regmap, + MAX98388_R2059_PCM_RX_SRC2, + MAX98388_RX_SRC_CH0_SHIFT, + cnt); + else + regmap_update_bits(max98388->regmap, + MAX98388_R2059_PCM_RX_SRC2, + MAX98388_RX_SRC_CH1_SHIFT, + cnt); + slot_found++; + if (slot_found >= MAX_NUM_CH) + break; + } + } + + /* speaker feedback slot configuration */ + slot_found = 0; + mask = tx_mask; + for (cnt = 0 ; cnt < MAX_NUM_SLOTS ; cnt++, mask >>= 1) { + if (mask & 0x1) { + addr = MAX98388_R2044_PCM_TX_CTRL1 + (cnt / 8); + bits = cnt % 8; + regmap_update_bits(max98388->regmap, addr, bits, bits); + if (slot_found >= MAX_NUM_CH) + break; + } + } + + return 0; +} + +#define MAX98388_RATES SNDRV_PCM_RATE_8000_96000 + +#define MAX98388_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dai_ops max98388_dai_ops = { + .set_fmt = max98388_dai_set_fmt, + .hw_params = max98388_dai_hw_params, + .set_tdm_slot = max98388_dai_tdm_slot, +}; + +static bool max98388_readable_register(struct device *dev, + unsigned int reg) +{ + switch (reg) { + case MAX98388_R2001_INT_RAW1 ... MAX98388_R2002_INT_RAW2: + case MAX98388_R2004_INT_STATE1... MAX98388_R2005_INT_STATE2: + case MAX98388_R2020_THERM_WARN_THRESH: + case MAX98388_R2031_SPK_MON_THRESH + ... MAX98388_R2033_SPK_MON_DURATION: + case MAX98388_R2037_ERR_MON_CTRL: + case MAX98388_R2040_PCM_MODE_CFG + ... MAX98388_R2042_PCM_SR_SETUP: + case MAX98388_R2044_PCM_TX_CTRL1 + ... MAX98388_R2045_PCM_TX_CTRL2: + case MAX98388_R2050_PCM_TX_HIZ_CTRL1 + ... MAX98388_R2059_PCM_RX_SRC2: + case MAX98388_R205C_PCM_TX_DRIVE_STRENGTH + ... MAX98388_R205F_PCM_TX_EN: + case MAX98388_R2090_SPK_CH_VOL_CTRL + ... MAX98388_R2094_SPK_AMP_ER_CTRL: + case MAX98388_R209E_SPK_CH_PINK_NOISE_EN + ... MAX98388_R209F_SPK_CH_AMP_EN: + case MAX98388_R20A0_IV_DATA_DSP_CTRL: + case MAX98388_R20A7_IV_DATA_EN: + case MAX98388_R20E0_BP_ALC_THRESH ... MAX98388_R20E4_BP_ALC_MUTE: + case MAX98388_R20EE_BP_INF_HOLD_REL ... MAX98388_R20EF_BP_ALC_EN: + case MAX98388_R210E_AUTO_RESTART: + case MAX98388_R210F_GLOBAL_EN: + case MAX98388_R22FF_REV_ID: + return true; + default: + return false; + } +}; + +static bool max98388_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MAX98388_R2001_INT_RAW1 ... MAX98388_R2005_INT_STATE2: + case MAX98388_R210F_GLOBAL_EN: + case MAX98388_R22FF_REV_ID: + return true; + default: + return false; + } +} + +static struct snd_soc_dai_driver max98388_dai[] = { + { + .name = "max98388-aif1", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98388_RATES, + .formats = MAX98388_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MAX98388_RATES, + .formats = MAX98388_FORMATS, + }, + .ops = &max98388_dai_ops, + } +}; + +static int max98388_suspend(struct device *dev) +{ + struct max98388_priv *max98388 = dev_get_drvdata(dev); + + regcache_cache_only(max98388->regmap, true); + regcache_mark_dirty(max98388->regmap); + + return 0; +} + +static int max98388_resume(struct device *dev) +{ + struct max98388_priv *max98388 = dev_get_drvdata(dev); + + regcache_cache_only(max98388->regmap, false); + max98388_reset(max98388, dev); + regcache_sync(max98388->regmap); + + return 0; +} + +static const struct dev_pm_ops max98388_pm = { + SET_SYSTEM_SLEEP_PM_OPS(max98388_suspend, max98388_resume) +}; + +static const struct regmap_config max98388_regmap = { + .reg_bits = 16, + .val_bits = 8, + .max_register = MAX98388_R22FF_REV_ID, + .reg_defaults = max98388_reg, + .num_reg_defaults = ARRAY_SIZE(max98388_reg), + .readable_reg = max98388_readable_register, + .volatile_reg = max98388_volatile_reg, + .cache_type = REGCACHE_RBTREE, +}; + +const struct snd_soc_component_driver soc_codec_dev_max98388 = { + .probe = max98388_probe, + .controls = max98388_snd_controls, + .num_controls = ARRAY_SIZE(max98388_snd_controls), + .dapm_widgets = max98388_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(max98388_dapm_widgets), + .dapm_routes = max98388_audio_map, + .num_dapm_routes = ARRAY_SIZE(max98388_audio_map), + .use_pmdown_time = 1, + .endianness = 1, +}; + +static void max98388_read_deveice_property(struct device *dev, + struct max98388_priv *max98388) +{ + int value; + + if (!device_property_read_u32(dev, "adi,vmon-slot-no", &value)) + max98388->v_slot = value & 0xF; + else + max98388->v_slot = 0; + + if (!device_property_read_u32(dev, "adi,imon-slot-no", &value)) + max98388->i_slot = value & 0xF; + else + max98388->i_slot = 1; + + if (device_property_read_bool(dev, "adi,interleave-mode")) + max98388->interleave_mode = true; + else + max98388->interleave_mode = false; +} + +static int max98388_i2c_probe(struct i2c_client *i2c) +{ + int ret = 0; + int reg = 0; + + struct max98388_priv *max98388 = NULL; + + max98388 = devm_kzalloc(&i2c->dev, sizeof(*max98388), GFP_KERNEL); + if (!max98388) + return -ENOMEM; + + i2c_set_clientdata(i2c, max98388); + + /* regmap initialization */ + max98388->regmap = devm_regmap_init_i2c(i2c, &max98388_regmap); + if (IS_ERR(max98388->regmap)) + return dev_err_probe(&i2c->dev, PTR_ERR(max98388->regmap), + "Failed to allocate register map.\n"); + + /* voltage/current slot & gpio configuration */ + max98388_read_deveice_property(&i2c->dev, max98388); + + /* Device Reset */ + max98388->reset_gpio = devm_gpiod_get_optional(&i2c->dev, + "reset", GPIOD_OUT_HIGH); + if (IS_ERR(max98388->reset_gpio)) + return dev_err_probe(&i2c->dev, PTR_ERR(max98388->reset_gpio), + "Unable to request GPIO\n"); + + if (max98388->reset_gpio) { + usleep_range(5000, 6000); + gpiod_set_value_cansleep(max98388->reset_gpio, 0); + /* Wait for the hw reset done */ + usleep_range(5000, 6000); + } + + /* Read Revision ID */ + ret = regmap_read(max98388->regmap, + MAX98388_R22FF_REV_ID, ®); + if (ret < 0) + return dev_err_probe(&i2c->dev, PTR_ERR(max98388->regmap), + "Failed to read the revision ID\n"); + + dev_info(&i2c->dev, "MAX98388 revisionID: 0x%02X\n", reg); + + /* codec registration */ + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_codec_dev_max98388, + max98388_dai, + ARRAY_SIZE(max98388_dai)); + if (ret < 0) + dev_err(&i2c->dev, "Failed to register codec: %d\n", ret); + + return ret; +} + +static const struct i2c_device_id max98388_i2c_id[] = { + { "max98388", 0}, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, max98388_i2c_id); + +static const struct of_device_id max98388_of_match[] = { + { .compatible = "adi,max98388", }, + { } +}; +MODULE_DEVICE_TABLE(of, max98388_of_match); + +static const struct acpi_device_id max98388_acpi_match[] = { + { "ADS8388", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, max98388_acpi_match); + +static struct i2c_driver max98388_i2c_driver = { + .driver = { + .name = "max98388", + .of_match_table = of_match_ptr(max98388_of_match), + .acpi_match_table = ACPI_PTR(max98388_acpi_match), + .pm = &max98388_pm, + }, + .probe = max98388_i2c_probe, + .id_table = max98388_i2c_id, +}; + +module_i2c_driver(max98388_i2c_driver) + +MODULE_DESCRIPTION("ALSA SoC MAX98388 driver"); +MODULE_AUTHOR("Ryan Lee "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max98388.h b/sound/soc/codecs/max98388.h new file mode 100644 index 000000000000..77833d181913 --- /dev/null +++ b/sound/soc/codecs/max98388.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * max98388.h -- MAX98388 ALSA SoC audio driver header + * + * Copyright(c) 2022, Analog Devices Inc. + */ + +#ifndef _MAX98388_H +#define _MAX98388_H + +/* Device Status Registers */ +#define MAX98388_R2000_SW_RESET 0x2000 +#define MAX98388_R2001_INT_RAW1 0x2001 +#define MAX98388_R2002_INT_RAW2 0x2002 +#define MAX98388_R2004_INT_STATE1 0x2004 +#define MAX98388_R2005_INT_STATE2 0x2005 +/* Thermal Protection Registers */ +#define MAX98388_R2020_THERM_WARN_THRESH 0x2020 +/* Error Monitor */ +#define MAX98388_R2031_SPK_MON_THRESH 0x2031 +#define MAX98388_R2032_SPK_MON_LD_SEL 0x2032 +#define MAX98388_R2033_SPK_MON_DURATION 0x2033 +#define MAX98388_R2037_ERR_MON_CTRL 0x2037 +/* PCM Registers */ +#define MAX98388_R2040_PCM_MODE_CFG 0x2040 +#define MAX98388_R2041_PCM_CLK_SETUP 0x2041 +#define MAX98388_R2042_PCM_SR_SETUP 0x2042 +#define MAX98388_R2044_PCM_TX_CTRL1 0x2044 +#define MAX98388_R2045_PCM_TX_CTRL2 0x2045 +#define MAX98388_R2050_PCM_TX_HIZ_CTRL1 0x2050 +#define MAX98388_R2051_PCM_TX_HIZ_CTRL2 0x2051 +#define MAX98388_R2052_PCM_TX_HIZ_CTRL3 0x2052 +#define MAX98388_R2053_PCM_TX_HIZ_CTRL4 0x2053 +#define MAX98388_R2054_PCM_TX_HIZ_CTRL5 0x2054 +#define MAX98388_R2055_PCM_TX_HIZ_CTRL6 0x2055 +#define MAX98388_R2056_PCM_TX_HIZ_CTRL7 0x2056 +#define MAX98388_R2057_PCM_TX_HIZ_CTRL8 0x2057 +#define MAX98388_R2058_PCM_RX_SRC1 0x2058 +#define MAX98388_R2059_PCM_RX_SRC2 0x2059 +#define MAX98388_R205C_PCM_TX_DRIVE_STRENGTH 0x205C +#define MAX98388_R205D_PCM_TX_SRC_EN 0x205D +#define MAX98388_R205E_PCM_RX_EN 0x205E +#define MAX98388_R205F_PCM_TX_EN 0x205F +/* Speaker Channel Control */ +#define MAX98388_R2090_SPK_CH_VOL_CTRL 0x2090 +#define MAX98388_R2091_SPK_CH_CFG 0x2091 +#define MAX98388_R2092_SPK_AMP_OUT_CFG 0x2092 +#define MAX98388_R2093_SPK_AMP_SSM_CFG 0x2093 +#define MAX98388_R2094_SPK_AMP_ER_CTRL 0x2094 +#define MAX98388_R209E_SPK_CH_PINK_NOISE_EN 0x209E +#define MAX98388_R209F_SPK_CH_AMP_EN 0x209F +#define MAX98388_R20A0_IV_DATA_DSP_CTRL 0x20A0 +#define MAX98388_R20A7_IV_DATA_EN 0x20A7 +#define MAX98388_R20E0_BP_ALC_THRESH 0x20E0 +#define MAX98388_R20E1_BP_ALC_RATES 0x20E1 +#define MAX98388_R20E2_BP_ALC_ATTEN 0x20E2 +#define MAX98388_R20E3_BP_ALC_REL 0x20E3 +#define MAX98388_R20E4_BP_ALC_MUTE 0x20E4 +#define MAX98388_R20EE_BP_INF_HOLD_REL 0x20EE +#define MAX98388_R20EF_BP_ALC_EN 0x20EF +#define MAX98388_R210E_AUTO_RESTART 0x210E +#define MAX98388_R210F_GLOBAL_EN 0x210F +#define MAX98388_R22FF_REV_ID 0x22FF + +/* MAX98388_R2000_SW_RESET */ +#define MAX98388_SOFT_RESET (0x1 << 0) + +/* MAX98388_R2020_THERM_WARN_THRESH */ +#define MAX98388_THERM_SHDN_THRESH_SHIFT (0) +#define MAX98388_THERM_WARN_THRESH_SHIFT (2) + +/* MAX98388_R2022_PCM_TX_SRC_1 */ +#define MAX98388_PCM_TX_CH_SRC_A_V_SHIFT (0) +#define MAX98388_PCM_TX_CH_SRC_A_I_SHIFT (4) + +/* MAX98388_R2024_PCM_DATA_FMT_CFG */ +#define MAX98388_PCM_MODE_CFG_FORMAT_MASK (0x7 << 3) +#define MAX98388_PCM_MODE_CFG_FORMAT_SHIFT (3) +#define MAX98388_PCM_TX_CH_INTERLEAVE_MASK (0x1 << 2) +#define MAX98388_PCM_FORMAT_I2S (0x0 << 0) +#define MAX98388_PCM_FORMAT_LJ (0x1 << 0) +#define MAX98388_PCM_FORMAT_TDM_MODE0 (0x3 << 0) +#define MAX98388_PCM_FORMAT_TDM_MODE1 (0x4 << 0) +#define MAX98388_PCM_FORMAT_TDM_MODE2 (0x5 << 0) +#define MAX98388_PCM_MODE_CFG_CHANSZ_MASK (0x3 << 6) +#define MAX98388_PCM_MODE_CFG_CHANSZ_16 (0x1 << 6) +#define MAX98388_PCM_MODE_CFG_CHANSZ_24 (0x2 << 6) +#define MAX98388_PCM_MODE_CFG_CHANSZ_32 (0x3 << 6) + +/* MAX98388_R2031_SPK_MON_THRESH */ +#define MAX98388_SPKMON_THRESH_SHIFT (0) + +/* MAX98388_R2032_SPK_MON_LD_SEL */ +#define MAX98388_SPKMON_LOAD_SHIFT (0) + +/* MAX98388_R2033_SPK_MON_DURATION */ +#define MAX98388_SPKMON_DURATION_SHIFT (0) + +/* MAX98388_R2037_ERR_MON_CTRL */ +#define MAX98388_CLOCK_MON_SHIFT (0) +#define MAX98388_SPK_MON_SHIFT (1) + +/* MAX98388_R203E_AMP_PATH_GAIN */ +#define MAX98388_SPK_DIGI_GAIN_MASK (0xF << 4) +#define MAX98388_SPK_DIGI_GAIN_SHIFT (4) +#define MAX98388_FS_GAIN_MAX_MASK (0xF << 0) +#define MAX98388_FS_GAIN_MAX_SHIFT (0) + +/* MAX98388_R2041_PCM_CLK_SETUP */ +#define MAX98388_PCM_MODE_CFG_PCM_BCLKEDGE (0x1 << 4) +#define MAX98388_PCM_CLK_SETUP_BSEL_MASK (0xF << 0) + +/* MAX98388_R2042_PCM_SR_SETUP */ +#define MAX98388_PCM_SR_MASK (0xF << 0) +#define MAX98388_PCM_SR_IV_MASK (0xF << 4) +#define MAX98388_PCM_SR_IV_SHIFT (4) +#define MAX98388_PCM_SR_8000 (0x0 << 0) +#define MAX98388_PCM_SR_11025 (0x1 << 0) +#define MAX98388_PCM_SR_12000 (0x2 << 0) +#define MAX98388_PCM_SR_16000 (0x3 << 0) +#define MAX98388_PCM_SR_22050 (0x4 << 0) +#define MAX98388_PCM_SR_24000 (0x5 << 0) +#define MAX98388_PCM_SR_32000 (0x6 << 0) +#define MAX98388_PCM_SR_44100 (0x7 << 0) +#define MAX98388_PCM_SR_48000 (0x8 << 0) +#define MAX98388_PCM_SR_88200 (0x9 << 0) +#define MAX98388_PCM_SR_96000 (0xA << 0) + +/* MAX98388_R2043_AMP_EN */ +#define MAX98388_SPK_EN_MASK (0x1 << 0) +#define MAX98388_SPKFB_EN_MASK (0x1 << 1) +#define MAX98388_SPKFB_EN_SHIFT (1) + +/* MAX98388_R2052_MEAS_ADC_PVDD_FLT_CFG */ +#define MAX98388_FLT_EN_SHIFT (4) + +/* MAX98388_R2058_PCM_RX_SRC1 */ +#define MAX98388_PCM_TO_SPK_MONOMIX_CFG_SHIFT (0) + +/* MAX98388_R2059_PCM_RX_SRC2 */ +#define MAX98388_RX_SRC_CH0_SHIFT (0) +#define MAX98388_RX_SRC_CH1_SHIFT (4) + +/* MAX98388_R2091_SPK_CH_CFG */ +#define MAX98388_SPK_CFG_DCBLK_SHIFT (0) +#define MAX98388_SPK_CFG_DITH_EN_SHIFT (1) +#define MAX98388_SPK_CFG_INV_SHIFT (2) +#define MAX98388_SPK_CFG_VOL_RMPUP_SHIFT (3) +#define MAX98388_SPK_CFG_VOL_RMPDN_SHIFT (4) + +/* MAX98388_R2092_SPK_AMP_OUT_CFG */ +#define MAX98388_SPK_AMP_OUT_GAIN_SHIFT (0) +#define MAX98388_SPK_AMP_OUT_MODE_SHIFT (3) + +/* MAX98388_R2093_SPK_AMP_SSM_CFG */ +#define MAX98388_SPK_AMP_SSM_EN_SHIFT (0) +#define MAX98388_SPK_AMP_SSM_MOD_SHIFT (1) + +/* MAX98388_R2094_SPK_AMP_ER_CTRL */ +#define MAX98388_EDGE_RATE_RISE_SHIFT (0) +#define MAX98388_EDGE_RATE_FALL_SHIFT (2) + +/* MAX98388_R209E_SPK_CH_PINK_NOISE_EN */ +#define MAX98388_PINK_NOISE_GEN_SHIFT (0) + +/* MAX98388_R20A0_IV_DATA_DSP_CTRL */ +#define MAX98388_AMP_DSP_CTRL_VOL_DCBLK_SHIFT (0) +#define MAX98388_AMP_DSP_CTRL_CUR_DCBLK_SHIFT (1) +#define MAX98388_AMP_DSP_CTRL_VOL_INV_SHIFT (2) +#define MAX98388_AMP_DSP_CTRL_CUR_INV_SHIFT (3) +#define MAX98388_AMP_DSP_CTRL_DITH_SHIFT (4) + +/* MAX98388_R20B2_BDE_L4_CFG_2 */ +#define MAX98388_LVL4_HOLD_EN_SHIFT (6) +#define MAX98388_LVL4_MUTE_EN_SHIFT (7) + +/* MAX98388_R20B5_BDE_EN */ +#define MAX98388_BDE_EN_SHIFT (0) + +/* MAX98388_R20D1_DHT_CFG */ +#define MAX98388_DHT_ROT_PNT_SHIFT (0) +#define MAX98388_DHT_SPK_GAIN_MIN_SHIFT (4) + +/* MAX98388_R20D2_DHT_ATTACK_CFG */ +#define MAX98388_DHT_ATTACK_RATE_SHIFT (0) +#define MAX98388_DHT_ATTACK_STEP_SHIFT (3) + +/* MAX98388_R20D3_DHT_RELEASE_CFG */ +#define MAX98388_DHT_RELEASE_RATE_SHIFT (0) +#define MAX98388_DHT_RELEASE_STEP_SHIFT (3) + +/* MAX98388_R20D4_DHT_EN */ +#define MAX98388_DHT_EN_SHIFT (0) + +/* MAX98388_R20E0_BP_ALC_THRESH */ +#define MAX98388_ALC_THRESH_SHIFT (0) + +/* MAX98388_R20E1_BP_ALC_RATES */ +#define MAX98388_ALC_RELEASE_RATE_SHIFT (0) +#define MAX98388_ALC_ATTACK_RATE_SHIFT (4) + +/* MAX98388_R20E2_BP_ALC_ATTEN */ +#define MAX98388_ALC_MAX_ATTEN_SHIFT (0) + +/* MAX98388_R20E3_BP_ALC_REL */ +#define MAX98388_ALC_DEBOUNCE_TIME_SHIFT (0) + +/* MAX98388_R20E4_BP_ALC_MUTE */ +#define MAX98388_ALC_MUTE_EN_SHIFT (0) +#define MAX98388_ALC_MUTE_DELAY_SHIFT (1) +#define MAX98388_ALC_MUTE_RAMP_EN_SHIFT (4) +#define MAX98388_ALC_UNMUTE_RAMP_EN_SHIFT (5) + +/* MAX98388_R210E_AUTO_RESTART */ +#define MAX98388_PVDD_UVLO_AUTORESTART_SHIFT (0) +#define MAX98388_THERM_AUTORESTART_SHIFT (1) +#define MAX98388_OVC_AUTORESTART_SHIFT (2) +#define MAX98388_CMON_AUTORESTART_SHIFT (3) + +/* MAX98388_R210F_GLOBAL_EN */ +#define MAX98388_GLOBAL_EN_MASK (0x1 << 0) + +struct max98388_priv { + struct regmap *regmap; + struct gpio_desc *reset_gpio; + unsigned int v_slot; + unsigned int i_slot; + unsigned int spkfb_slot; + bool interleave_mode; + unsigned int ch_size; + bool tdm_mode; +}; + +#endif -- cgit v1.2.3 From d24028606e7642261d33ad2a50aed940d35cfb66 Mon Sep 17 00:00:00 2001 From: Yingkun Meng Date: Wed, 14 Jun 2023 20:22:40 +0800 Subject: ASoC: loongson: Add Loongson ASoC Sound Card Support The Loongson ASoC Sound Card is a general ASoC DAI Link driver that can be used for Loongson CPU DAI drivers and external CODECs. The driver supports the use of ACPI table to describe device resources. On loongson 7axxx platforms, the audio device is an ACPI device. Signed-off-by: Yingkun Meng Link: https://lore.kernel.org/r/20230614122240.3402762-1-mengyingkun@loongson.cn Signed-off-by: Mark Brown --- sound/soc/loongson/Kconfig | 10 ++ sound/soc/loongson/Makefile | 4 + sound/soc/loongson/loongson_card.c | 230 +++++++++++++++++++++++++++++++++++++ 3 files changed, 244 insertions(+) create mode 100644 sound/soc/loongson/loongson_card.c (limited to 'sound') diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig index 4478ac91e402..c175f9de19a8 100644 --- a/sound/soc/loongson/Kconfig +++ b/sound/soc/loongson/Kconfig @@ -13,4 +13,14 @@ config SND_SOC_LOONGSON_I2S_PCI The controller is found in loongson bridge chips or SoCs, and work as a PCI device. +config SND_SOC_LOONGSON_CARD + tristate "Loongson Sound Card Driver" + select SND_SOC_LOONGSON_I2S_PCI + help + Say Y or M if you want to add support for SoC audio using + loongson I2S controller. + + The driver add support for ALSA SoC Audio support using + loongson I2S controller. + endmenu diff --git a/sound/soc/loongson/Makefile b/sound/soc/loongson/Makefile index 099af7989103..601a905a4860 100644 --- a/sound/soc/loongson/Makefile +++ b/sound/soc/loongson/Makefile @@ -2,3 +2,7 @@ #Platform Support snd-soc-loongson-i2s-pci-objs := loongson_i2s_pci.o loongson_i2s.o loongson_dma.o obj-$(CONFIG_SND_SOC_LOONGSON_I2S_PCI) += snd-soc-loongson-i2s-pci.o + +#Machine Support +snd-soc-loongson-card-objs := loongson_card.o +obj-$(CONFIG_SND_SOC_LOONGSON_CARD) += snd-soc-loongson-card.o diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c new file mode 100644 index 000000000000..965eaf4e9109 --- /dev/null +++ b/sound/soc/loongson/loongson_card.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Loongson ASoC Audio Machine driver +// +// Copyright (C) 2023 Loongson Technology Corporation Limited +// Author: Yingkun Meng +// + +#include +#include +#include +#include +#include +#include + +static char codec_name[SND_ACPI_I2C_ID_LEN]; + +struct loongson_card_data { + struct snd_soc_card snd_card; + unsigned int mclk_fs; +}; + +static int loongson_card_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct loongson_card_data *ls_card = snd_soc_card_get_drvdata(rtd->card); + int ret, mclk; + + if (ls_card->mclk_fs) { + mclk = ls_card->mclk_fs * params_rate(params); + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, + SND_SOC_CLOCK_OUT); + if (ret < 0) { + dev_err(codec_dai->dev, "cpu_dai clock not set\n"); + return ret; + } + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "codec_dai clock not set\n"); + return ret; + } + } + return 0; +} + +static const struct snd_soc_ops loongson_ops = { + .hw_params = loongson_card_hw_params, +}; + +SND_SOC_DAILINK_DEFS(analog, + DAILINK_COMP_ARRAY(COMP_CPU("loongson-i2s")), + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link loongson_dai_links[] = { + { + .name = "Loongson Audio Port", + .stream_name = "Loongson Audio", + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBC_CFC, + SND_SOC_DAILINK_REG(analog), + .ops = &loongson_ops, + }, +}; + +static int loongson_card_parse_acpi(struct loongson_card_data *data) +{ + struct snd_soc_card *card = &data->snd_card; + struct fwnode_handle *fwnode = card->dev->fwnode; + struct fwnode_reference_args args; + const char *codec_dai_name; + struct acpi_device *adev; + struct device *phy_dev; + int ret, i; + + /* fixup platform name based on reference node */ + memset(&args, 0, sizeof(args)); + ret = acpi_node_get_property_reference(fwnode, "cpu", 0, &args); + if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) { + dev_err(card->dev, "No matching phy in ACPI table\n"); + return ret; + } + adev = to_acpi_device_node(args.fwnode); + phy_dev = acpi_get_first_physical_node(adev); + if (!phy_dev) + return -EPROBE_DEFER; + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].platforms->name = dev_name(phy_dev); + + /* fixup codec name based on reference node */ + memset(&args, 0, sizeof(args)); + ret = acpi_node_get_property_reference(fwnode, "codec", 0, &args); + if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) { + dev_err(card->dev, "No matching phy in ACPI table\n"); + return ret; + } + adev = to_acpi_device_node(args.fwnode); + snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev)); + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->name = codec_name; + + device_property_read_string(card->dev, "codec-dai-name", + &codec_dai_name); + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->dai_name = codec_dai_name; + + return 0; +} + +static int loongson_card_parse_of(struct loongson_card_data *data) +{ + const char *cpu_dai_name, *codec_dai_name; + struct device_node *cpu, *codec; + struct snd_soc_card *card = &data->snd_card; + struct device *dev = card->dev; + struct of_phandle_args args; + int ret, i; + + cpu = of_get_child_by_name(dev->of_node, "cpu"); + if (!cpu) { + dev_err(dev, "platform property missing or invalid\n"); + return -EINVAL; + } + codec = of_get_child_by_name(dev->of_node, "codec"); + if (!codec) { + dev_err(dev, "audio-codec property missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + ret = of_parse_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(dev, "codec node missing #sound-dai-cells\n"); + goto err; + } + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].cpus->of_node = args.np; + + ret = of_parse_phandle_with_args(codec, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(dev, "codec node missing #sound-dai-cells\n"); + goto err; + } + for (i = 0; i < card->num_links; i++) + loongson_dai_links[i].codecs->of_node = args.np; + + snd_soc_of_get_dai_name(cpu, &cpu_dai_name); + snd_soc_of_get_dai_name(codec, &codec_dai_name); + for (i = 0; i < card->num_links; i++) { + loongson_dai_links[i].cpus->dai_name = cpu_dai_name; + loongson_dai_links[i].codecs->dai_name = codec_dai_name; + } + of_node_put(cpu); + of_node_put(codec); + + return 0; + +err: + of_node_put(cpu); + of_node_put(codec); + return ret; +} + +static int loongson_asoc_card_probe(struct platform_device *pdev) +{ + struct loongson_card_data *ls_priv; + struct snd_soc_card *card; + int ret; + + ls_priv = devm_kzalloc(&pdev->dev, sizeof(*ls_priv), GFP_KERNEL); + if (!ls_priv) + return -ENOMEM; + + card = &ls_priv->snd_card; + + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + card->dai_link = loongson_dai_links; + card->num_links = ARRAY_SIZE(loongson_dai_links); + snd_soc_card_set_drvdata(card, ls_priv); + + ret = device_property_read_string(&pdev->dev, "model", &card->name); + if (ret) { + dev_err(&pdev->dev, "Error parsing card name: %d\n", ret); + return ret; + } + ret = device_property_read_u32(&pdev->dev, "mclk-fs", &ls_priv->mclk_fs); + if (ret) { + dev_err(&pdev->dev, "Error parsing mclk-fs: %d\n", ret); + return ret; + } + + if (has_acpi_companion(&pdev->dev)) + ret = loongson_card_parse_acpi(ls_priv); + else + ret = loongson_card_parse_of(ls_priv); + if (ret < 0) + return ret; + + ret = devm_snd_soc_register_card(&pdev->dev, card); + + return ret; +} + +static const struct of_device_id loongson_asoc_dt_ids[] = { + { .compatible = "loongson,ls-audio-card" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids); + +static struct platform_driver loongson_audio_driver = { + .probe = loongson_asoc_card_probe, + .driver = { + .name = "loongson-asoc-card", + .pm = &snd_soc_pm_ops, + .of_match_table = of_match_ptr(loongson_asoc_dt_ids), + }, +}; +module_platform_driver(loongson_audio_driver); + +MODULE_DESCRIPTION("Loongson ASoc Sound Card driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 90ce7538659aad1c048653c23eadaba9d1648559 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 16 Jun 2023 12:00:32 +0200 Subject: ASoC: SOF: sof-audio: add is_virtual_widget helper Testing virtual widget is required in many functions. No function changed in this commit. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-2-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-audio.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index 1cbda595c518..c77d07d62517 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -14,6 +14,20 @@ #include "sof-of-dev.h" #include "ops.h" +static bool is_virtual_widget(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *widget, + const char *func) +{ + switch (widget->id) { + case snd_soc_dapm_out_drv: + case snd_soc_dapm_output: + case snd_soc_dapm_input: + dev_dbg(sdev->dev, "%s: %s is a virtual widget\n", func, widget->name); + return true; + default: + return false; + } +} + static void sof_reset_route_setup_status(struct snd_sof_dev *sdev, struct snd_sof_widget *widget) { const struct sof_ipc_tplg_ops *tplg_ops = sof_ipc_get_ops(sdev, tplg); @@ -231,23 +245,9 @@ int sof_route_setup(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget *wsourc bool route_found = false; /* ignore routes involving virtual widgets in topology */ - switch (src_widget->id) { - case snd_soc_dapm_out_drv: - case snd_soc_dapm_output: - case snd_soc_dapm_input: - return 0; - default: - break; - } - - switch (sink_widget->id) { - case snd_soc_dapm_out_drv: - case snd_soc_dapm_output: - case snd_soc_dapm_input: + if (is_virtual_widget(sdev, src_widget->widget, __func__) || + is_virtual_widget(sdev, sink_widget->widget, __func__)) return 0; - default: - break; - } /* find route matching source and sink widgets */ list_for_each_entry(sroute, &sdev->route_list, list) -- cgit v1.2.3 From 0557864e9dbe8f6c0f86110ad5712f81649f7288 Mon Sep 17 00:00:00 2001 From: Bard Liao Date: Fri, 16 Jun 2023 12:00:33 +0200 Subject: ASoC: SOF: sof-audio: test virtual widget in sof_walk_widgets_in_order Virtual widgets are added for the purpose of showing connections between aggregated DAIs in SDW topologies. However, we shouldn't touch them in SOF. Reviewed-by: Ranjani Sridharan Signed-off-by: Bard Liao Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-3-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-audio.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/sof-audio.c b/sound/soc/sof/sof-audio.c index c77d07d62517..e7ef77012c35 100644 --- a/sound/soc/sof/sof-audio.c +++ b/sound/soc/sof/sof-audio.c @@ -396,6 +396,9 @@ sof_unprepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widg const struct sof_ipc_tplg_widget_ops *widget_ops; struct snd_soc_dapm_path *p; + if (is_virtual_widget(sdev, widget, __func__)) + return; + /* skip if the widget is in use or if it is already unprepared */ if (!swidget || !swidget->prepared || swidget->use_count > 0) goto sink_unprepare; @@ -433,6 +436,9 @@ sof_prepare_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dapm_widget struct snd_soc_dapm_path *p; int ret; + if (is_virtual_widget(sdev, widget, __func__)) + return 0; + widget_ops = tplg_ops ? tplg_ops->widget : NULL; if (!widget_ops) return 0; @@ -488,6 +494,9 @@ static int sof_free_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_dap int err; int ret = 0; + if (is_virtual_widget(sdev, widget, __func__)) + return 0; + if (widget->dobj.private) { err = sof_widget_free(sdev, widget->dobj.private); if (err < 0) @@ -527,6 +536,9 @@ static int sof_set_up_widgets_in_path(struct snd_sof_dev *sdev, struct snd_soc_d struct snd_soc_dapm_path *p; int ret; + if (is_virtual_widget(sdev, widget, __func__)) + return 0; + if (swidget) { int i; @@ -592,6 +604,9 @@ sof_walk_widgets_in_order(struct snd_sof_dev *sdev, struct snd_sof_pcm *spcm, return 0; for_each_dapm_widgets(list, i, widget) { + if (is_virtual_widget(sdev, widget, __func__)) + continue; + /* starting widget for playback is AIF type */ if (dir == SNDRV_PCM_STREAM_PLAYBACK && widget->id != snd_soc_dapm_aif_in) continue; -- cgit v1.2.3 From d389dcb3a48cec4f03c16434c0bf98a4c635372a Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2023 12:00:34 +0200 Subject: ASoC: SOF: core: Free the firmware trace before calling snd_sof_shutdown() The shutdown is called on reboot/shutdown of the machine. At this point the firmware tracing cannot be used anymore but in case of IPC3 it is using and keeping a DMA channel active (dtrace). For Tiger Lake platforms we have a quirk in place to fix rare reboot issues when a DMA was active before rebooting the system. If the tracing is enabled this quirk will be always used and a print appears on the kernel log which might be misleading or not even correct. Release the fw tracing before executing the shutdown to make sure that this known DMA user is cleared away. Reviewed-by: Kai Vehmanen Reviewed-by: Daniel Baluta Reviewed-by: Ranjani Sridharan Reviewed-by: Rander Wang Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-4-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/core.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 9a9d82220fd0..30db685cc5f4 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -504,8 +504,10 @@ int snd_sof_device_shutdown(struct device *dev) if (IS_ENABLED(CONFIG_SND_SOC_SOF_PROBE_WORK_QUEUE)) cancel_work_sync(&sdev->probe_work); - if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) + if (sdev->fw_state == SOF_FW_BOOT_COMPLETE) { + sof_fw_trace_free(sdev); return snd_sof_shutdown(sdev); + } return 0; } -- cgit v1.2.3 From d498a3bdfe954afb4155ab2bdc3ae534c949b907 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2023 12:00:35 +0200 Subject: ASoC: SOF: Add new sof_debug flag to request message payload dump We only print out the header information of an IPC message in debug level, either in verbose or non verbose way (Kconfig option). On top of the header information the message itself can help reproducing and identifying issues. BIT(11) can be used to request a message payload dump if it is supported by the IPC implementation. Since IPC message payload printing is only implemented for IPC4, the flag will not have any effect to IPC3 for now. Reviewed-by: Kai Vehmanen Reviewed-by: Ranjani Sridharan Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-5-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/sof-priv.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/sof-priv.h b/sound/soc/sof/sof-priv.h index cd4f6ac126ec..d4f6702e93dc 100644 --- a/sound/soc/sof/sof-priv.h +++ b/sound/soc/sof/sof-priv.h @@ -48,6 +48,9 @@ struct snd_sof_pcm_stream; #define SOF_DBG_FORCE_NOCODEC BIT(10) /* ignore all codec-related * configurations */ +#define SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD BIT(11) /* On top of the IPC message header + * dump the message payload also + */ #define SOF_DBG_DSPLESS_MODE BIT(15) /* Do not initialize and use the DSP */ /* Flag definitions used for controlling the DSP dump behavior */ -- cgit v1.2.3 From d01c7636ffa051297672c55ab6088ae539d221ee Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2023 12:00:36 +0200 Subject: ASoC: SOF: ipc3: Dump IPC message payload Dump the IPC message payload if BIT(11) of sof_debug is set and the message contains more data than just a header. The header size differs between TX and RX and in case of set_get_data, the header is always the reply header for the message regardless if it is TX or RX. The use of printk(KERN_DEBUG "..."); is on purpose to keep the dmesg output tidy. Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-6-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc3.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'sound') diff --git a/sound/soc/sof/ipc3.c b/sound/soc/sof/ipc3.c index ec1ac0fb2d9f..2c5aac31e8b0 100644 --- a/sound/soc/sof/ipc3.c +++ b/sound/soc/sof/ipc3.c @@ -223,6 +223,14 @@ static inline void ipc3_log_header(struct device *dev, u8 *text, u32 cmd) } #endif +static void sof_ipc3_dump_payload(struct snd_sof_dev *sdev, + void *ipc_data, size_t size) +{ + printk(KERN_DEBUG "Size of payload following the header: %zu\n", size); + print_hex_dump_debug("Message payload: ", DUMP_PREFIX_OFFSET, + 16, 4, ipc_data, size, false); +} + static int sof_ipc3_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; @@ -374,6 +382,29 @@ static int sof_ipc3_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_ ret = ipc3_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); + if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) { + size_t payload_bytes, header_bytes; + char *payload = NULL; + + /* payload is indicated by non zero msg/reply_bytes */ + if (msg_bytes > sizeof(struct sof_ipc_cmd_hdr)) { + payload = msg_data; + + header_bytes = sizeof(struct sof_ipc_cmd_hdr); + payload_bytes = msg_bytes - header_bytes; + } else if (reply_bytes > sizeof(struct sof_ipc_reply)) { + payload = reply_data; + + header_bytes = sizeof(struct sof_ipc_reply); + payload_bytes = reply_bytes - header_bytes; + } + + if (payload) { + payload += header_bytes; + sof_ipc3_dump_payload(sdev, payload, payload_bytes); + } + } + mutex_unlock(&ipc->tx_mutex); return ret; @@ -472,6 +503,14 @@ static int sof_ipc3_set_get_data(struct snd_sof_dev *sdev, void *data, size_t da offset += payload_size; } + if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) { + size_t header_bytes = sizeof(struct sof_ipc_reply); + char *payload = (char *)cdata; + + payload += header_bytes; + sof_ipc3_dump_payload(sdev, payload, data_bytes - header_bytes); + } + mutex_unlock(&sdev->ipc->tx_mutex); kfree(cdata_chunk); -- cgit v1.2.3 From c3d275e3a84833368c47c803043105bda095a624 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2023 12:00:37 +0200 Subject: ASoC: SOF: ipc4: Switch to use the sof_debug:bit11 to dump message payload Use the SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD flag to print the message payload instead of the DEBUG_VERBOSE, which would need code modification and kernel re-compilation. Reviewed-by: Kai Vehmanen Reviewed-by: Ranjani Sridharan Reviewed-by: Bard Liao Signed-off-by: Peter Ujfalusi Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-7-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/ipc4.c | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/ipc4.c b/sound/soc/sof/ipc4.c index 246b56d24a6f..ab6eddd91bb7 100644 --- a/sound/soc/sof/ipc4.c +++ b/sound/soc/sof/ipc4.c @@ -17,15 +17,6 @@ #include "ipc4-priv.h" #include "ops.h" -#ifdef DEBUG_VERBOSE -#define sof_ipc4_dump_payload(sdev, ipc_data, size) \ - print_hex_dump_debug("Message payload: ", \ - DUMP_PREFIX_OFFSET, \ - 16, 4, ipc_data, size, false) -#else -#define sof_ipc4_dump_payload(sdev, ipc_data, size) do { } while (0) -#endif - static const struct sof_ipc4_fw_status { int status; char *msg; @@ -256,6 +247,13 @@ static void sof_ipc4_log_header(struct device *dev, u8 *text, struct sof_ipc4_ms } #endif +static void sof_ipc4_dump_payload(struct snd_sof_dev *sdev, + void *ipc_data, size_t size) +{ + print_hex_dump_debug("Message payload: ", DUMP_PREFIX_OFFSET, + 16, 4, ipc_data, size, false); +} + static int sof_ipc4_get_reply(struct snd_sof_dev *sdev) { struct snd_sof_ipc_msg *msg = sdev->msg; @@ -362,9 +360,6 @@ static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_ void *reply_data, size_t reply_bytes, bool no_pm) { struct snd_sof_ipc *ipc = sdev->ipc; -#ifdef DEBUG_VERBOSE - struct sof_ipc4_msg *msg = NULL; -#endif int ret; if (!msg_data) @@ -386,18 +381,20 @@ static int sof_ipc4_tx_msg(struct snd_sof_dev *sdev, void *msg_data, size_t msg_ ret = ipc4_tx_msg_unlocked(ipc, msg_data, msg_bytes, reply_data, reply_bytes); - mutex_unlock(&ipc->tx_mutex); + if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) { + struct sof_ipc4_msg *msg = NULL; -#ifdef DEBUG_VERBOSE - /* payload is indicated by non zero msg/reply_bytes */ - if (msg_bytes) - msg = msg_data; - else if (reply_bytes) - msg = reply_data; + /* payload is indicated by non zero msg/reply_bytes */ + if (msg_bytes) + msg = msg_data; + else if (reply_bytes) + msg = reply_data; - if (msg) - sof_ipc4_dump_payload(sdev, msg->data_ptr, msg->data_size); -#endif + if (msg) + sof_ipc4_dump_payload(sdev, msg->data_ptr, msg->data_size); + } + + mutex_unlock(&ipc->tx_mutex); return ret; } @@ -516,7 +513,8 @@ static int sof_ipc4_set_get_data(struct snd_sof_dev *sdev, void *data, if (!set && payload_bytes != offset) ipc4_msg->data_size = offset; - sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr, ipc4_msg->data_size); + if (sof_debug_check_flag(SOF_DBG_DUMP_IPC_MESSAGE_PAYLOAD)) + sof_ipc4_dump_payload(sdev, ipc4_msg->data_ptr, ipc4_msg->data_size); out: mutex_unlock(&sdev->ipc->tx_mutex); -- cgit v1.2.3 From 399961423314680c6cb14ac822600b9ede2b991f Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2023 12:00:38 +0200 Subject: ASoC: SOF: pm: Remove duplicated code in sof_suspend Over time the function has changed and now there is no need to have the duplicated sof_fw_trace_suspend() and sof_suspend_clients() in the if (target_state == SOF_DSP_PM_D0) branch. Remove it and add a simple check with a single goto statement. Reviewed-by: Daniel Baluta Reviewed-by: Paul Olaru Reviewed-by: Ranjani Sridharan Signed-off-by: Peter Ujfalusi Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-8-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/pm.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index 2b232442e84b..704b21413c71 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -234,20 +234,16 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) pm_state.event = target_state; - /* Skip to platform-specific suspend if DSP is entering D0 */ - if (target_state == SOF_DSP_PM_D0) { - sof_fw_trace_suspend(sdev, pm_state); - /* Notify clients not managed by pm framework about core suspend */ - sof_suspend_clients(sdev, pm_state); - goto suspend; - } - /* suspend DMA trace */ sof_fw_trace_suspend(sdev, pm_state); /* Notify clients not managed by pm framework about core suspend */ sof_suspend_clients(sdev, pm_state); + /* Skip to platform-specific suspend if DSP is entering D0 */ + if (target_state == SOF_DSP_PM_D0) + goto suspend; + #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) /* cache debugfs contents during runtime suspend */ if (runtime_suspend) -- cgit v1.2.3 From fd4e9e9bfa0b1c63946fde2ff61440ff1e5eb75b Mon Sep 17 00:00:00 2001 From: Rander Wang Date: Fri, 16 Jun 2023 12:00:39 +0200 Subject: ASoC: SOF: Intel: mtl: setup primary core info on MeteorLake platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set primary core mask and refcount. Reviewed-by: Ranjani Sridharan Reviewed-by: Péter Ujfalusi Signed-off-by: Rander Wang Signed-off-by: Pierre-Louis Bossart Link: https://lore.kernel.org/r/20230616100039.378150-9-pierre-louis.bossart@linux.intel.com Signed-off-by: Mark Brown --- sound/soc/sof/intel/mtl.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/sof/intel/mtl.c b/sound/soc/sof/intel/mtl.c index 8ae331faca4e..30fe77fd87bf 100644 --- a/sound/soc/sof/intel/mtl.c +++ b/sound/soc/sof/intel/mtl.c @@ -361,11 +361,17 @@ static int mtl_dsp_core_power_up(struct snd_sof_dev *sdev, int core) ret = snd_sof_dsp_read_poll_timeout(sdev, HDA_DSP_BAR, MTL_DSP2CXCTL_PRIMARY_CORE, dspcxctl, (dspcxctl & cpa) == cpa, HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_RESET_TIMEOUT_US); - if (ret < 0) + if (ret < 0) { dev_err(sdev->dev, "%s: timeout on MTL_DSP2CXCTL_PRIMARY_CORE read\n", __func__); + return ret; + } - return ret; + /* set primary core mask and refcount to 1 */ + sdev->enabled_cores_mask = BIT(SOF_DSP_PRIMARY_CORE); + sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 1; + + return 0; } static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core) @@ -388,10 +394,15 @@ static int mtl_dsp_core_power_down(struct snd_sof_dev *sdev, int core) !(dspcxctl & MTL_DSP2CXCTL_PRIMARY_CORE_CPA_MASK), HDA_DSP_REG_POLL_INTERVAL_US, HDA_DSP_PD_TIMEOUT * USEC_PER_MSEC); - if (ret < 0) + if (ret < 0) { dev_err(sdev->dev, "failed to power down primary core\n"); + return ret; + } - return ret; + sdev->enabled_cores_mask = 0; + sdev->dsp_core_ref_count[SOF_DSP_PRIMARY_CORE] = 0; + + return 0; } int mtl_power_down_dsp(struct snd_sof_dev *sdev) -- cgit v1.2.3 From 0c340ba05fda0fbf5a54207452728911c6388330 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 16 Jun 2023 11:00:37 +0200 Subject: ASoC: max98388: fix unused function warnings The PM functions are never referenced when CONFIG_PM_SLEEP is disabled: sound/soc/codecs/max98388.c:854:12: error: unused function 'max98388_suspend' [-Werror,-Wunused-function] static int max98388_suspend(struct device *dev) ^ sound/soc/codecs/max98388.c:864:12: error: unused function 'max98388_resume' [-Werror,-Wunused-function] static int max98388_resume(struct device *dev) Fix this by using the modern SYSTEM_SLEEP_PM_OPS() macro in place of the deprecated SET_SYSTEM_SLEEP_PM_OPS() version, and use pm_sleep_ptr() to hide the entire structure as well. On a related note, the of_match_ptr() and ACPI_PTR() macros have the same problem and would cause the device id table to be unused when the driver is built-in and the respective subsystems are disabled. This does not cause warnings unless -Wunused-const-variable is passed to the compiler, but it's better to just not use the macros at all here. Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230616090156.2347850-1-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/max98388.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index 8062a7115007..3d03c4bac6c5 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -873,7 +873,7 @@ static int max98388_resume(struct device *dev) } static const struct dev_pm_ops max98388_pm = { - SET_SYSTEM_SLEEP_PM_OPS(max98388_suspend, max98388_resume) + SYSTEM_SLEEP_PM_OPS(max98388_suspend, max98388_resume) }; static const struct regmap_config max98388_regmap = { @@ -998,9 +998,9 @@ MODULE_DEVICE_TABLE(acpi, max98388_acpi_match); static struct i2c_driver max98388_i2c_driver = { .driver = { .name = "max98388", - .of_match_table = of_match_ptr(max98388_of_match), - .acpi_match_table = ACPI_PTR(max98388_acpi_match), - .pm = &max98388_pm, + .of_match_table = max98388_of_match, + .acpi_match_table = max98388_acpi_match, + .pm = pm_sleep_ptr(&max98388_pm), }, .probe = max98388_i2c_probe, .id_table = max98388_i2c_id, -- cgit v1.2.3 From 041c5a1d065e5882299475326655f573e2a2a580 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 16 Jun 2023 11:00:38 +0200 Subject: ASoC: loongson: fix unused PM function warning Build testing without CONFIG_PM_SLEEP causes a warning: sound/soc/loongson/loongson_i2s.c:246:12: error: unused function 'i2s_suspend' [-Werror,-Wunused-function] sound/soc/loongson/loongson_i2s.c:255:12: error: unused function 'i2s_resume' [-Werror,-Wunused-function] Use the modern SYSTEM_SLEEP_PM_OPS() instead of the old one to avoid this. Fixes: d24028606e764 ("ASoC: loongson: Add Loongson ASoC Sound Card Support") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230616090156.2347850-2-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c index 35d34568be79..f73b6d6f16c2 100644 --- a/sound/soc/loongson/loongson_i2s.c +++ b/sound/soc/loongson/loongson_i2s.c @@ -265,5 +265,5 @@ static int i2s_resume(struct device *dev) } const struct dev_pm_ops loongson_i2s_pm = { - SET_SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) + SYSTEM_SLEEP_PM_OPS(i2s_suspend, i2s_resume) }; -- cgit v1.2.3 From 08432e59c7d9a958e69cf6b7a03777ba4f26f10b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 16 Jun 2023 11:00:39 +0200 Subject: ASoC: loongson: add PCI dependency The new driver fails to build when PCI is disabled: WARNING: unmet direct dependencies detected for SND_SOC_LOONGSON_I2S_PCI Depends on [n]: SOUND [=y] && !UML && SND [=y] && SND_SOC [=y] && (LOONGARCH || COMPILE_TEST [=y]) && PCI [=n] Selected by [y]: - SND_SOC_LOONGSON_CARD [=y] && SOUND [=y] && !UML && SND [=y] && SND_SOC [=y] && (LOONGARCH || COMPILE_TEST [=y]) sound/soc/loongson/loongson_i2s_pci.c:167:1: error: type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int [-Wimplicit-int] module_pci_driver(loongson_i2s_driver); Add the appropriate Kconfig dependency. Fixes: d24028606e764 ("ASoC: loongson: Add Loongson ASoC Sound Card Support") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230616090156.2347850-3-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/loongson/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/loongson/Kconfig b/sound/soc/loongson/Kconfig index c175f9de19a8..b8d7e2bade24 100644 --- a/sound/soc/loongson/Kconfig +++ b/sound/soc/loongson/Kconfig @@ -16,6 +16,7 @@ config SND_SOC_LOONGSON_I2S_PCI config SND_SOC_LOONGSON_CARD tristate "Loongson Sound Card Driver" select SND_SOC_LOONGSON_I2S_PCI + depends on PCI help Say Y or M if you want to add support for SoC audio using loongson I2S controller. -- cgit v1.2.3 From 928314eb06709e3861ce3e2c7e9ef3f83ba8691b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 16 Jun 2023 11:00:40 +0200 Subject: ASoC: loongson: fix compile testing on 32-bit DIV_ROUND_CLOSEST() does not work on 64-bit variables when building for a 32-bit target: ld.lld: error: undefined symbol: __udivdi3 >>> referenced by loongson_i2s.c >>> sound/soc/loongson/loongson_i2s.o:(loongson_i2s_hw_params) in archive vmlinux.a Use DIV_ROUND_CLOSEST_ULL() instead. Fixes: d24028606e764 ("ASoC: loongson: Add Loongson ASoC Sound Card Support") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/20230616090156.2347850-4-arnd@kernel.org Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_i2s.c b/sound/soc/loongson/loongson_i2s.c index f73b6d6f16c2..b919f0fe8361 100644 --- a/sound/soc/loongson/loongson_i2s.c +++ b/sound/soc/loongson/loongson_i2s.c @@ -89,7 +89,7 @@ static int loongson_i2s_hw_params(struct snd_pcm_substream *substream, bclk_ratio = DIV_ROUND_CLOSEST(sysclk, (bits * chans * fs * 2)) - 1; mclk_ratio = clk_rate / sysclk; - mclk_ratio_frac = DIV_ROUND_CLOSEST(((u64)clk_rate << 16), + mclk_ratio_frac = DIV_ROUND_CLOSEST_ULL(((u64)clk_rate << 16), sysclk) - (mclk_ratio << 16); regmap_read(i2s->regmap, LS_I2S_CFG, &val); -- cgit v1.2.3 From 0f9c14e57818d077ceca060b8a0d0ee5ed3abd95 Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Fri, 16 Jun 2023 13:55:49 +0200 Subject: ASoC: rt5677: Add MODULE_FIRMWARE macro The module loads firmware so add a MODULE_FIRMWARE macro to provide that information via modinfo. Signed-off-by: Juerg Haefliger Link: https://lore.kernel.org/r/20230616115549.1011903-1-juerg.haefliger@canonical.com Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index 60e38a9bcd1b..ad14d18860fc 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -5712,3 +5712,5 @@ module_i2c_driver(rt5677_i2c_driver); MODULE_DESCRIPTION("ASoC RT5677 driver"); MODULE_AUTHOR("Oder Chiou "); MODULE_LICENSE("GPL v2"); + +MODULE_FIRMWARE("rt5677_elf_vad"); -- cgit v1.2.3 From 60e07fa49b3201d7201cdd7286e7d51e8d937a28 Mon Sep 17 00:00:00 2001 From: Juerg Haefliger Date: Fri, 16 Jun 2023 13:54:32 +0200 Subject: ASoC: codecs: wm0010: Add MODULE_FIRMWARE macros The module loads firmware so add MODULE_FIRMWARE macros to provide that information via modinfo. Signed-off-by: Juerg Haefliger Acked-by: Charles Keepax Link: https://lore.kernel.org/r/20230616115432.1011707-1-juerg.haefliger@canonical.com Signed-off-by: Mark Brown --- sound/soc/codecs/wm0010.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/codecs/wm0010.c b/sound/soc/codecs/wm0010.c index 034a4e858c7e..1d4259433f47 100644 --- a/sound/soc/codecs/wm0010.c +++ b/sound/soc/codecs/wm0010.c @@ -994,3 +994,6 @@ module_spi_driver(wm0010_spi_driver); MODULE_DESCRIPTION("ASoC WM0010 driver"); MODULE_AUTHOR("Mark Brown "); MODULE_LICENSE("GPL"); + +MODULE_FIRMWARE("wm0010.dfw"); +MODULE_FIRMWARE("wm0010_stage2.bin"); -- cgit v1.2.3 From 320d0e2db9edcde026aac93359624c1d429cb865 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Sat, 17 Jun 2023 08:26:35 -0400 Subject: ASoC: max98388: set variable soc_codec_dev_max98388 storage-class-specifier to static smatch reports sound/soc/codecs/max98388.c:890:39: warning: symbol 'soc_codec_dev_max98388' was not declared. Should it be static? This variable is only used in its defining file, so it should be static. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/Message-Id: <20230617122635.3225639-1-trix@redhat.com> Signed-off-by: Mark Brown --- sound/soc/codecs/max98388.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index 3d03c4bac6c5..1fd50e56ecae 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -887,7 +887,7 @@ static const struct regmap_config max98388_regmap = { .cache_type = REGCACHE_RBTREE, }; -const struct snd_soc_component_driver soc_codec_dev_max98388 = { +static const struct snd_soc_component_driver soc_codec_dev_max98388 = { .probe = max98388_probe, .controls = max98388_snd_controls, .num_controls = ARRAY_SIZE(max98388_snd_controls), -- cgit v1.2.3 From 1075df4bdeb320bbf94a1f6d3761391264eb2c7f Mon Sep 17 00:00:00 2001 From: Hui Wang Date: Thu, 16 Jun 2022 12:00:45 +0800 Subject: ASoC: fsl-asoc-card: add nau8822 support This is for an imx6sx EVB which has a nau8822 codec connects to the SSI2 interface, so add the nau8822 support in this machine driver. Because the codec driver nau8822.c doesn't handle mclk enabling, here adding a codec_priv->mclk for nau8822 and similar codecs which need to enable the mclk in the machine driver, and enable the mclk in the card_late_probe() conditionally. Signed-off-by: Hui Wang Link: https://lore.kernel.org/r/Message-Id: <20220616040046.103524-1-hui.wang@canonical.com> Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'sound') diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 40870668ee24..8d0161ac8380 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -27,6 +27,7 @@ #include "../codecs/wm8960.h" #include "../codecs/wm8994.h" #include "../codecs/tlv320aic31xx.h" +#include "../codecs/nau8822.h" #define DRIVER_NAME "fsl-asoc-card" @@ -47,6 +48,7 @@ * @pll_id: PLL id for set_pll() */ struct codec_priv { + struct clk *mclk; unsigned long mclk_freq; unsigned long free_freq; u32 mclk_id; @@ -524,6 +526,9 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) return ret; } + if (!IS_ERR_OR_NULL(codec_priv->mclk)) + clk_prepare_enable(codec_priv->mclk); + return 0; } @@ -686,6 +691,14 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) priv->codec_priv.free_freq = priv->codec_priv.mclk_freq; priv->card.dapm_routes = NULL; priv->card.num_dapm_routes = 0; + } else if (of_device_is_compatible(np, "fsl,imx-audio-nau8822")) { + codec_dai_name = "nau8822-hifi"; + priv->codec_priv.mclk_id = NAU8822_CLK_MCLK; + priv->codec_priv.fll_id = NAU8822_CLK_PLL; + priv->codec_priv.pll_id = NAU8822_CLK_PLL; + priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; + if (codec_dev) + priv->codec_priv.mclk = devm_clk_get(codec_dev, NULL); } else { dev_err(&pdev->dev, "unknown Device Tree compatible\n"); ret = -EINVAL; @@ -911,6 +924,7 @@ static const struct of_device_id fsl_asoc_card_dt_ids[] = { { .compatible = "fsl,imx-audio-wm8524", }, { .compatible = "fsl,imx-audio-si476x", }, { .compatible = "fsl,imx-audio-wm8958", }, + { .compatible = "fsl,imx-audio-nau8822", }, {} }; MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids); -- cgit v1.2.3 From 7ae8039f87918e2f108d352f228e2ccee03994bc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 12:16:37 +0100 Subject: ASoC: es8316: Use maple tree register cache The es8316 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-es-maple-v1-1-45ada77f5643@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/es8316.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/es8316.c b/sound/soc/codecs/es8316.c index 069f1ce1cd50..34cf60769b62 100644 --- a/sound/soc/codecs/es8316.c +++ b/sound/soc/codecs/es8316.c @@ -825,7 +825,7 @@ static const struct regmap_config es8316_regmap = { .use_single_write = true, .max_register = 0x53, .volatile_reg = es8316_volatile_reg, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static int es8316_i2c_probe(struct i2c_client *i2c_client) -- cgit v1.2.3 From 9321015a5f40891e7cb094c6f68f6d4f67b5f3dc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 12:16:38 +0100 Subject: ASoC: es8328: Use maple tree register cache The es8328 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-es-maple-v1-2-45ada77f5643@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/es8328.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/es8328.c b/sound/soc/codecs/es8328.c index 160adc706cc6..0bd9ba5a11b4 100644 --- a/sound/soc/codecs/es8328.c +++ b/sound/soc/codecs/es8328.c @@ -822,7 +822,7 @@ const struct regmap_config es8328_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = ES8328_REG_MAX, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 39da3e152dc664ef13c0b8c1064cba5415767aa3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:47 +0100 Subject: ASoC: rt1011: Use maple tree register cache The rt1011 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-1-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1011.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1011.c b/sound/soc/codecs/rt1011.c index 4ceb410c5024..42bac8150f62 100644 --- a/sound/soc/codecs/rt1011.c +++ b/sound/soc/codecs/rt1011.c @@ -2184,7 +2184,7 @@ static const struct regmap_config rt1011_regmap = { .max_register = RT1011_MAX_REG + 1, .volatile_reg = rt1011_volatile_register, .readable_reg = rt1011_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt1011_reg, .num_reg_defaults = ARRAY_SIZE(rt1011_reg), .use_single_read = true, -- cgit v1.2.3 From f8abeb31c2a9dae470350487857d4b0c95ad316d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:48 +0100 Subject: ASoC: rt1019: Use maple tree register cache The rt1019 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-2-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1019.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1019.c b/sound/soc/codecs/rt1019.c index 735feea8fd92..fd55049920c1 100644 --- a/sound/soc/codecs/rt1019.c +++ b/sound/soc/codecs/rt1019.c @@ -535,7 +535,7 @@ static const struct regmap_config rt1019_regmap = { .max_register = RT1019_BEEP_2, .volatile_reg = rt1019_volatile_register, .readable_reg = rt1019_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt1019_reg, .num_reg_defaults = ARRAY_SIZE(rt1019_reg), }; -- cgit v1.2.3 From d2306faefa25b47fa133f39ffaef12b11f175585 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:49 +0100 Subject: ASoC: rt1305: Use maple tree register cache The rt1305 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-3-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1305.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1305.c b/sound/soc/codecs/rt1305.c index 28a4a70c3307..59895131e6e0 100644 --- a/sound/soc/codecs/rt1305.c +++ b/sound/soc/codecs/rt1305.c @@ -955,7 +955,7 @@ static const struct regmap_config rt1305_regmap = { RT1305_PR_SPACING), .volatile_reg = rt1305_volatile_register, .readable_reg = rt1305_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt1305_reg, .num_reg_defaults = ARRAY_SIZE(rt1305_reg), .ranges = rt1305_ranges, -- cgit v1.2.3 From 5bd8a567aaea5d2e79b024bb4ad42d88bbe8f7c2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:50 +0100 Subject: ASoC: rt1308: Use maple tree register cache The rt1308 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-4-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt1308.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt1308.c b/sound/soc/codecs/rt1308.c index 04c5d5de71ff..9d07756cda40 100644 --- a/sound/soc/codecs/rt1308.c +++ b/sound/soc/codecs/rt1308.c @@ -773,7 +773,7 @@ static const struct regmap_config rt1308_regmap = { .max_register = RT1308_MAX_REG, .volatile_reg = rt1308_volatile_register, .readable_reg = rt1308_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt1308_reg, .num_reg_defaults = ARRAY_SIZE(rt1308_reg), .use_single_read = true, -- cgit v1.2.3 From 77b5d6e98f452445bdc82096a992b8d05f54f5d3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:51 +0100 Subject: ASoC: rt5514: Use maple tree register cache The rt5514 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-5-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5514.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5514.c b/sound/soc/codecs/rt5514.c index 0f9f52b93e36..b3aac2373357 100644 --- a/sound/soc/codecs/rt5514.c +++ b/sound/soc/codecs/rt5514.c @@ -1195,7 +1195,7 @@ static const struct regmap_config rt5514_regmap = { .reg_read = rt5514_i2c_read, .reg_write = rt5514_i2c_write, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5514_reg, .num_reg_defaults = ARRAY_SIZE(rt5514_reg), .use_single_read = true, -- cgit v1.2.3 From eef0d85d964f21ec1817d397ab151b78f29ed047 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:52 +0100 Subject: ASoC: rt5616: Use maple tree register cache The rt5616 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-6-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5616.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5616.c b/sound/soc/codecs/rt5616.c index 91c967391de9..c13108b51eaf 100644 --- a/sound/soc/codecs/rt5616.c +++ b/sound/soc/codecs/rt5616.c @@ -1315,7 +1315,7 @@ static const struct regmap_config rt5616_regmap = { RT5616_PR_SPACING), .volatile_reg = rt5616_volatile_register, .readable_reg = rt5616_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5616_reg, .num_reg_defaults = ARRAY_SIZE(rt5616_reg), .ranges = rt5616_ranges, -- cgit v1.2.3 From 8a7384907e3f9ac380290414d7fd72df952757ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:53 +0100 Subject: ASoC: rt5631: Use maple tree register cache The rt5631 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-7-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5631.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5631.c b/sound/soc/codecs/rt5631.c index 9a4cb45e37d4..a64e66c2d3c4 100644 --- a/sound/soc/codecs/rt5631.c +++ b/sound/soc/codecs/rt5631.c @@ -1693,7 +1693,7 @@ static const struct regmap_config rt5631_regmap_config = { .max_register = RT5631_VENDOR_ID2, .reg_defaults = rt5631_reg, .num_reg_defaults = ARRAY_SIZE(rt5631_reg), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .use_single_read = true, .use_single_write = true, }; -- cgit v1.2.3 From 1ba8448b34b17755b873a42d5ebba499cb2feff7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:54 +0100 Subject: ASoC: rt5640: Use maple tree register cache The rt5640 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-8-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5640.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5640.c b/sound/soc/codecs/rt5640.c index c7d2f315273e..0ed4fa261abf 100644 --- a/sound/soc/codecs/rt5640.c +++ b/sound/soc/codecs/rt5640.c @@ -2949,7 +2949,7 @@ static const struct regmap_config rt5640_regmap = { .volatile_reg = rt5640_volatile_register, .readable_reg = rt5640_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5640_reg, .num_reg_defaults = ARRAY_SIZE(rt5640_reg), .ranges = rt5640_ranges, -- cgit v1.2.3 From ea3945cdf0a34e2418eb1bb56c79a10f2a2e8093 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:55 +0100 Subject: ASoC: rt5645: Use maple tree register cache The rt5645 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-9-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5645.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c index 01aa999fc6db..acc7fb1581b2 100644 --- a/sound/soc/codecs/rt5645.c +++ b/sound/soc/codecs/rt5645.c @@ -3546,7 +3546,7 @@ static const struct regmap_config rt5645_regmap = { .volatile_reg = rt5645_volatile_register, .readable_reg = rt5645_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5645_reg, .num_reg_defaults = ARRAY_SIZE(rt5645_reg), .ranges = rt5645_ranges, @@ -3563,7 +3563,7 @@ static const struct regmap_config rt5650_regmap = { .volatile_reg = rt5645_volatile_register, .readable_reg = rt5645_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5650_reg, .num_reg_defaults = ARRAY_SIZE(rt5650_reg), .ranges = rt5645_ranges, -- cgit v1.2.3 From 899585d5781e1709fe6ed0e6f56d1419b66780cb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:56 +0100 Subject: ASoC: rt5651: Use maple tree register cache The rt5651 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-10-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5651.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5651.c b/sound/soc/codecs/rt5651.c index b2ec44fa478b..0cee4fd1c84b 100644 --- a/sound/soc/codecs/rt5651.c +++ b/sound/soc/codecs/rt5651.c @@ -2172,7 +2172,7 @@ static const struct regmap_config rt5651_regmap = { .volatile_reg = rt5651_volatile_register, .readable_reg = rt5651_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5651_reg, .num_reg_defaults = ARRAY_SIZE(rt5651_reg), .ranges = rt5651_ranges, -- cgit v1.2.3 From 1fe38835d51f5cb977ddc02f8d7d5327a2a9f2b2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:57 +0100 Subject: ASoC: rt5660: Use maple tree register cache The rt5660 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-11-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5660.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5660.c b/sound/soc/codecs/rt5660.c index fd453f47455b..eade087b2d8b 100644 --- a/sound/soc/codecs/rt5660.c +++ b/sound/soc/codecs/rt5660.c @@ -1221,7 +1221,7 @@ static const struct regmap_config rt5660_regmap = { .volatile_reg = rt5660_volatile_register, .readable_reg = rt5660_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5660_reg, .num_reg_defaults = ARRAY_SIZE(rt5660_reg), .ranges = rt5660_ranges, -- cgit v1.2.3 From 72cd25891828072cb9726f90a0e8ba537a366221 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:58 +0100 Subject: ASoC: rt5665: Use maple tree register cache The rt5663 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-12-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5663.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5663.c b/sound/soc/codecs/rt5663.c index ceeadbb4f62d..77246f84de29 100644 --- a/sound/soc/codecs/rt5663.c +++ b/sound/soc/codecs/rt5663.c @@ -3268,7 +3268,7 @@ static const struct regmap_config rt5663_v2_regmap = { .max_register = 0x07fa, .volatile_reg = rt5663_v2_volatile_register, .readable_reg = rt5663_v2_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5663_v2_reg, .num_reg_defaults = ARRAY_SIZE(rt5663_v2_reg), }; @@ -3281,7 +3281,7 @@ static const struct regmap_config rt5663_regmap = { .max_register = 0x03f3, .volatile_reg = rt5663_volatile_register, .readable_reg = rt5663_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5663_reg, .num_reg_defaults = ARRAY_SIZE(rt5663_reg), }; -- cgit v1.2.3 From 487c9129c9d856eb8dd0dfa6e09c646649c91399 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:57:59 +0100 Subject: ASoC: rt5665: Use maple tree register cache The rt5665 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-13-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5665.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5665.c b/sound/soc/codecs/rt5665.c index 732e15e3a497..83c367af91da 100644 --- a/sound/soc/codecs/rt5665.c +++ b/sound/soc/codecs/rt5665.c @@ -4626,7 +4626,7 @@ static const struct regmap_config rt5665_regmap = { .max_register = 0x0400, .volatile_reg = rt5665_volatile_register, .readable_reg = rt5665_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5665_reg, .num_reg_defaults = ARRAY_SIZE(rt5665_reg), .use_single_read = true, -- cgit v1.2.3 From 470cb1d9b605557696791ca0ef6c151c3d92212d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:58:00 +0100 Subject: ASoC: rt5668: Use maple tree register cache The rt5668 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-14-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5668.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5668.c b/sound/soc/codecs/rt5668.c index 13fec49d4824..f04c810fd710 100644 --- a/sound/soc/codecs/rt5668.c +++ b/sound/soc/codecs/rt5668.c @@ -2370,7 +2370,7 @@ static const struct regmap_config rt5668_regmap = { .max_register = RT5668_I2C_MODE, .volatile_reg = rt5668_volatile_register, .readable_reg = rt5668_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5668_reg, .num_reg_defaults = ARRAY_SIZE(rt5668_reg), .use_single_read = true, -- cgit v1.2.3 From 11cce87f6453270ed63924ac55da764e060f8588 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:58:01 +0100 Subject: ASoC: rt5670: Use maple tree register cache The rt5670 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-15-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5670.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c index e8ce3e7c8e3f..0e34293f3395 100644 --- a/sound/soc/codecs/rt5670.c +++ b/sound/soc/codecs/rt5670.c @@ -2863,7 +2863,7 @@ static const struct regmap_config rt5670_regmap = { RT5670_PR_SPACING), .volatile_reg = rt5670_volatile_register, .readable_reg = rt5670_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5670_reg, .num_reg_defaults = ARRAY_SIZE(rt5670_reg), .ranges = rt5670_ranges, -- cgit v1.2.3 From eefc27ea14ad6f5aa4656cf302dec366e1fd62f1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 14:58:02 +0100 Subject: ASoC: rt5682: Use maple tree register cache The rt5682 can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-rt-maple-v1-16-729c6553cdcf@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/rt5682-i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/rt5682-i2c.c b/sound/soc/codecs/rt5682-i2c.c index 1742efe8dbcf..fb8ffb5b2ff6 100644 --- a/sound/soc/codecs/rt5682-i2c.c +++ b/sound/soc/codecs/rt5682-i2c.c @@ -46,7 +46,7 @@ static const struct regmap_config rt5682_regmap = { .max_register = RT5682_I2C_MODE, .volatile_reg = rt5682_volatile_register, .readable_reg = rt5682_readable_register, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_defaults = rt5682_reg, .num_reg_defaults = RT5682_REG_NUM, .use_single_read = true, -- cgit v1.2.3 From 5b7e984e22c43d217b3224b3118e5c8c88a5b708 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Mon, 19 Jun 2023 11:16:43 +0100 Subject: ASoC: qcom: SC7280: audioreach: Add sc7280 hardware param fixup callback Add support to set backend params such as sampling rate and number of channels using backend params fixup callback. Also add no pcm check for hardware params constraints setting. Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-2-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/sc7280.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/qcom/sc7280.c b/sound/soc/qcom/sc7280.c index da7469a6a267..787dd49e03f6 100644 --- a/sound/soc/qcom/sc7280.c +++ b/sound/soc/qcom/sc7280.c @@ -14,6 +14,7 @@ #include #include #include +#include #include "../codecs/rt5682.h" #include "../codecs/rt5682s.h" @@ -196,8 +197,10 @@ static int sc7280_snd_hw_params(struct snd_pcm_substream *substream, struct sdw_stream_runtime *sruntime; int i; - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); - snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000); + if (!rtd->dai_link->no_pcm) { + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, 2, 2); + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_RATE, 48000, 48000); + } switch (cpu_dai->id) { case LPASS_CDC_DMA_TX3: @@ -358,6 +361,20 @@ static const struct snd_soc_dapm_widget sc7280_snd_widgets[] = { SND_SOC_DAPM_MIC("Headset Mic", NULL), }; +static int sc7280_snd_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + static int sc7280_snd_platform_probe(struct platform_device *pdev) { struct snd_soc_card *card; @@ -387,6 +404,8 @@ static int sc7280_snd_platform_probe(struct platform_device *pdev) for_each_card_prelinks(card, i, link) { link->init = sc7280_init; link->ops = &sc7280_ops; + if (link->no_pcm == 1) + link->be_hw_params_fixup = sc7280_snd_be_hw_params_fixup; } return devm_snd_soc_register_card(dev, card); -- cgit v1.2.3 From 9d11a5431c929c5057e06ff86002f337980caa9e Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Mon, 19 Jun 2023 11:16:44 +0100 Subject: ASoC: q6dsp: q6apm: add end of stream events EOS event from dsp is currently not sent to the dai drivers, add the missing callback. Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-3-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index a7a3f973eb6d..b07fee8ccac1 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -497,6 +497,9 @@ static int graph_callback(struct gpr_resp_pkt *data, void *priv, int op) } break; case DATA_CMD_WR_SH_MEM_EP_EOS_RENDERED: + client_event = APM_CLIENT_EVENT_CMD_EOS_DONE; + if (graph->cb) + graph->cb(client_event, hdr->token, data->payload, graph->priv); break; case GPR_BASIC_RSP_RESULT: switch (result->opcode) { -- cgit v1.2.3 From 69bff594592b0582c36b3aae819deaad0e09eafd Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:45 +0100 Subject: ASoC: q6dsp: audioreach: add helper function to set u32 param Some of the Audioreach commands take a u32 value, ex: PARAM_ID_MODULE_ENABLE. It makes more sense to provide a helper function so that other new commands can reuse this. Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-4-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 100 +++++++++----------------------------- sound/soc/qcom/qdsp6/audioreach.h | 2 + 2 files changed, 26 insertions(+), 76 deletions(-) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 8d9410dcbd45..0acd4a75d5cd 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -732,33 +732,32 @@ static int audioreach_codec_dma_set_media_format(struct q6apm_graph *graph, return rc; } -static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, - struct audioreach_module *module, bool enable) +int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module, + uint32_t param_id, uint32_t param_val) { struct apm_module_param_data *param_data; - struct param_id_sal_limiter_enable *limiter_enable; - int payload_size; struct gpr_pkt *pkt; - int rc; + uint32_t *param; + int rc, payload_size; void *p; - payload_size = sizeof(*limiter_enable) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); + payload_size = sizeof(uint32_t) + APM_MODULE_PARAM_DATA_SIZE; + p = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); + if (IS_ERR(p)) + return -ENOMEM; - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; + pkt = p; + p = p + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; param_data = p; param_data->module_instance_id = module->instance_id; param_data->error_code = 0; - param_data->param_id = PARAM_ID_SAL_LIMITER_ENABLE; - param_data->param_size = sizeof(*limiter_enable); - p = p + APM_MODULE_PARAM_DATA_SIZE; - limiter_enable = p; + param_data->param_id = param_id; + param_data->param_size = sizeof(uint32_t); - limiter_enable->enable_lim = enable; + p = p + APM_MODULE_PARAM_DATA_SIZE; + param = p; + *param = param_val; rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); @@ -766,77 +765,26 @@ static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, return rc; } +EXPORT_SYMBOL_GPL(audioreach_send_u32_param); + +static int audioreach_sal_limiter_enable(struct q6apm_graph *graph, + struct audioreach_module *module, bool enable) +{ + return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_LIMITER_ENABLE, enable); +} static int audioreach_sal_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, struct audioreach_module_config *cfg) { - struct apm_module_param_data *param_data; - struct param_id_sal_output_config *media_format; - int payload_size; - struct gpr_pkt *pkt; - int rc; - void *p; - - payload_size = sizeof(*media_format) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); - - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; - - param_data = p; - param_data->module_instance_id = module->instance_id; - param_data->error_code = 0; - param_data->param_id = PARAM_ID_SAL_OUTPUT_CFG; - param_data->param_size = sizeof(*media_format); - p = p + APM_MODULE_PARAM_DATA_SIZE; - media_format = p; - - media_format->bits_per_sample = cfg->bit_width; - - rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); - - kfree(pkt); - - return rc; + return audioreach_send_u32_param(graph, module, PARAM_ID_SAL_OUTPUT_CFG, cfg->bit_width); } static int audioreach_module_enable(struct q6apm_graph *graph, struct audioreach_module *module, bool enable) { - struct apm_module_param_data *param_data; - struct param_id_module_enable *param; - int payload_size; - struct gpr_pkt *pkt; - int rc; - void *p; - - payload_size = sizeof(*param) + APM_MODULE_PARAM_DATA_SIZE; - - pkt = audioreach_alloc_apm_cmd_pkt(payload_size, APM_CMD_SET_CFG, 0); - if (IS_ERR(pkt)) - return PTR_ERR(pkt); - - p = (void *)pkt + GPR_HDR_SIZE + APM_CMD_HDR_SIZE; - - param_data = p; - param_data->module_instance_id = module->instance_id; - param_data->error_code = 0; - param_data->param_id = PARAM_ID_MODULE_ENABLE; - param_data->param_size = sizeof(*param); - p = p + APM_MODULE_PARAM_DATA_SIZE; - param = p; - - param->enable = enable; - - rc = q6apm_send_cmd_sync(graph->apm, pkt, 0); - - kfree(pkt); - - return rc; + return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, enable); } static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 3ebb81cd7cb0..18d8d243b06b 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -752,4 +752,6 @@ int audioreach_set_media_format(struct q6apm_graph *graph, int audioreach_shared_memory_send_eos(struct q6apm_graph *graph); int audioreach_gain_set_vol_ctrl(struct q6apm *apm, struct audioreach_module *module, int vol); +int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module, + uint32_t param_id, uint32_t param_val); #endif /* __AUDIOREACH_H__ */ -- cgit v1.2.3 From c7548f5990fb35ccf2bd731570d3cff7df9e1d2e Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:46 +0100 Subject: ASoC: q6dsp: audioreach: Add placeholder decoder for compress playback Add placeholder decoder graph module for compressed playback feature. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-5-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 2 ++ sound/soc/qcom/qdsp6/audioreach.h | 14 +++++++++ sound/soc/qcom/qdsp6/q6apm.c | 65 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6apm.h | 4 +++ 4 files changed, 85 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 0acd4a75d5cd..34cbc4d05918 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -1140,6 +1140,8 @@ int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_mod case MODULE_ID_PCM_DEC: case MODULE_ID_PCM_ENC: case MODULE_ID_PCM_CNV: + case MODULE_ID_PLACEHOLDER_DECODER: + case MODULE_ID_PLACEHOLDER_ENCODER: rc = audioreach_pcm_set_media_format(graph, module, cfg); break; case MODULE_ID_DISPLAY_PORT_SINK: diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index 18d8d243b06b..c4e03a49ac82 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -15,6 +15,8 @@ struct q6apm_graph; #define MODULE_ID_PCM_CNV 0x07001003 #define MODULE_ID_PCM_ENC 0x07001004 #define MODULE_ID_PCM_DEC 0x07001005 +#define MODULE_ID_PLACEHOLDER_ENCODER 0x07001008 +#define MODULE_ID_PLACEHOLDER_DECODER 0x07001009 #define MODULE_ID_SAL 0x07001010 #define MODULE_ID_MFC 0x07001015 #define MODULE_ID_CODEC_DMA_SINK 0x07001023 @@ -22,6 +24,9 @@ struct q6apm_graph; #define MODULE_ID_I2S_SINK 0x0700100A #define MODULE_ID_I2S_SOURCE 0x0700100B #define MODULE_ID_DATA_LOGGING 0x0700101A +#define MODULE_ID_AAC_DEC 0x0700101F +#define MODULE_ID_FLAC_DEC 0x0700102F +#define MODULE_ID_MP3_DECODE 0x0700103B #define MODULE_ID_DISPLAY_PORT_SINK 0x07001069 #define APM_CMD_GET_SPF_STATE 0x01001021 @@ -608,6 +613,15 @@ struct param_id_vol_ctrl_master_gain { } __packed; +#define PARAM_ID_REMOVE_INITIAL_SILENCE 0x0800114B +#define PARAM_ID_REMOVE_TRAILING_SILENCE 0x0800115D + +#define PARAM_ID_REAL_MODULE_ID 0x0800100B + +struct param_id_placeholder_real_module_id { + uint32_t real_module_id; +} __packed; + /* Graph */ struct audioreach_connection { /* Connections */ diff --git a/sound/soc/qcom/qdsp6/q6apm.c b/sound/soc/qcom/qdsp6/q6apm.c index b07fee8ccac1..7bfac9492ab5 100644 --- a/sound/soc/qcom/qdsp6/q6apm.c +++ b/sound/soc/qcom/qdsp6/q6apm.c @@ -298,6 +298,71 @@ int q6apm_unmap_memory_regions(struct q6apm_graph *graph, unsigned int dir) } EXPORT_SYMBOL_GPL(q6apm_unmap_memory_regions); +int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_INITIAL_SILENCE, samples); +} +EXPORT_SYMBOL_GPL(q6apm_remove_initial_silence); + +int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_REMOVE_TRAILING_SILENCE, samples); +} +EXPORT_SYMBOL_GPL(q6apm_remove_trailing_silence); + +int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en) +{ + struct audioreach_module *module; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, en); +} +EXPORT_SYMBOL_GPL(q6apm_enable_compress_module); + +int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, + uint32_t codec_id) +{ + struct audioreach_module *module; + uint32_t module_id; + + module = q6apm_find_module_by_mid(graph, MODULE_ID_PLACEHOLDER_DECODER); + if (!module) + return -ENODEV; + + switch (codec_id) { + case SND_AUDIOCODEC_MP3: + module_id = MODULE_ID_MP3_DECODE; + break; + case SND_AUDIOCODEC_AAC: + module_id = MODULE_ID_AAC_DEC; + break; + case SND_AUDIOCODEC_FLAC: + module_id = MODULE_ID_FLAC_DEC; + break; + default: + return -EINVAL; + } + + return audioreach_send_u32_param(graph, module, PARAM_ID_REAL_MODULE_ID, + module_id); +} +EXPORT_SYMBOL_GPL(q6apm_set_real_module_id); + int q6apm_graph_media_format_pcm(struct q6apm_graph *graph, struct audioreach_module_config *cfg) { struct audioreach_graph_info *info = graph->info; diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 7005be9b63e3..87d67faf5f1a 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -147,4 +147,8 @@ int q6apm_graph_get_rx_shmem_module_iid(struct q6apm_graph *graph); bool q6apm_is_adsp_ready(void); +int q6apm_enable_compress_module(struct device *dev, struct q6apm_graph *graph, bool en); +int q6apm_remove_initial_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); +int q6apm_remove_trailing_silence(struct device *dev, struct q6apm_graph *graph, uint32_t samples); +int q6apm_set_real_module_id(struct device *dev, struct q6apm_graph *graph, uint32_t codec_id); #endif /* __APM_GRAPH_ */ -- cgit v1.2.3 From e41521b6e2b3c965c64ff3dcd69042db003c3ef4 Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Mon, 19 Jun 2023 11:16:47 +0100 Subject: ASoC: q6dsp: audioreach: Add support to set compress format params Add function for setting compress params. Signed-off-by: Mohammad Rafi Shaik Co-developed-by: Srinivas Kandagatla Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-6-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 139 ++++++++++++++++++++++++++++++++------ sound/soc/qcom/qdsp6/audioreach.h | 28 ++++++++ sound/soc/qcom/qdsp6/q6apm-dai.c | 1 + 3 files changed, 149 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index 34cbc4d05918..ba262408b27a 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -834,6 +834,99 @@ static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, return rc; } +static int audioreach_set_compr_media_format(struct media_format *media_fmt_hdr, + void *p, struct audioreach_module_config *mcfg) +{ + struct payload_media_fmt_aac_t *aac_cfg; + struct payload_media_fmt_pcm *mp3_cfg; + struct payload_media_fmt_flac_t *flac_cfg; + + switch (mcfg->fmt) { + case SND_AUDIOCODEC_MP3: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_MP3; + media_fmt_hdr->payload_size = 0; + p = p + sizeof(*media_fmt_hdr); + mp3_cfg = p; + mp3_cfg->sample_rate = mcfg->sample_rate; + mp3_cfg->bit_width = mcfg->bit_width; + mp3_cfg->alignment = PCM_LSB_ALIGNED; + mp3_cfg->bits_per_sample = mcfg->bit_width; + mp3_cfg->q_factor = mcfg->bit_width - 1; + mp3_cfg->endianness = PCM_LITTLE_ENDIAN; + mp3_cfg->num_channels = mcfg->num_channels; + + if (mcfg->num_channels == 1) { + mp3_cfg->channel_mapping[0] = PCM_CHANNEL_L; + } else if (mcfg->num_channels == 2) { + mp3_cfg->channel_mapping[0] = PCM_CHANNEL_L; + mp3_cfg->channel_mapping[1] = PCM_CHANNEL_R; + } + break; + case SND_AUDIOCODEC_AAC: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_AAC; + media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_aac_t); + p = p + sizeof(*media_fmt_hdr); + aac_cfg = p; + aac_cfg->aac_fmt_flag = 0; + aac_cfg->audio_obj_type = 5; + aac_cfg->num_channels = mcfg->num_channels; + aac_cfg->total_size_of_PCE_bits = 0; + aac_cfg->sample_rate = mcfg->sample_rate; + break; + case SND_AUDIOCODEC_FLAC: + media_fmt_hdr->data_format = DATA_FORMAT_RAW_COMPRESSED; + media_fmt_hdr->fmt_id = MEDIA_FMT_ID_FLAC; + media_fmt_hdr->payload_size = sizeof(struct payload_media_fmt_flac_t); + p = p + sizeof(*media_fmt_hdr); + flac_cfg = p; + flac_cfg->sample_size = mcfg->codec.options.flac_d.sample_size; + flac_cfg->num_channels = mcfg->num_channels; + flac_cfg->min_blk_size = mcfg->codec.options.flac_d.min_blk_size; + flac_cfg->max_blk_size = mcfg->codec.options.flac_d.max_blk_size; + flac_cfg->sample_rate = mcfg->sample_rate; + flac_cfg->min_frame_size = mcfg->codec.options.flac_d.min_frame_size; + flac_cfg->max_frame_size = mcfg->codec.options.flac_d.max_frame_size; + break; + default: + return -EINVAL; + } + + return 0; +} + +int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg) +{ + struct media_format *header; + struct gpr_pkt *pkt; + int iid, payload_size, rc; + void *p; + + payload_size = sizeof(struct apm_sh_module_media_fmt_cmd); + + iid = q6apm_graph_get_rx_shmem_module_iid(graph); + pkt = audioreach_alloc_cmd_pkt(payload_size, DATA_CMD_WR_SH_MEM_EP_MEDIA_FORMAT, + 0, graph->port->id, iid); + + if (IS_ERR(pkt)) + return -ENOMEM; + + p = (void *)pkt + GPR_HDR_SIZE; + header = p; + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) { + kfree(pkt); + return rc; + } + + rc = gpr_send_port_pkt(graph->port, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(audioreach_compr_set_param); + static int audioreach_i2s_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, struct audioreach_module_config *cfg) @@ -1037,25 +1130,33 @@ static int audioreach_shmem_set_media_format(struct q6apm_graph *graph, p = p + APM_MODULE_PARAM_DATA_SIZE; header = p; - header->data_format = DATA_FORMAT_FIXED_POINT; - header->fmt_id = MEDIA_FMT_ID_PCM; - header->payload_size = payload_size - sizeof(*header); - - p = p + sizeof(*header); - cfg = p; - cfg->sample_rate = mcfg->sample_rate; - cfg->bit_width = mcfg->bit_width; - cfg->alignment = PCM_LSB_ALIGNED; - cfg->bits_per_sample = mcfg->bit_width; - cfg->q_factor = mcfg->bit_width - 1; - cfg->endianness = PCM_LITTLE_ENDIAN; - cfg->num_channels = mcfg->num_channels; - - if (mcfg->num_channels == 1) { - cfg->channel_mapping[0] = PCM_CHANNEL_L; - } else if (num_channels == 2) { - cfg->channel_mapping[0] = PCM_CHANNEL_L; - cfg->channel_mapping[1] = PCM_CHANNEL_R; + if (mcfg->fmt == SND_AUDIOCODEC_PCM) { + header->data_format = DATA_FORMAT_FIXED_POINT; + header->fmt_id = MEDIA_FMT_ID_PCM; + header->payload_size = payload_size - sizeof(*header); + + p = p + sizeof(*header); + cfg = p; + cfg->sample_rate = mcfg->sample_rate; + cfg->bit_width = mcfg->bit_width; + cfg->alignment = PCM_LSB_ALIGNED; + cfg->bits_per_sample = mcfg->bit_width; + cfg->q_factor = mcfg->bit_width - 1; + cfg->endianness = PCM_LITTLE_ENDIAN; + cfg->num_channels = mcfg->num_channels; + + if (mcfg->num_channels == 1) + cfg->channel_mapping[0] = PCM_CHANNEL_L; + else if (num_channels == 2) { + cfg->channel_mapping[0] = PCM_CHANNEL_L; + cfg->channel_mapping[1] = PCM_CHANNEL_R; + } + } else { + rc = audioreach_set_compr_media_format(header, p, mcfg); + if (rc) { + kfree(pkt); + return rc; + } } rc = audioreach_graph_send_cmd_sync(graph, pkt, 0); diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index c4e03a49ac82..dc089879b501 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -148,12 +148,15 @@ struct param_id_enc_bitrate_param { } __packed; #define DATA_FORMAT_FIXED_POINT 1 +#define DATA_FORMAT_GENERIC_COMPRESSED 5 +#define DATA_FORMAT_RAW_COMPRESSED 6 #define PCM_LSB_ALIGNED 1 #define PCM_MSB_ALIGNED 2 #define PCM_LITTLE_ENDIAN 1 #define PCM_BIT_ENDIAN 2 #define MEDIA_FMT_ID_PCM 0x09001000 +#define MEDIA_FMT_ID_MP3 0x09001009 #define PCM_CHANNEL_L 1 #define PCM_CHANNEL_R 2 #define SAMPLE_RATE_48K 48000 @@ -231,6 +234,28 @@ struct apm_media_format { uint32_t payload_size; } __packed; +#define MEDIA_FMT_ID_FLAC 0x09001004 + +struct payload_media_fmt_flac_t { + uint16_t num_channels; + uint16_t sample_size; + uint16_t min_blk_size; + uint16_t max_blk_size; + uint32_t sample_rate; + uint32_t min_frame_size; + uint32_t max_frame_size; +} __packed; + +#define MEDIA_FMT_ID_AAC 0x09001001 + +struct payload_media_fmt_aac_t { + uint16_t aac_fmt_flag; + uint16_t audio_obj_type; + uint16_t num_channels; + uint16_t total_size_of_PCE_bits; + uint32_t sample_rate; +} __packed; + #define DATA_CMD_WR_SH_MEM_EP_EOS 0x04001002 #define WR_SH_MEM_EP_EOS_POLICY_LAST 1 #define WR_SH_MEM_EP_EOS_POLICY_EACH 2 @@ -730,6 +755,7 @@ struct audioreach_module_config { u32 channel_allocation; u32 sd_line_mask; int fmt; + struct snd_codec codec; u8 channel_map[AR_PCM_MAX_NUM_CHANNEL]; }; @@ -768,4 +794,6 @@ int audioreach_gain_set_vol_ctrl(struct q6apm *apm, struct audioreach_module *module, int vol); int audioreach_send_u32_param(struct q6apm_graph *graph, struct audioreach_module *module, uint32_t param_id, uint32_t param_val); +int audioreach_compr_set_param(struct q6apm_graph *graph, struct audioreach_module_config *mcfg); + #endif /* __AUDIOREACH_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 7f02f5b2c33f..9fff41ee98eb 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -155,6 +155,7 @@ static int q6apm_dai_prepare(struct snd_soc_component *component, cfg.sample_rate = runtime->rate; cfg.num_channels = runtime->channels; cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = SND_AUDIOCODEC_PCM; if (prtd->state) { /* clear the previous setup if any */ -- cgit v1.2.3 From 2c954a3714b3b0d98354cb6801f88b0ef7c1249d Mon Sep 17 00:00:00 2001 From: Mohammad Rafi Shaik Date: Mon, 19 Jun 2023 11:16:48 +0100 Subject: ASoC: q6dsp: audioreach: Add gapless feature support Add support for setting EOS delay command and receive the EOS response from ADSP, for seamless compress offload playback feature. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-7-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/audioreach.c | 11 +++++++++++ sound/soc/qcom/qdsp6/audioreach.h | 7 +++++++ 2 files changed, 18 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/audioreach.c b/sound/soc/qcom/qdsp6/audioreach.c index ba262408b27a..5974c7929dd3 100644 --- a/sound/soc/qcom/qdsp6/audioreach.c +++ b/sound/soc/qcom/qdsp6/audioreach.c @@ -787,6 +787,14 @@ static int audioreach_module_enable(struct q6apm_graph *graph, return audioreach_send_u32_param(graph, module, PARAM_ID_MODULE_ENABLE, enable); } +static int audioreach_gapless_set_media_format(struct q6apm_graph *graph, + struct audioreach_module *module, + struct audioreach_module_config *cfg) +{ + return audioreach_send_u32_param(graph, module, PARAM_ID_EARLY_EOS_DELAY, + EARLY_EOS_DELAY_MS); +} + static int audioreach_mfc_set_media_format(struct q6apm_graph *graph, struct audioreach_module *module, struct audioreach_module_config *cfg) @@ -1270,6 +1278,9 @@ int audioreach_set_media_format(struct q6apm_graph *graph, struct audioreach_mod case MODULE_ID_MFC: rc = audioreach_mfc_set_media_format(graph, module, cfg); break; + case MODULE_ID_GAPLESS: + rc = audioreach_gapless_set_media_format(graph, module, cfg); + break; default: rc = 0; } diff --git a/sound/soc/qcom/qdsp6/audioreach.h b/sound/soc/qcom/qdsp6/audioreach.h index dc089879b501..e38111ffd7b9 100644 --- a/sound/soc/qcom/qdsp6/audioreach.h +++ b/sound/soc/qcom/qdsp6/audioreach.h @@ -27,6 +27,7 @@ struct q6apm_graph; #define MODULE_ID_AAC_DEC 0x0700101F #define MODULE_ID_FLAC_DEC 0x0700102F #define MODULE_ID_MP3_DECODE 0x0700103B +#define MODULE_ID_GAPLESS 0x0700104D #define MODULE_ID_DISPLAY_PORT_SINK 0x07001069 #define APM_CMD_GET_SPF_STATE 0x01001021 @@ -552,6 +553,8 @@ struct param_id_sal_limiter_enable { } __packed; #define PARAM_ID_MFC_OUTPUT_MEDIA_FORMAT 0x08001024 +#define PARAM_ID_EARLY_EOS_DELAY 0x0800114C +#define EARLY_EOS_DELAY_MS 150 struct param_id_mfc_media_format { uint32_t sample_rate; @@ -560,6 +563,10 @@ struct param_id_mfc_media_format { uint16_t channel_mapping[]; } __packed; +struct param_id_gapless_early_eos_delay_t { + uint32_t early_eos_delay_ms; +} __packed; + struct media_format { uint32_t data_format; uint32_t fmt_id; -- cgit v1.2.3 From 88b60bf047fd15b75a0d7b78322ad53f917976ce Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:49 +0100 Subject: ASoC: q6dsp: q6apm-dai: Add open/free compress DAI callbacks Add q6apm open and free compress DAI callbacks to support compress offload playback. Include compress event handler callback also. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-8-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 136 +++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6apm.h | 1 + 2 files changed, 137 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 9fff41ee98eb..32df5db014d3 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -28,6 +28,8 @@ #define CAPTURE_MIN_PERIOD_SIZE 320 #define BUFFER_BYTES_MAX (PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE) #define BUFFER_BYTES_MIN (PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE) +#define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024) +#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4) #define SID_MASK_DEFAULT 0xF enum stream_state { @@ -55,6 +57,7 @@ struct q6apm_dai_rtd { enum stream_state state; struct q6apm_graph *graph; spinlock_t lock; + bool notify_on_drain; }; struct q6apm_dai_data { @@ -132,6 +135,69 @@ static void event_handler(uint32_t opcode, uint32_t token, uint32_t *payload, vo } } +static void event_handler_compr(uint32_t opcode, uint32_t token, + uint32_t *payload, void *priv) +{ + struct q6apm_dai_rtd *prtd = priv; + struct snd_compr_stream *substream = prtd->cstream; + unsigned long flags; + uint32_t wflags = 0; + uint64_t avail; + uint32_t bytes_written, bytes_to_write; + bool is_last_buffer = false; + + switch (opcode) { + case APM_CLIENT_EVENT_CMD_EOS_DONE: + spin_lock_irqsave(&prtd->lock, flags); + if (prtd->notify_on_drain) { + snd_compr_drain_notify(prtd->cstream); + prtd->notify_on_drain = false; + } else { + prtd->state = Q6APM_STREAM_STOPPED; + } + spin_unlock_irqrestore(&prtd->lock, flags); + break; + case APM_CLIENT_EVENT_DATA_WRITE_DONE: + spin_lock_irqsave(&prtd->lock, flags); + bytes_written = token >> APM_WRITE_TOKEN_LEN_SHIFT; + prtd->copied_total += bytes_written; + snd_compr_fragment_elapsed(substream); + + if (prtd->state != Q6APM_STREAM_RUNNING) { + spin_unlock_irqrestore(&prtd->lock, flags); + break; + } + + avail = prtd->bytes_received - prtd->bytes_sent; + + if (avail > prtd->pcm_count) { + bytes_to_write = prtd->pcm_count; + } else { + if (substream->partial_drain || prtd->notify_on_drain) + is_last_buffer = true; + bytes_to_write = avail; + } + + if (bytes_to_write) { + if (substream->partial_drain && is_last_buffer) + wflags |= APM_LAST_BUFFER_FLAG; + + q6apm_write_async(prtd->graph, + bytes_to_write, 0, 0, wflags); + + prtd->bytes_sent += bytes_to_write; + + if (prtd->notify_on_drain && is_last_buffer) + audioreach_shared_memory_send_eos(prtd->graph); + } + + spin_unlock_irqrestore(&prtd->lock, flags); + break; + default: + break; + } +} + static int q6apm_dai_prepare(struct snd_soc_component *component, struct snd_pcm_substream *substream) { @@ -387,6 +453,75 @@ static int q6apm_dai_pcm_new(struct snd_soc_component *component, struct snd_soc return snd_pcm_set_fixed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, component->dev, size); } +static int q6apm_dai_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd; + struct q6apm_dai_data *pdata; + struct device *dev = component->dev; + int ret, size; + int graph_id; + + graph_id = cpu_dai->driver->id; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->cstream = stream; + prtd->graph = q6apm_graph_open(dev, (q6apm_cb)event_handler_compr, prtd, graph_id); + if (IS_ERR(prtd->graph)) { + ret = PTR_ERR(prtd->graph); + kfree(prtd); + return ret; + } + + runtime->private_data = prtd; + runtime->dma_bytes = BUFFER_BYTES_MAX; + size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer); + if (ret) + return ret; + + if (pdata->sid < 0) + prtd->phys = prtd->dma_buffer.addr; + else + prtd->phys = prtd->dma_buffer.addr | (pdata->sid << 32); + + snd_compr_set_runtime_buffer(stream, &prtd->dma_buffer); + spin_lock_init(&prtd->lock); + + q6apm_enable_compress_module(dev, prtd->graph, true); + return 0; +} + +static int q6apm_dai_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + + q6apm_graph_stop(prtd->graph); + q6apm_unmap_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK); + q6apm_graph_close(prtd->graph); + snd_dma_free_pages(&prtd->dma_buffer); + prtd->graph = NULL; + kfree(prtd); + runtime->private_data = NULL; + + return 0; +} +static const struct snd_compress_ops q6apm_dai_compress_ops = { + .open = q6apm_dai_compr_open, + .free = q6apm_dai_compr_free, +}; + static const struct snd_soc_component_driver q6apm_fe_dai_component = { .name = DRV_NAME, .open = q6apm_dai_open, @@ -396,6 +531,7 @@ static const struct snd_soc_component_driver q6apm_fe_dai_component = { .hw_params = q6apm_dai_hw_params, .pointer = q6apm_dai_pointer, .trigger = q6apm_dai_trigger, + .compress_ops = &q6apm_dai_compress_ops, }; static int q6apm_dai_probe(struct platform_device *pdev) diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index 87d67faf5f1a..d187d88c0a8c 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -45,6 +45,7 @@ #define APM_WRITE_TOKEN_LEN_SHIFT 16 #define APM_MAX_SESSIONS 8 +#define APM_LAST_BUFFER_FLAG BIT(30) struct q6apm { struct device *dev; -- cgit v1.2.3 From c0c87738a19d3e6d15dac4174d4b90c38a615112 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:50 +0100 Subject: ASoC: q6dsp: q6apm-dai: Add compress DAI and codec caps get callbacks Add q6apm get compress DAI capabilities and codec capabilities callbacks to support compress offload playback. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-9-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 53 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 32df5db014d3..d43705bf523a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -30,8 +30,25 @@ #define BUFFER_BYTES_MIN (PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE) #define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024) #define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4) +#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024) +#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) #define SID_MASK_DEFAULT 0xF +static const struct snd_compr_codec_caps q6apm_compr_caps = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates = { 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, + 96000, 176400, 192000 }, + .descriptor[0].num_sample_rates = 13, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 128, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + enum stream_state { Q6APM_STREAM_IDLE = 0, Q6APM_STREAM_STOPPED, @@ -41,6 +58,7 @@ enum stream_state { struct q6apm_dai_rtd { struct snd_pcm_substream *substream; struct snd_compr_stream *cstream; + struct snd_codec codec; struct snd_compr_params codec_param; struct snd_dma_buffer dma_buffer; phys_addr_t phys; @@ -54,6 +72,7 @@ struct q6apm_dai_rtd { uint16_t bits_per_sample; uint16_t source; /* Encoding source bit mask */ uint16_t session_id; + bool next_track; enum stream_state state; struct q6apm_graph *graph; spinlock_t lock; @@ -517,9 +536,43 @@ static int q6apm_dai_compr_free(struct snd_soc_component *component, return 0; } + +static int q6apm_dai_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + caps->direction = SND_COMPRESS_PLAYBACK; + caps->min_fragment_size = COMPR_PLAYBACK_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE; + caps->min_fragments = COMPR_PLAYBACK_MIN_NUM_FRAGMENTS; + caps->max_fragments = COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + caps->num_codecs = 3; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + caps->codecs[2] = SND_AUDIOCODEC_FLAC; + + return 0; +} + +static int q6apm_dai_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + *codec = q6apm_compr_caps; + break; + default: + break; + } + + return 0; +} static const struct snd_compress_ops q6apm_dai_compress_ops = { .open = q6apm_dai_compr_open, .free = q6apm_dai_compr_free, + .get_caps = q6apm_dai_compr_get_caps, + .get_codec_caps = q6apm_dai_compr_get_codec_caps, }; static const struct snd_soc_component_driver q6apm_fe_dai_component = { -- cgit v1.2.3 From c337bf33c41de423fa4d7353bd66d3c14df92445 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:51 +0100 Subject: ASoC: q6dsp: q6apm-dai: Add trigger/pointer compress DAI callbacks Add q6apm trigger and pointer compress DAI callbacks to support compress offload playback. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-10-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 67 ++++++++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6apm.h | 1 + 2 files changed, 68 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index d43705bf523a..9543b79ce83d 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -568,11 +568,78 @@ static int q6apm_dai_compr_get_codec_caps(struct snd_soc_component *component, return 0; } + +static int q6apm_dai_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + tstamp->copied_total = prtd->copied_total; + tstamp->byte_offset = prtd->copied_total % prtd->pcm_size; + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int q6apm_dai_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *stream, int cmd) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6apm_write_async(prtd->graph, prtd->pcm_count, 0, 0, NO_TIMESTAMP); + break; + case SNDRV_PCM_TRIGGER_STOP: + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + case SND_COMPR_TRIGGER_NEXT_TRACK: + prtd->next_track = true; + break; + case SND_COMPR_TRIGGER_DRAIN: + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + prtd->notify_on_drain = true; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6apm_dai_compr_ack(struct snd_soc_component *component, struct snd_compr_stream *stream, + size_t count) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + prtd->bytes_received += count; + spin_unlock_irqrestore(&prtd->lock, flags); + + return count; +} + static const struct snd_compress_ops q6apm_dai_compress_ops = { .open = q6apm_dai_compr_open, .free = q6apm_dai_compr_free, .get_caps = q6apm_dai_compr_get_caps, .get_codec_caps = q6apm_dai_compr_get_codec_caps, + .pointer = q6apm_dai_compr_pointer, + .trigger = q6apm_dai_compr_trigger, + .ack = q6apm_dai_compr_ack, }; static const struct snd_soc_component_driver q6apm_fe_dai_component = { diff --git a/sound/soc/qcom/qdsp6/q6apm.h b/sound/soc/qcom/qdsp6/q6apm.h index d187d88c0a8c..8ee40732ce9e 100644 --- a/sound/soc/qcom/qdsp6/q6apm.h +++ b/sound/soc/qcom/qdsp6/q6apm.h @@ -46,6 +46,7 @@ #define APM_MAX_SESSIONS 8 #define APM_LAST_BUFFER_FLAG BIT(30) +#define NO_TIMESTAMP 0xFF00 struct q6apm { struct device *dev; -- cgit v1.2.3 From b3f736d126d69ef3f3cc4f6b68795478954b2cf4 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:52 +0100 Subject: ASoC: q6dsp: q6apm-dai: Add compress set params and metadata DAI callbacks Add q6apm compress DAI callbacks for setting params and metadata to support compress offload playback. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-11-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 9543b79ce83d..c67147e5388b 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -76,6 +76,8 @@ struct q6apm_dai_rtd { enum stream_state state; struct q6apm_graph *graph; spinlock_t lock; + uint32_t initial_samples_drop; + uint32_t trailing_samples_drop; bool notify_on_drain; }; @@ -632,6 +634,109 @@ static int q6apm_dai_compr_ack(struct snd_soc_component *component, struct snd_c return count; } +static int q6apm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct q6apm_dai_data *pdata; + struct audioreach_module_config cfg; + struct snd_codec *codec = ¶ms->codec; + int dir = stream->direction; + int ret; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + prtd->periods = runtime->fragments; + prtd->pcm_count = runtime->fragment_size; + prtd->pcm_size = runtime->fragments * runtime->fragment_size; + prtd->bits_per_sample = 16; + + prtd->pos = 0; + + if (prtd->next_track != true) { + memcpy(&prtd->codec, codec, sizeof(*codec)); + + ret = q6apm_set_real_module_id(component->dev, prtd->graph, codec->id); + if (ret) + return ret; + + cfg.direction = dir; + cfg.sample_rate = codec->sample_rate; + cfg.num_channels = 2; + cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = codec->id; + memcpy(&cfg.codec, codec, sizeof(*codec)); + + ret = q6apm_graph_media_format_shmem(prtd->graph, &cfg); + if (ret < 0) + return ret; + + ret = q6apm_graph_media_format_pcm(prtd->graph, &cfg); + if (ret) + return ret; + + ret = q6apm_map_memory_regions(prtd->graph, SNDRV_PCM_STREAM_PLAYBACK, + prtd->phys, (prtd->pcm_size / prtd->periods), + prtd->periods); + if (ret < 0) + return -ENOMEM; + + ret = q6apm_graph_prepare(prtd->graph); + if (ret) + return ret; + + ret = q6apm_graph_start(prtd->graph); + if (ret) + return ret; + + } else { + cfg.direction = dir; + cfg.sample_rate = codec->sample_rate; + cfg.num_channels = 2; + cfg.bit_width = prtd->bits_per_sample; + cfg.fmt = codec->id; + memcpy(&cfg.codec, codec, sizeof(*codec)); + + ret = audioreach_compr_set_param(prtd->graph, &cfg); + if (ret < 0) + return ret; + } + prtd->state = Q6APM_STREAM_RUNNING; + + return 0; +} + +static int q6apm_dai_compr_set_metadata(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (metadata->key) { + case SNDRV_COMPRESS_ENCODER_PADDING: + prtd->trailing_samples_drop = metadata->value[0]; + q6apm_remove_trailing_silence(component->dev, prtd->graph, + prtd->trailing_samples_drop); + break; + case SNDRV_COMPRESS_ENCODER_DELAY: + prtd->initial_samples_drop = metadata->value[0]; + q6apm_remove_initial_silence(component->dev, prtd->graph, + prtd->initial_samples_drop); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + static const struct snd_compress_ops q6apm_dai_compress_ops = { .open = q6apm_dai_compr_open, .free = q6apm_dai_compr_free, @@ -640,6 +745,8 @@ static const struct snd_compress_ops q6apm_dai_compress_ops = { .pointer = q6apm_dai_compr_pointer, .trigger = q6apm_dai_compr_trigger, .ack = q6apm_dai_compr_ack, + .set_params = q6apm_dai_compr_set_params, + .set_metadata = q6apm_dai_compr_set_metadata, }; static const struct snd_soc_component_driver q6apm_fe_dai_component = { -- cgit v1.2.3 From c317d148a2b02c4756832fb4bd00a6480d874606 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 11:16:53 +0100 Subject: ASoC: q6dsp: q6apm-dai: Add mmap and copy compress DAI callbacks Add q6apm mmap and copy compress DAI callbacks to support compress offload playback. Co-developed-by: Mohammad Rafi Shaik Signed-off-by: Mohammad Rafi Shaik Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20230619101653.9750-12-srinivas.kandagatla@linaro.org Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index c67147e5388b..5eb0b864c740 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -737,6 +737,85 @@ static int q6apm_dai_compr_set_metadata(struct snd_soc_component *component, return ret; } +static int q6apm_dai_compr_mmap(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct vm_area_struct *vma) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + struct device *dev = component->dev; + + return dma_mmap_coherent(dev, vma, prtd->dma_buffer.area, prtd->dma_buffer.addr, + prtd->dma_buffer.bytes); +} + +static int q6apm_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *stream, char __user *buf, + size_t count) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6apm_dai_rtd *prtd = runtime->private_data; + void *dstn; + unsigned long flags; + size_t copy; + u32 wflags = 0; + u32 app_pointer; + u32 bytes_received; + uint32_t bytes_to_write; + int avail, bytes_in_flight = 0; + + bytes_received = prtd->bytes_received; + + /** + * Make sure that next track data pointer is aligned at 32 bit boundary + * This is a Mandatory requirement from DSP data buffers alignment + */ + if (prtd->next_track) + bytes_received = ALIGN(prtd->bytes_received, prtd->pcm_count); + + app_pointer = bytes_received/prtd->pcm_size; + app_pointer = bytes_received - (app_pointer * prtd->pcm_size); + dstn = prtd->dma_buffer.area + app_pointer; + + if (count < prtd->pcm_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = prtd->pcm_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(prtd->dma_buffer.area, buf + copy, count - copy)) + return -EFAULT; + } + + spin_lock_irqsave(&prtd->lock, flags); + bytes_in_flight = prtd->bytes_received - prtd->copied_total; + + if (prtd->next_track) { + prtd->next_track = false; + prtd->copied_total = ALIGN(prtd->copied_total, prtd->pcm_count); + prtd->bytes_sent = ALIGN(prtd->bytes_sent, prtd->pcm_count); + } + + prtd->bytes_received = bytes_received + count; + + /* Kick off the data to dsp if its starving!! */ + if (prtd->state == Q6APM_STREAM_RUNNING && (bytes_in_flight == 0)) { + bytes_to_write = prtd->pcm_count; + avail = prtd->bytes_received - prtd->bytes_sent; + + if (avail < prtd->pcm_count) + bytes_to_write = avail; + + q6apm_write_async(prtd->graph, bytes_to_write, 0, 0, wflags); + prtd->bytes_sent += bytes_to_write; + } + + spin_unlock_irqrestore(&prtd->lock, flags); + + return count; +} + static const struct snd_compress_ops q6apm_dai_compress_ops = { .open = q6apm_dai_compr_open, .free = q6apm_dai_compr_free, @@ -747,6 +826,8 @@ static const struct snd_compress_ops q6apm_dai_compress_ops = { .ack = q6apm_dai_compr_ack, .set_params = q6apm_dai_compr_set_params, .set_metadata = q6apm_dai_compr_set_metadata, + .mmap = q6apm_dai_compr_mmap, + .copy = q6apm_compr_copy, }; static const struct snd_soc_component_driver q6apm_fe_dai_component = { -- cgit v1.2.3 From 997905d523fb85ba1a45159cbb9ae3910275bada Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 00:58:44 +0100 Subject: ASoC: max98363: Remove cache defaults for volatile registers The max98363 driver provides cache defaults for a number of volatile registers. This is not meaningful, the cache values will never be used so at best they will just consume memory and at worst they will be used in preference to real values from the device, remove them. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-asoc-mx98363-volatile-v1-1-7acad55f5dd6@kernel.org Signed-off-by: Mark Brown --- sound/soc/codecs/max98363.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98363.c b/sound/soc/codecs/max98363.c index e6b84e222b50..b5c69bba0e48 100644 --- a/sound/soc/codecs/max98363.c +++ b/sound/soc/codecs/max98363.c @@ -15,11 +15,6 @@ #include "max98363.h" static struct reg_default max98363_reg[] = { - {MAX98363_R2001_INTR_RAW, 0x0}, - {MAX98363_R2003_INTR_STATE, 0x0}, - {MAX98363_R2005_INTR_FALG, 0x0}, - {MAX98363_R2007_INTR_EN, 0x0}, - {MAX98363_R2009_INTR_CLR, 0x0}, {MAX98363_R2021_ERR_MON_CTRL, 0x0}, {MAX98363_R2022_SPK_MON_THRESH, 0x0}, {MAX98363_R2023_SPK_MON_DURATION, 0x0}, @@ -28,7 +23,6 @@ static struct reg_default max98363_reg[] = { {MAX98363_R2040_AMP_VOL, 0x0}, {MAX98363_R2041_AMP_GAIN, 0x5}, {MAX98363_R2042_DSP_CFG, 0x0}, - {MAX98363_R21FF_REV_ID, 0x0}, }; static bool max98363_readable_register(struct device *dev, unsigned int reg) -- cgit v1.2.3 From 2f76e1d6ca524a888d29aafe29f2ad2003857971 Mon Sep 17 00:00:00 2001 From: Claudiu Beznea Date: Wed, 14 Jun 2023 15:15:09 +0300 Subject: ASoC: imx-audmix: check return value of devm_kasprintf() devm_kasprintf() returns a pointer to dynamically allocated memory. Pointer could be NULL in case allocation fails. Check pointer validity. Identified with coccinelle (kmerr.cocci script). Fixes: b86ef5367761 ("ASoC: fsl: Add Audio Mixer machine driver") Signed-off-by: Claudiu Beznea Link: https://lore.kernel.org/r/20230614121509.443926-1-claudiu.beznea@microchip.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-audmix.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'sound') diff --git a/sound/soc/fsl/imx-audmix.c b/sound/soc/fsl/imx-audmix.c index c93616e50f4d..0b58df56f4da 100644 --- a/sound/soc/fsl/imx-audmix.c +++ b/sound/soc/fsl/imx-audmix.c @@ -227,6 +227,8 @@ static int imx_audmix_probe(struct platform_device *pdev) dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s", fe_name_pref, args.np->full_name + 1); + if (!dai_name) + return -ENOMEM; dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name); @@ -235,6 +237,8 @@ static int imx_audmix_probe(struct platform_device *pdev) capture_dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", dai_name, "CPU-Capture"); + if (!capture_dai_name) + return -ENOMEM; } /* @@ -266,6 +270,8 @@ static int imx_audmix_probe(struct platform_device *pdev) "AUDMIX-Playback-%d", i); be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, "AUDMIX-Capture-%d", i); + if (!be_name || !be_pb || !be_cp) + return -ENOMEM; priv->dai[num_dai + i].cpus = &dlc[1]; priv->dai[num_dai + i].codecs = &asoc_dummy_dlc; @@ -288,6 +294,9 @@ static int imx_audmix_probe(struct platform_device *pdev) priv->dapm_routes[i].source = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", dai_name, "CPU-Playback"); + if (!priv->dapm_routes[i].source) + return -ENOMEM; + priv->dapm_routes[i].sink = be_pb; priv->dapm_routes[num_dai + i].source = be_pb; priv->dapm_routes[num_dai + i].sink = be_cp; -- cgit v1.2.3 From 8fba13f02c85b90deeba65a398f55d4b43a79595 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 19 Jun 2023 12:46:21 +0300 Subject: ASoC: loongson: fix error codes in loongson_card_parse_acpi() The acpi_node_get_property_reference() function returns kernel error codes and not ACPI error codes. So, although it does not affect the compiled code, using the ACPI_FAILURE() macro is wrong. Secondly, if the is_acpi_device_node() function returns false, then we should return -ENOENT instead of returning success. Fixes: d24028606e76 ("ASoC: loongson: Add Loongson ASoC Sound Card Support") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/fb14815d-2f9a-4b42-b193-cec61e7417ca@moroto.mountain Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_card.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 965eaf4e9109..08df05cb4328 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -81,9 +81,9 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) /* fixup platform name based on reference node */ memset(&args, 0, sizeof(args)); ret = acpi_node_get_property_reference(fwnode, "cpu", 0, &args); - if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) { + if (ret || !is_acpi_device_node(args.fwnode)) { dev_err(card->dev, "No matching phy in ACPI table\n"); - return ret; + return ret ?: -ENOENT; } adev = to_acpi_device_node(args.fwnode); phy_dev = acpi_get_first_physical_node(adev); @@ -95,9 +95,9 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) /* fixup codec name based on reference node */ memset(&args, 0, sizeof(args)); ret = acpi_node_get_property_reference(fwnode, "codec", 0, &args); - if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) { + if (ret || !is_acpi_device_node(args.fwnode)) { dev_err(card->dev, "No matching phy in ACPI table\n"); - return ret; + return ret ?: -ENOENT; } adev = to_acpi_device_node(args.fwnode); snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev)); -- cgit v1.2.3 From 915f5eadebd29ba185ac506766a90120153b7e14 Mon Sep 17 00:00:00 2001 From: Shenghao Ding <13916275206@139.com> Date: Sun, 18 Jun 2023 20:28:17 +0800 Subject: ASoC: tas2781: firmware lib Create tas2781 firmware lib. Signed-off-by: Shenghao Ding <13916275206@139.com> Link: https://lore.kernel.org/r/20230618122819.23143-2-13916275206@139.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 2427 +++++++++++++++++++++++++++++++++++++ 1 file changed, 2427 insertions(+) create mode 100644 sound/soc/codecs/tas2781-fmwlib.c (limited to 'sound') diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c new file mode 100644 index 000000000000..432b19ccec8c --- /dev/null +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -0,0 +1,2427 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// tasdevice-fmw.c -- TASDEVICE firmware support +// +// Copyright 2023 Texas Instruments, Inc. +// +// Author: Shenghao Ding + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define ERROR_PRAM_CRCCHK 0x0000000 +#define ERROR_YRAM_CRCCHK 0x0000001 +#define PPC_DRIVER_CRCCHK 0x00000200 + +#define TAS2781_SA_COEFF_SWAP_REG TASDEVICE_REG(0, 0x35, 0x2c) +#define TAS2781_YRAM_BOOK1 140 +#define TAS2781_YRAM1_PAGE 42 +#define TAS2781_YRAM1_START_REG 88 + +#define TAS2781_YRAM2_START_PAGE 43 +#define TAS2781_YRAM2_END_PAGE 49 +#define TAS2781_YRAM2_START_REG 8 +#define TAS2781_YRAM2_END_REG 127 + +#define TAS2781_YRAM3_PAGE 50 +#define TAS2781_YRAM3_START_REG 8 +#define TAS2781_YRAM3_END_REG 27 + +/*should not include B0_P53_R44-R47 */ +#define TAS2781_YRAM_BOOK2 0 +#define TAS2781_YRAM4_START_PAGE 50 +#define TAS2781_YRAM4_END_PAGE 60 + +#define TAS2781_YRAM5_PAGE 61 +#define TAS2781_YRAM5_START_REG TAS2781_YRAM3_START_REG +#define TAS2781_YRAM5_END_REG TAS2781_YRAM3_END_REG + +#define TASDEVICE_MAXPROGRAM_NUM_KERNEL 5 +#define TASDEVICE_MAXCONFIG_NUM_KERNEL_MULTIPLE_AMPS 64 +#define TASDEVICE_MAXCONFIG_NUM_KERNEL 10 +#define MAIN_ALL_DEVICES_1X 0x01 +#define MAIN_DEVICE_A_1X 0x02 +#define MAIN_DEVICE_B_1X 0x03 +#define MAIN_DEVICE_C_1X 0x04 +#define MAIN_DEVICE_D_1X 0x05 +#define COEFF_DEVICE_A_1X 0x12 +#define COEFF_DEVICE_B_1X 0x13 +#define COEFF_DEVICE_C_1X 0x14 +#define COEFF_DEVICE_D_1X 0x15 +#define PRE_DEVICE_A_1X 0x22 +#define PRE_DEVICE_B_1X 0x23 +#define PRE_DEVICE_C_1X 0x24 +#define PRE_DEVICE_D_1X 0x25 +#define PRE_SOFTWARE_RESET_DEVICE_A 0x41 +#define PRE_SOFTWARE_RESET_DEVICE_B 0x42 +#define PRE_SOFTWARE_RESET_DEVICE_C 0x43 +#define PRE_SOFTWARE_RESET_DEVICE_D 0x44 +#define POST_SOFTWARE_RESET_DEVICE_A 0x45 +#define POST_SOFTWARE_RESET_DEVICE_B 0x46 +#define POST_SOFTWARE_RESET_DEVICE_C 0x47 +#define POST_SOFTWARE_RESET_DEVICE_D 0x48 + +struct tas_crc { + unsigned char offset; + unsigned char len; +}; + +static const char deviceNumber[TASDEVICE_DSP_TAS_MAX_DEVICE] = { + 1, 2, 1, 2, 1, 1, 0, 2, 4, 3, 1, 2, 3, 4 +}; + +static struct tasdevice_config_info *tasdevice_add_config( + struct tasdevice_priv *tas_priv, unsigned char *config_data, + unsigned int config_size, int *status) +{ + struct tasdevice_config_info *cfg_info; + struct tasdev_blk_data **bk_da; + unsigned int config_offset = 0; + unsigned int i; + + /* In most projects are many audio cases, such as music, handfree, + * receiver, games, audio-to-haptics, PMIC record, bypass mode, + * portrait, landscape, etc. Even in multiple audios, one or + * two of the chips will work for the special case, such as + * ultrasonic application. In order to support these variable-numbers + * of audio cases, flexible configs have been introduced in the + * dsp firmware. + */ + cfg_info = kzalloc(sizeof(struct tasdevice_config_info), GFP_KERNEL); + if (!cfg_info) { + *status = -ENOMEM; + goto out; + } + + if (tas_priv->rcabin.fw_hdr.binary_version_num >= 0x105) { + if (config_offset + 64 > (int)config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, "add conf: Out of boundary\n"); + goto out; + } + config_offset += 64; + } + + if (config_offset + 4 > (int)config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, "add config: Out of boundary\n"); + goto out; + } + + /* convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host + */ + cfg_info->nblocks = + be32_to_cpup((__be32 *)&config_data[config_offset]); + config_offset += 4; + + /* Several kinds of dsp/algorithm firmwares can run on tas2781, + * the number and size of blk are not fixed and different among + * these firmwares. + */ + bk_da = cfg_info->blk_data = kcalloc(cfg_info->nblocks, + sizeof(struct tasdev_blk_data *), GFP_KERNEL); + if (!bk_da) { + *status = -ENOMEM; + goto out; + } + cfg_info->real_nblocks = 0; + for (i = 0; i < cfg_info->nblocks; i++) { + if (config_offset + 12 > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, + "%s: Out of boundary: i = %d nblocks = %u!\n", + __func__, i, cfg_info->nblocks); + break; + } + bk_da[i] = kzalloc(sizeof(struct tasdev_blk_data), GFP_KERNEL); + if (!bk_da[i]) { + *status = -ENOMEM; + break; + } + + bk_da[i]->dev_idx = config_data[config_offset]; + config_offset++; + + bk_da[i]->block_type = config_data[config_offset]; + config_offset++; + + if (bk_da[i]->block_type == TASDEVICE_BIN_BLK_PRE_POWER_UP) { + if (bk_da[i]->dev_idx == 0) + cfg_info->active_dev = + (1 << tas_priv->ndev) - 1; + else + cfg_info->active_dev |= 1 << + (bk_da[i]->dev_idx - 1); + + } + bk_da[i]->yram_checksum = + be16_to_cpup((__be16 *)&config_data[config_offset]); + config_offset += 2; + bk_da[i]->block_size = + be32_to_cpup((__be32 *)&config_data[config_offset]); + config_offset += 4; + + bk_da[i]->n_subblks = + be32_to_cpup((__be32 *)&config_data[config_offset]); + + config_offset += 4; + + if (config_offset + bk_da[i]->block_size > config_size) { + *status = -EINVAL; + dev_err(tas_priv->dev, + "%s: Out of boundary: i = %d blks = %u!\n", + __func__, i, cfg_info->nblocks); + break; + } + /* instead of kzalloc+memcpy */ + bk_da[i]->regdata = kmemdup(&config_data[config_offset], + bk_da[i]->block_size, GFP_KERNEL); + if (!bk_da[i]->regdata) { + *status = -ENOMEM; + goto out; + } + + config_offset += bk_da[i]->block_size; + cfg_info->real_nblocks += 1; + } + +out: + return cfg_info; +} + +int tasdevice_rca_parser(void *context, const struct firmware *fmw) +{ + struct tasdevice_priv *tas_priv = context; + struct tasdevice_config_info **cfg_info; + struct tasdevice_rca_hdr *fw_hdr; + struct tasdevice_rca *rca; + unsigned int total_config_sz = 0; + unsigned char *buf; + int offset = 0; + int ret = 0; + int i; + + rca = &(tas_priv->rcabin); + fw_hdr = &(rca->fw_hdr); + if (!fmw || !fmw->data) { + dev_err(tas_priv->dev, "Failed to read %s\n", + tas_priv->rca_binaryname); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + ret = -EINVAL; + goto out; + } + buf = (unsigned char *)fmw->data; + + fw_hdr->img_sz = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + if (fw_hdr->img_sz != fmw->size) { + dev_err(tas_priv->dev, + "File size not match, %d %u", (int)fmw->size, + fw_hdr->img_sz); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + ret = -EINVAL; + goto out; + } + + fw_hdr->checksum = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + fw_hdr->binary_version_num = be32_to_cpup((__be32 *)&buf[offset]); + if (fw_hdr->binary_version_num < 0x103) { + dev_err(tas_priv->dev, "File version 0x%04x is too low", + fw_hdr->binary_version_num); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + ret = -EINVAL; + goto out; + } + offset += 4; + fw_hdr->drv_fw_version = be32_to_cpup((__be32 *)&buf[offset]); + offset += 8; + fw_hdr->plat_type = buf[offset]; + offset += 1; + fw_hdr->dev_family = buf[offset]; + offset += 1; + fw_hdr->reserve = buf[offset]; + offset += 1; + fw_hdr->ndev = buf[offset]; + offset += 1; + if (fw_hdr->ndev != tas_priv->ndev) { + dev_err(tas_priv->dev, + "ndev(%u) in rcabin mismatch ndev(%u) in DTS\n", + fw_hdr->ndev, tas_priv->ndev); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + ret = -EINVAL; + goto out; + } + if (offset + TASDEVICE_DEVICE_SUM > fw_hdr->img_sz) { + dev_err(tas_priv->dev, "rca_ready: Out of boundary!\n"); + ret = -EINVAL; + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + + for (i = 0; i < TASDEVICE_DEVICE_SUM; i++, offset++) + fw_hdr->devs[i] = buf[offset]; + + fw_hdr->nconfig = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + + for (i = 0; i < TASDEVICE_CONFIG_SUM; i++) { + fw_hdr->config_size[i] = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + total_config_sz += fw_hdr->config_size[i]; + } + + if (fw_hdr->img_sz - total_config_sz != (unsigned int)offset) { + dev_err(tas_priv->dev, "Bin file error!\n"); + ret = -EINVAL; + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + + cfg_info = kcalloc(fw_hdr->nconfig, sizeof(*cfg_info), GFP_KERNEL); + if (!cfg_info) { + ret = -ENOMEM; + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + rca->cfg_info = cfg_info; + rca->ncfgs = 0; + for (i = 0; i < (int)fw_hdr->nconfig; i++) { + rca->ncfgs += 1; + cfg_info[i] = tasdevice_add_config(tas_priv, &buf[offset], + fw_hdr->config_size[i], &ret); + if (ret) { + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + offset += (int)fw_hdr->config_size[i]; + } +out: + return ret; +} +EXPORT_SYMBOL_NS_GPL(tasdevice_rca_parser, SND_SOC_TAS2781_FMWLIB); + +static int fw_parse_block_data_kernel(struct tasdevice_fw *tas_fmw, + struct tasdev_blk *block, const struct firmware *fmw, int offset) +{ + const unsigned char *data = fmw->data; + + if (offset + 16 > fmw->size) { + dev_err(tas_fmw->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + + /* convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host + */ + block->type = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + block->is_pchksum_present = data[offset]; + offset++; + + block->pchksum = data[offset]; + offset++; + + block->is_ychksum_present = data[offset]; + offset++; + + block->ychksum = data[offset]; + offset++; + + block->blk_size = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + block->nr_subblocks = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + if (offset + block->blk_size > fmw->size) { + dev_err(tas_fmw->dev, "%s: nSublocks error\n", __func__); + offset = -EINVAL; + goto out; + } + /* instead of kzalloc+memcpy */ + block->data = kmemdup(&data[offset], block->blk_size, GFP_KERNEL); + if (!block->data) { + offset = -ENOMEM; + goto out; + } + offset += block->blk_size; + +out: + return offset; +} + +static int fw_parse_data_kernel(struct tasdevice_fw *tas_fmw, + struct tasdevice_data *img_data, const struct firmware *fmw, + int offset) +{ + const unsigned char *data = fmw->data; + struct tasdev_blk *blk; + unsigned int i; + + if (offset + 4 > fmw->size) { + dev_err(tas_fmw->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + img_data->nr_blk = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + img_data->dev_blks = kcalloc(img_data->nr_blk, + sizeof(struct tasdev_blk), GFP_KERNEL); + if (!img_data->dev_blks) { + offset = -ENOMEM; + goto out; + } + + for (i = 0; i < img_data->nr_blk; i++) { + blk = &(img_data->dev_blks[i]); + offset = fw_parse_block_data_kernel(tas_fmw, blk, fmw, offset); + if (offset < 0) { + offset = -EINVAL; + break; + } + } + +out: + return offset; +} + +static int fw_parse_program_data_kernel( + struct tasdevice_priv *tas_priv, struct tasdevice_fw *tas_fmw, + const struct firmware *fmw, int offset) +{ + struct tasdevice_prog *program; + unsigned int i; + + for (i = 0; i < tas_fmw->nr_programs; i++) { + program = &(tas_fmw->programs[i]); + if (offset + 72 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + offset = -EINVAL; + goto out; + } + /*skip 72 unused byts*/ + offset += 72; + + offset = fw_parse_data_kernel(tas_fmw, &(program->dev_data), + fmw, offset); + if (offset < 0) + goto out; + } + +out: + return offset; +} + +static int fw_parse_configuration_data_kernel( + struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + const unsigned char *data = fmw->data; + struct tasdevice_config *config; + unsigned int i; + + for (i = 0; i < tas_fmw->nr_configurations; i++) { + config = &(tas_fmw->configs[i]); + if (offset + 80 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + offset = -EINVAL; + goto out; + } + memcpy(config->name, &data[offset], 64); + /*skip extra 16 bytes*/ + offset += 80; + + offset = fw_parse_data_kernel(tas_fmw, &(config->dev_data), + fmw, offset); + if (offset < 0) + goto out; + } + +out: + return offset; +} + +static int fw_parse_variable_header_kernel( + struct tasdevice_priv *tas_priv, const struct firmware *fmw, + int offset) +{ + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_dspfw_hdr *fw_hdr = &(tas_fmw->fw_hdr); + struct tasdevice_prog *program; + struct tasdevice_config *config; + const unsigned char *buf = fmw->data; + unsigned short max_confs; + unsigned int i; + + if (offset + 12 + 4 * TASDEVICE_MAXPROGRAM_NUM_KERNEL > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + fw_hdr->device_family = be16_to_cpup((__be16 *)&buf[offset]); + if (fw_hdr->device_family != 0) { + dev_err(tas_priv->dev, "%s:not TAS device\n", __func__); + offset = -EINVAL; + goto out; + } + offset += 2; + fw_hdr->device = be16_to_cpup((__be16 *)&buf[offset]); + if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE || + fw_hdr->device == 6) { + dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device); + offset = -EINVAL; + goto out; + } + offset += 2; + fw_hdr->ndev = deviceNumber[fw_hdr->device]; + + if (fw_hdr->ndev != tas_priv->ndev) { + dev_err(tas_priv->dev, + "%s: ndev(%u) in dspbin mismatch ndev(%u) in DTS\n", + __func__, fw_hdr->ndev, tas_priv->ndev); + offset = -EINVAL; + goto out; + } + + tas_fmw->nr_programs = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + + if (tas_fmw->nr_programs == 0 || tas_fmw->nr_programs > + TASDEVICE_MAXPROGRAM_NUM_KERNEL) { + dev_err(tas_priv->dev, "mnPrograms is invalid\n"); + offset = -EINVAL; + goto out; + } + + tas_fmw->programs = kcalloc(tas_fmw->nr_programs, + sizeof(struct tasdevice_prog), GFP_KERNEL); + if (!tas_fmw->programs) { + offset = -ENOMEM; + goto out; + } + + for (i = 0; i < tas_fmw->nr_programs; i++) { + program = &(tas_fmw->programs[i]); + program->prog_size = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + } + + /* Skip the unused prog_size */ + offset += 4 * (TASDEVICE_MAXPROGRAM_NUM_KERNEL - tas_fmw->nr_programs); + + tas_fmw->nr_configurations = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + + /* The max number of config in firmware greater than 4 pieces of + * tas2781s is different from the one lower than 4 pieces of + * tas2781s. + */ + max_confs = (fw_hdr->ndev >= 4) ? + TASDEVICE_MAXCONFIG_NUM_KERNEL_MULTIPLE_AMPS : + TASDEVICE_MAXCONFIG_NUM_KERNEL; + if (tas_fmw->nr_configurations == 0 || + tas_fmw->nr_configurations > max_confs) { + dev_err(tas_priv->dev, "%s: Conf is invalid\n", __func__); + offset = -EINVAL; + goto out; + } + + if (offset + 4 * max_confs > fmw->size) { + dev_err(tas_priv->dev, "%s: mpConfigurations err\n", __func__); + offset = -EINVAL; + goto out; + } + + tas_fmw->configs = kcalloc(tas_fmw->nr_configurations, + sizeof(struct tasdevice_config), GFP_KERNEL); + if (!tas_fmw->configs) { + offset = -ENOMEM; + goto out; + } + + for (i = 0; i < tas_fmw->nr_programs; i++) { + config = &(tas_fmw->configs[i]); + config->cfg_size = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + } + + /* Skip the unused configs */ + offset += 4 * (max_confs - tas_fmw->nr_programs); + +out: + return offset; +} + +static int tasdevice_process_block(void *context, unsigned char *data, + unsigned char dev_idx, int sublocksize) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + int subblk_offset, chn, chnend, rc; + unsigned char subblk_typ = data[1]; + int blktyp = dev_idx & 0xC0; + int idx = dev_idx & 0x3F; + bool is_err = false; + + if (idx) { + chn = idx - 1; + chnend = idx; + } else { + chn = 0; + chnend = tas_priv->ndev; + } + + for (; chn < chnend; chn++) { + if (tas_priv->tasdevice[chn].is_loading == false) + continue; + + is_err = false; + subblk_offset = 2; + switch (subblk_typ) { + case TASDEVICE_CMD_SING_W: { + int i; + unsigned short len = be16_to_cpup((__be16 *)&data[2]); + + subblk_offset += 2; + if (subblk_offset + 4 * len > sublocksize) { + dev_err(tas_priv->dev, + "process_block: Out of boundary\n"); + is_err = true; + break; + } + + for (i = 0; i < len; i++) { + rc = tasdevice_dev_write(tas_priv, chn, + TASDEVICE_REG(data[subblk_offset], + data[subblk_offset + 1], + data[subblk_offset + 2]), + data[subblk_offset + 3]); + if (rc < 0) { + is_err = true; + dev_err(tas_priv->dev, + "process_block: single write error\n"); + } + subblk_offset += 4; + } + } + break; + case TASDEVICE_CMD_BURST: { + unsigned short len = be16_to_cpup((__be16 *)&data[2]); + + subblk_offset += 2; + if (subblk_offset + 4 + len > sublocksize) { + dev_err(tas_priv->dev, + "%s: BST Out of boundary\n", + __func__); + is_err = true; + break; + } + if (len % 4) { + dev_err(tas_priv->dev, + "%s:Bst-len(%u)not div by 4\n", + __func__, len); + break; + } + + rc = tasdevice_dev_bulk_write(tas_priv, chn, + TASDEVICE_REG(data[subblk_offset], + data[subblk_offset + 1], + data[subblk_offset + 2]), + &(data[subblk_offset + 4]), len); + if (rc < 0) { + is_err = true; + dev_err(tas_priv->dev, + "%s: bulk_write error = %d\n", + __func__, rc); + } + subblk_offset += (len + 4); + } + break; + case TASDEVICE_CMD_DELAY: { + unsigned int sleep_time = 0; + + if (subblk_offset + 2 > sublocksize) { + dev_err(tas_priv->dev, + "%s: delay Out of boundary\n", + __func__); + is_err = true; + break; + } + sleep_time = be16_to_cpup((__be16 *)&data[2]) * 1000; + usleep_range(sleep_time, sleep_time + 50); + subblk_offset += 2; + } + break; + case TASDEVICE_CMD_FIELD_W: + if (subblk_offset + 6 > sublocksize) { + dev_err(tas_priv->dev, + "%s: bit write Out of boundary\n", + __func__); + is_err = true; + break; + } + rc = tasdevice_dev_update_bits(tas_priv, chn, + TASDEVICE_REG(data[subblk_offset + 2], + data[subblk_offset + 3], + data[subblk_offset + 4]), + data[subblk_offset + 1], + data[subblk_offset + 5]); + if (rc < 0) { + is_err = true; + dev_err(tas_priv->dev, + "%s: update_bits error = %d\n", + __func__, rc); + } + subblk_offset += 6; + break; + default: + break; + } + if (is_err == true && blktyp != 0) { + if (blktyp == 0x80) { + tas_priv->tasdevice[chn].cur_prog = -1; + tas_priv->tasdevice[chn].cur_conf = -1; + } else + tas_priv->tasdevice[chn].cur_conf = -1; + } + } + + return subblk_offset; +} + +void tasdevice_select_cfg_blk(void *pContext, int conf_no, + unsigned char block_type) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) pContext; + struct tasdevice_rca *rca = &(tas_priv->rcabin); + struct tasdevice_config_info **cfg_info = rca->cfg_info; + struct tasdev_blk_data **blk_data; + int j, k, chn, chnend; + + if (conf_no >= rca->ncfgs || conf_no < 0 || !cfg_info) { + dev_err(tas_priv->dev, "conf_no should be not more than %u\n", + rca->ncfgs); + return; + } + blk_data = cfg_info[conf_no]->blk_data; + + for (j = 0; j < (int)cfg_info[conf_no]->real_nblocks; j++) { + unsigned int length = 0, rc = 0; + + if (block_type > 5 || block_type < 2) { + dev_err(tas_priv->dev, + "block_type should be in range from 2 to 5\n"); + break; + } + if (block_type != blk_data[j]->block_type) + continue; + + for (k = 0; k < (int)blk_data[j]->n_subblks; k++) { + if (blk_data[j]->dev_idx) { + chn = blk_data[j]->dev_idx - 1; + chnend = blk_data[j]->dev_idx; + } else { + chn = 0; + chnend = tas_priv->ndev; + } + for (; chn < chnend; chn++) + tas_priv->tasdevice[chn].is_loading = true; + + rc = tasdevice_process_block(tas_priv, + blk_data[j]->regdata + length, + blk_data[j]->dev_idx, + blk_data[j]->block_size - length); + length += rc; + if (blk_data[j]->block_size < length) { + dev_err(tas_priv->dev, + "%s: %u %u out of boundary\n", + __func__, length, + blk_data[j]->block_size); + break; + } + } + if (length != blk_data[j]->block_size) + dev_err(tas_priv->dev, "%s: %u %u size is not same\n", + __func__, length, blk_data[j]->block_size); + } +} +EXPORT_SYMBOL_NS_GPL(tasdevice_select_cfg_blk, SND_SOC_TAS2781_FMWLIB); + +static int tasdevice_load_block_kernel( + struct tasdevice_priv *tasdevice, struct tasdev_blk *block) +{ + struct tasdevice_dspfw_hdr *fw_hdr = &(tasdevice->fmw->fw_hdr); + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &(fw_hdr->fixed_hdr); + const unsigned int blk_size = block->blk_size; + unsigned int i, length; + unsigned char *data = block->data; + unsigned char dev_idx = 0; + + if (fw_fixed_hdr->ppcver >= PPC3_VERSION_TAS2781) { + switch (block->type) { + case MAIN_ALL_DEVICES_1X: + dev_idx = 0x80; + break; + case MAIN_DEVICE_A_1X: + dev_idx = 0x81; + break; + case COEFF_DEVICE_A_1X: + case PRE_DEVICE_A_1X: + case PRE_SOFTWARE_RESET_DEVICE_A: + case POST_SOFTWARE_RESET_DEVICE_A: + dev_idx = 0xC1; + break; + case MAIN_DEVICE_B_1X: + dev_idx = 0x82; + break; + case COEFF_DEVICE_B_1X: + case PRE_DEVICE_B_1X: + case PRE_SOFTWARE_RESET_DEVICE_B: + case POST_SOFTWARE_RESET_DEVICE_B: + dev_idx = 0xC2; + break; + case MAIN_DEVICE_C_1X: + dev_idx = 0x83; + break; + case COEFF_DEVICE_C_1X: + case PRE_DEVICE_C_1X: + case PRE_SOFTWARE_RESET_DEVICE_C: + case POST_SOFTWARE_RESET_DEVICE_C: + dev_idx = 0xC3; + break; + case MAIN_DEVICE_D_1X: + dev_idx = 0x84; + break; + case COEFF_DEVICE_D_1X: + case PRE_DEVICE_D_1X: + case PRE_SOFTWARE_RESET_DEVICE_D: + case POST_SOFTWARE_RESET_DEVICE_D: + dev_idx = 0xC4; + break; + default: + dev_info(tasdevice->dev, + "%s: load block: Other Type = 0x%02x\n", + __func__, block->type); + break; + } + } else if (fw_fixed_hdr->ppcver >= + PPC3_VERSION) { + switch (block->type) { + case MAIN_ALL_DEVICES_1X: + dev_idx = 0x80; + break; + case MAIN_DEVICE_A_1X: + dev_idx = 0x81; + break; + case COEFF_DEVICE_A_1X: + case PRE_DEVICE_A_1X: + dev_idx = 0xC1; + break; + case MAIN_DEVICE_B_1X: + dev_idx = 0x82; + break; + case COEFF_DEVICE_B_1X: + case PRE_DEVICE_B_1X: + dev_idx = 0xC2; + break; + case MAIN_DEVICE_C_1X: + dev_idx = 0x83; + break; + case COEFF_DEVICE_C_1X: + case PRE_DEVICE_C_1X: + dev_idx = 0xC3; + break; + case MAIN_DEVICE_D_1X: + dev_idx = 0x84; + break; + case COEFF_DEVICE_D_1X: + case PRE_DEVICE_D_1X: + dev_idx = 0xC4; + break; + default: + dev_info(tasdevice->dev, + "%s: load block: Other Type = 0x%02x\n", + __func__, block->type); + break; + } + } else { + switch (block->type) { + case MAIN_ALL_DEVICES: + dev_idx = 0|0x80; + break; + case MAIN_DEVICE_A: + dev_idx = 0x81; + break; + case COEFF_DEVICE_A: + case PRE_DEVICE_A: + dev_idx = 0xC1; + break; + case MAIN_DEVICE_B: + dev_idx = 0x82; + break; + case COEFF_DEVICE_B: + case PRE_DEVICE_B: + dev_idx = 0xC2; + break; + case MAIN_DEVICE_C: + dev_idx = 0x83; + break; + case COEFF_DEVICE_C: + case PRE_DEVICE_C: + dev_idx = 0xC3; + break; + case MAIN_DEVICE_D: + dev_idx = 0x84; + break; + case COEFF_DEVICE_D: + case PRE_DEVICE_D: + dev_idx = 0xC4; + break; + default: + dev_info(tasdevice->dev, + "%s: load block: Other Type = 0x%02x\n", + __func__, block->type); + break; + } + } + + for (i = 0, length = 0; i < block->nr_subblocks; i++) { + int rc = tasdevice_process_block(tasdevice, data + length, + dev_idx, blk_size - length); + if (rc < 0) { + dev_err(tasdevice->dev, + "%s: %u %u sublock write error\n", + __func__, length, blk_size); + break; + } + length += (unsigned int)rc; + if (blk_size < length) { + dev_err(tasdevice->dev, "%s: %u %u out of boundary\n", + __func__, length, blk_size); + break; + } + } + + return 0; +} + +static int fw_parse_variable_hdr(struct tasdevice_priv + *tas_priv, struct tasdevice_dspfw_hdr *fw_hdr, + const struct firmware *fmw, int offset) +{ + const unsigned char *buf = fmw->data; + int len = strlen((char *)&buf[offset]); + + len++; + + if (offset + len + 8 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + + offset += len; + + fw_hdr->device_family = be32_to_cpup((__be32 *)&buf[offset]); + if (fw_hdr->device_family != 0) { + dev_err(tas_priv->dev, "%s: not TAS device\n", __func__); + offset = -EINVAL; + goto out; + } + offset += 4; + + fw_hdr->device = be32_to_cpup((__be32 *)&buf[offset]); + if (fw_hdr->device >= TASDEVICE_DSP_TAS_MAX_DEVICE || + fw_hdr->device == 6) { + dev_err(tas_priv->dev, "Unsupported dev %d\n", fw_hdr->device); + offset = -EINVAL; + goto out; + } + offset += 4; + fw_hdr->ndev = deviceNumber[fw_hdr->device]; + +out: + return offset; +} + +static int fw_parse_variable_header_git(struct tasdevice_priv + *tas_priv, const struct firmware *fmw, int offset) +{ + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_dspfw_hdr *fw_hdr = &(tas_fmw->fw_hdr); + + offset = fw_parse_variable_hdr(tas_priv, fw_hdr, fmw, offset); + if (offset < 0) + goto out; + if (fw_hdr->ndev != tas_priv->ndev) { + dev_err(tas_priv->dev, + "%s: ndev(%u) in dspbin mismatch ndev(%u) in DTS\n", + __func__, fw_hdr->ndev, tas_priv->ndev); + offset = -EINVAL; + } + +out: + return offset; +} + +static int fw_parse_block_data(struct tasdevice_fw *tas_fmw, + struct tasdev_blk *block, const struct firmware *fmw, int offset) +{ + unsigned char *data = (unsigned char *)fmw->data; + int n; + + if (offset + 8 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Type error\n", __func__); + offset = -EINVAL; + goto out; + } + block->type = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + if (tas_fmw->fw_hdr.fixed_hdr.drv_ver >= PPC_DRIVER_CRCCHK) { + if (offset + 8 > fmw->size) { + dev_err(tas_fmw->dev, "PChkSumPresent error\n"); + offset = -EINVAL; + goto out; + } + block->is_pchksum_present = data[offset]; + offset++; + + block->pchksum = data[offset]; + offset++; + + block->is_ychksum_present = data[offset]; + offset++; + + block->ychksum = data[offset]; + offset++; + } else { + block->is_pchksum_present = 0; + block->is_ychksum_present = 0; + } + + block->nr_cmds = be32_to_cpup((__be32 *)&data[offset]); + offset += 4; + + n = block->nr_cmds * 4; + if (offset + n > fmw->size) { + dev_err(tas_fmw->dev, + "%s: File Size(%lu) error offset = %d n = %d\n", + __func__, (unsigned long)fmw->size, offset, n); + offset = -EINVAL; + goto out; + } + /* instead of kzalloc+memcpy */ + block->data = kmemdup(&data[offset], n, GFP_KERNEL); + if (!block->data) { + offset = -ENOMEM; + goto out; + } + offset += n; + +out: + return offset; +} + +/* When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_data(struct tasdevice_fw *tas_fmw, + struct tasdevice_data *img_data, const struct firmware *fmw, + int offset) +{ + const unsigned char *data = (unsigned char *)fmw->data; + struct tasdev_blk *blk; + unsigned int i; + int n; + + if (offset + 64 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Name error\n", __func__); + offset = -EINVAL; + goto out; + } + memcpy(img_data->name, &data[offset], 64); + offset += 64; + + n = strlen((char *)&data[offset]); + n++; + if (offset + n + 2 > fmw->size) { + dev_err(tas_fmw->dev, "%s: Description error\n", __func__); + offset = -EINVAL; + goto out; + } + offset += n; + img_data->nr_blk = be16_to_cpup((__be16 *)&data[offset]); + offset += 2; + + img_data->dev_blks = kcalloc(img_data->nr_blk, + sizeof(struct tasdev_blk), GFP_KERNEL); + if (!img_data->dev_blks) { + offset = -ENOMEM; + goto out; + } + for (i = 0; i < img_data->nr_blk; i++) { + blk = &(img_data->dev_blks[i]); + offset = fw_parse_block_data(tas_fmw, blk, fmw, offset); + if (offset < 0) { + offset = -EINVAL; + goto out; + } + } + +out: + return offset; +} + +/* When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_program_data(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + unsigned char *buf = (unsigned char *)fmw->data; + struct tasdevice_prog *program; + int i; + + if (offset + 2 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + tas_fmw->nr_programs = be16_to_cpup((__be16 *)&buf[offset]); + offset += 2; + + if (tas_fmw->nr_programs == 0) { + /*Not error in calibration Data file, return directly*/ + dev_info(tas_priv->dev, "%s: No Programs data, maybe calbin\n", + __func__); + goto out; + } + + tas_fmw->programs = + kcalloc(tas_fmw->nr_programs, sizeof(struct tasdevice_prog), + GFP_KERNEL); + if (!tas_fmw->programs) { + offset = -ENOMEM; + goto out; + } + for (i = 0; i < tas_fmw->nr_programs; i++) { + int n = 0; + + program = &(tas_fmw->programs[i]); + if (offset + 64 > fmw->size) { + dev_err(tas_priv->dev, "%s: mpName error\n", __func__); + offset = -EINVAL; + goto out; + } + offset += 64; + + n = strlen((char *)&buf[offset]); + /* skip '\0' and 5 unused bytes */ + n += 6; + if (offset + n > fmw->size) { + dev_err(tas_priv->dev, "Description err\n"); + offset = -EINVAL; + goto out; + } + + offset += n; + + offset = fw_parse_data(tas_fmw, &(program->dev_data), fmw, + offset); + if (offset < 0) + goto out; + } + +out: + return offset; +} + +/* When parsing error occurs, all the memory resource will be released + * in the end of tasdevice_rca_ready. + */ +static int fw_parse_configuration_data( + struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, + const struct firmware *fmw, int offset) +{ + unsigned char *data = (unsigned char *)fmw->data; + struct tasdevice_config *config; + unsigned int i; + int n; + + if (offset + 2 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + tas_fmw->nr_configurations = be16_to_cpup((__be16 *)&data[offset]); + offset += 2; + + if (tas_fmw->nr_configurations == 0) { + dev_err(tas_priv->dev, "%s: Conf is zero\n", __func__); + /*Not error for calibration Data file, return directly*/ + goto out; + } + tas_fmw->configs = kcalloc(tas_fmw->nr_configurations, + sizeof(struct tasdevice_config), GFP_KERNEL); + if (!tas_fmw->configs) { + offset = -ENOMEM; + goto out; + } + for (i = 0; i < tas_fmw->nr_configurations; i++) { + config = &(tas_fmw->configs[i]); + if (offset + 64 > fmw->size) { + dev_err(tas_priv->dev, "File Size err\n"); + offset = -EINVAL; + goto out; + } + memcpy(config->name, &data[offset], 64); + offset += 64; + + n = strlen((char *)&data[offset]); + n += 15; + if (offset + n > fmw->size) { + dev_err(tas_priv->dev, "Description err\n"); + offset = -EINVAL; + goto out; + } + + offset += n; + + offset = fw_parse_data(tas_fmw, &(config->dev_data), + fmw, offset); + if (offset < 0) + goto out; + } + +out: + return offset; +} + +static bool check_inpage_yram_rg(struct tas_crc *cd, + unsigned char reg, unsigned char len) +{ + bool in = false; + + + if (reg <= TAS2781_YRAM5_END_REG && + reg >= TAS2781_YRAM5_START_REG) { + if (reg + len > TAS2781_YRAM5_END_REG) + cd->len = TAS2781_YRAM5_END_REG - reg + 1; + else + cd->len = len; + cd->offset = reg; + in = true; + } else if (reg < TAS2781_YRAM5_START_REG) { + if (reg + len > TAS2781_YRAM5_START_REG) { + cd->offset = TAS2781_YRAM5_START_REG; + cd->len = len - TAS2781_YRAM5_START_REG + reg; + in = true; + } + } + + return in; +} + +static bool check_inpage_yram_bk1(struct tas_crc *cd, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (page == TAS2781_YRAM1_PAGE) { + if (reg >= TAS2781_YRAM1_START_REG) { + cd->offset = reg; + cd->len = len; + in = true; + } else if (reg + len > TAS2781_YRAM1_START_REG) { + cd->offset = TAS2781_YRAM1_START_REG; + cd->len = len - TAS2781_YRAM1_START_REG + reg; + in = true; + } + } else if (page == TAS2781_YRAM3_PAGE) + in = check_inpage_yram_rg(cd, reg, len); + + return in; +} + +/* Return Code: + * true -- the registers are in the inpage yram + * false -- the registers are NOT in the inpage yram + */ +static bool check_inpage_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (book == TAS2781_YRAM_BOOK1) { + in = check_inpage_yram_bk1(cd, page, reg, len); + goto end; + } + if (book == TAS2781_YRAM_BOOK2 && page == TAS2781_YRAM5_PAGE) + in = check_inpage_yram_rg(cd, reg, len); + +end: + return in; +} + +static bool check_inblock_yram_bk(struct tas_crc *cd, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if ((page >= TAS2781_YRAM4_START_PAGE && + page <= TAS2781_YRAM4_END_PAGE) || + (page >= TAS2781_YRAM2_START_PAGE && + page <= TAS2781_YRAM2_END_PAGE)) { + if (reg <= TAS2781_YRAM2_END_REG && + reg >= TAS2781_YRAM2_START_REG) { + cd->offset = reg; + cd->len = len; + in = true; + } else if (reg < TAS2781_YRAM2_START_REG) { + if (reg + len - 1 >= TAS2781_YRAM2_START_REG) { + cd->offset = TAS2781_YRAM2_START_REG; + cd->len = reg + len - TAS2781_YRAM2_START_REG; + in = true; + } + } + } + + return in; +} + +/* Return Code: + * true -- the registers are in the inblock yram + * false -- the registers are NOT in the inblock yram + */ +static bool check_inblock_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in = false; + + if (book == TAS2781_YRAM_BOOK1 || book == TAS2781_YRAM_BOOK2) + in = check_inblock_yram_bk(cd, page, reg, len); + + return in; +} + +static bool check_yram(struct tas_crc *cd, unsigned char book, + unsigned char page, unsigned char reg, unsigned char len) +{ + bool in; + + in = check_inpage_yram(cd, book, page, reg, len); + if (in) + goto end; + in = check_inblock_yram(cd, book, page, reg, len); + +end: + return in; +} + +static int tasdev_multibytes_chksum(struct tasdevice_priv *tasdevice, + unsigned short chn, unsigned char book, unsigned char page, + unsigned char reg, unsigned int len) +{ + struct tas_crc crc_data; + unsigned char crc_chksum = 0; + unsigned char nBuf1[128]; + int ret = 0; + int i; + bool in; + + if ((reg + len - 1) > 127) { + ret = -EINVAL; + dev_err(tasdevice->dev, "firmware error\n"); + goto end; + } + + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) + && (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) + && (reg == TASDEVICE_PAGE_REG(TAS2781_SA_COEFF_SWAP_REG)) + && (len == 4)) { + /*DSP swap command, pass */ + ret = 0; + goto end; + } + + in = check_yram(&crc_data, book, page, reg, len); + if (!in) + goto end; + + if (len == 1) { + dev_err(tasdevice->dev, "firmware error\n"); + ret = -EINVAL; + goto end; + } + + ret = tasdevice_dev_bulk_read(tasdevice, chn, + TASDEVICE_REG(book, page, crc_data.offset), + nBuf1, crc_data.len); + if (ret < 0) + goto end; + + for (i = 0; i < crc_data.len; i++) { + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) + && (page == TASDEVICE_PAGE_ID( + TAS2781_SA_COEFF_SWAP_REG)) + && ((i + crc_data.offset) + >= TASDEVICE_PAGE_REG(TAS2781_SA_COEFF_SWAP_REG)) + && ((i + crc_data.offset) + <= (TASDEVICE_PAGE_REG(TAS2781_SA_COEFF_SWAP_REG) + + 4))) + /*DSP swap command, bypass */ + continue; + else + crc_chksum += crc8(tasdevice->crc8_lkp_tbl, &nBuf1[i], + 1, 0); + } + + ret = crc_chksum; + +end: + return ret; +} + +static int do_singlereg_checksum(struct tasdevice_priv *tasdevice, + unsigned short chl, unsigned char book, unsigned char page, + unsigned char reg, unsigned char val) +{ + struct tas_crc crc_data; + unsigned int nData1; + int ret = 0; + bool in; + + if ((book == TASDEVICE_BOOK_ID(TAS2781_SA_COEFF_SWAP_REG)) + && (page == TASDEVICE_PAGE_ID(TAS2781_SA_COEFF_SWAP_REG)) + && (reg >= TASDEVICE_PAGE_REG(TAS2781_SA_COEFF_SWAP_REG)) + && (reg <= (TASDEVICE_PAGE_REG( + TAS2781_SA_COEFF_SWAP_REG) + 4))) { + /*DSP swap command, pass */ + ret = 0; + goto end; + } + + in = check_yram(&crc_data, book, page, reg, 1); + if (!in) + goto end; + ret = tasdevice_dev_read(tasdevice, chl, + TASDEVICE_REG(book, page, reg), &nData1); + if (ret < 0) + goto end; + + if (nData1 != val) { + dev_err(tasdevice->dev, + "B[0x%x]P[0x%x]R[0x%x] W[0x%x], R[0x%x]\n", + book, page, reg, val, nData1); + tasdevice->tasdevice[chl].err_code |= ERROR_YRAM_CRCCHK; + ret = -EAGAIN; + goto end; + } + + ret = crc8(tasdevice->crc8_lkp_tbl, &val, 1, 0); + +end: + return ret; +} + +static void set_err_prg_cfg(unsigned int type, struct tasdevice *dev) +{ + if ((type == MAIN_ALL_DEVICES) || (type == MAIN_DEVICE_A) + || (type == MAIN_DEVICE_B) || (type == MAIN_DEVICE_C) + || (type == MAIN_DEVICE_D)) + dev->cur_prog = -1; + else + dev->cur_conf = -1; +} + +static int tasdev_bytes_chksum(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, int chn, unsigned char book, + unsigned char page, unsigned char reg, unsigned int len, + unsigned char val, unsigned char *crc_chksum) +{ + int ret; + + if (len > 1) + ret = tasdev_multibytes_chksum(tas_priv, chn, book, page, reg, + len); + else + ret = do_singlereg_checksum(tas_priv, chn, book, page, reg, + val); + + if (ret > 0) { + *crc_chksum += (unsigned char)ret; + goto end; + } + + if (ret != -EAGAIN) + goto end; + + block->nr_retry--; + if (block->nr_retry > 0) + goto end; + + set_err_prg_cfg(block->type, &tas_priv->tasdevice[chn]); + +end: + return ret; +} + +static int tasdev_multibytes_wr(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, int chn, unsigned char book, + unsigned char page, unsigned char reg, unsigned char *data, + unsigned int len, unsigned int *nr_cmds, + unsigned char *crc_chksum) +{ + int ret; + + if (len > 1) { + ret = tasdevice_dev_bulk_write(tas_priv, chn, + TASDEVICE_REG(book, page, reg), data + 3, len); + if (ret < 0) + goto end; + if (block->is_ychksum_present) + ret = tasdev_bytes_chksum(tas_priv, block, chn, + book, page, reg, len, 0, crc_chksum); + } else { + ret = tasdevice_dev_write(tas_priv, chn, + TASDEVICE_REG(book, page, reg), data[3]); + if (ret < 0) + goto end; + if (block->is_ychksum_present) + ret = tasdev_bytes_chksum(tas_priv, block, chn, book, + page, reg, 1, data[3], crc_chksum); + } + + if (!block->is_ychksum_present || ret >= 0) { + *nr_cmds += 1; + if (len >= 2) + *nr_cmds += ((len - 2) / 4) + 1; + } + +end: + return ret; +} + +static int tasdev_block_chksum(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, int chn) +{ + unsigned int nr_value; + int ret; + + ret = tasdevice_dev_read(tas_priv, chn, TASDEVICE_I2CChecksum, + &nr_value); + if (ret < 0) { + dev_err(tas_priv->dev, "%s: Chn %d\n", __func__, chn); + set_err_prg_cfg(block->type, &tas_priv->tasdevice[chn]); + goto end; + } + + if ((nr_value & 0xff) != block->pchksum) { + dev_err(tas_priv->dev, "%s: Blk PChkSum Chn %d ", __func__, + chn); + dev_err(tas_priv->dev, "PChkSum = 0x%x, Reg = 0x%x\n", + block->pchksum, (nr_value & 0xff)); + tas_priv->tasdevice[chn].err_code |= ERROR_PRAM_CRCCHK; + ret = -EAGAIN; + block->nr_retry--; + + if (block->nr_retry <= 0) + set_err_prg_cfg(block->type, + &tas_priv->tasdevice[chn]); + } else + tas_priv->tasdevice[chn].err_code &= ~ERROR_PRAM_CRCCHK; + +end: + return ret; +} + +static int tasdev_load_blk(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block, int chn) +{ + unsigned int sleep_time; + unsigned int len; + unsigned int nr_cmds; + unsigned char *data = block->data; + unsigned char crc_chksum = 0; + unsigned char offset; + unsigned char book; + unsigned char page; + unsigned char val; + int ret = 0; + + while (block->nr_retry > 0) { + if (block->is_pchksum_present) { + ret = tasdevice_dev_write(tas_priv, chn, + TASDEVICE_I2CChecksum, 0); + if (ret < 0) + break; + } + + if (block->is_ychksum_present) + crc_chksum = 0; + + nr_cmds = 0; + + while (nr_cmds < block->nr_cmds) { + data = block->data + nr_cmds * 4; + + book = data[0]; + page = data[1]; + offset = data[2]; + val = data[3]; + + nr_cmds++; + /*Single byte write*/ + if (offset <= 0x7F) { + ret = tasdevice_dev_write(tas_priv, chn, + TASDEVICE_REG(book, page, offset), + val); + if (ret < 0) + goto end; + if (block->is_ychksum_present) { + ret = tasdev_bytes_chksum(tas_priv, + block, chn, book, page, offset, + 1, val, &crc_chksum); + if (ret < 0) + break; + } + continue; + } + /*sleep command*/ + if (offset == 0x81) { + /*book -- data[0] page -- data[1]*/ + sleep_time = ((book << 8) + page)*1000; + usleep_range(sleep_time, sleep_time + 50); + continue; + } + /*Multiple bytes write*/ + if (offset == 0x85) { + data += 4; + len = (book << 8) + page; + book = data[0]; + page = data[1]; + offset = data[2]; + ret = tasdev_multibytes_wr(tas_priv, + block, chn, book, page, offset, data, + len, &nr_cmds, &crc_chksum); + if (ret < 0) + break; + } + } + if (ret == -EAGAIN) { + if (block->nr_retry > 0) + continue; + } else if (ret < 0) /*err in current device, skip it*/ + break; + + if (block->is_pchksum_present) { + ret = tasdev_block_chksum(tas_priv, block, chn); + if (ret == -EAGAIN) { + if (block->nr_retry > 0) + continue; + } else if (ret < 0) /*err in current device, skip it*/ + break; + } + + if (block->is_ychksum_present) { + /* TBD, open it when FW ready */ + dev_err(tas_priv->dev, + "Blk YChkSum: FW = 0x%x, YCRC = 0x%x\n", + block->ychksum, crc_chksum); + + tas_priv->tasdevice[chn].err_code &= + ~ERROR_YRAM_CRCCHK; + ret = 0; + } + /*skip current blk*/ + break; + } + +end: + return ret; +} + +static int tasdevice_load_block(struct tasdevice_priv *tas_priv, + struct tasdev_blk *block) +{ + int chnend = 0; + int ret = 0; + int chn = 0; + int rc = 0; + + switch (block->type) { + case MAIN_ALL_DEVICES: + chn = 0; + chnend = tas_priv->ndev; + break; + case MAIN_DEVICE_A: + case COEFF_DEVICE_A: + case PRE_DEVICE_A: + chn = 0; + chnend = 1; + break; + case MAIN_DEVICE_B: + case COEFF_DEVICE_B: + case PRE_DEVICE_B: + chn = 1; + chnend = 2; + break; + case MAIN_DEVICE_C: + case COEFF_DEVICE_C: + case PRE_DEVICE_C: + chn = 2; + chnend = 3; + break; + case MAIN_DEVICE_D: + case COEFF_DEVICE_D: + case PRE_DEVICE_D: + chn = 3; + chnend = 4; + break; + default: + dev_dbg(tas_priv->dev, "load blk: Other Type = 0x%02x\n", + block->type); + break; + } + + for (; chn < chnend; chn++) { + block->nr_retry = 6; + if (tas_priv->tasdevice[chn].is_loading == false) + continue; + ret = tasdev_load_blk(tas_priv, block, chn); + if (ret < 0) + dev_err(tas_priv->dev, "dev %d, Blk (%d) load error\n", + chn, block->type); + rc |= ret; + } + + return rc; +} + +static int dspfw_default_callback(struct tasdevice_priv *tas_priv, + unsigned int drv_ver, unsigned int ppcver) +{ + int rc = 0; + + if (drv_ver == 0x100) { + if (ppcver >= PPC3_VERSION) { + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_kernel; + tas_priv->fw_parse_program_data = + fw_parse_program_data_kernel; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data_kernel; + tas_priv->tasdevice_load_block = + tasdevice_load_block_kernel; + } else { + switch (ppcver) { + case 0x00: + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_git; + tas_priv->fw_parse_program_data = + fw_parse_program_data; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data; + tas_priv->tasdevice_load_block = + tasdevice_load_block; + break; + default: + dev_err(tas_priv->dev, + "%s: PPCVer must be 0x0 or 0x%02x", + __func__, PPC3_VERSION); + dev_err(tas_priv->dev, " Current:0x%02x\n", + ppcver); + rc = -EINVAL; + break; + } + } + } else { + dev_err(tas_priv->dev, + "DrvVer must be 0x0, 0x230 or above 0x230 "); + dev_err(tas_priv->dev, "current is 0x%02x\n", drv_ver); + rc = -EINVAL; + } + + return rc; +} + +static int load_calib_data(struct tasdevice_priv *tas_priv, + struct tasdevice_data *dev_data) +{ + struct tasdev_blk *block; + unsigned int i; + int ret = 0; + + for (i = 0; i < dev_data->nr_blk; i++) { + block = &(dev_data->dev_blks[i]); + ret = tasdevice_load_block(tas_priv, block); + if (ret < 0) + break; + } + + return ret; +} + +static int fw_parse_header(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + struct tasdevice_dspfw_hdr *fw_hdr = &(tas_fmw->fw_hdr); + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr = &(fw_hdr->fixed_hdr); + const unsigned char magic_number[] = { 0x35, 0x35, 0x35, 0x32 }; + const unsigned char *buf = (unsigned char *)fmw->data; + + if (offset + 92 > fmw->size) { + dev_err(tas_priv->dev, "%s: File Size error\n", __func__); + offset = -EINVAL; + goto out; + } + if (memcmp(&buf[offset], magic_number, 4)) { + dev_err(tas_priv->dev, "%s: Magic num NOT match\n", __func__); + offset = -EINVAL; + goto out; + } + offset += 4; + + /* Convert data[offset], data[offset + 1], data[offset + 2] and + * data[offset + 3] into host + */ + fw_fixed_hdr->fwsize = be32_to_cpup((__be32 *)&buf[offset]); + offset += 4; + if (fw_fixed_hdr->fwsize != fmw->size) { + dev_err(tas_priv->dev, "File size not match, %lu %u", + (unsigned long)fmw->size, fw_fixed_hdr->fwsize); + offset = -EINVAL; + goto out; + } + offset += 4; + fw_fixed_hdr->ppcver = be32_to_cpup((__be32 *)&buf[offset]); + offset += 8; + fw_fixed_hdr->drv_ver = be32_to_cpup((__be32 *)&buf[offset]); + offset += 72; + + out: + return offset; +} + +static int fw_parse_variable_hdr_cal(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + struct tasdevice_dspfw_hdr *fw_hdr = &(tas_fmw->fw_hdr); + + offset = fw_parse_variable_hdr(tas_priv, fw_hdr, fmw, offset); + if (offset < 0) + goto out; + if (fw_hdr->ndev != 1) { + dev_err(tas_priv->dev, + "%s: calbin must be 1, but currently ndev(%u)\n", + __func__, fw_hdr->ndev); + offset = -EINVAL; + } + +out: + return offset; +} + +/* When calibrated data parsing error occurs, DSP can still work with default + * calibrated data, memory resource related to calibrated data will be + * released in the tasdevice_codec_remove. + */ +static int fw_parse_calibration_data(struct tasdevice_priv *tas_priv, + struct tasdevice_fw *tas_fmw, const struct firmware *fmw, int offset) +{ + struct tasdevice_calibration *calibration; + unsigned char *data = (unsigned char *)fmw->data; + unsigned int i, n; + + if (offset + 2 > fmw->size) { + dev_err(tas_priv->dev, "%s: Calibrations error\n", __func__); + offset = -EINVAL; + goto out; + } + tas_fmw->nr_calibrations = be16_to_cpup((__be16 *)&data[offset]); + offset += 2; + + if (tas_fmw->nr_calibrations != 1) { + dev_err(tas_priv->dev, + "%s: only support one calibraiton(%d)!\n", + __func__, tas_fmw->nr_calibrations); + goto out; + } + + tas_fmw->calibrations = kcalloc(tas_fmw->nr_calibrations, + sizeof(struct tasdevice_calibration), GFP_KERNEL); + if (!tas_fmw->calibrations) { + offset = -ENOMEM; + goto out; + } + for (i = 0; i < tas_fmw->nr_calibrations; i++) { + if (offset + 64 > fmw->size) { + dev_err(tas_priv->dev, "Calibrations error\n"); + offset = -EINVAL; + goto out; + } + calibration = &(tas_fmw->calibrations[i]); + offset += 64; + + n = strlen((char *)&data[offset]); + /* skip '\0' and 2 unused bytes */ + n += 3; + if (offset + n > fmw->size) { + dev_err(tas_priv->dev, "Description err\n"); + offset = -EINVAL; + goto out; + } + offset += n; + + offset = fw_parse_data(tas_fmw, &(calibration->dev_data), fmw, + offset); + if (offset < 0) + goto out; + } + +out: + return offset; +} + +int tas2781_load_calibration(void *context, char *file_name, + unsigned short i) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + struct tasdevice *tasdev = &(tas_priv->tasdevice[i]); + const struct firmware *fw_entry; + struct tasdevice_fw *tas_fmw; + struct firmware fmw; + int offset = 0; + int ret; + + ret = request_firmware(&fw_entry, file_name, tas_priv->dev); + if (ret) { + dev_err(tas_priv->dev, "%s: Request firmware %s failed\n", + __func__, file_name); + goto out; + } + + if (!fw_entry->size) { + dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", + __func__, (unsigned long)fw_entry->size); + goto out; + } + fmw.size = fw_entry->size; + fmw.data = fw_entry->data; + + tas_fmw = tasdev->cali_data_fmw = kzalloc(sizeof(struct tasdevice_fw), + GFP_KERNEL); + if (!tasdev->cali_data_fmw) { + ret = -ENOMEM; + goto out; + } + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, "fw_parse_header EXIT!\n"); + ret = offset; + goto out; + } + offset = fw_parse_variable_hdr_cal(tas_priv, tas_fmw, &fmw, offset); + if (offset == -EINVAL) { + dev_err(tas_priv->dev, + "%s: fw_parse_variable_header_cal EXIT!\n", __func__); + ret = offset; + goto out; + } + offset = fw_parse_program_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_program_data EXIT!\n"); + ret = offset; + goto out; + } + offset = fw_parse_configuration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_configuration_data EXIT!\n"); + ret = offset; + goto out; + } + offset = fw_parse_calibration_data(tas_priv, tas_fmw, &fmw, offset); + if (offset < 0) { + dev_err(tas_priv->dev, "fw_parse_calibration_data EXIT!\n"); + ret = offset; + goto out; + } + +out: + if (fw_entry) + release_firmware(fw_entry); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(tas2781_load_calibration, SND_SOC_TAS2781_FMWLIB); + +static int tasdevice_dspfw_ready(const struct firmware *fmw, + void *context) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw_fixed_hdr *fw_fixed_hdr; + struct tasdevice_fw *tas_fmw; + int offset = 0; + int ret = 0; + + if (!fmw || !fmw->data) { + dev_err(tas_priv->dev, "%s: Failed to read firmware %s\n", + __func__, tas_priv->coef_binaryname); + ret = -EINVAL; + goto out; + } + + tas_priv->fmw = kzalloc(sizeof(struct tasdevice_fw), GFP_KERNEL); + if (!tas_priv->fmw) { + ret = -ENOMEM; + goto out; + } + tas_fmw = tas_priv->fmw; + tas_fmw->dev = tas_priv->dev; + offset = fw_parse_header(tas_priv, tas_fmw, fmw, offset); + + if (offset == -EINVAL) { + ret = -EINVAL; + goto out; + } + fw_fixed_hdr = &(tas_fmw->fw_hdr.fixed_hdr); + /* Support different versions of firmware */ + switch (fw_fixed_hdr->drv_ver) { + case 0x301: + case 0x302: + case 0x502: + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_kernel; + tas_priv->fw_parse_program_data = + fw_parse_program_data_kernel; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data_kernel; + tas_priv->tasdevice_load_block = + tasdevice_load_block_kernel; + break; + case 0x202: + case 0x400: + tas_priv->fw_parse_variable_header = + fw_parse_variable_header_git; + tas_priv->fw_parse_program_data = + fw_parse_program_data; + tas_priv->fw_parse_configuration_data = + fw_parse_configuration_data; + tas_priv->tasdevice_load_block = + tasdevice_load_block; + break; + default: + ret = dspfw_default_callback(tas_priv, + fw_fixed_hdr->drv_ver, fw_fixed_hdr->ppcver); + if (ret) + goto out; + break; + } + + offset = tas_priv->fw_parse_variable_header(tas_priv, fmw, offset); + if (offset < 0) { + ret = offset; + goto out; + } + offset = tas_priv->fw_parse_program_data(tas_priv, tas_fmw, fmw, + offset); + if (offset < 0) { + ret = offset; + goto out; + } + offset = tas_priv->fw_parse_configuration_data(tas_priv, + tas_fmw, fmw, offset); + if (offset < 0) + ret = offset; + +out: + return ret; +} + +int tasdevice_dsp_parser(void *context) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *)context; + const struct firmware *fw_entry; + int ret; + + ret = request_firmware(&fw_entry, tas_priv->coef_binaryname, + tas_priv->dev); + if (ret) { + dev_err(tas_priv->dev, "%s: load %s error\n", __func__, + tas_priv->coef_binaryname); + goto out; + } + + ret = tasdevice_dspfw_ready(fw_entry, tas_priv); + release_firmware(fw_entry); + fw_entry = NULL; + +out: + return ret; +} +EXPORT_SYMBOL_NS_GPL(tasdevice_dsp_parser, SND_SOC_TAS2781_FMWLIB); + +static void tas2781_clear_calfirmware(struct tasdevice_fw *tas_fmw) +{ + struct tasdevice_calibration *calibration; + struct tasdev_blk *block; + struct tasdevice_data *im; + unsigned int blks; + int i; + + if (!tas_fmw->calibrations) + goto out; + + for (i = 0; i < tas_fmw->nr_calibrations; i++) { + calibration = &(tas_fmw->calibrations[i]); + if (!calibration) + continue; + + im = &(calibration->dev_data); + + if (!im->dev_blks) + continue; + + for (blks = 0; blks < im->nr_blk; blks++) { + block = &(im->dev_blks[blks]); + if (!block) + continue; + kfree(block->data); + } + kfree(im->dev_blks); + } + kfree(tas_fmw->calibrations); +out: + kfree(tas_fmw); +} + +void tasdevice_calbin_remove(void *context) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice *tasdev; + int i; + + if (!tas_priv) + return; + + for (i = 0; i < tas_priv->ndev; i++) { + tasdev = &(tas_priv->tasdevice[i]); + if (!tasdev->cali_data_fmw) + continue; + tas2781_clear_calfirmware(tasdev->cali_data_fmw); + tasdev->cali_data_fmw = NULL; + } +} +EXPORT_SYMBOL_NS_GPL(tasdevice_calbin_remove, SND_SOC_TAS2781_FMWLIB); + +void tasdevice_config_info_remove(void *context) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_rca *rca = &(tas_priv->rcabin); + struct tasdevice_config_info **ci = rca->cfg_info; + int i, j; + + if (!ci) + return; + for (i = 0; i < rca->ncfgs; i++) { + if (!ci[i]) + continue; + if (ci[i]->blk_data) { + for (j = 0; j < (int)ci[i]->real_nblocks; j++) { + if (!ci[i]->blk_data[j]) + continue; + kfree(ci[i]->blk_data[j]->regdata); + kfree(ci[i]->blk_data[j]); + } + kfree(ci[i]->blk_data); + } + kfree(ci[i]); + } + kfree(ci); +} +EXPORT_SYMBOL_NS_GPL(tasdevice_config_info_remove, SND_SOC_TAS2781_FMWLIB); + +static int tasdevice_load_data(struct tasdevice_priv *tas_priv, + struct tasdevice_data *dev_data) +{ + struct tasdev_blk *block; + unsigned int i; + int ret = 0; + + for (i = 0; i < dev_data->nr_blk; i++) { + block = &(dev_data->dev_blks[i]); + ret = tas_priv->tasdevice_load_block(tas_priv, block); + if (ret < 0) + break; + } + + return ret; +} + +int tasdevice_select_tuningprm_cfg(void *context, int prm_no, + int cfg_no, int rca_conf_no) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_rca *rca = &(tas_priv->rcabin); + struct tasdevice_config_info **cfg_info = rca->cfg_info; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_prog *program; + struct tasdevice_config *conf; + int prog_status = 0; + int status, i; + + if (!tas_fmw) { + dev_err(tas_priv->dev, "%s: Firmware is NULL\n", __func__); + goto out; + } + + if (cfg_no >= tas_fmw->nr_configurations) { + dev_err(tas_priv->dev, + "%s: cfg(%d) is not in range of conf %u\n", + __func__, cfg_no, tas_fmw->nr_configurations); + goto out; + } + + if (prm_no >= tas_fmw->nr_programs) { + dev_err(tas_priv->dev, + "%s: prm(%d) is not in range of Programs %u\n", + __func__, prm_no, tas_fmw->nr_programs); + goto out; + } + + if (rca_conf_no >= rca->ncfgs || rca_conf_no < 0 || + !cfg_info) { + dev_err(tas_priv->dev, + "conf_no:%d should be in range from 0 to %u\n", + rca_conf_no, rca->ncfgs-1); + goto out; + } + + conf = &(tas_fmw->configs[cfg_no]); + for (i = 0, prog_status = 0; i < tas_priv->ndev; i++) { + if (cfg_info[rca_conf_no]->active_dev & (1 << i)) { + if (tas_priv->tasdevice[i].cur_prog != prm_no + || tas_priv->force_fwload_status) { + tas_priv->tasdevice[i].cur_conf = -1; + tas_priv->tasdevice[i].is_loading = true; + prog_status++; + } + } else + tas_priv->tasdevice[i].is_loading = false; + tas_priv->tasdevice[i].is_loaderr = false; + } + + if (prog_status) { + program = &(tas_fmw->programs[prm_no]); + tasdevice_load_data(tas_priv, &(program->dev_data)); + for (i = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].is_loaderr == true) + continue; + else if (tas_priv->tasdevice[i].is_loaderr == false + && tas_priv->tasdevice[i].is_loading == true) { + struct tasdevice_fw *cal_fmw = + tas_priv->tasdevice[i].cali_data_fmw; + + if (cal_fmw) { + struct tasdevice_calibration + *cal = cal_fmw->calibrations; + + if (cal) + load_calib_data(tas_priv, + &(cal->dev_data)); + } + tas_priv->tasdevice[i].cur_prog = prm_no; + } + } + } + + for (i = 0, status = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].cur_conf != cfg_no + && (cfg_info[rca_conf_no]->active_dev & (1 << i)) + && (tas_priv->tasdevice[i].is_loaderr == false)) { + status++; + tas_priv->tasdevice[i].is_loading = true; + } else + tas_priv->tasdevice[i].is_loading = false; + } + + if (status) { + status = 0; + tasdevice_load_data(tas_priv, &(conf->dev_data)); + for (i = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].is_loaderr == true) { + status |= 1 << (i + 4); + continue; + } else if (tas_priv->tasdevice[i].is_loaderr == false + && tas_priv->tasdevice[i].is_loading == true) + tas_priv->tasdevice[i].cur_conf = cfg_no; + } + } else + dev_dbg(tas_priv->dev, "%s: Unneeded loading dsp conf %d\n", + __func__, cfg_no); + + status |= cfg_info[rca_conf_no]->active_dev; + +out: + return prog_status; +} +EXPORT_SYMBOL_NS_GPL(tasdevice_select_tuningprm_cfg, + SND_SOC_TAS2781_FMWLIB); + +int tasdevice_prmg_load(void *context, int prm_no) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_prog *program; + int prog_status = 0; + int i; + + if (!tas_fmw) { + dev_err(tas_priv->dev, "%s: Firmware is NULL\n", __func__); + goto out; + } + + if (prm_no >= tas_fmw->nr_programs) { + dev_err(tas_priv->dev, + "%s: prm(%d) is not in range of Programs %u\n", + __func__, prm_no, tas_fmw->nr_programs); + goto out; + } + + for (i = 0, prog_status = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].cur_prog != prm_no) { + tas_priv->tasdevice[i].cur_conf = -1; + tas_priv->tasdevice[i].is_loading = true; + prog_status++; + } + } + + if (prog_status) { + program = &(tas_fmw->programs[prm_no]); + tasdevice_load_data(tas_priv, &(program->dev_data)); + for (i = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].is_loaderr == true) + continue; + else if (tas_priv->tasdevice[i].is_loaderr == false + && tas_priv->tasdevice[i].is_loading == true) + tas_priv->tasdevice[i].cur_prog = prm_no; + } + } + +out: + return prog_status; +} +EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_load, SND_SOC_TAS2781_FMWLIB); + +int tasdevice_prmg_calibdata_load(void *context, int prm_no) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + struct tasdevice_prog *program; + int prog_status = 0; + int i; + + if (!tas_fmw) { + dev_err(tas_priv->dev, "%s: Firmware is NULL\n", __func__); + goto out; + } + + if (prm_no >= tas_fmw->nr_programs) { + dev_err(tas_priv->dev, + "%s: prm(%d) is not in range of Programs %u\n", + __func__, prm_no, tas_fmw->nr_programs); + goto out; + } + + for (i = 0, prog_status = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].cur_prog != prm_no) { + tas_priv->tasdevice[i].cur_conf = -1; + tas_priv->tasdevice[i].is_loading = true; + prog_status++; + } + tas_priv->tasdevice[i].is_loaderr = false; + } + + if (prog_status) { + program = &(tas_fmw->programs[prm_no]); + tasdevice_load_data(tas_priv, &(program->dev_data)); + for (i = 0; i < tas_priv->ndev; i++) { + if (tas_priv->tasdevice[i].is_loaderr == true) + continue; + else if (tas_priv->tasdevice[i].is_loaderr == false + && tas_priv->tasdevice[i].is_loading == true) { + struct tasdevice_fw *cal_fmw = + tas_priv->tasdevice[i].cali_data_fmw; + + if (cal_fmw) { + struct tasdevice_calibration *cal = + cal_fmw->calibrations; + + if (cal) + load_calib_data(tas_priv, + &(cal->dev_data)); + } + tas_priv->tasdevice[i].cur_prog = prm_no; + } + } + } + +out: + return prog_status; +} +EXPORT_SYMBOL_NS_GPL(tasdevice_prmg_calibdata_load, + SND_SOC_TAS2781_FMWLIB); + +void tasdevice_tuning_switch(void *context, int state) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_priv->fmw; + int profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + + if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) { + dev_err(tas_priv->dev, "DSP bin file not loaded\n"); + return; + } + + if (state == 0) { + if (tas_priv->cur_prog < tas_fmw->nr_programs) { + /*dsp mode or tuning mode*/ + profile_cfg_id = tas_priv->rcabin.profile_cfg_id; + tasdevice_select_tuningprm_cfg(tas_priv, + tas_priv->cur_prog, tas_priv->cur_conf, + profile_cfg_id); + } + + tasdevice_select_cfg_blk(tas_priv, profile_cfg_id, + TASDEVICE_BIN_BLK_PRE_POWER_UP); + } else + tasdevice_select_cfg_blk(tas_priv, profile_cfg_id, + TASDEVICE_BIN_BLK_PRE_SHUTDOWN); +} +EXPORT_SYMBOL_NS_GPL(tasdevice_tuning_switch, + SND_SOC_TAS2781_FMWLIB); + +MODULE_DESCRIPTION("Texas Firmware Support"); +MODULE_AUTHOR("Shenghao Ding, TI, "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From ef3bcde75d06d65f78ba38a30d5a87fb83a5cdae Mon Sep 17 00:00:00 2001 From: Shenghao Ding <13916275206@139.com> Date: Sun, 18 Jun 2023 20:28:18 +0800 Subject: ASoC: tas2781: Add tas2781 driver Create tas2781 driver. Signed-off-by: Shenghao Ding <13916275206@139.com> Link: https://lore.kernel.org/r/20230618122819.23143-3-13916275206@139.com Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 25 ++ sound/soc/codecs/Makefile | 6 + sound/soc/codecs/tas2781-comlib.c | 534 ++++++++++++++++++++++++++ sound/soc/codecs/tas2781-i2c.c | 763 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1328 insertions(+) create mode 100644 sound/soc/codecs/tas2781-comlib.c create mode 100644 sound/soc/codecs/tas2781-i2c.c (limited to 'sound') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7422cd10c1da..c8dd553ea6d2 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -237,6 +237,9 @@ config SND_SOC_ALL_CODECS imply SND_SOC_TAS2764 imply SND_SOC_TAS2770 imply SND_SOC_TAS2780 + imply SND_SOC_TAS2781_COMLIB + imply SND_SOC_TAS2781_FMWLIB + imply SND_SOC_TAS2781_I2C imply SND_SOC_TAS5086 imply SND_SOC_TAS571X imply SND_SOC_TAS5720 @@ -1730,6 +1733,28 @@ config SND_SOC_TAS2780 Enable support for Texas Instruments TAS2780 high-efficiency digital input mono Class-D audio power amplifiers. +config SND_SOC_TAS2781_COMLIB + depends on I2C + select CRC8 + select REGMAP_I2C + tristate + +config SND_SOC_TAS2781_FMWLIB + tristate + default n + +config SND_SOC_TAS2781_I2C + tristate "Texas Instruments TAS2781 speaker amplifier based on I2C" + depends on I2C + select SND_SOC_TAS2781_COMLIB + select SND_SOC_TAS2781_FMWLIB + help + Enable support for Texas Instruments TAS2781 Smart Amplifier + Digital input mono Class-D and DSP-inside audio power amplifiers. + Note the TAS2781 driver implements a flexible and configurable + algo coefficient setting, for one, two or even multiple TAS2781 + chips. + config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 0fd003d432e5..b532bbdabd74 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -273,6 +273,9 @@ snd-soc-tas5805m-objs := tas5805m.o snd-soc-tas6424-objs := tas6424.o snd-soc-tda7419-objs := tda7419.o snd-soc-tas2770-objs := tas2770.o +snd-soc-tas2781-comlib-objs := tas2781-comlib.o +snd-soc-tas2781-fmwlib-objs := tas2781-fmwlib.o +snd-soc-tas2781-i2c-objs := tas2781-i2c.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tfa989x-objs := tfa989x.o snd-soc-tlv320adc3xxx-objs := tlv320adc3xxx.o @@ -641,6 +644,9 @@ obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS2562) += snd-soc-tas2562.o obj-$(CONFIG_SND_SOC_TAS2764) += snd-soc-tas2764.o obj-$(CONFIG_SND_SOC_TAS2780) += snd-soc-tas2780.o +obj-$(CONFIG_SND_SOC_TAS2781_COMLIB) += snd-soc-tas2781-comlib.o +obj-$(CONFIG_SND_SOC_TAS2781_FMWLIB) += snd-soc-tas2781-fmwlib.o +obj-$(CONFIG_SND_SOC_TAS2781_I2C) += snd-soc-tas2781-i2c.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o diff --git a/sound/soc/codecs/tas2781-comlib.c b/sound/soc/codecs/tas2781-comlib.c new file mode 100644 index 000000000000..a88c6c28a394 --- /dev/null +++ b/sound/soc/codecs/tas2781-comlib.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// tas2781-lib.c -- TAS2781 Common functions for HDA and ASoC Audio drivers +// +// Copyright 2023 Texas Instruments, Inc. +// +// Author: Shenghao Ding + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TASDEVICE_CRC8_POLYNOMIAL 0x4d + +static const struct regmap_range_cfg tasdevice_ranges[] = { + { + .range_min = 0, + .range_max = 256 * 128, + .selector_reg = TASDEVICE_PAGE_SELECT, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 128, + }, +}; + +static const struct regmap_config tasdevice_regmap = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .ranges = tasdevice_ranges, + .num_ranges = ARRAY_SIZE(tasdevice_ranges), + .max_register = 256 * 128, +}; + +static int tasdevice_change_chn_book(struct tasdevice_priv *tas_priv, + unsigned short chn, int book) +{ + struct i2c_client *client = (struct i2c_client *)tas_priv->client; + int ret = 0; + + if (chn < tas_priv->ndev) { + struct tasdevice *tasdev = &tas_priv->tasdevice[chn]; + struct regmap *map = tas_priv->regmap; + + if (client->addr != tasdev->dev_addr) { + client->addr = tasdev->dev_addr; + if (tasdev->cur_book == book) { + ret = regmap_write(map, + TASDEVICE_PAGE_SELECT, 0); + if (ret < 0) { + dev_err(tas_priv->dev, "%s, E=%d\n", + __func__, ret); + goto out; + } + } + goto out; + } + + if (tasdev->cur_book != book) { + ret = regmap_write(map, TASDEVICE_BOOKCTL_REG, book); + if (ret < 0) { + dev_err(tas_priv->dev, "%s, E=%d\n", + __func__, ret); + goto out; + } + tasdev->cur_book = book; + } + } else { + ret = -EINVAL; + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + } + +out: + return ret; +} + +int tasdevice_dev_read(struct tasdevice_priv *tas_priv, + unsigned short chn, unsigned int reg, unsigned int *val) +{ + int ret = 0; + + if (chn < tas_priv->ndev) { + struct regmap *map = tas_priv->regmap; + + ret = tasdevice_change_chn_book(tas_priv, chn, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) + goto out; + + ret = regmap_read(map, TASDEVICE_PGRG(reg), val); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + } else { + ret = -EINVAL; + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_dev_read); + +int tasdevice_dev_write(struct tasdevice_priv *tas_priv, + unsigned short chn, unsigned int reg, unsigned int value) +{ + int ret = 0; + + if (chn < tas_priv->ndev) { + struct regmap *map = tas_priv->regmap; + + ret = tasdevice_change_chn_book(tas_priv, chn, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) + goto out; + + ret = regmap_write(map, TASDEVICE_PGRG(reg), + value); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + } else { + ret = -EINVAL; + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_dev_write); + +int tasdevice_dev_bulk_write( + struct tasdevice_priv *tas_priv, unsigned short chn, + unsigned int reg, unsigned char *data, + unsigned int len) +{ + int ret = 0; + + if (chn < tas_priv->ndev) { + struct regmap *map = tas_priv->regmap; + + ret = tasdevice_change_chn_book(tas_priv, chn, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) + goto out; + + ret = regmap_bulk_write(map, TASDEVICE_PGRG(reg), + data, len); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + } else { + ret = -EINVAL; + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_write); + +int tasdevice_dev_bulk_read(struct tasdevice_priv *tas_priv, + unsigned short chn, unsigned int reg, unsigned char *data, + unsigned int len) +{ + int ret = 0; + + if (chn < tas_priv->ndev) { + struct regmap *map = tas_priv->regmap; + + ret = tasdevice_change_chn_book(tas_priv, chn, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(map, TASDEVICE_PGRG(reg), data, len); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + } else + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_dev_bulk_read); + +int tasdevice_dev_update_bits( + struct tasdevice_priv *tas_priv, unsigned short chn, + unsigned int reg, unsigned int mask, unsigned int value) +{ + int ret = 0; + + if (chn < tas_priv->ndev) { + struct regmap *map = tas_priv->regmap; + + ret = tasdevice_change_chn_book(tas_priv, chn, + TASDEVICE_BOOK_ID(reg)); + if (ret < 0) + goto out; + + ret = regmap_update_bits(map, TASDEVICE_PGRG(reg), + mask, value); + if (ret < 0) + dev_err(tas_priv->dev, "%s, E=%d\n", __func__, ret); + } else { + dev_err(tas_priv->dev, "%s, no such channel(%d)\n", __func__, + chn); + ret = -EINVAL; + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_dev_update_bits); + +struct tasdevice_priv *tasdevice_kzalloc(struct i2c_client *i2c) +{ + struct tasdevice_priv *tas_priv; + + tas_priv = devm_kzalloc(&i2c->dev, sizeof(*tas_priv), GFP_KERNEL); + if (!tas_priv) + return NULL; + tas_priv->dev = &i2c->dev; + tas_priv->client = (void *)i2c; + + return tas_priv; +} +EXPORT_SYMBOL_GPL(tasdevice_kzalloc); + +void tas2781_reset(struct tasdevice_priv *tas_dev) +{ + int ret, i; + + if (tas_dev->reset) { + gpiod_set_value_cansleep(tas_dev->reset, 0); + usleep_range(500, 1000); + gpiod_set_value_cansleep(tas_dev->reset, 1); + } else { + for (i = 0; i < tas_dev->ndev; i++) { + ret = tasdevice_dev_write(tas_dev, i, + TAS2781_REG_SWRESET, + TAS2781_REG_SWRESET_RESET); + if (ret < 0) + dev_err(tas_dev->dev, + "dev %d swreset fail, %d\n", + i, ret); + } + } + usleep_range(1000, 1050); +} +EXPORT_SYMBOL_GPL(tas2781_reset); + +int tascodec_init(struct tasdevice_priv *tas_priv, void *codec, + void (*cont)(const struct firmware *fw, void *context)) +{ + int ret = 0; + + /* Codec Lock Hold to ensure that codec_probe and firmware parsing and + * loading do not simultaneously execute. + */ + mutex_lock(&tas_priv->codec_lock); + + scnprintf(tas_priv->rca_binaryname, 64, "%sRCA%d.bin", + tas_priv->dev_name, tas_priv->ndev); + crc8_populate_msb(tas_priv->crc8_lkp_tbl, TASDEVICE_CRC8_POLYNOMIAL); + tas_priv->codec = codec; + ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT, + tas_priv->rca_binaryname, tas_priv->dev, GFP_KERNEL, tas_priv, + cont); + if (ret) + dev_err(tas_priv->dev, "request_firmware_nowait err:0x%08x\n", + ret); + + /* Codec Lock Release*/ + mutex_unlock(&tas_priv->codec_lock); + return ret; +} +EXPORT_SYMBOL_GPL(tascodec_init); + +int tasdevice_init(struct tasdevice_priv *tas_priv) +{ + int ret = 0; + int i; + + tas_priv->regmap = devm_regmap_init_i2c(tas_priv->client, + &tasdevice_regmap); + if (IS_ERR(tas_priv->regmap)) { + ret = PTR_ERR(tas_priv->regmap); + dev_err(tas_priv->dev, "Failed to allocate register map: %d\n", + ret); + goto out; + } + + tas_priv->cur_prog = -1; + tas_priv->cur_conf = -1; + + for (i = 0; i < tas_priv->ndev; i++) { + tas_priv->tasdevice[i].cur_book = -1; + tas_priv->tasdevice[i].cur_prog = -1; + tas_priv->tasdevice[i].cur_conf = -1; + } + + dev_set_drvdata(tas_priv->dev, tas_priv); + + mutex_init(&tas_priv->codec_lock); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(tasdevice_init); + +static void tasdev_dsp_prog_blk_remove(struct tasdevice_prog *prog) +{ + struct tasdevice_data *tas_dt; + struct tasdev_blk *blk; + unsigned int i; + + if (!prog) + return; + + tas_dt = &(prog->dev_data); + + if (!tas_dt->dev_blks) + return; + + for (i = 0; i < tas_dt->nr_blk; i++) { + blk = &(tas_dt->dev_blks[i]); + kfree(blk->data); + } + kfree(tas_dt->dev_blks); +} + +static void tasdev_dsp_prog_remove(struct tasdevice_prog *prog, + unsigned short nr) +{ + int i; + + for (i = 0; i < nr; i++) + tasdev_dsp_prog_blk_remove(&prog[i]); + kfree(prog); +} + +static void tasdev_dsp_cfg_blk_remove(struct tasdevice_config *cfg) +{ + struct tasdevice_data *tas_dt; + struct tasdev_blk *blk; + unsigned int i; + + if (cfg) { + tas_dt = &(cfg->dev_data); + + if (!tas_dt->dev_blks) + return; + + for (i = 0; i < tas_dt->nr_blk; i++) { + blk = &(tas_dt->dev_blks[i]); + kfree(blk->data); + } + kfree(tas_dt->dev_blks); + } +} + +static void tasdev_dsp_cfg_remove(struct tasdevice_config *config, + unsigned short nr) +{ + int i; + + for (i = 0; i < nr; i++) + tasdev_dsp_cfg_blk_remove(&config[i]); + kfree(config); +} + +void tasdevice_dsp_remove(void *context) +{ + struct tasdevice_priv *tas_dev = (struct tasdevice_priv *) context; + struct tasdevice_fw *tas_fmw = tas_dev->fmw; + + if (!tas_dev->fmw) + return; + + if (tas_fmw->programs) + tasdev_dsp_prog_remove(tas_fmw->programs, + tas_fmw->nr_programs); + if (tas_fmw->configs) + tasdev_dsp_cfg_remove(tas_fmw->configs, + tas_fmw->nr_configurations); + kfree(tas_fmw); + tas_dev->fmw = NULL; +} +EXPORT_SYMBOL_GPL(tasdevice_dsp_remove); + +void tasdevice_remove(struct tasdevice_priv *tas_priv) +{ + if (gpio_is_valid(tas_priv->irq_info.irq_gpio)) + gpio_free(tas_priv->irq_info.irq_gpio); + kfree(tas_priv->acpi_subsystem_id); + mutex_destroy(&tas_priv->codec_lock); +} +EXPORT_SYMBOL_GPL(tasdevice_remove); + +static int tasdevice_clamp(int val, int max, unsigned int invert) +{ + if (val > max) + val = max; + if (invert) + val = max - val; + if (val < 0) + val = 0; + return val; +} + +int tasdevice_amp_putvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + unsigned char mask; + int max = mc->max; + int err_cnt = 0; + int val, i, ret; + + mask = (1 << fls(max)) - 1; + mask <<= mc->shift; + val = tasdevice_clamp(ucontrol->value.integer.value[0], max, invert); + for (i = 0; i < tas_priv->ndev; i++) { + ret = tasdevice_dev_update_bits(tas_priv, i, + mc->reg, mask, (unsigned int)(val << mc->shift)); + if (!ret) + continue; + err_cnt++; + dev_err(tas_priv->dev, "set AMP vol error in dev %d\n", i); + } + + /* All the devices set error, return 0 */ + return (err_cnt == tas_priv->ndev) ? 0 : 1; +} +EXPORT_SYMBOL_GPL(tasdevice_amp_putvol); + +int tasdevice_amp_getvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + unsigned char mask = 0; + int max = mc->max; + int ret = 0; + int val; + + /* Read the primary device */ + ret = tasdevice_dev_read(tas_priv, 0, mc->reg, &val); + if (ret) { + dev_err(tas_priv->dev, "%s, get AMP vol error\n", __func__); + goto out; + } + + mask = (1 << fls(max)) - 1; + mask <<= mc->shift; + val = (val & mask) >> mc->shift; + val = tasdevice_clamp(val, max, invert); + ucontrol->value.integer.value[0] = val; + +out: + return ret; + +} +EXPORT_SYMBOL_GPL(tasdevice_amp_getvol); + +int tasdevice_digital_putvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + int max = mc->max; + int err_cnt = 0; + int ret; + int val, i; + + val = tasdevice_clamp(ucontrol->value.integer.value[0], max, invert); + + for (i = 0; i < tas_priv->ndev; i++) { + ret = tasdevice_dev_write(tas_priv, i, mc->reg, + (unsigned int)val); + if (!ret) + continue; + err_cnt++; + dev_err(tas_priv->dev, + "set digital vol err in dev %d\n", i); + } + + /* All the devices set error, return 0 */ + return (err_cnt == tas_priv->ndev) ? 0 : 1; + +} +EXPORT_SYMBOL_GPL(tasdevice_digital_putvol); + +int tasdevice_digital_getvol(struct tasdevice_priv *tas_priv, + struct snd_ctl_elem_value *ucontrol, struct soc_mixer_control *mc) +{ + unsigned int invert = mc->invert; + int max = mc->max; + int ret, val; + + /* Read the primary device as the whole */ + ret = tasdevice_dev_read(tas_priv, 0, mc->reg, &val); + if (ret) { + dev_err(tas_priv->dev, "%s, get digital vol error\n", + __func__); + goto out; + } + + val = tasdevice_clamp(val, max, invert); + ucontrol->value.integer.value[0] = val; + +out: + return ret; + +} +EXPORT_SYMBOL_GPL(tasdevice_digital_getvol); + +MODULE_DESCRIPTION("TAS2781 common library"); +MODULE_AUTHOR("Shenghao Ding, TI, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas2781-i2c.c b/sound/soc/codecs/tas2781-i2c.c new file mode 100644 index 000000000000..4c59429a42b7 --- /dev/null +++ b/sound/soc/codecs/tas2781-i2c.c @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// ALSA SoC Texas Instruments TAS2781 Audio Smart Amplifier +// +// Copyright (C) 2022 - 2023 Texas Instruments Incorporated +// https://www.ti.com +// +// The TAS2781 driver implements a flexible and configurable +// algo coefficient setting for one, two, or even multiple +// TAS2781 chips. +// +// Author: Shenghao Ding +// Author: Kevin Lu +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct i2c_device_id tasdevice_id[] = { + { "tas2781", TAS2781 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, tasdevice_id); + +#ifdef CONFIG_OF +static const struct of_device_id tasdevice_of_match[] = { + { .compatible = "ti,tas2781" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tasdevice_of_match); +#endif + +/** + * tas2781_digital_getvol - get the volum control + * @kcontrol: control pointer + * @ucontrol: User data + * Customer Kcontrol for tas2781 is primarily for regmap booking, paging + * depends on internal regmap mechanism. + * tas2781 contains book and page two-level register map, especially + * book switching will set the register BXXP00R7F, after switching to the + * correct book, then leverage the mechanism for paging to access the + * register. + */ +static int tas2781_digital_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_digital_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_digital_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_digital_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_getvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_amp_getvol(tas_priv, ucontrol, mc); +} + +static int tas2781_amp_putvol(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = + snd_soc_component_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + + return tasdevice_amp_putvol(tas_priv, ucontrol, mc); +} + +static int tas2781_force_fwload_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = + snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = (int)tas_priv->force_fwload_status; + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + tas_priv->force_fwload_status ? "ON" : "OFF"); + + return 0; +} + +static int tas2781_force_fwload_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = + snd_soc_component_get_drvdata(component); + bool change, val = (bool)ucontrol->value.integer.value[0]; + + if (tas_priv->force_fwload_status == val) + change = false; + else { + change = true; + tas_priv->force_fwload_status = val; + } + dev_dbg(tas_priv->dev, "%s : Force FWload %s\n", __func__, + tas_priv->force_fwload_status ? "ON" : "OFF"); + + return change; +} + +static const struct snd_kcontrol_new tas2781_snd_controls[] = { + SOC_SINGLE_RANGE_EXT_TLV("Speaker Analog Gain", TAS2781_AMP_LEVEL, + 1, 0, 20, 0, tas2781_amp_getvol, + tas2781_amp_putvol, amp_vol_tlv), + SOC_SINGLE_RANGE_EXT_TLV("Speaker Digital Gain", TAS2781_DVC_LVL, + 0, 0, 200, 1, tas2781_digital_getvol, + tas2781_digital_putvol, dvc_tlv), + SOC_SINGLE_BOOL_EXT("Speaker Force Firmware Load", 0, + tas2781_force_fwload_get, tas2781_force_fwload_put), +}; + +static int tasdevice_set_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int ret = 0; + + if (tas_priv->rcabin.profile_cfg_id != + ucontrol->value.integer.value[0]) { + tas_priv->rcabin.profile_cfg_id = + ucontrol->value.integer.value[0]; + ret = 1; + } + + return ret; +} + +static int tasdevice_info_programs(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = (int)tas_fw->nr_programs; + + return 0; +} + +static int tasdevice_info_configurations( + struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *codec = + snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + struct tasdevice_fw *tas_fw = tas_priv->fmw; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = (int)tas_fw->nr_configurations - 1; + + return 0; +} + +static int tasdevice_info_profile(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = tas_priv->rcabin.ncfgs - 1; + + return 0; +} + +static int tasdevice_get_profile_id(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tas_priv->rcabin.profile_cfg_id; + + return 0; +} + +static int tasdevice_create_control(struct tasdevice_priv *tas_priv) +{ + struct snd_kcontrol_new *prof_ctrls; + int nr_controls = 1; + int mix_index = 0; + int ret; + char *name; + + prof_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, + sizeof(prof_ctrls[0]), GFP_KERNEL); + if (!prof_ctrls) { + ret = -ENOMEM; + goto out; + } + + /* Create a mixer item for selecting the active profile */ + name = devm_kzalloc(tas_priv->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + GFP_KERNEL); + if (!name) { + ret = -ENOMEM; + goto out; + } + scnprintf(name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, "Speaker Profile Id"); + prof_ctrls[mix_index].name = name; + prof_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + prof_ctrls[mix_index].info = tasdevice_info_profile; + prof_ctrls[mix_index].get = tasdevice_get_profile_id; + prof_ctrls[mix_index].put = tasdevice_set_profile_id; + mix_index++; + + ret = snd_soc_add_component_controls(tas_priv->codec, + prof_ctrls, nr_controls < mix_index ? nr_controls : mix_index); + +out: + return ret; +} + +static int tasdevice_program_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tas_priv->cur_prog; + + return 0; +} + +static int tasdevice_program_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + unsigned int nr_program = ucontrol->value.integer.value[0]; + int ret = 0; + + if (tas_priv->cur_prog != nr_program) { + tas_priv->cur_prog = nr_program; + ret = 1; + } + + return ret; +} + +static int tasdevice_configuration_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + ucontrol->value.integer.value[0] = tas_priv->cur_conf; + + return 0; +} + +static int tasdevice_configuration_put( + struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *codec = snd_soc_kcontrol_component(kcontrol); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + unsigned int nr_configuration = ucontrol->value.integer.value[0]; + int ret = 0; + + if (tas_priv->cur_conf != nr_configuration) { + tas_priv->cur_conf = nr_configuration; + ret = 1; + } + + return ret; +} + +static int tasdevice_dsp_create_ctrls( + struct tasdevice_priv *tas_priv) +{ + struct snd_kcontrol_new *dsp_ctrls; + char *prog_name, *conf_name; + int nr_controls = 2; + int mix_index = 0; + int ret; + + /* Alloc kcontrol via devm_kzalloc, which don't manually + * free the kcontrol + */ + dsp_ctrls = devm_kcalloc(tas_priv->dev, nr_controls, + sizeof(dsp_ctrls[0]), GFP_KERNEL); + if (!dsp_ctrls) { + ret = -ENOMEM; + goto out; + } + + /* Create a mixer item for selecting the active profile */ + prog_name = devm_kzalloc(tas_priv->dev, + SNDRV_CTL_ELEM_ID_NAME_MAXLEN, GFP_KERNEL); + conf_name = devm_kzalloc(tas_priv->dev, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + GFP_KERNEL); + if (!prog_name || !conf_name) { + ret = -ENOMEM; + goto out; + } + + scnprintf(prog_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Speaker Program Id"); + dsp_ctrls[mix_index].name = prog_name; + dsp_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + dsp_ctrls[mix_index].info = tasdevice_info_programs; + dsp_ctrls[mix_index].get = tasdevice_program_get; + dsp_ctrls[mix_index].put = tasdevice_program_put; + mix_index++; + + scnprintf(conf_name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Speaker Config Id"); + dsp_ctrls[mix_index].name = conf_name; + dsp_ctrls[mix_index].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + dsp_ctrls[mix_index].info = tasdevice_info_configurations; + dsp_ctrls[mix_index].get = tasdevice_configuration_get; + dsp_ctrls[mix_index].put = tasdevice_configuration_put; + mix_index++; + + ret = snd_soc_add_component_controls(tas_priv->codec, dsp_ctrls, + nr_controls < mix_index ? nr_controls : mix_index); + +out: + return ret; +} + +static void tasdevice_fw_ready(const struct firmware *fmw, + void *context) +{ + struct tasdevice_priv *tas_priv = context; + int ret = 0; + int i; + + mutex_lock(&tas_priv->codec_lock); + + ret = tasdevice_rca_parser(tas_priv, fmw); + if (ret) + goto out; + tasdevice_create_control(tas_priv); + + tasdevice_dsp_remove(tas_priv); + tasdevice_calbin_remove(tas_priv); + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; + scnprintf(tas_priv->coef_binaryname, 64, "%s_coef.bin", + tas_priv->dev_name); + ret = tasdevice_dsp_parser(tas_priv); + if (ret) { + dev_err(tas_priv->dev, "dspfw load %s error\n", + tas_priv->coef_binaryname); + tas_priv->fw_state = TASDEVICE_DSP_FW_FAIL; + goto out; + } + tasdevice_dsp_create_ctrls(tas_priv); + + tas_priv->fw_state = TASDEVICE_DSP_FW_ALL_OK; + + /* If calibrated data occurs error, dsp will still works with default + * calibrated data inside algo. + */ + for (i = 0; i < tas_priv->ndev; i++) { + scnprintf(tas_priv->cal_binaryname[i], 64, "%s_cal_0x%02x.bin", + tas_priv->dev_name, tas_priv->tasdevice[i].dev_addr); + ret = tas2781_load_calibration(tas_priv, + tas_priv->cal_binaryname[i], i); + if (ret != 0) + dev_err(tas_priv->dev, + "%s: load %s error, default will effect\n", + __func__, tas_priv->cal_binaryname[i]); + } + + tasdevice_prmg_calibdata_load(tas_priv, 0); + tas_priv->cur_prog = 0; +out: + if (tas_priv->fw_state == TASDEVICE_DSP_FW_FAIL) { + /*If DSP FW fail, kcontrol won't be created */ + tasdevice_config_info_remove(tas_priv); + tasdevice_dsp_remove(tas_priv); + } + mutex_unlock(&tas_priv->codec_lock); + if (fmw) + release_firmware(fmw); +} + +static int tasdevice_dapm_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *codec = snd_soc_dapm_to_component(w->dapm); + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int state = 0; + + /* Codec Lock Hold */ + mutex_lock(&tas_priv->codec_lock); + if (event == SND_SOC_DAPM_PRE_PMD) + state = 1; + tasdevice_tuning_switch(tas_priv, state); + /* Codec Lock Release*/ + mutex_unlock(&tas_priv->codec_lock); + + return 0; +} + +static const struct snd_soc_dapm_widget tasdevice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI", "ASI Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT_E("ASI OUT", "ASI Capture", 0, SND_SOC_NOPM, + 0, 0, tasdevice_dapm_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SPK("SPK", tasdevice_dapm_event), + SND_SOC_DAPM_OUTPUT("OUT"), + SND_SOC_DAPM_INPUT("DMIC") +}; + +static const struct snd_soc_dapm_route tasdevice_audio_map[] = { + {"SPK", NULL, "ASI"}, + {"OUT", NULL, "SPK"}, + {"ASI OUT", NULL, "DMIC"} +}; + +static int tasdevice_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *codec = dai->component; + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + int ret = 0; + + if (tas_priv->fw_state != TASDEVICE_DSP_FW_ALL_OK) { + dev_err(tas_priv->dev, "DSP bin file not loaded\n"); + ret = -EINVAL; + } + + return ret; +} + +static int tasdevice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct tasdevice_priv *tas_priv = snd_soc_dai_get_drvdata(dai); + unsigned int slot_width; + unsigned int fsrate; + int bclk_rate; + int rc = 0; + + fsrate = params_rate(params); + switch (fsrate) { + case 48000: + case 44100: + break; + default: + dev_err(tas_priv->dev, "%s: incorrect sample rate = %u\n", + __func__, fsrate); + rc = -EINVAL; + goto out; + } + + slot_width = params_width(params); + switch (slot_width) { + case 16: + case 20: + case 24: + case 32: + break; + default: + dev_err(tas_priv->dev, "%s: incorrect slot width = %u\n", + __func__, slot_width); + rc = -EINVAL; + goto out; + } + + bclk_rate = snd_soc_params_to_bclk(params); + if (bclk_rate < 0) { + dev_err(tas_priv->dev, "%s: incorrect bclk rate = %d\n", + __func__, bclk_rate); + rc = bclk_rate; + goto out; + } + +out: + return rc; +} + +static int tasdevice_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct tasdevice_priv *tas_priv = snd_soc_dai_get_drvdata(codec_dai); + + tas_priv->sysclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops tasdevice_dai_ops = { + .startup = tasdevice_startup, + .hw_params = tasdevice_hw_params, + .set_sysclk = tasdevice_set_dai_sysclk, +}; + +static struct snd_soc_dai_driver tasdevice_dai_driver[] = { + { + .name = "tas2781_codec", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 4, + .rates = TASDEVICE_RATES, + .formats = TASDEVICE_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 4, + .rates = TASDEVICE_RATES, + .formats = TASDEVICE_FORMATS, + }, + .ops = &tasdevice_dai_ops, + .symmetric_rate = 1, + }, +}; + +static int tasdevice_codec_probe(struct snd_soc_component *codec) +{ + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + return tascodec_init(tas_priv, codec, tasdevice_fw_ready); +} + +static void tasdevice_deinit(void *context) +{ + struct tasdevice_priv *tas_priv = (struct tasdevice_priv *) context; + + tasdevice_config_info_remove(tas_priv); + tasdevice_dsp_remove(tas_priv); + tasdevice_calbin_remove(tas_priv); + tas_priv->fw_state = TASDEVICE_DSP_FW_PENDING; +} + +static void tasdevice_codec_remove( + struct snd_soc_component *codec) +{ + struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(codec); + + tasdevice_deinit(tas_priv); +} + +static const struct snd_soc_component_driver + soc_codec_driver_tasdevice = { + .probe = tasdevice_codec_probe, + .remove = tasdevice_codec_remove, + .controls = tas2781_snd_controls, + .num_controls = ARRAY_SIZE(tas2781_snd_controls), + .dapm_widgets = tasdevice_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tasdevice_dapm_widgets), + .dapm_routes = tasdevice_audio_map, + .num_dapm_routes = ARRAY_SIZE(tasdevice_audio_map), + .idle_bias_on = 1, + .endianness = 1, +}; + +static void tasdevice_parse_dt(struct tasdevice_priv *tas_priv) +{ + struct i2c_client *client = (struct i2c_client *)tas_priv->client; + unsigned int dev_addrs[TASDEVICE_MAX_CHANNELS]; + int rc, i, ndev = 0; + + if (tas_priv->isacpi) { + ndev = device_property_read_u32_array(&client->dev, + "ti,audio-slots", NULL, 0); + if (ndev <= 0) { + ndev = 1; + dev_addrs[0] = client->addr; + } else { + ndev = (ndev < ARRAY_SIZE(dev_addrs)) + ? ndev : ARRAY_SIZE(dev_addrs); + ndev = device_property_read_u32_array(&client->dev, + "ti,audio-slots", dev_addrs, ndev); + } + + tas_priv->irq_info.irq_gpio = + acpi_dev_gpio_irq_get(ACPI_COMPANION(&client->dev), 0); + } else { + struct device_node *np = tas_priv->dev->of_node; +#ifdef CONFIG_OF + const __be32 *reg, *reg_end; + int len, sw, aw; + + aw = of_n_addr_cells(np); + sw = of_n_size_cells(np); + if (sw == 0) { + reg = (const __be32 *)of_get_property(np, + "reg", &len); + reg_end = reg + len/sizeof(*reg); + ndev = 0; + do { + dev_addrs[ndev] = of_read_number(reg, aw); + reg += aw; + ndev++; + } while (reg < reg_end); + } else { + ndev = 1; + dev_addrs[0] = client->addr; + } +#else + ndev = 1; + dev_addrs[0] = client->addr; +#endif + tas_priv->irq_info.irq_gpio = of_irq_get(np, 0); + } + tas_priv->ndev = ndev; + for (i = 0; i < ndev; i++) + tas_priv->tasdevice[i].dev_addr = dev_addrs[i]; + + tas_priv->reset = devm_gpiod_get_optional(&client->dev, + "reset-gpios", GPIOD_OUT_HIGH); + if (IS_ERR(tas_priv->reset)) + dev_err(tas_priv->dev, "%s Can't get reset GPIO\n", + __func__); + + strcpy(tas_priv->dev_name, tasdevice_id[tas_priv->chip_id].name); + + if (gpio_is_valid(tas_priv->irq_info.irq_gpio)) { + rc = gpio_request(tas_priv->irq_info.irq_gpio, + "AUDEV-IRQ"); + if (!rc) { + gpio_direction_input( + tas_priv->irq_info.irq_gpio); + + tas_priv->irq_info.irq = + gpio_to_irq(tas_priv->irq_info.irq_gpio); + } else + dev_err(tas_priv->dev, "%s: GPIO %d request error\n", + __func__, tas_priv->irq_info.irq_gpio); + } else + dev_err(tas_priv->dev, + "Looking up irq-gpio property failed %d\n", + tas_priv->irq_info.irq_gpio); +} + +static int tasdevice_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_match_id(tasdevice_id, i2c); + const struct acpi_device_id *acpi_id; + struct tasdevice_priv *tas_priv; + int ret; + + tas_priv = tasdevice_kzalloc(i2c); + if (!tas_priv) + return -ENOMEM; + + if (ACPI_HANDLE(&i2c->dev)) { + acpi_id = acpi_match_device(i2c->dev.driver->acpi_match_table, + &i2c->dev); + if (!acpi_id) { + dev_err(&i2c->dev, "No driver data\n"); + ret = -EINVAL; + goto err; + } + tas_priv->chip_id = acpi_id->driver_data; + tas_priv->isacpi = true; + } else { + tas_priv->chip_id = id ? id->driver_data : 0; + tas_priv->isacpi = false; + } + + tasdevice_parse_dt(tas_priv); + + ret = tasdevice_init(tas_priv); + if (ret) + goto err; + + ret = devm_snd_soc_register_component(tas_priv->dev, + &soc_codec_driver_tasdevice, + tasdevice_dai_driver, ARRAY_SIZE(tasdevice_dai_driver)); + if (ret) { + dev_err(tas_priv->dev, "%s: codec register error:0x%08x\n", + __func__, ret); + goto err; + } +err: + if (ret < 0) + tasdevice_remove(tas_priv); + return ret; +} + +static void tasdevice_i2c_remove(struct i2c_client *client) +{ + struct tasdevice_priv *tas_priv = i2c_get_clientdata(client); + + tasdevice_remove(tas_priv); +} + +#ifdef CONFIG_ACPI +static const struct acpi_device_id tasdevice_acpi_match[] = { + { "TAS2781", TAS2781 }, + {}, +}; + +MODULE_DEVICE_TABLE(acpi, tasdevice_acpi_match); +#endif + +static struct i2c_driver tasdevice_i2c_driver = { + .driver = { + .name = "tas2781-codec", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tasdevice_of_match), +#ifdef CONFIG_ACPI + .acpi_match_table = ACPI_PTR(tasdevice_acpi_match), +#endif + }, + .probe = tasdevice_i2c_probe, + .remove = tasdevice_i2c_remove, + .id_table = tasdevice_id, +}; + +module_i2c_driver(tasdevice_i2c_driver); + +MODULE_AUTHOR("Shenghao Ding "); +MODULE_AUTHOR("Kevin Lu "); +MODULE_DESCRIPTION("ASoC TAS2781 Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(SND_SOC_TAS2781_FMWLIB); -- cgit v1.2.3 From 1650e8a8818d516219b2c0cbc203f53cc6cd77a0 Mon Sep 17 00:00:00 2001 From: YingKun Meng Date: Mon, 19 Jun 2023 15:46:49 +0800 Subject: ASoC: loongson: change the type of variable irq to int We use variable 'irq' to store the return value of fwnode_get_irq_byname(). A negative value indicates that the operation failed. If the type of 'irq' is unsigned int, we never know if the operation failed. Reported-by: Harshit Mogalapalli Closes: https://lore.kernel.org/loongarch/325dd825-6fa5-0ebc-4b7e-7acf2d2840e4@loongson.cn/ Signed-off-by: YingKun Meng Link: https://lore.kernel.org/r/20230619074649.3608726-1-mengyingkun@loongson.cn Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_i2s.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_i2s.h b/sound/soc/loongson/loongson_i2s.h index 52788f6a94ad..89492eebf834 100644 --- a/sound/soc/loongson/loongson_i2s.h +++ b/sound/soc/loongson/loongson_i2s.h @@ -45,7 +45,7 @@ struct loongson_dma_data { dma_addr_t dev_addr; /* device physical address for DMA */ void __iomem *order_addr; /* DMA order register */ - u32 irq; /* DMA irq */ + int irq; /* DMA irq */ }; struct loongson_i2s { -- cgit v1.2.3 From 02474880e8fdd8533f21da4264a7ebfce8196be7 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 19 Jun 2023 12:46:48 +0300 Subject: ASoC: max98388: fix error code in probe() This seems like a copy and paste bug. Return the correct variable. It should be "ret" instead of PTR_ERR(max98388->regmap). Fixes: 6a8e1d46f062 ("ASoC: max98388: add amplifier driver") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/d44c8388-b12b-4045-95de-0d4bc7b428ac@moroto.mountain Signed-off-by: Mark Brown --- sound/soc/codecs/max98388.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index 1fd50e56ecae..cde5e85946cb 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -960,7 +960,7 @@ static int max98388_i2c_probe(struct i2c_client *i2c) ret = regmap_read(max98388->regmap, MAX98388_R22FF_REV_ID, ®); if (ret < 0) - return dev_err_probe(&i2c->dev, PTR_ERR(max98388->regmap), + return dev_err_probe(&i2c->dev, ret, "Failed to read the revision ID\n"); dev_info(&i2c->dev, "MAX98388 revisionID: 0x%02X\n", reg); -- cgit v1.2.3 From a42e988b6265dcd489feb1adab8551b40c988f43 Mon Sep 17 00:00:00 2001 From: Maxim Kochetkov Date: Tue, 13 Jun 2023 22:19:08 +0300 Subject: ASoC: dwc: add DMA handshake control DMA mode uses hardware handshake signals. DMACR register is used to enable the DMA Controller interface operation. So add DMA enable/disable to i2s_start()/i2s_stop() functions if using DMA mode. Signed-off-by: Maxim Kochetkov Link: https://lore.kernel.org/r/20230613191910.725049-1-fido_max@inbox.ru Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-i2s.c | 39 +++++++++++++++++++++++++++++++++++++-- sound/soc/dwc/local.h | 6 ++++++ 2 files changed, 43 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 9e7065dd854c..02b9894e99a7 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -150,19 +150,51 @@ static irqreturn_t i2s_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +static void i2s_enable_dma(struct dw_i2s_dev *dev, u32 stream) +{ + u32 dma_reg = i2s_read_reg(dev->i2s_base, I2S_DMACR); + + /* Enable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_reg |= I2S_DMAEN_TXBLOCK; + else + dma_reg |= I2S_DMAEN_RXBLOCK; + + i2s_write_reg(dev->i2s_base, I2S_DMACR, dma_reg); +} + +static void i2s_disable_dma(struct dw_i2s_dev *dev, u32 stream) +{ + u32 dma_reg = i2s_read_reg(dev->i2s_base, I2S_DMACR); + + /* Disable DMA handshake for stream */ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dma_reg &= ~I2S_DMAEN_TXBLOCK; + i2s_write_reg(dev->i2s_base, I2S_RTXDMA, 1); + } else { + dma_reg &= ~I2S_DMAEN_RXBLOCK; + i2s_write_reg(dev->i2s_base, I2S_RRXDMA, 1); + } + i2s_write_reg(dev->i2s_base, I2S_DMACR, dma_reg); +} + static void i2s_start(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { struct i2s_clk_config_data *config = &dev->config; i2s_write_reg(dev->i2s_base, IER, 1); - i2s_enable_irqs(dev, substream->stream, config->chan_nr); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) i2s_write_reg(dev->i2s_base, ITER, 1); else i2s_write_reg(dev->i2s_base, IRER, 1); + if (dev->use_pio) + i2s_enable_irqs(dev, substream->stream, config->chan_nr); + else + i2s_enable_dma(dev, substream->stream); + i2s_write_reg(dev->i2s_base, CER, 1); } @@ -176,7 +208,10 @@ static void i2s_stop(struct dw_i2s_dev *dev, else i2s_write_reg(dev->i2s_base, IRER, 0); - i2s_disable_irqs(dev, substream->stream, 8); + if (dev->use_pio) + i2s_disable_irqs(dev, substream->stream, 8); + else + i2s_disable_dma(dev, substream->stream); if (!dev->active) { i2s_write_reg(dev->i2s_base, CER, 0); diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h index d64bd4f8fd34..ba4e397099be 100644 --- a/sound/soc/dwc/local.h +++ b/sound/soc/dwc/local.h @@ -53,6 +53,12 @@ #define I2S_COMP_VERSION 0x01F8 #define I2S_COMP_TYPE 0x01FC +#define I2S_RRXDMA 0x01C4 +#define I2S_RTXDMA 0x01CC +#define I2S_DMACR 0x0200 +#define I2S_DMAEN_RXBLOCK (1 << 16) +#define I2S_DMAEN_TXBLOCK (1 << 17) + /* * Component parameter register fields - define the I2S block's * configuration. -- cgit v1.2.3 From 6f80197f40515853814d0f22e5209d53f899ab91 Mon Sep 17 00:00:00 2001 From: Maxim Kochetkov Date: Tue, 13 Jun 2023 22:15:51 +0300 Subject: ASoC: dwc: don't assign addr_width for dt configs For proper DMA operation addr_width must corresponds with audio format (S16, S24, S32, etc). Proper bus width calculations is performed by snd_hwparams_to_dma_slave_config(). So drop wrong addr_width asignment for dt configs and let snd_hwparams_to_dma_slave_config() do the job. Signed-off-by: Maxim Kochetkov Link: https://lore.kernel.org/r/20230613191552.724748-1-fido_max@inbox.ru Signed-off-by: Mark Brown --- sound/soc/dwc/dwc-i2s.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c index 02b9894e99a7..97d652f0e84d 100644 --- a/sound/soc/dwc/dwc-i2s.c +++ b/sound/soc/dwc/dwc-i2s.c @@ -593,13 +593,9 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2); u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); - u32 idx = COMP1_APB_DATA_WIDTH(comp1); u32 idx2; int ret; - if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) - return -EINVAL; - ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000); if (ret < 0) return ret; @@ -609,7 +605,6 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->capability |= DWC_I2S_PLAY; dev->play_dma_data.dt.addr = res->start + I2S_TXDMA; - dev->play_dma_data.dt.addr_width = bus_widths[idx]; dev->play_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2]) >> 8; dev->play_dma_data.dt.maxburst = 16; @@ -619,7 +614,6 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, dev->capability |= DWC_I2S_RECORD; dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA; - dev->capture_dma_data.dt.addr_width = bus_widths[idx]; dev->capture_dma_data.dt.fifo_size = fifo_depth * (fifo_width[idx2] >> 8); dev->capture_dma_data.dt.maxburst = 16; -- cgit v1.2.3 From 05722a0ce6fbd1c603ec0f0ecb5ed839dd561ac7 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:06 +0000 Subject: ASoC: soc-core.c: add snd_soc_{of_}get_dlc() Current soc-core.c has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). It gets .dai_name, but we need .of_node too. Therefor user need to arrange. It will be more useful if it gets both .dai_name and .of_node. This patch adds snd_soc_{of_}get_dlc() for it, and existing functions uses it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87r0q6dgnm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 6 ++++++ sound/soc/soc-core.c | 53 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/include/sound/soc.h b/include/sound/soc.h index a7ae8b26737e..943f0a1b2d27 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1309,6 +1309,12 @@ unsigned int snd_soc_daifmt_parse_clock_provider_raw(struct device_node *np, snd_soc_daifmt_parse_clock_provider_as_bitmap(np, prefix)) int snd_soc_get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream); +int snd_soc_get_dlc(const struct of_phandle_args *args, + struct snd_soc_dai_link_component *dlc); +int snd_soc_of_get_dlc(struct device_node *of_node, + struct of_phandle_args *args, + struct snd_soc_dai_link_component *dlc, + int index); int snd_soc_get_dai_id(struct device_node *ep); int snd_soc_get_dai_name(const struct of_phandle_args *args, const char **dai_name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e8308926bd98..8dba5bb26ffe 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3257,8 +3257,7 @@ int snd_soc_get_dai_id(struct device_node *ep) } EXPORT_SYMBOL_GPL(snd_soc_get_dai_id); -int snd_soc_get_dai_name(const struct of_phandle_args *args, - const char **dai_name) +int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_component *dlc) { struct snd_soc_component *pos; int ret = -EPROBE_DEFER; @@ -3270,7 +3269,7 @@ int snd_soc_get_dai_name(const struct of_phandle_args *args, if (component_of_node != args->np || !pos->num_dai) continue; - ret = snd_soc_component_of_xlate_dai_name(pos, args, dai_name); + ret = snd_soc_component_of_xlate_dai_name(pos, args, &dlc->dai_name); if (ret == -ENOTSUPP) { struct snd_soc_dai *dai; int id = -1; @@ -3301,9 +3300,10 @@ int snd_soc_get_dai_name(const struct of_phandle_args *args, id--; } - *dai_name = dai->driver->name; - if (!*dai_name) - *dai_name = pos->name; + dlc->of_node = args->np; + dlc->dai_name = dai->driver->name; + if (!dlc->dai_name) + dlc->dai_name = pos->name; } else if (ret) { /* * if another error than ENOTSUPP is returned go on and @@ -3319,22 +3319,49 @@ int snd_soc_get_dai_name(const struct of_phandle_args *args, mutex_unlock(&client_mutex); return ret; } -EXPORT_SYMBOL_GPL(snd_soc_get_dai_name); +EXPORT_SYMBOL_GPL(snd_soc_get_dlc); -int snd_soc_of_get_dai_name(struct device_node *of_node, - const char **dai_name) +int snd_soc_of_get_dlc(struct device_node *of_node, + struct of_phandle_args *args, + struct snd_soc_dai_link_component *dlc, + int index) { - struct of_phandle_args args; + struct of_phandle_args __args; int ret; + if (!args) + args = &__args; + ret = of_parse_phandle_with_args(of_node, "sound-dai", - "#sound-dai-cells", 0, &args); + "#sound-dai-cells", index, args); if (ret) return ret; - ret = snd_soc_get_dai_name(&args, dai_name); + return snd_soc_get_dlc(args, dlc); +} +EXPORT_SYMBOL_GPL(snd_soc_of_get_dlc); + +int snd_soc_get_dai_name(const struct of_phandle_args *args, + const char **dai_name) +{ + struct snd_soc_dai_link_component dlc; + int ret = snd_soc_get_dlc(args, &dlc); - of_node_put(args.np); + if (ret == 0) + *dai_name = dlc.dai_name; + + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_get_dai_name); + +int snd_soc_of_get_dai_name(struct device_node *of_node, + const char **dai_name) +{ + struct snd_soc_dai_link_component dlc; + int ret = snd_soc_of_get_dlc(of_node, NULL, &dlc, 0); + + if (ret == 0) + *dai_name = dlc.dai_name; return ret; } -- cgit v1.2.3 From 3c8b5861850c734add65233e538d4a8c2dff95d9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:11 +0000 Subject: ASoC: soc-core.c: add index on snd_soc_of_get_dai_name() Current snd_soc_of_get_dai_name() doesn't accept index for #sound-dai-cells. It is not useful for user. This patch adds it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87pm5qdgng.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc.h | 2 +- sound/soc/fsl/imx-card.c | 2 +- sound/soc/generic/simple-card.c | 2 +- sound/soc/loongson/loongson_card.c | 4 ++-- sound/soc/mediatek/mt8173/mt8173-rt5650.c | 2 +- sound/soc/qcom/common.c | 2 +- sound/soc/soc-core.c | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/include/sound/soc.h b/include/sound/soc.h index 943f0a1b2d27..b27f84580c5b 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1319,7 +1319,7 @@ int snd_soc_get_dai_id(struct device_node *ep); int snd_soc_get_dai_name(const struct of_phandle_args *args, const char **dai_name); int snd_soc_of_get_dai_name(struct device_node *of_node, - const char **dai_name); + const char **dai_name, int index); int snd_soc_of_get_dai_link_codecs(struct device *dev, struct device_node *of_node, struct snd_soc_dai_link *dai_link); diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 78e2e3932ba5..6f3b1428a5ba 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -586,7 +586,7 @@ static int imx_card_parse_of(struct imx_card_data *data) link->platforms->of_node = link->cpus->of_node; link->id = args.args[0]; - ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name); + ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, 0); if (ret) { dev_err_probe(card->dev, ret, "%s: error getting cpu dai name\n", link->name); diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 5a5e4ecd0f61..5b59198a0384 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -89,7 +89,7 @@ static int asoc_simple_parse_dai(struct device_node *node, * 2) user need to rebind Sound Card everytime * if he unbinded CPU or Codec. */ - ret = snd_soc_of_get_dai_name(node, &dlc->dai_name); + ret = snd_soc_of_get_dai_name(node, &dlc->dai_name, 0); if (ret < 0) return ret; diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 08df05cb4328..94f02b787c98 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -151,8 +151,8 @@ static int loongson_card_parse_of(struct loongson_card_data *data) for (i = 0; i < card->num_links; i++) loongson_dai_links[i].codecs->of_node = args.np; - snd_soc_of_get_dai_name(cpu, &cpu_dai_name); - snd_soc_of_get_dai_name(codec, &codec_dai_name); + snd_soc_of_get_dai_name(cpu, &cpu_dai_name, 0); + snd_soc_of_get_dai_name(codec, &codec_dai_name, 0); for (i = 0; i < card->num_links; i++) { loongson_dai_links[i].cpus->dai_name = cpu_dai_name; loongson_dai_links[i].codecs->dai_name = codec_dai_name; diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c index e05f2b0231fe..3ece4b5eaca2 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -288,7 +288,7 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); if (np) { - ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + ret = snd_soc_of_get_dai_name(np, &codec_capture_dai, 0); of_node_put(np); if (ret < 0) { dev_err(&pdev->dev, diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index cab5a7937a57..d9ebb883b999 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -105,7 +105,7 @@ int qcom_snd_parse_of(struct snd_soc_card *card) link->cpus->of_node = args.np; link->id = args.args[0]; - ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name); + ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, 0); if (ret) { dev_err_probe(card->dev, ret, "%s: error getting cpu dai name\n", link->name); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 8dba5bb26ffe..7b13b1b232ef 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3355,10 +3355,10 @@ int snd_soc_get_dai_name(const struct of_phandle_args *args, EXPORT_SYMBOL_GPL(snd_soc_get_dai_name); int snd_soc_of_get_dai_name(struct device_node *of_node, - const char **dai_name) + const char **dai_name, int index) { struct snd_soc_dai_link_component dlc; - int ret = snd_soc_of_get_dlc(of_node, NULL, &dlc, 0); + int ret = snd_soc_of_get_dlc(of_node, NULL, &dlc, index); if (ret == 0) *dai_name = dlc.dai_name; -- cgit v1.2.3 From aa560f5e796ce63074942251197c7161db2392d3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:18 +0000 Subject: ASoC: fsl: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87o7ladgn9.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-card.c | 14 +++----------- sound/soc/fsl/imx-rpmsg.c | 3 +-- 2 files changed, 4 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/fsl/imx-card.c b/sound/soc/fsl/imx-card.c index 6f3b1428a5ba..356a0bc3b126 100644 --- a/sound/soc/fsl/imx-card.c +++ b/sound/soc/fsl/imx-card.c @@ -551,10 +551,10 @@ static int imx_card_parse_of(struct imx_card_data *data) goto err; } - ret = of_parse_phandle_with_args(cpu, "sound-dai", - "#sound-dai-cells", 0, &args); + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); if (ret) { - dev_err(card->dev, "%s: error getting cpu phandle\n", link->name); + dev_err_probe(card->dev, ret, + "%s: error getting cpu dai info\n", link->name); goto err; } @@ -582,17 +582,9 @@ static int imx_card_parse_of(struct imx_card_data *data) } } - link->cpus->of_node = args.np; link->platforms->of_node = link->cpus->of_node; link->id = args.args[0]; - ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, 0); - if (ret) { - dev_err_probe(card->dev, ret, - "%s: error getting cpu dai name\n", link->name); - goto err; - } - codec = of_get_child_by_name(np, "codec"); if (codec) { ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); diff --git a/sound/soc/fsl/imx-rpmsg.c b/sound/soc/fsl/imx-rpmsg.c index 93fc976e98dc..3c7b95db2eac 100644 --- a/sound/soc/fsl/imx-rpmsg.c +++ b/sound/soc/fsl/imx-rpmsg.c @@ -96,8 +96,7 @@ static int imx_rpmsg_probe(struct platform_device *pdev) } else { struct clk *clk; - data->dai.codecs->of_node = args.np; - ret = snd_soc_get_dai_name(&args, &data->dai.codecs->dai_name); + ret = snd_soc_get_dlc(&args, data->dai.codecs); if (ret) { dev_err(&pdev->dev, "Unable to get codec_dai_name\n"); goto fail; -- cgit v1.2.3 From 6cf881b7f1608fd5625d916380ed57d45c2879e9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:24 +0000 Subject: ASoC: qcom: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87mt0udgn3.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index d9ebb883b999..43b0a888f1e8 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -96,22 +96,15 @@ int qcom_snd_parse_of(struct snd_soc_card *card) goto err; } - ret = of_parse_phandle_with_args(cpu, "sound-dai", - "#sound-dai-cells", 0, &args); - if (ret) { - dev_err(card->dev, "%s: error getting cpu phandle\n", link->name); - goto err; - } - link->cpus->of_node = args.np; - link->id = args.args[0]; - - ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, 0); + ret = snd_soc_of_get_dlc(cpu, &args, link->cpus, 0); if (ret) { dev_err_probe(card->dev, ret, "%s: error getting cpu dai name\n", link->name); goto err; } + link->id = args.args[0]; + if (platform) { link->platforms->of_node = of_parse_phandle(platform, "sound-dai", -- cgit v1.2.3 From 2e1dbea1f8a3584399ff15b1f1773dbbb1f0d10f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:30 +0000 Subject: ASoC: meson: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87legedgmy.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/meson/axg-card.c | 3 +-- sound/soc/meson/gx-card.c | 3 +-- sound/soc/meson/meson-card-utils.c | 16 +++++----------- sound/soc/meson/meson-card.h | 3 +-- 4 files changed, 8 insertions(+), 17 deletions(-) (limited to 'sound') diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c index 74e7cf0ef8d5..f10c0c17863e 100644 --- a/sound/soc/meson/axg-card.c +++ b/sound/soc/meson/axg-card.c @@ -319,8 +319,7 @@ static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, dai_link->cpus = cpu; dai_link->num_cpus = 1; - ret = meson_card_parse_dai(card, np, &dai_link->cpus->of_node, - &dai_link->cpus->dai_name); + ret = meson_card_parse_dai(card, np, dai_link->cpus); if (ret) return ret; diff --git a/sound/soc/meson/gx-card.c b/sound/soc/meson/gx-card.c index 58c411d3c489..a26b620fc177 100644 --- a/sound/soc/meson/gx-card.c +++ b/sound/soc/meson/gx-card.c @@ -90,8 +90,7 @@ static int gx_card_add_link(struct snd_soc_card *card, struct device_node *np, dai_link->cpus = cpu; dai_link->num_cpus = 1; - ret = meson_card_parse_dai(card, np, &dai_link->cpus->of_node, - &dai_link->cpus->dai_name); + ret = meson_card_parse_dai(card, np, dai_link->cpus); if (ret) return ret; diff --git a/sound/soc/meson/meson-card-utils.c b/sound/soc/meson/meson-card-utils.c index ffc5111f9e3c..f7fd9c013e19 100644 --- a/sound/soc/meson/meson-card-utils.c +++ b/sound/soc/meson/meson-card-utils.c @@ -74,23 +74,18 @@ EXPORT_SYMBOL_GPL(meson_card_reallocate_links); int meson_card_parse_dai(struct snd_soc_card *card, struct device_node *node, - struct device_node **dai_of_node, - const char **dai_name) + struct snd_soc_dai_link_component *dlc) { - struct of_phandle_args args; int ret; - if (!dai_name || !dai_of_node || !node) + if (!dlc || !node) return -EINVAL; - ret = of_parse_phandle_with_args(node, "sound-dai", - "#sound-dai-cells", 0, &args); + ret = snd_soc_of_get_dlc(node, NULL, dlc, 0); if (ret) return dev_err_probe(card->dev, ret, "can't parse dai\n"); - *dai_of_node = args.np; - - return snd_soc_get_dai_name(&args, dai_name); + return ret; } EXPORT_SYMBOL_GPL(meson_card_parse_dai); @@ -160,8 +155,7 @@ int meson_card_set_be_link(struct snd_soc_card *card, link->num_codecs = num_codecs; for_each_child_of_node(node, np) { - ret = meson_card_parse_dai(card, np, &codec->of_node, - &codec->dai_name); + ret = meson_card_parse_dai(card, np, codec); if (ret) { of_node_put(np); return ret; diff --git a/sound/soc/meson/meson-card.h b/sound/soc/meson/meson-card.h index 74314071c80d..a5374324a189 100644 --- a/sound/soc/meson/meson-card.h +++ b/sound/soc/meson/meson-card.h @@ -39,8 +39,7 @@ int meson_card_reallocate_links(struct snd_soc_card *card, unsigned int num_links); int meson_card_parse_dai(struct snd_soc_card *card, struct device_node *node, - struct device_node **dai_of_node, - const char **dai_name); + struct snd_soc_dai_link_component *dlc); int meson_card_set_be_link(struct snd_soc_card *card, struct snd_soc_dai_link *link, struct device_node *node); -- cgit v1.2.3 From 50233f28f9a2c06140a7bf539ef569ba1ad58ff6 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:35 +0000 Subject: ASoC: samsung: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. - note: need deep check Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87jzvydgms.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/samsung/odroid.c | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) (limited to 'sound') diff --git a/sound/soc/samsung/odroid.c b/sound/soc/samsung/odroid.c index fd95a79cc9fa..a5442592bde4 100644 --- a/sound/soc/samsung/odroid.c +++ b/sound/soc/samsung/odroid.c @@ -205,7 +205,6 @@ static int odroid_audio_probe(struct platform_device *pdev) struct snd_soc_card *card; struct snd_soc_dai_link *link, *codec_link; int num_pcms, ret, i; - struct of_phandle_args args = {}; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -260,20 +259,7 @@ static int odroid_audio_probe(struct platform_device *pdev) } for (i = 0; i < num_pcms; i++, link += 2) { - ret = of_parse_phandle_with_args(cpu, "sound-dai", - "#sound-dai-cells", i, &args); - if (ret < 0) - break; - - if (!args.np) { - dev_err(dev, "sound-dai property parse error: %d\n", ret); - ret = -EINVAL; - break; - } - - ret = snd_soc_get_dai_name(&args, &link->cpus->dai_name); - of_node_put(args.np); - + ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name, i); if (ret < 0) break; } -- cgit v1.2.3 From db588ea1a352df9673464b1bc6d4acb83f5e8256 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:40 +0000 Subject: ASoC: loongson: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87ilbidgmn.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_card.c | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 94f02b787c98..9ded16329747 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -114,11 +114,9 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) static int loongson_card_parse_of(struct loongson_card_data *data) { - const char *cpu_dai_name, *codec_dai_name; struct device_node *cpu, *codec; struct snd_soc_card *card = &data->snd_card; struct device *dev = card->dev; - struct of_phandle_args args; int ret, i; cpu = of_get_child_by_name(dev->of_node, "cpu"); @@ -133,30 +131,20 @@ static int loongson_card_parse_of(struct loongson_card_data *data) goto err; } - ret = of_parse_phandle_with_args(cpu, "sound-dai", - "#sound-dai-cells", 0, &args); - if (ret) { - dev_err(dev, "codec node missing #sound-dai-cells\n"); - goto err; - } - for (i = 0; i < card->num_links; i++) - loongson_dai_links[i].cpus->of_node = args.np; + for (i = 0; i < card->num_links; i++) { + ret = snd_soc_of_get_dlc(cpu, NULL, loongson_dai_links[i].cpus, 0); + if (ret < 0) { + dev_err(dev, "getting cpu dlc error (%d)\n", ret); + goto err; + } - ret = of_parse_phandle_with_args(codec, "sound-dai", - "#sound-dai-cells", 0, &args); - if (ret) { - dev_err(dev, "codec node missing #sound-dai-cells\n"); - goto err; + ret = snd_soc_of_get_dlc(codec, NULL, loongson_dai_links[i].codecs, 0); + if (ret < 0) { + dev_err(dev, "getting codec dlc error (%d)\n", ret); + goto err; + } } - for (i = 0; i < card->num_links; i++) - loongson_dai_links[i].codecs->of_node = args.np; - snd_soc_of_get_dai_name(cpu, &cpu_dai_name, 0); - snd_soc_of_get_dai_name(codec, &codec_dai_name, 0); - for (i = 0; i < card->num_links; i++) { - loongson_dai_links[i].cpus->dai_name = cpu_dai_name; - loongson_dai_links[i].codecs->dai_name = codec_dai_name; - } of_node_put(cpu); of_node_put(codec); -- cgit v1.2.3 From 14c9b25f632b561be33af99942833a618811ac3d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:46 +0000 Subject: ASoC: soc-core.c: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87h6r2dgmi.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7b13b1b232ef..f06a20773a34 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3402,26 +3402,6 @@ static int __snd_soc_of_get_dai_link_component_alloc( return 0; } -static int __snd_soc_of_get_dai_link_component_parse( - struct device_node *of_node, - struct snd_soc_dai_link_component *component, int index) -{ - struct of_phandle_args args; - int ret; - - ret = of_parse_phandle_with_args(of_node, "sound-dai", "#sound-dai-cells", - index, &args); - if (ret) - return ret; - - ret = snd_soc_get_dai_name(&args, &component->dai_name); - if (ret < 0) - return ret; - - component->of_node = args.np; - return 0; -} - /* * snd_soc_of_put_dai_link_codecs - Dereference device nodes in the codecs array * @dai_link: DAI link @@ -3466,7 +3446,7 @@ int snd_soc_of_get_dai_link_codecs(struct device *dev, /* Parse the list */ for_each_link_codecs(dai_link, index, component) { - ret = __snd_soc_of_get_dai_link_component_parse(of_node, component, index); + ret = snd_soc_of_get_dlc(of_node, NULL, component, index); if (ret) goto err; } @@ -3521,7 +3501,7 @@ int snd_soc_of_get_dai_link_cpus(struct device *dev, /* Parse the list */ for_each_link_cpus(dai_link, index, component) { - ret = __snd_soc_of_get_dai_link_component_parse(of_node, component, index); + ret = snd_soc_of_get_dlc(of_node, NULL, component, index); if (ret) goto err; } -- cgit v1.2.3 From 0baa2c3abc525c79c21ce64a1722f4034d042ac9 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Tue, 20 Jun 2023 02:14:51 +0000 Subject: ASoC: simple-card.c: use snd_soc_{of_}get_dlc() Current ASoC has snd_soc_{of_}get_dai_name() to get DAI name for dlc (snd_soc_dai_link_component). But we now can use snd_soc_{of_}get_dlc() for it. Let's use it. Signed-off-by: Kuninori Morimoto Link: https://lore.kernel.org/r/87fs6mdgmc.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/generic/simple-card-utils.c | 4 +--- sound/soc/generic/simple-card.c | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/generic/simple-card-utils.c b/sound/soc/generic/simple-card-utils.c index 3af056026fa2..3019626b0592 100644 --- a/sound/soc/generic/simple-card-utils.c +++ b/sound/soc/generic/simple-card-utils.c @@ -1103,14 +1103,12 @@ int asoc_graph_parse_dai(struct device_node *ep, * 2) user need to rebind Sound Card everytime * if he unbinded CPU or Codec. */ - ret = snd_soc_get_dai_name(&args, &dlc->dai_name); + ret = snd_soc_get_dlc(&args, dlc); if (ret < 0) { of_node_put(node); return ret; } - dlc->of_node = node; - if (is_single_link) *is_single_link = of_graph_get_endpoint_count(node) == 1; diff --git a/sound/soc/generic/simple-card.c b/sound/soc/generic/simple-card.c index 5b59198a0384..0745bf6a09aa 100644 --- a/sound/soc/generic/simple-card.c +++ b/sound/soc/generic/simple-card.c @@ -89,12 +89,10 @@ static int asoc_simple_parse_dai(struct device_node *node, * 2) user need to rebind Sound Card everytime * if he unbinded CPU or Codec. */ - ret = snd_soc_of_get_dai_name(node, &dlc->dai_name, 0); + ret = snd_soc_get_dlc(&args, dlc); if (ret < 0) return ret; - dlc->of_node = args.np; - if (is_single_link) *is_single_link = !args.args_count; -- cgit v1.2.3 From 0a08778126284481c300336f1ba3d7b1906851a5 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Tue, 20 Jun 2023 10:56:20 +0100 Subject: ASoC: tas2781: Fix spelling mistake "calibraiton" -> "calibration" There is a spelling mistake in a dev_err message. Fix it. Also fix grammar and add space between last word and (%d)". Signed-off-by: Colin Ian King Link: https://lore.kernel.org/r/20230620095620.2522058-1-colin.i.king@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index 432b19ccec8c..cbf0aef2c001 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -1863,7 +1863,7 @@ static int fw_parse_calibration_data(struct tasdevice_priv *tas_priv, if (tas_fmw->nr_calibrations != 1) { dev_err(tas_priv->dev, - "%s: only support one calibraiton(%d)!\n", + "%s: only supports one calibration (%d)!\n", __func__, tas_fmw->nr_calibrations); goto out; } -- cgit v1.2.3 From d1351c30ac8a6cf61b0bbe9fcbc8d2851cd44f3c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:28:55 +0530 Subject: ASoC: amd: ps: create platform devices based on acp config Based on ACP pin configuration and scanning child devices under ACP pci device ACPI scope, platform device configuration (pdev_config) and platform device count(pdev_count) will be calculated. Using pdev_config and pdev_count values, ACP PCI driver will create platform devices for Pink Sardine platform. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 73 ++++++++++++- sound/soc/amd/ps/pci-ps.c | 271 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 323 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 2f94448102d0..80ab542529a7 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -10,7 +10,7 @@ #define ACP_DEVICE_ID 0x15E2 #define ACP63_REG_START 0x1240000 #define ACP63_REG_END 0x1250200 -#define ACP63_DEVS 3 +#define ACP63_DEVS 5 #define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001 #define ACP_PGFSM_CNTL_POWER_ON_MASK 1 @@ -53,14 +53,53 @@ /* time in ms for runtime suspend delay */ #define ACP_SUSPEND_DELAY_MS 2000 -#define ACP63_DMIC_ADDR 2 -#define ACP63_PDM_MODE_DEVS 3 -#define ACP63_PDM_DEV_MASK 1 #define ACP_DMIC_DEV 2 +/* ACP63_PDM_MODE_DEVS corresponds to platform devices count for ACP PDM configuration */ +#define ACP63_PDM_MODE_DEVS 3 + +/* + * ACP63_SDW0_MODE_DEVS corresponds to platform devices count for + * SW0 SoundWire manager instance configuration + */ +#define ACP63_SDW0_MODE_DEVS 2 + +/* + * ACP63_SDW0_SDW1_MODE_DEVS corresponds to platform devices count for SW0 + SW1 SoundWire manager + * instances configuration + */ +#define ACP63_SDW0_SDW1_MODE_DEVS 3 + +/* + * ACP63_SDW0_PDM_MODE_DEVS corresponds to platform devices count for SW0 manager + * instance + ACP PDM controller configuration + */ +#define ACP63_SDW0_PDM_MODE_DEVS 4 + +/* + * ACP63_SDW0_SDW1_PDM_MODE_DEVS corresponds to platform devices count for + * SW0 + SW1 SoundWire manager instances + ACP PDM controller configuration + */ +#define ACP63_SDW0_SDW1_PDM_MODE_DEVS 5 +#define ACP63_DMIC_ADDR 2 +#define ACP63_SDW_ADDR 5 +#define AMD_SDW_MAX_MANAGERS 2 + /* time in ms for acp timeout */ #define ACP_TIMEOUT 500 +/* ACP63_PDM_DEV_CONFIG corresponds to platform device configuration for ACP PDM controller */ +#define ACP63_PDM_DEV_CONFIG BIT(0) + +/* ACP63_SDW_DEV_CONFIG corresponds to platform device configuration for SDW manager instances */ +#define ACP63_SDW_DEV_CONFIG BIT(1) + +/* + * ACP63_SDW_PDM_DEV_CONFIG corresponds to platform device configuration for ACP PDM + SoundWire + * manager instance combination. + */ +#define ACP63_SDW_PDM_DEV_CONFIG GENMASK(1, 0) + enum acp_config { ACP_CONFIG_0 = 0, ACP_CONFIG_1, @@ -95,14 +134,38 @@ struct pdm_dev_data { struct snd_pcm_substream *capture_stream; }; +/** + * struct acp63_dev_data - acp pci driver context + * @acp63_base: acp mmio base + * @res: resource + * @pdev: array of child platform device node structures + * @acp_lock: used to protect acp common registers + * @sdw_fw_node: SoundWire controller fw node handle + * @pdev_config: platform device configuration + * @pdev_count: platform devices count + * @pdm_dev_index: pdm platform device index + * @sdw_manager_count: SoundWire manager instance count + * @sdw0_dev_index: SoundWire Manager-0 platform device index + * @sdw1_dev_index: SoundWire Manager-1 platform device index + * @sdw_dma_dev_index: SoundWire DMA controller platform device index + * @acp_reset: flag set to true when bus reset is applied across all + * the active SoundWire manager instances + */ + struct acp63_dev_data { void __iomem *acp63_base; struct resource *res; struct platform_device *pdev[ACP63_DEVS]; struct mutex acp_lock; /* protect shared registers */ - u16 pdev_mask; + struct fwnode_handle *sdw_fw_node; + u16 pdev_config; u16 pdev_count; u16 pdm_dev_index; + u8 sdw_manager_count; + u16 sdw0_dev_index; + u16 sdw1_dev_index; + u16 sdw_dma_dev_index; + bool acp_reset; }; int snd_amd_acp_find_config(struct pci_dev *pci); diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index 54752d6040d6..cf57ad2d7ccc 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include "acp63.h" @@ -119,37 +121,164 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) return IRQ_NONE; } -static void get_acp63_device_config(u32 config, struct pci_dev *pci, - struct acp63_dev_data *acp_data) +static int sdw_amd_scan_controller(struct device *dev) +{ + struct acp63_dev_data *acp_data; + struct fwnode_handle *link; + char name[32]; + u32 sdw_manager_bitmap; + u8 count = 0; + u32 acp_sdw_power_mode = 0; + int index; + int ret; + + acp_data = dev_get_drvdata(dev); + /* + * Current implementation is based on MIPI DisCo 2.0 spec. + * Found controller, find links supported. + */ + ret = fwnode_property_read_u32_array((acp_data->sdw_fw_node), "mipi-sdw-manager-list", + &sdw_manager_bitmap, 1); + + if (ret) { + dev_err(dev, "Failed to read mipi-sdw-manager-list: %d\n", ret); + return -EINVAL; + } + count = hweight32(sdw_manager_bitmap); + /* Check count is within bounds */ + if (count > AMD_SDW_MAX_MANAGERS) { + dev_err(dev, "Manager count %d exceeds max %d\n", count, AMD_SDW_MAX_MANAGERS); + return -EINVAL; + } + + if (!count) { + dev_dbg(dev, "No SoundWire Managers detected\n"); + return -EINVAL; + } + dev_dbg(dev, "ACPI reports %d SoundWire Manager devices\n", count); + acp_data->sdw_manager_count = count; + for (index = 0; index < count; index++) { + snprintf(name, sizeof(name), "mipi-sdw-link-%d-subproperties", index); + link = fwnode_get_named_child_node(acp_data->sdw_fw_node, name); + if (!link) { + dev_err(dev, "Manager node %s not found\n", name); + return -EIO; + } + + ret = fwnode_property_read_u32(link, "amd-sdw-power-mode", &acp_sdw_power_mode); + if (ret) + return ret; + /* + * when SoundWire configuration is selected from acp pin config, + * based on manager instances count, acp init/de-init sequence should be + * executed as part of PM ops only when Bus reset is applied for the active + * SoundWire manager instances. + */ + if (acp_sdw_power_mode != AMD_SDW_POWER_OFF_MODE) { + acp_data->acp_reset = false; + return 0; + } + } + return 0; +} + +static int get_acp63_device_config(u32 config, struct pci_dev *pci, struct acp63_dev_data *acp_data) { struct acpi_device *dmic_dev; + struct acpi_device *sdw_dev; const union acpi_object *obj; bool is_dmic_dev = false; + bool is_sdw_dev = false; + int ret; dmic_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_DMIC_ADDR, 0); if (dmic_dev) { + /* is_dmic_dev flag will be set when ACP PDM controller device exists */ if (!acpi_dev_get_property(dmic_dev, "acp-audio-device-type", ACPI_TYPE_INTEGER, &obj) && obj->integer.value == ACP_DMIC_DEV) is_dmic_dev = true; } + sdw_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_SDW_ADDR, 0); + if (sdw_dev) { + acp_data->sdw_fw_node = acpi_fwnode_handle(sdw_dev); + ret = sdw_amd_scan_controller(&pci->dev); + /* is_sdw_dev flag will be set when SoundWire Manager device exists */ + if (!ret) + is_sdw_dev = true; + } + if (!is_dmic_dev && !is_sdw_dev) + return -ENODEV; + dev_dbg(&pci->dev, "Audio Mode %d\n", config); switch (config) { - case ACP_CONFIG_0: - case ACP_CONFIG_1: + case ACP_CONFIG_4: + case ACP_CONFIG_5: + case ACP_CONFIG_10: + case ACP_CONFIG_11: + if (is_dmic_dev) { + acp_data->pdev_config = ACP63_PDM_DEV_CONFIG; + acp_data->pdev_count = ACP63_PDM_MODE_DEVS; + } + break; case ACP_CONFIG_2: case ACP_CONFIG_3: - case ACP_CONFIG_9: - case ACP_CONFIG_15: - dev_dbg(&pci->dev, "Audio Mode %d\n", config); + if (is_sdw_dev) { + switch (acp_data->sdw_manager_count) { + case 1: + acp_data->pdev_config = ACP63_SDW_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_MODE_DEVS; + break; + case 2: + acp_data->pdev_config = ACP63_SDW_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS; + break; + default: + return -EINVAL; + } + } break; - default: - if (is_dmic_dev) { - acp_data->pdev_mask = ACP63_PDM_DEV_MASK; + case ACP_CONFIG_6: + case ACP_CONFIG_7: + case ACP_CONFIG_12: + case ACP_CONFIG_8: + case ACP_CONFIG_13: + case ACP_CONFIG_14: + if (is_dmic_dev && is_sdw_dev) { + switch (acp_data->sdw_manager_count) { + case 1: + acp_data->pdev_config = ACP63_SDW_PDM_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_PDM_MODE_DEVS; + break; + case 2: + acp_data->pdev_config = ACP63_SDW_PDM_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_SDW1_PDM_MODE_DEVS; + break; + default: + return -EINVAL; + } + } else if (is_dmic_dev) { + acp_data->pdev_config = ACP63_PDM_DEV_CONFIG; acp_data->pdev_count = ACP63_PDM_MODE_DEVS; + } else if (is_sdw_dev) { + switch (acp_data->sdw_manager_count) { + case 1: + acp_data->pdev_config = ACP63_SDW_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_MODE_DEVS; + break; + case 2: + acp_data->pdev_config = ACP63_SDW_DEV_CONFIG; + acp_data->pdev_count = ACP63_SDW0_SDW1_MODE_DEVS; + break; + default: + return -EINVAL; + } } break; + default: + break; } + return 0; } static void acp63_fill_platform_dev_info(struct platform_device_info *pdevinfo, @@ -173,6 +302,7 @@ static void acp63_fill_platform_dev_info(struct platform_device_info *pdevinfo, static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data *adata, u32 addr) { + struct acp_sdw_pdata *sdw_pdata; struct platform_device_info pdevinfo[ACP63_DEVS]; struct device *parent; int index; @@ -180,9 +310,9 @@ static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data parent = &pci->dev; dev_dbg(&pci->dev, - "%s pdev_mask:0x%x pdev_count:0x%x\n", __func__, adata->pdev_mask, + "%s pdev_config:0x%x pdev_count:0x%x\n", __func__, adata->pdev_config, adata->pdev_count); - if (adata->pdev_mask) { + if (adata->pdev_config) { adata->res = devm_kzalloc(&pci->dev, sizeof(struct resource), GFP_KERNEL); if (!adata->res) { ret = -ENOMEM; @@ -194,8 +324,8 @@ static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data memset(&pdevinfo, 0, sizeof(pdevinfo)); } - switch (adata->pdev_mask) { - case ACP63_PDM_DEV_MASK: + switch (adata->pdev_config) { + case ACP63_PDM_DEV_CONFIG: adata->pdm_dev_index = 0; acp63_fill_platform_dev_info(&pdevinfo[0], parent, NULL, "acp_ps_pdm_dma", 0, adata->res, 1, NULL, 0); @@ -204,8 +334,104 @@ static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "acp_ps_mach", 0, NULL, 0, NULL, 0); break; + case ACP63_SDW_DEV_CONFIG: + if (adata->pdev_count == ACP63_SDW0_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata), + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata->instance = 0; + sdw_pdata->acp_sdw_lock = &adata->acp_lock; + adata->sdw0_dev_index = 0; + adata->sdw_dma_dev_index = 1; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, adata->sdw_fw_node, + "amd_sdw_manager", 0, adata->res, 1, + sdw_pdata, sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, NULL, 0); + } else if (adata->pdev_count == ACP63_SDW0_SDW1_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata) * 2, + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata[0].instance = 0; + sdw_pdata[1].instance = 1; + sdw_pdata[0].acp_sdw_lock = &adata->acp_lock; + sdw_pdata[1].acp_sdw_lock = &adata->acp_lock; + sdw_pdata->acp_sdw_lock = &adata->acp_lock; + adata->sdw0_dev_index = 0; + adata->sdw1_dev_index = 1; + adata->sdw_dma_dev_index = 2; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, adata->sdw_fw_node, + "amd_sdw_manager", 0, adata->res, 1, + &sdw_pdata[0], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_manager", 1, adata->res, 1, + &sdw_pdata[1], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, NULL, 0); + } + break; + case ACP63_SDW_PDM_DEV_CONFIG: + if (adata->pdev_count == ACP63_SDW0_PDM_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata), + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + + sdw_pdata->instance = 0; + sdw_pdata->acp_sdw_lock = &adata->acp_lock; + adata->pdm_dev_index = 0; + adata->sdw0_dev_index = 1; + adata->sdw_dma_dev_index = 2; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, NULL, "acp_ps_pdm_dma", + 0, adata->res, 1, NULL, 0); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_manager", 0, adata->res, 1, + sdw_pdata, sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, NULL, 0); + acp63_fill_platform_dev_info(&pdevinfo[3], parent, NULL, "dmic-codec", + 0, NULL, 0, NULL, 0); + } else if (adata->pdev_count == ACP63_SDW0_SDW1_PDM_MODE_DEVS) { + sdw_pdata = devm_kzalloc(&pci->dev, sizeof(struct acp_sdw_pdata) * 2, + GFP_KERNEL); + if (!sdw_pdata) { + ret = -ENOMEM; + goto de_init; + } + sdw_pdata[0].instance = 0; + sdw_pdata[1].instance = 1; + sdw_pdata[0].acp_sdw_lock = &adata->acp_lock; + sdw_pdata[1].acp_sdw_lock = &adata->acp_lock; + adata->pdm_dev_index = 0; + adata->sdw0_dev_index = 1; + adata->sdw1_dev_index = 2; + adata->sdw_dma_dev_index = 3; + acp63_fill_platform_dev_info(&pdevinfo[0], parent, NULL, "acp_ps_pdm_dma", + 0, adata->res, 1, NULL, 0); + acp63_fill_platform_dev_info(&pdevinfo[1], parent, adata->sdw_fw_node, + "amd_sdw_manager", 0, adata->res, 1, + &sdw_pdata[0], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[2], parent, adata->sdw_fw_node, + "amd_sdw_manager", 1, adata->res, 1, + &sdw_pdata[1], sizeof(struct acp_sdw_pdata)); + acp63_fill_platform_dev_info(&pdevinfo[3], parent, NULL, "amd_ps_sdw_dma", + 0, adata->res, 1, NULL, 0); + acp63_fill_platform_dev_info(&pdevinfo[4], parent, NULL, "dmic-codec", + 0, NULL, 0, NULL, 0); + } + break; default: - dev_dbg(&pci->dev, "No PDM devices found\n"); + dev_dbg(&pci->dev, "No PDM or SoundWire manager devices found\n"); return 0; } @@ -276,6 +502,13 @@ static int snd_acp63_probe(struct pci_dev *pci, ret = -ENOMEM; goto release_regions; } + /* + * By default acp_reset flag is set to true. i.e acp_deinit() and acp_init() + * will be invoked for all ACP configurations during suspend/resume callbacks. + * This flag should be set to false only when SoundWire manager power mode + * set to ClockStopMode. + */ + adata->acp_reset = true; pci_set_master(pci); pci_set_drvdata(pci, adata); mutex_init(&adata->acp_lock); @@ -289,12 +522,18 @@ static int snd_acp63_probe(struct pci_dev *pci, goto de_init; } val = readl(adata->acp63_base + ACP_PIN_CONFIG); - get_acp63_device_config(val, pci, adata); + ret = get_acp63_device_config(val, pci, adata); + /* ACP PCI driver probe should be continued even PDM or SoundWire Devices are not found */ + if (ret) { + dev_err(&pci->dev, "get acp device config failed:%d\n", ret); + goto skip_pdev_creation; + } ret = create_acp63_platform_devs(pci, adata, addr); if (ret < 0) { dev_err(&pci->dev, "ACP platform devices creation failed\n"); goto de_init; } +skip_pdev_creation: pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&pci->dev); pm_runtime_put_noidle(&pci->dev); -- cgit v1.2.3 From e1cb350610ce88d9995b8b287930d3ba821d9f2b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:28:56 +0530 Subject: ASoC: amd: ps: handle SoundWire interrupts in acp pci driver Handle SoundWire manager related interrupts in ACP PCI driver interrupt handler and schedule SoundWire manager work queue for further processing. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 3 +++ sound/soc/amd/ps/pci-ps.c | 48 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 80ab542529a7..494f498bdc91 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -99,6 +99,9 @@ * manager instance combination. */ #define ACP63_SDW_PDM_DEV_CONFIG GENMASK(1, 0) +#define ACP_SDW0_STAT BIT(21) +#define ACP_SDW1_STAT BIT(2) +#define ACP_ERROR_IRQ BIT(29) enum acp_config { ACP_CONFIG_0 = 0, diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index cf57ad2d7ccc..ac82dbe13351 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -56,6 +56,7 @@ static int acp63_reset(void __iomem *acp_base) static void acp63_enable_interrupts(void __iomem *acp_base) { writel(1, acp_base + ACP_EXTERNAL_INTR_ENB); + writel(ACP_ERROR_IRQ, acp_base + ACP_EXTERNAL_INTR_CNTL); } static void acp63_disable_interrupts(void __iomem *acp_base) @@ -102,23 +103,60 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) { struct acp63_dev_data *adata; struct pdm_dev_data *ps_pdm_data; - u32 val; + struct amd_sdw_manager *amd_manager; + u32 ext_intr_stat, ext_intr_stat1; + u16 irq_flag = 0; u16 pdev_index; adata = dev_id; if (!adata) return IRQ_NONE; + /* ACP interrupts will be cleared by reading particular bit and writing + * same value to the status register. writing zero's doesn't have any + * effect. + * Bit by bit checking of IRQ field is implemented. + */ + ext_intr_stat = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + if (ext_intr_stat & ACP_SDW0_STAT) { + writel(ACP_SDW0_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + pdev_index = adata->sdw0_dev_index; + amd_manager = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + irq_flag = 1; + } + + ext_intr_stat1 = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + if (ext_intr_stat1 & ACP_SDW1_STAT) { + writel(ACP_SDW1_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + pdev_index = adata->sdw1_dev_index; + amd_manager = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + irq_flag = 1; + } - val = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT); - if (val & BIT(PDM_DMA_STAT)) { + if (ext_intr_stat & ACP_ERROR_IRQ) { + writel(ACP_ERROR_IRQ, adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + /* TODO: Report SoundWire Manager instance errors */ + writel(0, adata->acp63_base + ACP_SW0_I2S_ERROR_REASON); + writel(0, adata->acp63_base + ACP_SW1_I2S_ERROR_REASON); + writel(0, adata->acp63_base + ACP_ERROR_STATUS); + irq_flag = 1; + } + + if (ext_intr_stat & BIT(PDM_DMA_STAT)) { pdev_index = adata->pdm_dev_index; ps_pdm_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev); writel(BIT(PDM_DMA_STAT), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); if (ps_pdm_data->capture_stream) snd_pcm_period_elapsed(ps_pdm_data->capture_stream); - return IRQ_HANDLED; + irq_flag = 1; } - return IRQ_NONE; + if (irq_flag) + return IRQ_HANDLED; + else + return IRQ_NONE; } static int sdw_amd_scan_controller(struct device *dev) -- cgit v1.2.3 From 665dd181a97ff9588060f76887c3b61fd4ccb8b0 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:28:57 +0530 Subject: ASoC: amd: ps: add SoundWire dma driver SoundWire DMA platform driver binds to the platform device created by ACP PCI device. SoundWire DMA driver registers ALSA DMA component with ASoC framework. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 5 ++++ sound/soc/amd/ps/ps-sdw-dma.c | 70 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 sound/soc/amd/ps/ps-sdw-dma.c (limited to 'sound') diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 494f498bdc91..c95c57970a27 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -137,6 +137,11 @@ struct pdm_dev_data { struct snd_pcm_substream *capture_stream; }; +struct sdw_dma_dev_data { + void __iomem *acp_base; + struct mutex *acp_lock; /* used to protect acp common register access */ +}; + /** * struct acp63_dev_data - acp pci driver context * @acp63_base: acp mmio base diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c new file mode 100644 index 000000000000..f4a8d4022dc8 --- /dev/null +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * AMD ALSA SoC Pink Sardine SoundWire DMA Driver + * + * Copyright 2023 Advanced Micro Devices, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "acp63.h" + +#define DRV_NAME "amd_ps_sdw_dma" + +static const struct snd_soc_component_driver acp63_sdw_component = { + .name = DRV_NAME, +}; + +static int acp63_sdw_platform_probe(struct platform_device *pdev) +{ + struct resource *res; + struct sdw_dma_dev_data *sdw_data; + struct acp63_dev_data *acp_data; + struct device *parent; + int status; + + parent = pdev->dev.parent; + acp_data = dev_get_drvdata(parent); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n"); + return -ENODEV; + } + + sdw_data = devm_kzalloc(&pdev->dev, sizeof(*sdw_data), GFP_KERNEL); + if (!sdw_data) + return -ENOMEM; + + sdw_data->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!sdw_data->acp_base) + return -ENOMEM; + + sdw_data->acp_lock = &acp_data->acp_lock; + dev_set_drvdata(&pdev->dev, sdw_data); + status = devm_snd_soc_register_component(&pdev->dev, + &acp63_sdw_component, + NULL, 0); + if (status) + dev_err(&pdev->dev, "Fail to register sdw dma component\n"); + + return status; +} + +static struct platform_driver acp63_sdw_dma_driver = { + .probe = acp63_sdw_platform_probe, + .driver = { + .name = "amd_ps_sdw_dma", + }, +}; + +module_platform_driver(acp63_sdw_dma_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD ACP6.3 PS SDW DMA Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); -- cgit v1.2.3 From f722917350ee0b802a62d888f4e8b23bd5f1f641 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:28:58 +0530 Subject: ASoC: amd: ps: add SoundWire dma driver dma ops Add SoundWire DMA driver dma ops for Pink Sardine platform. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 73 ++++++++ sound/soc/amd/ps/ps-sdw-dma.c | 391 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 464 insertions(+) (limited to 'sound') diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index c95c57970a27..5f7ddcc31842 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -103,6 +103,49 @@ #define ACP_SDW1_STAT BIT(2) #define ACP_ERROR_IRQ BIT(29) +#define ACP_AUDIO0_TX_THRESHOLD 0x1c +#define ACP_AUDIO1_TX_THRESHOLD 0x1a +#define ACP_AUDIO2_TX_THRESHOLD 0x18 +#define ACP_AUDIO0_RX_THRESHOLD 0x1b +#define ACP_AUDIO1_RX_THRESHOLD 0x19 +#define ACP_AUDIO2_RX_THRESHOLD 0x17 +#define ACP_P1_AUDIO1_TX_THRESHOLD BIT(6) +#define ACP_P1_AUDIO1_RX_THRESHOLD BIT(5) +#define ACP_SDW_DMA_IRQ_MASK 0x1F800000 +#define ACP_P1_SDW_DMA_IRQ_MASK 0x60 +#define ACP63_SDW0_DMA_MAX_STREAMS 6 +#define ACP63_SDW1_DMA_MAX_STREAMS 2 +#define ACP_P1_AUDIO_TX_THRESHOLD 6 +#define SDW0_DMA_TX_IRQ_MASK(i) (ACP_AUDIO0_TX_THRESHOLD - (2 * (i))) +#define SDW0_DMA_RX_IRQ_MASK(i) (ACP_AUDIO0_RX_THRESHOLD - (2 * (i))) +#define SDW1_DMA_IRQ_MASK(i) (ACP_P1_AUDIO_TX_THRESHOLD - (i)) + +#define ACP_DELAY_US 5 +#define ACP_SDW_RING_BUFF_ADDR_OFFSET (128 * 1024) +#define SDW0_MEM_WINDOW_START 0x4800000 +#define ACP_SDW_SRAM_PTE_OFFSET 0x03800400 +#define SDW0_PTE_OFFSET 0x400 +#define SDW_FIFO_SIZE 0x100 +#define SDW_DMA_SIZE 0x40 +#define ACP_SDW0_FIFO_OFFSET 0x100 +#define ACP_SDW_PTE_OFFSET 0x100 +#define SDW_FIFO_OFFSET 0x100 +#define SDW_PTE_OFFSET(i) (SDW0_PTE_OFFSET + ((i) * 0x600)) +#define ACP_SDW_FIFO_OFFSET(i) (ACP_SDW0_FIFO_OFFSET + ((i) * 0x500)) +#define SDW_MEM_WINDOW_START(i) (SDW0_MEM_WINDOW_START + ((i) * 0xC0000)) + +#define SDW_PLAYBACK_MIN_NUM_PERIODS 2 +#define SDW_PLAYBACK_MAX_NUM_PERIODS 8 +#define SDW_PLAYBACK_MAX_PERIOD_SIZE 8192 +#define SDW_PLAYBACK_MIN_PERIOD_SIZE 1024 +#define SDW_CAPTURE_MIN_NUM_PERIODS 2 +#define SDW_CAPTURE_MAX_NUM_PERIODS 8 +#define SDW_CAPTURE_MAX_PERIOD_SIZE 8192 +#define SDW_CAPTURE_MIN_PERIOD_SIZE 1024 + +#define SDW_MAX_BUFFER (SDW_PLAYBACK_MAX_PERIOD_SIZE * SDW_PLAYBACK_MAX_NUM_PERIODS) +#define SDW_MIN_BUFFER SDW_MAX_BUFFER + enum acp_config { ACP_CONFIG_0 = 0, ACP_CONFIG_1, @@ -140,6 +183,36 @@ struct pdm_dev_data { struct sdw_dma_dev_data { void __iomem *acp_base; struct mutex *acp_lock; /* used to protect acp common register access */ + struct snd_pcm_substream *sdw0_dma_stream[ACP63_SDW0_DMA_MAX_STREAMS]; + struct snd_pcm_substream *sdw1_dma_stream[ACP63_SDW1_DMA_MAX_STREAMS]; +}; + +struct acp_sdw_dma_stream { + u16 num_pages; + u16 channels; + u32 stream_id; + u32 instance; + dma_addr_t dma_addr; + u64 bytescount; +}; + +union acp_sdw_dma_count { + struct { + u32 low; + u32 high; + } bcount; + u64 bytescount; +}; + +struct sdw_dma_ring_buf_reg { + u32 reg_dma_size; + u32 reg_fifo_addr; + u32 reg_fifo_size; + u32 reg_ring_buf_size; + u32 reg_ring_buf_addr; + u32 water_mark_size_reg; + u32 pos_low_reg; + u32 pos_high_reg; }; /** diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index f4a8d4022dc8..1de78948f859 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,12 +12,403 @@ #include #include #include +#include #include "acp63.h" #define DRV_NAME "amd_ps_sdw_dma" +static struct sdw_dma_ring_buf_reg sdw0_dma_ring_buf_reg[ACP63_SDW0_DMA_MAX_STREAMS] = { + {ACP_AUDIO0_TX_DMA_SIZE, ACP_AUDIO0_TX_FIFOADDR, ACP_AUDIO0_TX_FIFOSIZE, + ACP_AUDIO0_TX_RINGBUFSIZE, ACP_AUDIO0_TX_RINGBUFADDR, ACP_AUDIO0_TX_INTR_WATERMARK_SIZE, + ACP_AUDIO0_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_TX_LINEARPOSITIONCNTR_HIGH}, + {ACP_AUDIO1_TX_DMA_SIZE, ACP_AUDIO1_TX_FIFOADDR, ACP_AUDIO1_TX_FIFOSIZE, + ACP_AUDIO1_TX_RINGBUFSIZE, ACP_AUDIO1_TX_RINGBUFADDR, ACP_AUDIO1_TX_INTR_WATERMARK_SIZE, + ACP_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH}, + {ACP_AUDIO2_TX_DMA_SIZE, ACP_AUDIO2_TX_FIFOADDR, ACP_AUDIO2_TX_FIFOSIZE, + ACP_AUDIO2_TX_RINGBUFSIZE, ACP_AUDIO2_TX_RINGBUFADDR, ACP_AUDIO2_TX_INTR_WATERMARK_SIZE, + ACP_AUDIO2_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_TX_LINEARPOSITIONCNTR_HIGH}, + {ACP_AUDIO0_RX_DMA_SIZE, ACP_AUDIO0_RX_FIFOADDR, ACP_AUDIO0_RX_FIFOSIZE, + ACP_AUDIO0_RX_RINGBUFSIZE, ACP_AUDIO0_RX_RINGBUFADDR, ACP_AUDIO0_RX_INTR_WATERMARK_SIZE, + ACP_AUDIO0_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_TX_LINEARPOSITIONCNTR_HIGH}, + {ACP_AUDIO1_RX_DMA_SIZE, ACP_AUDIO1_RX_FIFOADDR, ACP_AUDIO1_RX_FIFOSIZE, + ACP_AUDIO1_RX_RINGBUFSIZE, ACP_AUDIO1_RX_RINGBUFADDR, ACP_AUDIO1_RX_INTR_WATERMARK_SIZE, + ACP_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH}, + {ACP_AUDIO2_RX_DMA_SIZE, ACP_AUDIO2_RX_FIFOADDR, ACP_AUDIO2_RX_FIFOSIZE, + ACP_AUDIO2_RX_RINGBUFSIZE, ACP_AUDIO2_RX_RINGBUFADDR, ACP_AUDIO2_RX_INTR_WATERMARK_SIZE, + ACP_AUDIO2_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_RX_LINEARPOSITIONCNTR_HIGH} +}; + +static struct sdw_dma_ring_buf_reg sdw1_dma_ring_buf_reg[ACP63_SDW1_DMA_MAX_STREAMS] = { + {ACP_P1_AUDIO1_TX_DMA_SIZE, ACP_P1_AUDIO1_TX_FIFOADDR, ACP_P1_AUDIO1_TX_FIFOSIZE, + ACP_P1_AUDIO1_TX_RINGBUFSIZE, ACP_P1_AUDIO1_TX_RINGBUFADDR, + ACP_P1_AUDIO1_TX_INTR_WATERMARK_SIZE, + ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH}, + {ACP_P1_AUDIO1_RX_DMA_SIZE, ACP_P1_AUDIO1_RX_FIFOADDR, ACP_P1_AUDIO1_RX_FIFOSIZE, + ACP_P1_AUDIO1_RX_RINGBUFSIZE, ACP_P1_AUDIO1_RX_RINGBUFADDR, + ACP_P1_AUDIO1_RX_INTR_WATERMARK_SIZE, + ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH}, +}; + +static u32 sdw0_dma_enable_reg[ACP63_SDW0_DMA_MAX_STREAMS] = { + ACP_SW0_AUDIO0_TX_EN, + ACP_SW0_AUDIO1_TX_EN, + ACP_SW0_AUDIO2_TX_EN, + ACP_SW0_AUDIO0_RX_EN, + ACP_SW0_AUDIO1_RX_EN, + ACP_SW0_AUDIO2_RX_EN, +}; + +static u32 sdw1_dma_enable_reg[ACP63_SDW1_DMA_MAX_STREAMS] = { + ACP_SW1_AUDIO1_TX_EN, + ACP_SW1_AUDIO1_RX_EN, +}; + +static const struct snd_pcm_hardware acp63_sdw_hardware_playback = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .buffer_bytes_max = SDW_PLAYBACK_MAX_NUM_PERIODS * SDW_PLAYBACK_MAX_PERIOD_SIZE, + .period_bytes_min = SDW_PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = SDW_PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = SDW_PLAYBACK_MIN_NUM_PERIODS, + .periods_max = SDW_PLAYBACK_MAX_NUM_PERIODS, +}; + +static const struct snd_pcm_hardware acp63_sdw_hardware_capture = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .buffer_bytes_max = SDW_CAPTURE_MAX_NUM_PERIODS * SDW_CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = SDW_CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = SDW_CAPTURE_MAX_PERIOD_SIZE, + .periods_min = SDW_CAPTURE_MIN_NUM_PERIODS, + .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS, +}; + +static void acp63_config_dma(struct acp_sdw_dma_stream *stream, void __iomem *acp_base, + u32 stream_id) +{ + u16 page_idx; + u32 low, high, val; + u32 sdw_dma_pte_offset; + dma_addr_t addr; + + addr = stream->dma_addr; + sdw_dma_pte_offset = SDW_PTE_OFFSET(stream->instance); + val = sdw_dma_pte_offset + (stream_id * ACP_SDW_PTE_OFFSET); + + /* Group Enable */ + writel(ACP_SDW_SRAM_PTE_OFFSET | BIT(31), acp_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_2); + writel(PAGE_SIZE_4K_ENABLE, acp_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2); + for (page_idx = 0; page_idx < stream->num_pages; page_idx++) { + /* Load the low address of page int ACP SRAM through SRBM */ + low = lower_32_bits(addr); + high = upper_32_bits(addr); + + writel(low, acp_base + ACP_SCRATCH_REG_0 + val); + high |= BIT(31); + writel(high, acp_base + ACP_SCRATCH_REG_0 + val + 4); + val += 8; + addr += PAGE_SIZE; + } + writel(0x1, acp_base + ACPAXI2AXI_ATU_CTRL); +} + +static int acp63_configure_sdw_ringbuffer(void __iomem *acp_base, u32 stream_id, u32 size, + u32 manager_instance) +{ + u32 reg_dma_size; + u32 reg_fifo_addr; + u32 reg_fifo_size; + u32 reg_ring_buf_size; + u32 reg_ring_buf_addr; + u32 sdw_fifo_addr; + u32 sdw_fifo_offset; + u32 sdw_ring_buf_addr; + u32 sdw_ring_buf_size; + u32 sdw_mem_window_offset; + + switch (manager_instance) { + case ACP_SDW0: + reg_dma_size = sdw0_dma_ring_buf_reg[stream_id].reg_dma_size; + reg_fifo_addr = sdw0_dma_ring_buf_reg[stream_id].reg_fifo_addr; + reg_fifo_size = sdw0_dma_ring_buf_reg[stream_id].reg_fifo_size; + reg_ring_buf_size = sdw0_dma_ring_buf_reg[stream_id].reg_ring_buf_size; + reg_ring_buf_addr = sdw0_dma_ring_buf_reg[stream_id].reg_ring_buf_addr; + break; + case ACP_SDW1: + reg_dma_size = sdw1_dma_ring_buf_reg[stream_id].reg_dma_size; + reg_fifo_addr = sdw1_dma_ring_buf_reg[stream_id].reg_fifo_addr; + reg_fifo_size = sdw1_dma_ring_buf_reg[stream_id].reg_fifo_size; + reg_ring_buf_size = sdw1_dma_ring_buf_reg[stream_id].reg_ring_buf_size; + reg_ring_buf_addr = sdw1_dma_ring_buf_reg[stream_id].reg_ring_buf_addr; + break; + default: + return -EINVAL; + } + sdw_fifo_offset = ACP_SDW_FIFO_OFFSET(manager_instance); + sdw_mem_window_offset = SDW_MEM_WINDOW_START(manager_instance); + sdw_fifo_addr = sdw_fifo_offset + (stream_id * SDW_FIFO_OFFSET); + sdw_ring_buf_addr = sdw_mem_window_offset + (stream_id * ACP_SDW_RING_BUFF_ADDR_OFFSET); + sdw_ring_buf_size = size; + writel(sdw_ring_buf_size, acp_base + reg_ring_buf_size); + writel(sdw_ring_buf_addr, acp_base + reg_ring_buf_addr); + writel(sdw_fifo_addr, acp_base + reg_fifo_addr); + writel(SDW_DMA_SIZE, acp_base + reg_dma_size); + writel(SDW_FIFO_SIZE, acp_base + reg_fifo_size); + return 0; +} + +static int acp63_sdw_dma_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct acp_sdw_dma_stream *stream; + struct snd_soc_dai *cpu_dai; + struct amd_sdw_manager *amd_manager; + struct snd_soc_pcm_runtime *prtd = substream->private_data; + int ret; + + runtime = substream->runtime; + cpu_dai = asoc_rtd_to_cpu(prtd, 0); + amd_manager = snd_soc_dai_get_drvdata(cpu_dai); + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = acp63_sdw_hardware_playback; + else + runtime->hw = acp63_sdw_hardware_capture; + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + dev_err(component->dev, "set integer constraint failed\n"); + kfree(stream); + return ret; + } + + stream->stream_id = cpu_dai->id; + stream->instance = amd_manager->instance; + runtime->private_data = stream; + return ret; +} + +static int acp63_sdw_dma_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct acp_sdw_dma_stream *stream; + struct sdw_dma_dev_data *sdw_data; + u32 period_bytes; + u32 water_mark_size_reg; + u32 irq_mask, ext_intr_ctrl; + u64 size; + u32 stream_id; + u32 acp_ext_intr_cntl_reg; + int ret; + + sdw_data = dev_get_drvdata(component->dev); + stream = substream->runtime->private_data; + if (!stream) + return -EINVAL; + stream_id = stream->stream_id; + switch (stream->instance) { + case ACP_SDW0: + sdw_data->sdw0_dma_stream[stream_id] = substream; + water_mark_size_reg = sdw0_dma_ring_buf_reg[stream_id].water_mark_size_reg; + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + irq_mask = BIT(SDW0_DMA_TX_IRQ_MASK(stream_id)); + else + irq_mask = BIT(SDW0_DMA_RX_IRQ_MASK(stream_id)); + break; + case ACP_SDW1: + sdw_data->sdw1_dma_stream[stream_id] = substream; + acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1; + water_mark_size_reg = sdw1_dma_ring_buf_reg[stream_id].water_mark_size_reg; + irq_mask = BIT(SDW1_DMA_IRQ_MASK(stream_id)); + break; + default: + return -EINVAL; + } + size = params_buffer_bytes(params); + period_bytes = params_period_bytes(params); + stream->dma_addr = substream->runtime->dma_addr; + stream->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); + acp63_config_dma(stream, sdw_data->acp_base, stream_id); + ret = acp63_configure_sdw_ringbuffer(sdw_data->acp_base, stream_id, size, + stream->instance); + if (ret) { + dev_err(component->dev, "Invalid DMA channel\n"); + return -EINVAL; + } + ext_intr_ctrl = readl(sdw_data->acp_base + acp_ext_intr_cntl_reg); + ext_intr_ctrl |= irq_mask; + writel(ext_intr_ctrl, sdw_data->acp_base + acp_ext_intr_cntl_reg); + writel(period_bytes, sdw_data->acp_base + water_mark_size_reg); + return 0; +} + +static u64 acp63_sdw_get_byte_count(struct acp_sdw_dma_stream *stream, void __iomem *acp_base) +{ + union acp_sdw_dma_count byte_count; + u32 pos_low_reg, pos_high_reg; + + byte_count.bytescount = 0; + switch (stream->instance) { + case ACP_SDW0: + pos_low_reg = sdw0_dma_ring_buf_reg[stream->stream_id].pos_low_reg; + pos_high_reg = sdw0_dma_ring_buf_reg[stream->stream_id].pos_high_reg; + break; + case ACP_SDW1: + pos_low_reg = sdw1_dma_ring_buf_reg[stream->stream_id].pos_low_reg; + pos_high_reg = sdw1_dma_ring_buf_reg[stream->stream_id].pos_high_reg; + break; + default: + return -EINVAL; + } + if (pos_low_reg) { + byte_count.bcount.high = readl(acp_base + pos_high_reg); + byte_count.bcount.low = readl(acp_base + pos_low_reg); + } + return byte_count.bytescount; +} + +static snd_pcm_uframes_t acp63_sdw_dma_pointer(struct snd_soc_component *comp, + struct snd_pcm_substream *substream) +{ + struct sdw_dma_dev_data *sdw_data; + struct acp_sdw_dma_stream *stream; + u32 pos, buffersize; + u64 bytescount; + + sdw_data = dev_get_drvdata(comp->dev); + stream = substream->runtime->private_data; + buffersize = frames_to_bytes(substream->runtime, + substream->runtime->buffer_size); + bytescount = acp63_sdw_get_byte_count(stream, sdw_data->acp_base); + if (bytescount > stream->bytescount) + bytescount -= stream->bytescount; + pos = do_div(bytescount, buffersize); + return bytes_to_frames(substream->runtime, pos); +} + +static int acp63_sdw_dma_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct device *parent = component->dev->parent; + + snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV, + parent, SDW_MIN_BUFFER, SDW_MAX_BUFFER); + return 0; +} + +static int acp63_sdw_dma_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct sdw_dma_dev_data *sdw_data; + struct acp_sdw_dma_stream *stream; + + sdw_data = dev_get_drvdata(component->dev); + stream = substream->runtime->private_data; + if (!stream) + return -EINVAL; + switch (stream->instance) { + case ACP_SDW0: + sdw_data->sdw0_dma_stream[stream->stream_id] = NULL; + break; + case ACP_SDW1: + sdw_data->sdw1_dma_stream[stream->stream_id] = NULL; + break; + default: + return -EINVAL; + } + kfree(stream); + return 0; +} + +static int acp63_sdw_dma_enable(struct snd_pcm_substream *substream, + void __iomem *acp_base, bool sdw_dma_enable) +{ + struct acp_sdw_dma_stream *stream; + u32 stream_id; + u32 sdw_dma_en_reg; + u32 sdw_dma_en_stat_reg; + u32 sdw_dma_stat; + u32 dma_enable; + + stream = substream->runtime->private_data; + stream_id = stream->stream_id; + switch (stream->instance) { + case ACP_SDW0: + sdw_dma_en_reg = sdw0_dma_enable_reg[stream_id]; + break; + case ACP_SDW1: + sdw_dma_en_reg = sdw1_dma_enable_reg[stream_id]; + break; + default: + return -EINVAL; + } + sdw_dma_en_stat_reg = sdw_dma_en_reg + 4; + dma_enable = sdw_dma_enable; + writel(dma_enable, acp_base + sdw_dma_en_reg); + return readl_poll_timeout(acp_base + sdw_dma_en_stat_reg, sdw_dma_stat, + (sdw_dma_stat == dma_enable), ACP_DELAY_US, ACP_COUNTER); +} + +static int acp63_sdw_dma_trigger(struct snd_soc_component *comp, + struct snd_pcm_substream *substream, + int cmd) +{ + struct sdw_dma_dev_data *sdw_data; + int ret; + + sdw_data = dev_get_drvdata(comp->dev); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + ret = acp63_sdw_dma_enable(substream, sdw_data->acp_base, true); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + ret = acp63_sdw_dma_enable(substream, sdw_data->acp_base, false); + break; + default: + ret = -EINVAL; + } + if (ret) + dev_err(comp->dev, "trigger %d failed: %d", cmd, ret); + return ret; +} + static const struct snd_soc_component_driver acp63_sdw_component = { .name = DRV_NAME, + .open = acp63_sdw_dma_open, + .close = acp63_sdw_dma_close, + .hw_params = acp63_sdw_dma_hw_params, + .trigger = acp63_sdw_dma_trigger, + .pointer = acp63_sdw_dma_pointer, + .pcm_construct = acp63_sdw_dma_new, }; static int acp63_sdw_platform_probe(struct platform_device *pdev) -- cgit v1.2.3 From 298d4f7b176538d41356d145c044442b8456a14e Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:28:59 +0530 Subject: ASoC: amd: ps: add support for SoundWire DMA interrupts Move to request_threaded_irq and use thread for handling SoundWire DMA interrupts. Whenever audio data equal to the SoundWire FIFO watermark level are produced/consumed, interrupt is generated. Acknowledge the interrupt and wake up the irq thread. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/acp63.h | 18 +++++++++++ sound/soc/amd/ps/pci-ps.c | 82 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/acp63.h b/sound/soc/amd/ps/acp63.h index 5f7ddcc31842..e96e6dc9d90f 100644 --- a/sound/soc/amd/ps/acp63.h +++ b/sound/soc/amd/ps/acp63.h @@ -165,6 +165,20 @@ enum acp_config { ACP_CONFIG_15, }; +enum amd_sdw0_channel { + ACP_SDW0_AUDIO0_TX = 0, + ACP_SDW0_AUDIO1_TX, + ACP_SDW0_AUDIO2_TX, + ACP_SDW0_AUDIO0_RX, + ACP_SDW0_AUDIO1_RX, + ACP_SDW0_AUDIO2_RX, +}; + +enum amd_sdw1_channel { + ACP_SDW1_AUDIO1_TX, + ACP_SDW1_AUDIO1_RX, +}; + struct pdm_stream_instance { u16 num_pages; u16 channels; @@ -229,6 +243,8 @@ struct sdw_dma_ring_buf_reg { * @sdw0_dev_index: SoundWire Manager-0 platform device index * @sdw1_dev_index: SoundWire Manager-1 platform device index * @sdw_dma_dev_index: SoundWire DMA controller platform device index + * @sdw0-dma_intr_stat: DMA interrupt status array for SoundWire manager-SW0 instance + * @sdw_dma_intr_stat: DMA interrupt status array for SoundWire manager-SW1 instance * @acp_reset: flag set to true when bus reset is applied across all * the active SoundWire manager instances */ @@ -246,6 +262,8 @@ struct acp63_dev_data { u16 sdw0_dev_index; u16 sdw1_dev_index; u16 sdw_dma_dev_index; + u16 sdw0_dma_intr_stat[ACP63_SDW0_DMA_MAX_STREAMS]; + u16 sdw1_dma_intr_stat[ACP63_SDW1_DMA_MAX_STREAMS]; bool acp_reset; }; diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index ac82dbe13351..ff734a90951b 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -99,14 +99,44 @@ static int acp63_deinit(void __iomem *acp_base, struct device *dev) return 0; } +static irqreturn_t acp63_irq_thread(int irq, void *context) +{ + struct sdw_dma_dev_data *sdw_dma_data; + struct acp63_dev_data *adata = context; + u32 stream_index; + u16 pdev_index; + + pdev_index = adata->sdw_dma_dev_index; + sdw_dma_data = dev_get_drvdata(&adata->pdev[pdev_index]->dev); + + for (stream_index = 0; stream_index < ACP63_SDW0_DMA_MAX_STREAMS; stream_index++) { + if (adata->sdw0_dma_intr_stat[stream_index]) { + if (sdw_dma_data->sdw0_dma_stream[stream_index]) + snd_pcm_period_elapsed(sdw_dma_data->sdw0_dma_stream[stream_index]); + adata->sdw0_dma_intr_stat[stream_index] = 0; + } + } + for (stream_index = 0; stream_index < ACP63_SDW1_DMA_MAX_STREAMS; stream_index++) { + if (adata->sdw1_dma_intr_stat[stream_index]) { + if (sdw_dma_data->sdw1_dma_stream[stream_index]) + snd_pcm_period_elapsed(sdw_dma_data->sdw1_dma_stream[stream_index]); + adata->sdw1_dma_intr_stat[stream_index] = 0; + } + } + return IRQ_HANDLED; +} + static irqreturn_t acp63_irq_handler(int irq, void *dev_id) { struct acp63_dev_data *adata; struct pdm_dev_data *ps_pdm_data; struct amd_sdw_manager *amd_manager; u32 ext_intr_stat, ext_intr_stat1; + u32 stream_id = 0; u16 irq_flag = 0; + u16 sdw_dma_irq_flag = 0; u16 pdev_index; + u16 index; adata = dev_id; if (!adata) @@ -153,6 +183,54 @@ static irqreturn_t acp63_irq_handler(int irq, void *dev_id) snd_pcm_period_elapsed(ps_pdm_data->capture_stream); irq_flag = 1; } + if (ext_intr_stat & ACP_SDW_DMA_IRQ_MASK) { + for (index = ACP_AUDIO2_RX_THRESHOLD; index <= ACP_AUDIO0_TX_THRESHOLD; index++) { + if (ext_intr_stat & BIT(index)) { + writel(BIT(index), adata->acp63_base + ACP_EXTERNAL_INTR_STAT); + switch (index) { + case ACP_AUDIO0_TX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO0_TX; + break; + case ACP_AUDIO1_TX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO1_TX; + break; + case ACP_AUDIO2_TX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO2_TX; + break; + case ACP_AUDIO0_RX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO0_RX; + break; + case ACP_AUDIO1_RX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO1_RX; + break; + case ACP_AUDIO2_RX_THRESHOLD: + stream_id = ACP_SDW0_AUDIO2_RX; + break; + } + + adata->sdw0_dma_intr_stat[stream_id] = 1; + sdw_dma_irq_flag = 1; + } + } + } + + if (ext_intr_stat1 & ACP_P1_AUDIO1_RX_THRESHOLD) { + writel(ACP_P1_AUDIO1_RX_THRESHOLD, + adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + adata->sdw1_dma_intr_stat[ACP_SDW1_AUDIO1_RX] = 1; + sdw_dma_irq_flag = 1; + } + + if (ext_intr_stat1 & ACP_P1_AUDIO1_TX_THRESHOLD) { + writel(ACP_P1_AUDIO1_TX_THRESHOLD, + adata->acp63_base + ACP_EXTERNAL_INTR_STAT1); + adata->sdw1_dma_intr_stat[ACP_SDW1_AUDIO1_TX] = 1; + sdw_dma_irq_flag = 1; + } + + if (sdw_dma_irq_flag) + return IRQ_WAKE_THREAD; + if (irq_flag) return IRQ_HANDLED; else @@ -553,8 +631,8 @@ static int snd_acp63_probe(struct pci_dev *pci, ret = acp63_init(adata->acp63_base, &pci->dev); if (ret) goto release_regions; - ret = devm_request_irq(&pci->dev, pci->irq, acp63_irq_handler, - irqflags, "ACP_PCI_IRQ", adata); + ret = devm_request_threaded_irq(&pci->dev, pci->irq, acp63_irq_handler, + acp63_irq_thread, irqflags, "ACP_PCI_IRQ", adata); if (ret) { dev_err(&pci->dev, "ACP PCI IRQ request failed\n"); goto de_init; -- cgit v1.2.3 From 5a06c3ac4cf9a8ca5edf99a07a1129ae25ab581e Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:29:00 +0530 Subject: ASoC: amd: ps: add pm ops support for SoundWire dma driver Add support pm ops support for SoundWire dma driver. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/ps-sdw-dma.c | 98 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/ps-sdw-dma.c b/sound/soc/amd/ps/ps-sdw-dma.c index 1de78948f859..ade130a8062a 100644 --- a/sound/soc/amd/ps/ps-sdw-dma.c +++ b/sound/soc/amd/ps/ps-sdw-dma.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "acp63.h" @@ -102,6 +103,29 @@ static const struct snd_pcm_hardware acp63_sdw_hardware_capture = { .periods_max = SDW_CAPTURE_MAX_NUM_PERIODS, }; +static void acp63_enable_disable_sdw_dma_interrupts(void __iomem *acp_base, bool enable) +{ + u32 ext_intr_cntl, ext_intr_cntl1; + u32 irq_mask = ACP_SDW_DMA_IRQ_MASK; + u32 irq_mask1 = ACP_P1_SDW_DMA_IRQ_MASK; + + if (enable) { + ext_intr_cntl = readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl |= irq_mask; + writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl1 = readl(acp_base + ACP_EXTERNAL_INTR_CNTL1); + ext_intr_cntl1 |= irq_mask1; + writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1); + } else { + ext_intr_cntl = readl(acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl &= ~irq_mask; + writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL); + ext_intr_cntl1 = readl(acp_base + ACP_EXTERNAL_INTR_CNTL1); + ext_intr_cntl1 &= ~irq_mask1; + writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1); + } +} + static void acp63_config_dma(struct acp_sdw_dma_stream *stream, void __iomem *acp_base, u32 stream_id) { @@ -440,16 +464,86 @@ static int acp63_sdw_platform_probe(struct platform_device *pdev) status = devm_snd_soc_register_component(&pdev->dev, &acp63_sdw_component, NULL, 0); - if (status) + if (status) { dev_err(&pdev->dev, "Fail to register sdw dma component\n"); + return status; + } + pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + return 0; +} - return status; +static int acp63_sdw_platform_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; } +static int acp_restore_sdw_dma_config(struct sdw_dma_dev_data *sdw_data) +{ + struct acp_sdw_dma_stream *stream; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + u32 period_bytes, buf_size, water_mark_size_reg; + u32 stream_count; + int index, instance, ret; + + for (instance = 0; instance < AMD_SDW_MAX_MANAGERS; instance++) { + if (instance == ACP_SDW0) + stream_count = ACP63_SDW0_DMA_MAX_STREAMS; + else + stream_count = ACP63_SDW1_DMA_MAX_STREAMS; + + for (index = 0; index < stream_count; index++) { + if (instance == ACP_SDW0) { + substream = sdw_data->sdw0_dma_stream[index]; + water_mark_size_reg = + sdw0_dma_ring_buf_reg[index].water_mark_size_reg; + } else { + substream = sdw_data->sdw1_dma_stream[index]; + water_mark_size_reg = + sdw1_dma_ring_buf_reg[index].water_mark_size_reg; + } + + if (substream && substream->runtime) { + runtime = substream->runtime; + stream = runtime->private_data; + period_bytes = frames_to_bytes(runtime, runtime->period_size); + buf_size = frames_to_bytes(runtime, runtime->buffer_size); + acp63_config_dma(stream, sdw_data->acp_base, index); + ret = acp63_configure_sdw_ringbuffer(sdw_data->acp_base, index, + buf_size, instance); + if (ret) + return ret; + writel(period_bytes, sdw_data->acp_base + water_mark_size_reg); + } + } + } + acp63_enable_disable_sdw_dma_interrupts(sdw_data->acp_base, true); + return 0; +} + +static int __maybe_unused acp63_sdw_pcm_resume(struct device *dev) +{ + struct sdw_dma_dev_data *sdw_data; + + sdw_data = dev_get_drvdata(dev); + return acp_restore_sdw_dma_config(sdw_data); +} + +static const struct dev_pm_ops acp63_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(NULL, acp63_sdw_pcm_resume) +}; + static struct platform_driver acp63_sdw_dma_driver = { .probe = acp63_sdw_platform_probe, + .remove = acp63_sdw_platform_remove, .driver = { .name = "amd_ps_sdw_dma", + .pm = &acp63_pm_ops, }, }; -- cgit v1.2.3 From 7b33594130405cbcfdef2a4c582f9c67aef8d292 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:29:01 +0530 Subject: ASoC: amd: ps: enable SoundWire dma driver build Enable SoundWire dma driver build for PS platform. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/Makefile | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/soc/amd/ps/Makefile b/sound/soc/amd/ps/Makefile index 383973a12f6a..f2a5eaf2fa4d 100644 --- a/sound/soc/amd/ps/Makefile +++ b/sound/soc/amd/ps/Makefile @@ -3,7 +3,9 @@ snd-pci-ps-objs := pci-ps.o snd-ps-pdm-dma-objs := ps-pdm-dma.o snd-soc-ps-mach-objs := ps-mach.o +snd-ps-sdw-dma-objs := ps-sdw-dma.o obj-$(CONFIG_SND_SOC_AMD_PS) += snd-pci-ps.o obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-pdm-dma.o +obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-sdw-dma.o obj-$(CONFIG_SND_SOC_AMD_PS_MACH) += snd-soc-ps-mach.o -- cgit v1.2.3 From 6e8f7cb4cbae8e2b03190d26674a14be22d7df53 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:29:02 +0530 Subject: ASoC: amd: update comments in Kconfig file Update comments in Kconfig file for Pink Sardine platform. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig index 57d5e342a8eb..1dd8579e8034 100644 --- a/sound/soc/amd/Kconfig +++ b/sound/soc/amd/Kconfig @@ -138,7 +138,8 @@ config SND_SOC_AMD_PS help This option enables Audio Coprocessor i.e ACP v6.3 support on AMD Pink sardine platform. By enabling this flag build will be - triggered for ACP PCI driver, ACP PDM DMA driver. + triggered for ACP PCI driver, ACP PDM DMA driver, ACP SoundWire + DMA driver. Say m if you have such a device. If unsure select "N". -- cgit v1.2.3 From 198c93e2fc0b74ae520393118d7cb02762c04bf3 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Mon, 12 Jun 2023 15:29:03 +0530 Subject: ASoC: amd: ps: add acp_reset flag check in acp pci driver pm ops. AMD SoundWire manager supports different power modes. acp_reset flag will be set to false only when SoundWire manager power mode is selected as ClockStop Mode. For rest of the combinations (ACP PDM + SDW), acp_reset flag will be set to true. When acp_reset flag is set, acp de-init and acp init sequence should be invoked during suspend/resume callbacks. Signed-off-by: Vijendar Mukunda Link: https://lore.kernel.org/r/20230612095903.2113464-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/ps/pci-ps.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/pci-ps.c b/sound/soc/amd/ps/pci-ps.c index ff734a90951b..5b46dc8573f8 100644 --- a/sound/soc/amd/ps/pci-ps.c +++ b/sound/soc/amd/ps/pci-ps.c @@ -669,24 +669,28 @@ disable_pci: static int __maybe_unused snd_acp63_suspend(struct device *dev) { struct acp63_dev_data *adata; - int ret; + int ret = 0; adata = dev_get_drvdata(dev); - ret = acp63_deinit(adata->acp63_base, dev); - if (ret) - dev_err(dev, "ACP de-init failed\n"); + if (adata->acp_reset) { + ret = acp63_deinit(adata->acp63_base, dev); + if (ret) + dev_err(dev, "ACP de-init failed\n"); + } return ret; } static int __maybe_unused snd_acp63_resume(struct device *dev) { struct acp63_dev_data *adata; - int ret; + int ret = 0; adata = dev_get_drvdata(dev); - ret = acp63_init(adata->acp63_base, dev); - if (ret) - dev_err(dev, "ACP init failed\n"); + if (adata->acp_reset) { + ret = acp63_init(adata->acp63_base, dev); + if (ret) + dev_err(dev, "ACP init failed\n"); + } return ret; } -- cgit v1.2.3 From 7ea9ee0064281ef630e038f8dd2f6e8f4030a696 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 10:28:05 +0100 Subject: ALSA: compress: allow setting codec params after next track For gapless playback it is possible that each track can have different codec profile with same decoder, for example we have WMA album, we may have different tracks as WMA v9, WMA v10 and so on Or if DSP's like QDSP have abililty to switch decoders on single stream for each track, then this call could be used to set new codec parameters. Existing code does not allow to change this profile while doing gapless playback. Reuse existing SNDRV_COMPRESS_SET_PARAMS to set this new track params along some additional checks to enforce proper state machine. With this new changes now the user can call SNDRV_COMPRESS_SET_PARAMS anytime after setting next track and additional check in write should also ensure that params are set before writing new data. Signed-off-by: Srinivas Kandagatla Acked-by: Vinod Koul Link: https://lore.kernel.org/r/20230619092805.21649-1-srinivas.kandagatla@linaro.org Signed-off-by: Takashi Iwai --- Documentation/sound/designs/compress-offload.rst | 11 ++++++----- sound/core/compress_offload.c | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/designs/compress-offload.rst b/Documentation/sound/designs/compress-offload.rst index 935f325dbc77..655624f77092 100644 --- a/Documentation/sound/designs/compress-offload.rst +++ b/Documentation/sound/designs/compress-offload.rst @@ -268,11 +268,12 @@ with setting of meta_data and signalling for next track :: | | | V | +----------+ - | | | - | |NEXT_TRACK| - | | | - | +----------+ - | | + | compr_set_params() | | + | +-----------|NEXT_TRACK| + | | | | + | | +--+-------+ + | | | | + | +--------------+ | | | | | compr_partial_drain() | | diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 243acad89fd3..30f73097447b 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -589,7 +589,7 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) struct snd_compr_params *params; int retval; - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN || stream->next_track) { /* * we should allow parameter change only when stream has been * opened not in other cases @@ -612,6 +612,9 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) goto out; + if (stream->next_track) + goto out; + stream->metadata_set = false; stream->next_track = false; -- cgit v1.2.3 From 8d0cf150d299148a97653610c256f10c42f85ce0 Mon Sep 17 00:00:00 2001 From: Ivan Orlov Date: Tue, 20 Jun 2023 19:56:34 +0200 Subject: sound: make all 'class' structures const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that the driver core allows for struct class to be in read-only memory, making all 'class' structures to be declared at build time placing them into read-only memory, instead of having to be dynamically allocated at load time. Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: Ivan Orlov Cc: Greg Kroah-Hartman Cc: Geoff Levand Cc: Thierry Reding Cc: "Uwe Kleine-König" Cc: alsa-devel@alsa-project.org Suggested-by: Greg Kroah-Hartman Signed-off-by: Ivan Orlov Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20230620175633.641141-2-gregkh@linuxfoundation.org Signed-off-by: Takashi Iwai --- include/sound/core.h | 2 +- sound/core/control_led.c | 2 +- sound/core/init.c | 4 ++-- sound/sound_core.c | 23 ++++++++++++----------- 4 files changed, 16 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/include/sound/core.h b/include/sound/core.h index 4ea5f66b59d7..f6e0dd648b80 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -232,7 +232,7 @@ static inline struct device *snd_card_get_device_link(struct snd_card *card) extern int snd_major; extern int snd_ecards_limit; -extern struct class *sound_class; +extern const struct class sound_class; #ifdef CONFIG_SND_DEBUG extern struct dentry *sound_debugfs_root; #endif diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3cadd40100f3..ee77547bf8dc 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -737,7 +737,7 @@ static int __init snd_ctl_led_init(void) unsigned int group; device_initialize(&snd_ctl_led_dev); - snd_ctl_led_dev.class = sound_class; + snd_ctl_led_dev.class = &sound_class; snd_ctl_led_dev.release = snd_ctl_led_dev_release; dev_set_name(&snd_ctl_led_dev, "ctl-led"); if (device_add(&snd_ctl_led_dev)) { diff --git a/sound/core/init.c b/sound/core/init.c index df0c22480375..baef2688d0cf 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -129,7 +129,7 @@ void snd_device_initialize(struct device *dev, struct snd_card *card) device_initialize(dev); if (card) dev->parent = &card->card_dev; - dev->class = sound_class; + dev->class = &sound_class; dev->release = default_release; } EXPORT_SYMBOL_GPL(snd_device_initialize); @@ -331,7 +331,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, device_initialize(&card->card_dev); card->card_dev.parent = parent; - card->card_dev.class = sound_class; + card->card_dev.class = &sound_class; card->card_dev.release = release_card_device; card->card_dev.groups = card->dev_groups; card->dev_groups[0] = &card_dev_attr_group; diff --git a/sound/sound_core.c b/sound/sound_core.c index 4f6911274d56..d81fed1c1226 100644 --- a/sound/sound_core.c +++ b/sound/sound_core.c @@ -23,9 +23,6 @@ static inline int init_oss_soundcore(void) { return 0; } static inline void cleanup_oss_soundcore(void) { } #endif -struct class *sound_class; -EXPORT_SYMBOL(sound_class); - MODULE_DESCRIPTION("Core sound module"); MODULE_AUTHOR("Alan Cox"); MODULE_LICENSE("GPL"); @@ -37,6 +34,12 @@ static char *sound_devnode(const struct device *dev, umode_t *mode) return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); } +const struct class sound_class = { + .name = "sound", + .devnode = sound_devnode, +}; +EXPORT_SYMBOL(sound_class); + static int __init init_soundcore(void) { int rc; @@ -45,21 +48,19 @@ static int __init init_soundcore(void) if (rc) return rc; - sound_class = class_create("sound"); - if (IS_ERR(sound_class)) { + rc = class_register(&sound_class); + if (rc) { cleanup_oss_soundcore(); - return PTR_ERR(sound_class); + return rc; } - sound_class->devnode = sound_devnode; - return 0; } static void __exit cleanup_soundcore(void) { cleanup_oss_soundcore(); - class_destroy(sound_class); + class_unregister(&sound_class); } subsys_initcall(init_soundcore); @@ -276,7 +277,7 @@ retry: } } - device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), + device_create(&sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), NULL, "%s", s->name+6); return s->unit_minor; @@ -302,7 +303,7 @@ static void sound_remove_unit(struct sound_unit **list, int unit) if (!preclaim_oss) __unregister_chrdev(SOUND_MAJOR, p->unit_minor, 1, p->name); - device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); + device_destroy(&sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); kfree(p); } } -- cgit v1.2.3 From a79807683781d3f215e9d958494e52ed70f4ad27 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:39 +0200 Subject: ALSA: ump: Add helper to change MIDI protocol This is a preliminary patch for MIDI 2.0 USB gadget driver. Export a new helper to allow changing the current MIDI protocol from the outside. Link: https://lore.kernel.org/r/20230621110241.4751-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 2 ++ sound/core/ump.c | 31 ++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 68478e7be3b4..3c7e67475676 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -108,6 +108,8 @@ static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, } #endif +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol); + /* * Some definitions for UMP */ diff --git a/sound/core/ump.c b/sound/core/ump.c index a64dc2d8a129..4150b9c0b35b 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -671,18 +671,35 @@ static void seq_notify_protocol(struct snd_ump_endpoint *ump) #endif /* CONFIG_SND_SEQUENCER */ } +/** + * snd_ump_switch_protocol - switch MIDI protocol + * @ump: UMP endpoint + * @protocol: protocol to switch to + * + * Returns 1 if the protocol is actually switched, 0 if unchanged + */ +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol) +{ + protocol &= ump->info.protocol_caps; + if (protocol == ump->info.protocol) + return 0; + + ump->info.protocol = protocol; + ump_dbg(ump, "New protocol = %x (caps = %x)\n", + protocol, ump->info.protocol_caps); + seq_notify_protocol(ump); + return 1; +} +EXPORT_SYMBOL_GPL(snd_ump_switch_protocol); + /* handle EP stream config message; update the UMP protocol */ static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) { - unsigned int old_protocol = ump->info.protocol; - - ump->info.protocol = + unsigned int protocol = (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; - ump_dbg(ump, "Current protocol = %x (caps = %x)\n", - ump->info.protocol, ump->info.protocol_caps); - if (ump->parsed && ump->info.protocol != old_protocol) - seq_notify_protocol(ump); + + snd_ump_switch_protocol(ump, protocol); return 1; /* finished */ } -- cgit v1.2.3 From eacd9c7f1d3ab8381a99b98b36652b5cf6ae8387 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:40 +0200 Subject: ALSA: ump: Add no_process_stream flag This is another preliminary patch for USB MIDI 2.0 gadget driver. Add a new flag, no_process_stream, to snd_ump for suppressing the UMP Stream message handling in UMP core. Link: https://lore.kernel.org/r/20230621110241.4751-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/ump.c | 4 ++++ 2 files changed, 5 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 3c7e67475676..2f6a9944c6ef 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -28,6 +28,7 @@ struct snd_ump_endpoint { u32 stream_wait_for; /* expected stream message status */ bool stream_finished; /* set when message has been processed */ bool parsed; /* UMP / FB parse finished? */ + bool no_process_stream; /* suppress UMP stream messages handling */ wait_queue_head_t stream_wait; struct snd_rawmidi_file stream_rfile; diff --git a/sound/core/ump.c b/sound/core/ump.c index 4150b9c0b35b..5e73c9cf5919 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -854,6 +854,10 @@ static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, unsigned int status; int ret; + /* UMP stream message suppressed (for gadget UMP)? */ + if (ump->no_process_stream) + return; + BUILD_BUG_ON(sizeof(*msg) != 16); ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", buf[0], buf[1], buf[2], buf[3]); -- cgit v1.2.3 From 4dce2f076b7d0a0a99867b58eea7c212ff4e2be5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:41 +0200 Subject: ALSA: ump: Export snd_ump_receive_ump_val() This is another preliminary patch for USB MIDI 2.0 gadget driver. Export the currently local snd_ump_receive_ump_val(). It can be used by the gadget driver for processing the UMP data. Link: https://lore.kernel.org/r/20230621110241.4751-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/ump.c | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 2f6a9944c6ef..44d2c2fd021d 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -109,6 +109,7 @@ static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, } #endif +int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val); int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol); /* diff --git a/sound/core/ump.c b/sound/core/ump.c index 5e73c9cf5919..5e17351ca984 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -263,12 +263,16 @@ static unsigned char ump_packet_words[0x10] = { 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 }; -/* parse the UMP packet data; - * the data is copied onto ump->input_buf[]. +/** + * snd_ump_receive_ump_val - parse the UMP packet data + * @ump: UMP endpoint + * @val: UMP packet data + * + * The data is copied onto ump->input_buf[]. * When a full packet is completed, returns the number of words (from 1 to 4). * OTOH, if the packet is incomplete, returns 0. */ -static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) { int words; @@ -284,6 +288,7 @@ static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) } return 0; } +EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val); /** * snd_ump_receive - transfer UMP packets from the device -- cgit v1.2.3 From 1f583cbdc342e15bfbde61ba142480c70e7694b4 Mon Sep 17 00:00:00 2001 From: Terry Cheong Date: Fri, 16 Jun 2023 02:36:16 -0400 Subject: ASoC: Intel: sof_rt5682: reorder quirk table Reorder the entries in quirk table to group entries with same platform. Signed-off-by: Terry Cheong Signed-off-by: Mac Chiang Link: https://lore.kernel.org/r/20230616063617.25900-1-mac.chiang@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index 86bbc1fea6ff..be34d7595537 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -981,14 +981,13 @@ static const struct platform_device_id board_ids[] = { .name = "sof_rt5682", }, { - .name = "tgl_mx98357_rt5682", + .name = "cml_rt1015_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | - SOF_RT5682_SSP_AMP(1) | - SOF_RT5682_NUM_HDMIDEV(4) | - SOF_BT_OFFLOAD_SSP(2) | - SOF_SSP_BT_OFFLOAD_PRESENT), + SOF_RT1015_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), }, { .name = "jsl_rt5682_rt1015", @@ -1000,33 +999,38 @@ static const struct platform_device_id board_ids[] = { SOF_RT5682_SSP_AMP(1)), }, { - .name = "tgl_mx98373_rt5682", + .name = "jsl_rt5682_mx98360", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_MCLK_24MHZ | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | - SOF_MAX98373_SPEAKER_AMP_PRESENT | - SOF_RT5682_SSP_AMP(1) | - SOF_RT5682_NUM_HDMIDEV(4) | - SOF_BT_OFFLOAD_SSP(2) | - SOF_SSP_BT_OFFLOAD_PRESENT), + SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1)), }, { - .name = "jsl_rt5682_mx98360", + .name = "jsl_rt5682_rt1015p", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_MCLK_24MHZ | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | - SOF_MAX98360A_SPEAKER_AMP_PRESENT | + SOF_RT1015P_SPEAKER_AMP_PRESENT | SOF_RT5682_SSP_AMP(1)), }, { - .name = "cml_rt1015_rt5682", + .name = "jsl_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_MCLK_24MHZ | + SOF_RT5682_SSP_CODEC(0)), + }, + { + .name = "tgl_mx98357_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | - SOF_RT1015_SPEAKER_AMP_PRESENT | - SOF_RT5682_SSP_AMP(1)), + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), }, { .name = "tgl_rt1011_rt5682", @@ -1040,13 +1044,15 @@ static const struct platform_device_id board_ids[] = { SOF_SSP_BT_OFFLOAD_PRESENT), }, { - .name = "jsl_rt5682_rt1015p", + .name = "tgl_mx98373_rt5682", .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | - SOF_RT5682_MCLK_24MHZ | SOF_RT5682_SSP_CODEC(0) | SOF_SPEAKER_AMP_PRESENT | - SOF_RT1015P_SPEAKER_AMP_PRESENT | - SOF_RT5682_SSP_AMP(1)), + SOF_MAX98373_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(1) | + SOF_RT5682_NUM_HDMIDEV(4) | + SOF_BT_OFFLOAD_SSP(2) | + SOF_SSP_BT_OFFLOAD_PRESENT), }, { .name = "adl_mx98373_rt5682", @@ -1149,12 +1155,6 @@ static const struct platform_device_id board_ids[] = { SOF_RT5682_SSP_AMP(1) | SOF_RT5682_NUM_HDMIDEV(4)), }, - { - .name = "jsl_rt5682", - .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | - SOF_RT5682_MCLK_24MHZ | - SOF_RT5682_SSP_CODEC(0)), - }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); -- cgit v1.2.3 From b20c81371a96b87478d2430d80615df189d17cd8 Mon Sep 17 00:00:00 2001 From: Mac Chiang Date: Fri, 16 Jun 2023 02:36:17 -0400 Subject: ASoC: Intel: sof_rt5682: Add mtl support RT1019P speaker This patch support below hardware configuration: SSP2: 10EC5682/RTL5682 codec SSP0: RTL1019 amplifier Signed-off-by: Mac Chiang Signed-off-by: Rui Zhou Link: https://lore.kernel.org/r/20230616063617.25900-2-mac.chiang@intel.com Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_rt5682.c | 23 +++++++++++++++++++++++ sound/soc/intel/common/soc-acpi-intel-mtl-match.c | 12 ++++++++++++ 2 files changed, 35 insertions(+) (limited to 'sound') diff --git a/sound/soc/intel/boards/sof_rt5682.c b/sound/soc/intel/boards/sof_rt5682.c index be34d7595537..7c034d671cf3 100644 --- a/sound/soc/intel/boards/sof_rt5682.c +++ b/sound/soc/intel/boards/sof_rt5682.c @@ -239,6 +239,20 @@ static const struct dmi_system_id sof_rt5682_quirk_table[] = { SOF_SSP_BT_OFFLOAD_PRESENT ), }, + { + .callback = sof_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_PRODUCT_FAMILY, "Google_Rex"), + DMI_MATCH(DMI_OEM_STRING, "AUDIO-ALC1019_ALC5682I_I2S"), + }, + .driver_data = (void *)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(2) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1019_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(0) | + SOF_RT5682_NUM_HDMIDEV(3) + ), + }, { .callback = sof_rt5682_quirk_cb, .matches = { @@ -1155,6 +1169,15 @@ static const struct platform_device_id board_ids[] = { SOF_RT5682_SSP_AMP(1) | SOF_RT5682_NUM_HDMIDEV(4)), }, + { + .name = "mtl_rt1019_rt5682", + .driver_data = (kernel_ulong_t)(SOF_RT5682_MCLK_EN | + SOF_RT5682_SSP_CODEC(2) | + SOF_SPEAKER_AMP_PRESENT | + SOF_RT1019_SPEAKER_AMP_PRESENT | + SOF_RT5682_SSP_AMP(0) | + SOF_RT5682_NUM_HDMIDEV(3)), + }, { } }; MODULE_DEVICE_TABLE(platform, board_ids); diff --git a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c index 3d5cf8867926..ed9821adc1d9 100644 --- a/sound/soc/intel/common/soc-acpi-intel-mtl-match.c +++ b/sound/soc/intel/common/soc-acpi-intel-mtl-match.c @@ -20,6 +20,11 @@ static const struct snd_soc_acpi_codecs mtl_max98360a_amp = { .codecs = {"MX98360A"} }; +static const struct snd_soc_acpi_codecs mtl_rt1019p_amp = { + .num_codecs = 1, + .codecs = {"RTL1019"} +}; + static const struct snd_soc_acpi_codecs mtl_rt5682_rt5682s_hp = { .num_codecs = 2, .codecs = {"10EC5682", "RTL5682"}, @@ -40,6 +45,13 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_mtl_machines[] = { .quirk_data = &mtl_max98360a_amp, .sof_tplg_filename = "sof-mtl-max98360a-rt5682.tplg", }, + { + .comp_ids = &mtl_rt5682_rt5682s_hp, + .drv_name = "mtl_rt1019_rt5682", + .machine_quirk = snd_soc_acpi_codec_list, + .quirk_data = &mtl_rt1019p_amp, + .sof_tplg_filename = "sof-mtl-rt1019-rt5682.tplg", + }, {}, }; EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_mtl_machines); -- cgit v1.2.3 From aa21a7d4f68a0a5067578cbb93c136ab5ac09cfa Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 16 Jun 2023 13:57:51 +0200 Subject: ASoC: codecs: wsa884x: Add WSA884x family of speakers Add drivers for Qualcomm WSA8840/WSA8845/WSA8845H smart speaker amplifiers. Signed-off-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20230616115751.392886-2-krzysztof.kozlowski@linaro.org Signed-off-by: Mark Brown --- MAINTAINERS | 1 + sound/soc/codecs/Kconfig | 10 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wsa884x.c | 1936 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1949 insertions(+) create mode 100644 sound/soc/codecs/wsa884x.c (limited to 'sound') diff --git a/MAINTAINERS b/MAINTAINERS index fc758fc19589..bcd9da307818 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17163,6 +17163,7 @@ F: sound/soc/codecs/wcd9335.* F: sound/soc/codecs/wcd934x.c F: sound/soc/codecs/wsa881x.c F: sound/soc/codecs/wsa883x.c +F: sound/soc/codecs/wsa884x.c F: sound/soc/qcom/ QCOM EMBEDDED USB DEBUGGER (EUD) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c8dd553ea6d2..7895969bcc39 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -330,6 +330,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_WM9713 imply SND_SOC_WSA881X imply SND_SOC_WSA883X + imply SND_SOC_WSA884X imply SND_SOC_ZL38060 help Normally ASoC codec drivers are only built if a machine driver which @@ -2218,6 +2219,15 @@ config SND_SOC_WSA883X This enables support for Qualcomm WSA8830/WSA8835 Class-D Smart Speaker Amplifier. +config SND_SOC_WSA884X + tristate "WSA884X Codec" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + tristate + help + This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D + Smart Speaker Amplifier. + config SND_SOC_ZL38060 tristate "Microsemi ZL38060 Connected Home Audio Processor" depends on SPI_MASTER diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index b532bbdabd74..b48a9a323b84 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -366,6 +366,7 @@ snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-wsa881x-objs := wsa881x.o snd-soc-wsa883x-objs := wsa883x.o +snd-soc-wsa884x-objs := wsa884x.o snd-soc-zl38060-objs := zl38060.o # Amp snd-soc-max9877-objs := max9877.o @@ -747,6 +748,7 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o +obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o # Amp diff --git a/sound/soc/codecs/wsa884x.c b/sound/soc/codecs/wsa884x.c new file mode 100644 index 000000000000..993d76b18b53 --- /dev/null +++ b/sound/soc/codecs/wsa884x.c @@ -0,0 +1,1936 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WSA884X_BASE 0x3000 +#define WSA884X_ANA_BG_TSADC_BASE (WSA884X_BASE + 0x0001) +#define WSA884X_BG_CTRL (WSA884X_ANA_BG_TSADC_BASE + 0x00) +#define WSA884X_ADC_CTRL (WSA884X_ANA_BG_TSADC_BASE + 0x01) +#define WSA884X_BOP1_PROG (WSA884X_ANA_BG_TSADC_BASE + 0x02) +#define WSA884X_BOP2_PROG (WSA884X_ANA_BG_TSADC_BASE + 0x03) +#define WSA884X_BOP2_PROG_BOP2_VTH_MASK 0xf0 +#define WSA884X_BOP2_PROG_BOP2_VTH_SHIFT 4 +#define WSA884X_BOP2_PROG_BOP2_HYST_MASK 0x0f +#define WSA884X_BOP2_PROG_BOP2_HYST_SHIFT 0 +#define WSA884X_UVLO_PROG (WSA884X_ANA_BG_TSADC_BASE + 0x04) +#define WSA884X_UVLO_PROG1 (WSA884X_ANA_BG_TSADC_BASE + 0x05) +#define WSA884X_SPARE_CTRL_0 (WSA884X_ANA_BG_TSADC_BASE + 0x06) +#define WSA884X_SPARE_CTRL_1 (WSA884X_ANA_BG_TSADC_BASE + 0x07) +#define WSA884X_SPARE_CTRL_2 (WSA884X_ANA_BG_TSADC_BASE + 0x08) +#define WSA884X_SPARE_CTRL_3 (WSA884X_ANA_BG_TSADC_BASE + 0x09) +#define WSA884X_REF_CTRL (WSA884X_ANA_BG_TSADC_BASE + 0x0a) +#define WSA884X_REF_CTRL_BG_RDY_SEL_MASK 0x03 +#define WSA884X_REF_CTRL_BG_RDY_SEL_SHIFT 0 +#define WSA884X_BG_TEST_CTL (WSA884X_ANA_BG_TSADC_BASE + 0x0b) +#define WSA884X_BG_BIAS (WSA884X_ANA_BG_TSADC_BASE + 0x0c) +#define WSA884X_ADC_PROG (WSA884X_ANA_BG_TSADC_BASE + 0x0d) +#define WSA884X_ADC_IREF_CTL (WSA884X_ANA_BG_TSADC_BASE + 0x0e) +#define WSA884X_ADC_ISENS_CTL (WSA884X_ANA_BG_TSADC_BASE + 0x0f) +#define WSA884X_ADC_CLK_CTL (WSA884X_ANA_BG_TSADC_BASE + 0x10) +#define WSA884X_ADC_TEST_CTL (WSA884X_ANA_BG_TSADC_BASE + 0x11) +#define WSA884X_ADC_BIAS (WSA884X_ANA_BG_TSADC_BASE + 0x12) +#define WSA884X_VBAT_SNS (WSA884X_ANA_BG_TSADC_BASE + 0x13) +#define WSA884X_DOUT_MSB (WSA884X_ANA_BG_TSADC_BASE + 0x14) +#define WSA884X_DOUT_LSB (WSA884X_ANA_BG_TSADC_BASE + 0x15) +#define WSA884X_BOP_ATEST_SEL (WSA884X_ANA_BG_TSADC_BASE + 0x16) +#define WSA884X_MISC0 (WSA884X_ANA_BG_TSADC_BASE + 0x17) +#define WSA884X_MISC1 (WSA884X_ANA_BG_TSADC_BASE + 0x18) +#define WSA884X_MISC2 (WSA884X_ANA_BG_TSADC_BASE + 0x19) +#define WSA884X_MISC3 (WSA884X_ANA_BG_TSADC_BASE + 0x1a) +#define WSA884X_SPARE_TSBG_0 (WSA884X_ANA_BG_TSADC_BASE + 0x1b) +#define WSA884X_SPARE_TUNE_0 (WSA884X_ANA_BG_TSADC_BASE + 0x1c) +#define WSA884X_SPARE_TUNE_1 (WSA884X_ANA_BG_TSADC_BASE + 0x1d) + +#define WSA884X_ANA_IVSENSE_BASE (WSA884X_BASE + 0x0020) +#define WSA884X_VSENSE1 (WSA884X_ANA_IVSENSE_BASE + 0x00) +#define WSA884X_VSENSE1_GAIN_VSENSE_FE_MASK 0xe0 +#define WSA884X_VSENSE1_GAIN_VSENSE_FE_SHIFT 5 +#define WSA884X_ISENSE2 (WSA884X_ANA_IVSENSE_BASE + 0x01) +#define WSA884X_ISENSE2_ISENSE_GAIN_CTL_MASK 0xe0 +#define WSA884X_ISENSE2_ISENSE_GAIN_CTL_SHIFT 5 + +#define WSA884X_SPARE_CTL_1 (WSA884X_ANA_IVSENSE_BASE + 0x02) +#define WSA884X_SPARE_CTL_2 (WSA884X_ANA_IVSENSE_BASE + 0x03) +#define WSA884X_SPARE_CTL_3 (WSA884X_ANA_IVSENSE_BASE + 0x04) +#define WSA884X_SPARE_CTL_4 (WSA884X_ANA_IVSENSE_BASE + 0x05) +#define WSA884X_EN (WSA884X_ANA_IVSENSE_BASE + 0x06) +#define WSA884X_OVERRIDE1 (WSA884X_ANA_IVSENSE_BASE + 0x07) +#define WSA884X_OVERRIDE2 (WSA884X_ANA_IVSENSE_BASE + 0x08) +#define WSA884X_ISENSE1 (WSA884X_ANA_IVSENSE_BASE + 0x09) +#define WSA884X_ISENSE_CAL (WSA884X_ANA_IVSENSE_BASE + 0x0a) +#define WSA884X_MISC (WSA884X_ANA_IVSENSE_BASE + 0x0b) +#define WSA884X_ADC_0 (WSA884X_ANA_IVSENSE_BASE + 0x0c) +#define WSA884X_ADC_1 (WSA884X_ANA_IVSENSE_BASE + 0x0d) +#define WSA884X_ADC_2 (WSA884X_ANA_IVSENSE_BASE + 0x0e) +#define WSA884X_ADC_3 (WSA884X_ANA_IVSENSE_BASE + 0x0f) +#define WSA884X_ADC_4 (WSA884X_ANA_IVSENSE_BASE + 0x10) +#define WSA884X_ADC_5 (WSA884X_ANA_IVSENSE_BASE + 0x11) +#define WSA884X_ADC_6 (WSA884X_ANA_IVSENSE_BASE + 0x12) +#define WSA884X_ADC_7 (WSA884X_ANA_IVSENSE_BASE + 0x13) +#define WSA884X_STATUS (WSA884X_ANA_IVSENSE_BASE + 0x14) +#define WSA884X_IVSENSE_SPARE_TUNE_1 (WSA884X_ANA_IVSENSE_BASE + 0x15) +#define WSA884X_SPARE_TUNE_2 (WSA884X_ANA_IVSENSE_BASE + 0x16) +#define WSA884X_SPARE_TUNE_3 (WSA884X_ANA_IVSENSE_BASE + 0x17) +#define WSA884X_SPARE_TUNE_4 (WSA884X_ANA_IVSENSE_BASE + 0x18) + +#define WSA884X_ANA_SPK_TOP_BASE (WSA884X_BASE + 0x0040) +#define WSA884X_TOP_CTRL1 (WSA884X_ANA_SPK_TOP_BASE + 0x00) +#define WSA884X_TOP_CTRL1_OCP_LOWVBAT_ITH_EN_MASK 0x01 +#define WSA884X_CLIP_DET_CTRL1 (WSA884X_ANA_SPK_TOP_BASE + 0x01) +#define WSA884X_CLIP_DET_CTRL2 (WSA884X_ANA_SPK_TOP_BASE + 0x02) +#define WSA884X_DAC_CTRL1 (WSA884X_ANA_SPK_TOP_BASE + 0x03) +#define WSA884X_DAC_VCM_CTRL_REG1 (WSA884X_ANA_SPK_TOP_BASE + 0x04) +#define WSA884X_DAC_VCM_CTRL_REG2 (WSA884X_ANA_SPK_TOP_BASE + 0x05) +#define WSA884X_DAC_VCM_CTRL_REG3 (WSA884X_ANA_SPK_TOP_BASE + 0x06) +#define WSA884X_DAC_VCM_CTRL_REG4 (WSA884X_ANA_SPK_TOP_BASE + 0x07) +#define WSA884X_DAC_VCM_CTRL_REG5 (WSA884X_ANA_SPK_TOP_BASE + 0x08) +#define WSA884X_DAC_VCM_CTRL_REG6 (WSA884X_ANA_SPK_TOP_BASE + 0x09) +#define WSA884X_PWM_CLK_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x0a) +#define WSA884X_PWM_CLK_CTL_VCMO_INT1_IDLE_MODE_OVRT_MASK 0x80 +#define WSA884X_PWM_CLK_CTL_VCMO_INT1_IDLE_MODE_OVRT_SHIFT 7 +#define WSA884X_PWM_CLK_CTL_REG_MCLK_DIV_RATIO_MASK 0x40 +#define WSA884X_PWM_CLK_CTL_REG_MCLK_DIV_RATIO_SHIFT 6 +#define WSA884X_PWM_CLK_CTL_PWM_DEGLITCH_CLK_DELAY_CTRL_MASK 0x30 +#define WSA884X_PWM_CLK_CTL_PWM_DEGLITCH_CLK_DELAY_CTRL_SHIFT 4 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_FREQ_SEL_MASK 0x08 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_FREQ_SEL_SHIFT 3 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_DIV_RATIO_MASK 0x06 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_DIV_RATIO_SHIFT 1 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_DIV_BYPASS_MASK 0x01 +#define WSA884X_PWM_CLK_CTL_PWM_CLK_DIV_BYPASS_SHIFT 0 +#define WSA884X_DRV_LF_LDO_SEL (WSA884X_ANA_SPK_TOP_BASE + 0x0b) +#define WSA884X_OCP_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x0c) +#define WSA884X_PDRV_HS_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x0d) +#define WSA884X_PDRV_LS_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x0e) +#define WSA884X_SPK_TOP_SPARE_CTL_1 (WSA884X_ANA_SPK_TOP_BASE + 0x0f) +#define WSA884X_SPK_TOP_SPARE_CTL_2 (WSA884X_ANA_SPK_TOP_BASE + 0x10) +#define WSA884X_SPK_TOP_SPARE_CTL_3 (WSA884X_ANA_SPK_TOP_BASE + 0x11) +#define WSA884X_SPK_TOP_SPARE_CTL_4 (WSA884X_ANA_SPK_TOP_BASE + 0x12) +#define WSA884X_SPARE_CTL_5 (WSA884X_ANA_SPK_TOP_BASE + 0x13) +#define WSA884X_DAC_EN_DEBUG_REG (WSA884X_ANA_SPK_TOP_BASE + 0x14) +#define WSA884X_DAC_OPAMP_BIAS1_REG (WSA884X_ANA_SPK_TOP_BASE + 0x15) +#define WSA884X_DAC_OPAMP_BIAS2_REG (WSA884X_ANA_SPK_TOP_BASE + 0x16) +#define WSA884X_DAC_TUNE1 (WSA884X_ANA_SPK_TOP_BASE + 0x17) +#define WSA884X_DAC_VOLTAGE_CTRL_REG (WSA884X_ANA_SPK_TOP_BASE + 0x18) +#define WSA884X_ATEST1_REG (WSA884X_ANA_SPK_TOP_BASE + 0x19) +#define WSA884X_ATEST2_REG (WSA884X_ANA_SPK_TOP_BASE + 0x1a) +#define WSA884X_TOP_BIAS_REG1 (WSA884X_ANA_SPK_TOP_BASE + 0x1b) +#define WSA884X_TOP_BIAS_REG2 (WSA884X_ANA_SPK_TOP_BASE + 0x1c) +#define WSA884X_TOP_BIAS_REG3 (WSA884X_ANA_SPK_TOP_BASE + 0x1d) +#define WSA884X_TOP_BIAS_REG4 (WSA884X_ANA_SPK_TOP_BASE + 0x1e) +#define WSA884X_PWRSTG_DBG2 (WSA884X_ANA_SPK_TOP_BASE + 0x1f) +#define WSA884X_DRV_LF_BLK_EN (WSA884X_ANA_SPK_TOP_BASE + 0x20) +#define WSA884X_DRV_LF_EN (WSA884X_ANA_SPK_TOP_BASE + 0x21) +#define WSA884X_DRV_LF_MASK_DCC_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x22) +#define WSA884X_DRV_LF_MISC_CTL1 (WSA884X_ANA_SPK_TOP_BASE + 0x23) +#define WSA884X_DRV_LF_REG_GAIN (WSA884X_ANA_SPK_TOP_BASE + 0x24) +#define WSA884X_DRV_OS_CAL_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x25) +#define WSA884X_DRV_OS_CAL_CTL1 (WSA884X_ANA_SPK_TOP_BASE + 0x26) +#define WSA884X_PWRSTG_DBG (WSA884X_ANA_SPK_TOP_BASE + 0x27) +#define WSA884X_BBM_CTL (WSA884X_ANA_SPK_TOP_BASE + 0x28) +#define WSA884X_TOP_MISC1 (WSA884X_ANA_SPK_TOP_BASE + 0x29) +#define WSA884X_DAC_VCM_CTRL_REG7 (WSA884X_ANA_SPK_TOP_BASE + 0x2a) +#define WSA884X_TOP_BIAS_REG5 (WSA884X_ANA_SPK_TOP_BASE + 0x2b) +#define WSA884X_DRV_LF_MISC_CTL2 (WSA884X_ANA_SPK_TOP_BASE + 0x2c) +#define WSA884X_SPK_TOP_SPARE_TUNE_2 (WSA884X_ANA_SPK_TOP_BASE + 0x2d) +#define WSA884X_SPK_TOP_SPARE_TUNE_3 (WSA884X_ANA_SPK_TOP_BASE + 0x2e) +#define WSA884X_SPK_TOP_SPARE_TUNE_4 (WSA884X_ANA_SPK_TOP_BASE + 0x2f) +#define WSA884X_SPARE_TUNE_5 (WSA884X_ANA_SPK_TOP_BASE + 0x30) +#define WSA884X_SPARE_TUNE_6 (WSA884X_ANA_SPK_TOP_BASE + 0x31) +#define WSA884X_SPARE_TUNE_7 (WSA884X_ANA_SPK_TOP_BASE + 0x32) +#define WSA884X_SPARE_TUNE_8 (WSA884X_ANA_SPK_TOP_BASE + 0x33) +#define WSA884X_SPARE_TUNE_9 (WSA884X_ANA_SPK_TOP_BASE + 0x34) +#define WSA884X_SPARE_TUNE_10 (WSA884X_ANA_SPK_TOP_BASE + 0x35) +#define WSA884X_PA_STATUS0 (WSA884X_ANA_SPK_TOP_BASE + 0x36) +#define WSA884X_PA_STATUS1 (WSA884X_ANA_SPK_TOP_BASE + 0x37) +#define WSA884X_PA_STATUS2 (WSA884X_ANA_SPK_TOP_BASE + 0x38) +#define WSA884X_PA_STATUS3 (WSA884X_ANA_SPK_TOP_BASE + 0x39) +#define WSA884X_PA_STATUS4 (WSA884X_ANA_SPK_TOP_BASE + 0x3a) +#define WSA884X_PA_STATUS5 (WSA884X_ANA_SPK_TOP_BASE + 0x3b) +#define WSA884X_SPARE_RO_1 (WSA884X_ANA_SPK_TOP_BASE + 0x3c) +#define WSA884X_SPARE_RO_2 (WSA884X_ANA_SPK_TOP_BASE + 0x3d) +#define WSA884X_SPARE_RO_3 (WSA884X_ANA_SPK_TOP_BASE + 0x3e) + +#define WSA884X_ANA_BOOST_BASE (WSA884X_BASE + 0x0090) +#define WSA884X_STB_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x00) +#define WSA884X_STB_CTRL1_SLOPE_COMP_CURRENT_MASK 0xf8 +#define WSA884X_STB_CTRL1_SLOPE_COMP_CURRENT_SHIFT 3 +#define WSA884X_STB_CTRL1_VOUT_FS_MASK 0x07 +#define WSA884X_STB_CTRL1_VOUT_FS_SHIFT 0 +#define WSA884X_CURRENT_LIMIT (WSA884X_ANA_BOOST_BASE + 0x01) +#define WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_OVRD_EN_MASK 0x80 +#define WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_OVRD_EN_SHIFT 7 +#define WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_MASK 0x7c +#define WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_SHIFT 2 +#define WSA884X_CURRENT_LIMIT_CLK_PHASE_SHIFT 0 +#define WSA884X_BYP_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x02) +#define WSA884X_SPARE_CTL_0 (WSA884X_ANA_BOOST_BASE + 0x03) +#define WSA884X_BOOST_SPARE_CTL_1 (WSA884X_ANA_BOOST_BASE + 0x04) +#define WSA884X_SPARE_RO_0 (WSA884X_ANA_BOOST_BASE + 0x05) +#define WSA884X_BOOST_SPARE_RO_1 (WSA884X_ANA_BOOST_BASE + 0x06) +#define WSA884X_IBIAS1 (WSA884X_ANA_BOOST_BASE + 0x07) +#define WSA884X_IBIAS2 (WSA884X_ANA_BOOST_BASE + 0x08) +#define WSA884X_IBIAS3 (WSA884X_ANA_BOOST_BASE + 0x09) +#define WSA884X_EN_CTRL (WSA884X_ANA_BOOST_BASE + 0x0a) +#define WSA884X_STB_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x0b) +#define WSA884X_STB_CTRL3 (WSA884X_ANA_BOOST_BASE + 0x0c) +#define WSA884X_STB_CTRL4 (WSA884X_ANA_BOOST_BASE + 0x0d) +#define WSA884X_BYP_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x0e) +#define WSA884X_BYP_CTRL3 (WSA884X_ANA_BOOST_BASE + 0x0f) +#define WSA884X_ZX_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x10) +#define WSA884X_ZX_CTRL1_ZX_DET_EN_MASK 0x80 +#define WSA884X_ZX_CTRL1_ZX_DET_EN_SHIFT 7 +#define WSA884X_ZX_CTRL1_ZX_DET_SW_EN_MASK 0x40 +#define WSA884X_ZX_CTRL1_ZX_DET_SW_EN_SHIFT 6 +#define WSA884X_ZX_CTRL1_ZX_DET_STAGE_DEFAULT_MASK 0x20 +#define WSA884X_ZX_CTRL1_ZX_DET_STAGE_DEFAULT_SHIFT 5 +#define WSA884X_ZX_CTRL1_ZX_DET_SW_SEL_MASK 0x18 +#define WSA884X_ZX_CTRL1_ZX_DET_SW_SEL_SHIFT 3 +#define WSA884X_ZX_CTRL1_ZX_BYP_MASK_IGNORE_MASK 0x04 +#define WSA884X_ZX_CTRL1_ZX_BYP_MASK_IGNORE_SHIFT 2 +#define WSA884X_ZX_CTRL1_ZX_BYP_MASK_DEL_MASK 0x02 +#define WSA884X_ZX_CTRL1_ZX_BYP_MASK_DEL_SHIFT 1 +#define WSA884X_ZX_CTRL1_BOOTCAP_REFRESH_DIS_MASK 0x01 +#define WSA884X_ZX_CTRL1_BOOTCAP_REFRESH_DIS_SHIFT 0 +#define WSA884X_ZX_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x11) +#define WSA884X_BLEEDER_CTRL (WSA884X_ANA_BOOST_BASE + 0x12) +#define WSA884X_BOOST_MISC (WSA884X_ANA_BOOST_BASE + 0x13) +#define WSA884X_PWRSTAGE_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x14) +#define WSA884X_PWRSTAGE_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x15) +#define WSA884X_PWRSTAGE_CTRL3 (WSA884X_ANA_BOOST_BASE + 0x16) +#define WSA884X_PWRSTAGE_CTRL4 (WSA884X_ANA_BOOST_BASE + 0x17) +#define WSA884X_MAXD_REG1 (WSA884X_ANA_BOOST_BASE + 0x18) +#define WSA884X_MAXD_REG2 (WSA884X_ANA_BOOST_BASE + 0x19) +#define WSA884X_ILIM_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x1a) +#define WSA884X_ILIM_CTRL1_EN_AUTO_MAXD_SEL_MASK 0x80 +#define WSA884X_ILIM_CTRL1_EN_AUTO_MAXD_SEL_SHIFT 0x07 +#define WSA884X_ILIM_CTRL1_EN_ILIM_SW_CLH_MASK 0x40 +#define WSA884X_ILIM_CTRL1_EN_ILIM_SW_CLH_SHIFT 0x06 +#define WSA884X_ILIM_CTRL1_ILIM_OFFSET_CLH_MASK 0x38 +#define WSA884X_ILIM_CTRL1_ILIM_OFFSET_CLH_SHIFT 0x03 +#define WSA884X_ILIM_CTRL1_ILIM_OFFSET_PB_MASK 0x07 +#define WSA884X_ILIM_CTRL1_ILIM_OFFSET_PB_SHIFT 0x00 +#define WSA884X_ILIM_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x1b) +#define WSA884X_TEST_CTRL1 (WSA884X_ANA_BOOST_BASE + 0x1c) +#define WSA884X_TEST_CTRL2 (WSA884X_ANA_BOOST_BASE + 0x1d) +#define WSA884X_SPARE1 (WSA884X_ANA_BOOST_BASE + 0x1e) +#define WSA884X_BOOT_CAP_CHECK (WSA884X_ANA_BOOST_BASE + 0x1f) + +#define WSA884X_ANA_PON_LDOL_BASE (WSA884X_BASE + 0x00b0) +#define WSA884X_PON_CTL_0 (WSA884X_ANA_PON_LDOL_BASE + 0x00) +#define WSA884X_PWRSAV_CTL (WSA884X_ANA_PON_LDOL_BASE + 0x01) +#define WSA884X_PON_LDOL_SPARE_CTL_0 (WSA884X_ANA_PON_LDOL_BASE + 0x02) +#define WSA884X_PON_LDOL_SPARE_CTL_1 (WSA884X_ANA_PON_LDOL_BASE + 0x03) +#define WSA884X_PON_LDOL_SPARE_CTL_2 (WSA884X_ANA_PON_LDOL_BASE + 0x04) +#define WSA884X_PON_LDOL_SPARE_CTL_3 (WSA884X_ANA_PON_LDOL_BASE + 0x05) +#define WSA884X_PON_CLT_1 (WSA884X_ANA_PON_LDOL_BASE + 0x06) +#define WSA884X_PON_CTL_2 (WSA884X_ANA_PON_LDOL_BASE + 0x07) +#define WSA884X_PON_CTL_3 (WSA884X_ANA_PON_LDOL_BASE + 0x08) +#define WSA884X_CKWD_CTL_0 (WSA884X_ANA_PON_LDOL_BASE + 0x09) +#define WSA884X_CKWD_CTL_1 (WSA884X_ANA_PON_LDOL_BASE + 0x0a) +#define WSA884X_CKWD_CTL_1_VPP_SW_CTL_MASK 0x20 +#define WSA884X_CKWD_CTL_1_VPP_SW_CTL_SHIFT 5 +#define WSA884X_CKWD_CTL_1_CKWD_VCOMP_VREF_SEL_MASK 0x1f +#define WSA884X_CKWD_CTL_1_CKWD_VCOMP_VREF_SEL_SHIFT 0 +#define WSA884X_CKWD_CTL_2 (WSA884X_ANA_PON_LDOL_BASE + 0x0b) +#define WSA884X_CKSK_CTL_0 (WSA884X_ANA_PON_LDOL_BASE + 0x0c) +#define WSA884X_PADSW_CTL_0 (WSA884X_ANA_PON_LDOL_BASE + 0x0d) +#define WSA884X_TEST_0 (WSA884X_ANA_PON_LDOL_BASE + 0x0e) +#define WSA884X_TEST_1 (WSA884X_ANA_PON_LDOL_BASE + 0x0f) +#define WSA884X_STATUS_0 (WSA884X_ANA_PON_LDOL_BASE + 0x10) +#define WSA884X_STATUS_1 (WSA884X_ANA_PON_LDOL_BASE + 0x11) +#define WSA884X_PON_LDOL_SPARE_TUNE_0 (WSA884X_ANA_PON_LDOL_BASE + 0x12) +#define WSA884X_PON_LDOL_SPARE_TUNE_1 (WSA884X_ANA_PON_LDOL_BASE + 0x13) +#define WSA884X_PON_LDOL_SPARE_TUNE_2 (WSA884X_ANA_PON_LDOL_BASE + 0x14) +#define WSA884X_PON_LDOL_SPARE_TUNE_3 (WSA884X_ANA_PON_LDOL_BASE + 0x15) +#define WSA884X_PON_LDOL_SPARE_TUNE_4 (WSA884X_ANA_PON_LDOL_BASE + 0x16) + +#define WSA884X_DIG_CTRL0_BASE (WSA884X_BASE + 0x0400) +#define WSA884X_DIG_CTRL0_PAGE (WSA884X_DIG_CTRL0_BASE + 0x00) +#define WSA884X_CHIP_ID0 (WSA884X_DIG_CTRL0_BASE + 0x01) +#define WSA884X_CHIP_ID1 (WSA884X_DIG_CTRL0_BASE + 0x02) +#define WSA884X_CHIP_ID2 (WSA884X_DIG_CTRL0_BASE + 0x03) +#define WSA884X_CHIP_ID3 (WSA884X_DIG_CTRL0_BASE + 0x04) +#define WSA884X_BUS_ID (WSA884X_DIG_CTRL0_BASE + 0x05) +#define WSA884X_CDC_RST_CTL (WSA884X_DIG_CTRL0_BASE + 0x10) +#define WSA884X_SWR_RESET_EN (WSA884X_DIG_CTRL0_BASE + 0x14) +#define WSA884X_TOP_CLK_CFG (WSA884X_DIG_CTRL0_BASE + 0x18) +#define WSA884X_SWR_CLK_RATE (WSA884X_DIG_CTRL0_BASE + 0x19) +#define WSA884X_CDC_PATH_MODE (WSA884X_DIG_CTRL0_BASE + 0x1a) +#define WSA884X_CDC_PATH_MODE_RXD_MODE_MASK 0x02 +#define WSA884X_CDC_PATH_MODE_RXD_MODE_SHIFT 0 +#define WSA884X_CDC_PATH_MODE_TXD_MODE_MASK 0x01 +#define WSA884X_CDC_PATH_MODE_TXD_MODE_SHIFT 0 +#define WSA884X_CDC_CLK_CTL (WSA884X_DIG_CTRL0_BASE + 0x1c) +#define WSA884X_PA_FSM_EN (WSA884X_DIG_CTRL0_BASE + 0x30) +#define WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK 0x01 +#define WSA884X_PA_FSM_EN_GLOBAL_PA_EN_SHIFT 0 +#define WSA884X_PA_FSM_CTL0 (WSA884X_DIG_CTRL0_BASE + 0x31) +#define WSA884X_PA_FSM_CTL1 (WSA884X_DIG_CTRL0_BASE + 0x32) +#define WSA884X_PA_FSM_CTL1_NOISE_GATE_BLOCK_MASK 0x38 +#define WSA884X_PA_FSM_TIMER0 (WSA884X_DIG_CTRL0_BASE + 0x33) +#define WSA884X_PA_FSM_TIMER1 (WSA884X_DIG_CTRL0_BASE + 0x34) +#define WSA884X_PA_FSM_STA0 (WSA884X_DIG_CTRL0_BASE + 0x35) +#define WSA884X_PA_FSM_STA1 (WSA884X_DIG_CTRL0_BASE + 0x36) +#define WSA884X_PA_FSM_ERR_CTL (WSA884X_DIG_CTRL0_BASE + 0x37) +#define WSA884X_PA_FSM_ERR_COND0 (WSA884X_DIG_CTRL0_BASE + 0x38) +#define WSA884X_PA_FSM_ERR_COND1 (WSA884X_DIG_CTRL0_BASE + 0x39) +#define WSA884X_PA_FSM_MSK0 (WSA884X_DIG_CTRL0_BASE + 0x3a) +#define WSA884X_PA_FSM_MSK1 (WSA884X_DIG_CTRL0_BASE + 0x3b) +#define WSA884X_PA_FSM_BYP_CTL (WSA884X_DIG_CTRL0_BASE + 0x3c) +#define WSA884X_PA_FSM_BYP0 (WSA884X_DIG_CTRL0_BASE + 0x3d) +#define WSA884X_PA_FSM_BYP1 (WSA884X_DIG_CTRL0_BASE + 0x3e) +#define WSA884X_TADC_VALUE_CTL (WSA884X_DIG_CTRL0_BASE + 0x50) +#define WSA884X_TEMP_DETECT_CTL (WSA884X_DIG_CTRL0_BASE + 0x51) +#define WSA884X_TEMP_DIN_MSB (WSA884X_DIG_CTRL0_BASE + 0x52) +#define WSA884X_TEMP_DIN_LSB (WSA884X_DIG_CTRL0_BASE + 0x53) +#define WSA884X_TEMP_DOUT_MSB (WSA884X_DIG_CTRL0_BASE + 0x54) +#define WSA884X_TEMP_DOUT_LSB (WSA884X_DIG_CTRL0_BASE + 0x55) +#define WSA884X_TEMP_CONFIG0 (WSA884X_DIG_CTRL0_BASE + 0x56) +#define WSA884X_TEMP_CONFIG1 (WSA884X_DIG_CTRL0_BASE + 0x57) +#define WSA884X_VBAT_THRM_FLT_CTL (WSA884X_DIG_CTRL0_BASE + 0x58) +#define WSA884X_VBAT_THRM_FLT_CTL_THRM_COEF_SEL_MASK 0xe0 +#define WSA884X_VBAT_THRM_FLT_CTL_THRM_COEF_SEL_SHIFT 5 +#define WSA884X_VBAT_THRM_FLT_CTL_THRM_FLT_EN_SHIFT 4 +#define WSA884X_VBAT_THRM_FLT_CTL_VBAT_COEF_SEL_MASK 0x0e +#define WSA884X_VBAT_THRM_FLT_CTL_VBAT_COEF_SEL_SHIFT 1 +#define WSA884X_VBAT_THRM_FLT_CTL_VBAT_FLT_EN_SHIFT 0 +#define WSA884X_VBAT_CAL_CTL (WSA884X_DIG_CTRL0_BASE + 0x59) +#define WSA884X_VBAT_CAL_CTL_RESERVE_MASK 0x0e +#define WSA884X_VBAT_CAL_CTL_VBAT_CAL_EN_MASK 0x01 +#define WSA884X_VBAT_DIN_MSB (WSA884X_DIG_CTRL0_BASE + 0x5a) +#define WSA884X_VBAT_DIN_LSB (WSA884X_DIG_CTRL0_BASE + 0x5b) +#define WSA884X_VBAT_DOUT_MSB (WSA884X_DIG_CTRL0_BASE + 0x5c) +#define WSA884X_VBAT_DOUT_LSB (WSA884X_DIG_CTRL0_BASE + 0x5d) +#define WSA884X_VBAT_CAL_MSB (WSA884X_DIG_CTRL0_BASE + 0x5e) +#define WSA884X_VBAT_CAL_LSB (WSA884X_DIG_CTRL0_BASE + 0x5f) +#define WSA884X_UVLO_DEGLITCH_CTL (WSA884X_DIG_CTRL0_BASE + 0x60) +#define WSA884X_BOP_DEGLITCH_CTL (WSA884X_DIG_CTRL0_BASE + 0x61) +#define WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_SETTING_MASK 0x1e +#define WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_SETTING_SHIFT 1 +#define WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_EN_MASK 0x1 +#define WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_EN_SHIFT 0 +#define WSA884X_VBAT_ZONE_DETC_CTL (WSA884X_DIG_CTRL0_BASE + 0x64) +#define WSA884X_CPS_CTL (WSA884X_DIG_CTRL0_BASE + 0x68) +#define WSA884X_CDC_RX_CTL (WSA884X_DIG_CTRL0_BASE + 0x70) +#define WSA884X_CDC_SPK_DSM_A1_0 (WSA884X_DIG_CTRL0_BASE + 0x71) +#define WSA884X_CDC_SPK_DSM_A1_1 (WSA884X_DIG_CTRL0_BASE + 0x72) +#define WSA884X_CDC_SPK_DSM_A2_0 (WSA884X_DIG_CTRL0_BASE + 0x73) +#define WSA884X_CDC_SPK_DSM_A2_1 (WSA884X_DIG_CTRL0_BASE + 0x74) +#define WSA884X_CDC_SPK_DSM_A3_0 (WSA884X_DIG_CTRL0_BASE + 0x75) +#define WSA884X_CDC_SPK_DSM_A3_1 (WSA884X_DIG_CTRL0_BASE + 0x76) +#define WSA884X_CDC_SPK_DSM_A4_0 (WSA884X_DIG_CTRL0_BASE + 0x77) +#define WSA884X_CDC_SPK_DSM_A4_1 (WSA884X_DIG_CTRL0_BASE + 0x78) +#define WSA884X_CDC_SPK_DSM_A5_0 (WSA884X_DIG_CTRL0_BASE + 0x79) +#define WSA884X_CDC_SPK_DSM_A5_1 (WSA884X_DIG_CTRL0_BASE + 0x7a) +#define WSA884X_CDC_SPK_DSM_A6_0 (WSA884X_DIG_CTRL0_BASE + 0x7b) +#define WSA884X_CDC_SPK_DSM_A7_0 (WSA884X_DIG_CTRL0_BASE + 0x7c) +#define WSA884X_CDC_SPK_DSM_C_0 (WSA884X_DIG_CTRL0_BASE + 0x7d) +#define WSA884X_CDC_SPK_DSM_C_0_COEF_C3_MASK 0xf0 +#define WSA884X_CDC_SPK_DSM_C_0_COEF_C3_SHIFT 4 +#define WSA884X_CDC_SPK_DSM_C_0_COEF_C2_MASK 0x0f +#define WSA884X_CDC_SPK_DSM_C_0_COEF_C2_SHIFT 0 +#define WSA884X_CDC_SPK_DSM_C_1 (WSA884X_DIG_CTRL0_BASE + 0x7e) +#define WSA884X_CDC_SPK_DSM_C_2 (WSA884X_DIG_CTRL0_BASE + 0x7f) +#define WSA884X_CDC_SPK_DSM_C_2_COEF_C7_MASK 0xf0 +#define WSA884X_CDC_SPK_DSM_C_2_COEF_C7_SHIFT 4 +#define WSA884X_CDC_SPK_DSM_C_2_COEF_C6_MASK 0x0f +#define WSA884X_CDC_SPK_DSM_C_2_COEF_C6_SHIFT 0 +#define WSA884X_CDC_SPK_DSM_C_3 (WSA884X_DIG_CTRL0_BASE + 0x80) +#define WSA884X_CDC_SPK_DSM_C_3_COEF_C7_MASK 0x3f +#define WSA884X_CDC_SPK_DSM_C_3_COEF_C7_SHIFT 0 +#define WSA884X_CDC_SPK_DSM_R1 (WSA884X_DIG_CTRL0_BASE + 0x81) +#define WSA884X_CDC_SPK_DSM_R2 (WSA884X_DIG_CTRL0_BASE + 0x82) +#define WSA884X_CDC_SPK_DSM_R3 (WSA884X_DIG_CTRL0_BASE + 0x83) +#define WSA884X_CDC_SPK_DSM_R4 (WSA884X_DIG_CTRL0_BASE + 0x84) +#define WSA884X_CDC_SPK_DSM_R5 (WSA884X_DIG_CTRL0_BASE + 0x85) +#define WSA884X_CDC_SPK_DSM_R6 (WSA884X_DIG_CTRL0_BASE + 0x86) +#define WSA884X_CDC_SPK_DSM_R7 (WSA884X_DIG_CTRL0_BASE + 0x87) +#define WSA884X_CDC_SPK_GAIN_PDM_0 (WSA884X_DIG_CTRL0_BASE + 0x88) +#define WSA884X_CDC_SPK_GAIN_PDM_1 (WSA884X_DIG_CTRL0_BASE + 0x89) +#define WSA884X_CDC_SPK_GAIN_PDM_2 (WSA884X_DIG_CTRL0_BASE + 0x8a) +#define WSA884X_PDM_WD_CTL (WSA884X_DIG_CTRL0_BASE + 0x8b) +#define WSA884X_PDM_WD_CTL_HOLD_OFF_MASK 0x04 +#define WSA884X_PDM_WD_CTL_HOLD_OFF_SHIFT 2 +#define WSA884X_PDM_WD_CTL_TIME_OUT_SEL_MASK 0x02 +#define WSA884X_PDM_WD_CTL_TIME_OUT_SEL_SHIFT 1 +#define WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK 0x01 +#define WSA884X_PDM_WD_CTL_PDM_WD_EN_SHIFT 0 +#define WSA884X_DEM_BYPASS_DATA0 (WSA884X_DIG_CTRL0_BASE + 0x90) +#define WSA884X_DEM_BYPASS_DATA1 (WSA884X_DIG_CTRL0_BASE + 0x91) +#define WSA884X_DEM_BYPASS_DATA2 (WSA884X_DIG_CTRL0_BASE + 0x92) +#define WSA884X_DEM_BYPASS_DATA3 (WSA884X_DIG_CTRL0_BASE + 0x93) +#define WSA884X_DRE_CTL_0 (WSA884X_DIG_CTRL0_BASE + 0xb0) +#define WSA884X_DRE_CTL_0_PROG_DELAY_MASK 0xf0 +#define WSA884X_DRE_CTL_0_PROG_DELAY_SHIFT 4 +#define WSA884X_DRE_CTL_0_OFFSET_MASK 0x07 +#define WSA884X_DRE_CTL_0_OFFSET_SHIFT 0 +#define WSA884X_DRE_CTL_1 (WSA884X_DIG_CTRL0_BASE + 0xb1) +#define WSA884X_DRE_CTL_1_CSR_GAIN_MASK 0x3e +#define WSA884X_DRE_CTL_1_CSR_GAIN_SHIFT 1 +#define WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK 0x01 +#define WSA884X_DRE_CTL_1_CSR_GAIN_EN_SHIFT 0 +#define WSA884X_DRE_IDLE_DET_CTL (WSA884X_DIG_CTRL0_BASE + 0xb2) +#define WSA884X_GAIN_RAMPING_CTL (WSA884X_DIG_CTRL0_BASE + 0xb8) +#define WSA884X_GAIN_RAMPING_MIN (WSA884X_DIG_CTRL0_BASE + 0xb9) +#define WSA884X_GAIN_RAMPING_MIN_MIN_GAIN_MASK 0x1f +#define WSA884X_GAIN_RAMPING_MIN_MIN_GAIN_SHIFT 0 +#define WSA884X_TAGC_CTL (WSA884X_DIG_CTRL0_BASE + 0xc0) +#define WSA884X_TAGC_TIME (WSA884X_DIG_CTRL0_BASE + 0xc1) +#define WSA884X_TAGC_FORCE_VAL (WSA884X_DIG_CTRL0_BASE + 0xc2) +#define WSA884X_VAGC_CTL (WSA884X_DIG_CTRL0_BASE + 0xc8) +#define WSA884X_VAGC_TIME (WSA884X_DIG_CTRL0_BASE + 0xc9) +#define WSA884X_VAGC_ATTN_LVL_1 (WSA884X_DIG_CTRL0_BASE + 0xca) +#define WSA884X_VAGC_ATTN_LVL_2 (WSA884X_DIG_CTRL0_BASE + 0xcb) +#define WSA884X_VAGC_ATTN_LVL_3 (WSA884X_DIG_CTRL0_BASE + 0xcc) +#define WSA884X_CLSH_CTL_0 (WSA884X_DIG_CTRL0_BASE + 0xd0) +#define WSA884X_CLSH_CTL_0_CSR_GAIN_EN_SHIFT 7 +#define WSA884X_CLSH_CTL_0_DLY_CODE_MASK 0x70 +#define WSA884X_CLSH_CTL_0_DLY_CODE_SHIFT 4 +#define WSA884X_CLSH_CTL_0_DLY_RST_SHIFT 3 +#define WSA884X_CLSH_CTL_0_DLY_EN_SHIFT 2 +#define WSA884X_CLSH_CTL_0_INPUT_EN_SHIFT 1 +#define WSA884X_CLSH_CTL_0_CLSH_EN_SHIFT 0 +#define WSA884X_CLSH_CTL_1 (WSA884X_DIG_CTRL0_BASE + 0xd1) +#define WSA884X_CLSH_V_HD_PA (WSA884X_DIG_CTRL0_BASE + 0xd2) +#define WSA884X_CLSH_V_PA_MIN (WSA884X_DIG_CTRL0_BASE + 0xd3) +#define WSA884X_CLSH_OVRD_VAL (WSA884X_DIG_CTRL0_BASE + 0xd4) +#define WSA884X_CLSH_HARD_MAX (WSA884X_DIG_CTRL0_BASE + 0xd5) +#define WSA884X_CLSH_SOFT_MAX (WSA884X_DIG_CTRL0_BASE + 0xd6) +#define WSA884X_CLSH_SIG_DP (WSA884X_DIG_CTRL0_BASE + 0xd7) +#define WSA884X_PBR_DELAY_CTL (WSA884X_DIG_CTRL0_BASE + 0xd8) +#define WSA884X_CLSH_SRL_MAX_PBR (WSA884X_DIG_CTRL0_BASE + 0xe0) +#define WSA884X_PBR_MAX_VOLTAGE 20 +#define WSA884X_PBR_MAX_CODE 255 +#define WSA884X_VTH_TO_REG(vth) \ + ((vth) != 0 ? (((vth) - 150) * WSA884X_PBR_MAX_CODE / (WSA884X_PBR_MAX_VOLTAGE * 100) + 1) : 0) +#define WSA884X_CLSH_VTH1 (WSA884X_DIG_CTRL0_BASE + 0xe1) +#define WSA884X_CLSH_VTH2 (WSA884X_DIG_CTRL0_BASE + 0xe2) +#define WSA884X_CLSH_VTH3 (WSA884X_DIG_CTRL0_BASE + 0xe3) +#define WSA884X_CLSH_VTH4 (WSA884X_DIG_CTRL0_BASE + 0xe4) +#define WSA884X_CLSH_VTH5 (WSA884X_DIG_CTRL0_BASE + 0xe5) +#define WSA884X_CLSH_VTH6 (WSA884X_DIG_CTRL0_BASE + 0xe6) +#define WSA884X_CLSH_VTH7 (WSA884X_DIG_CTRL0_BASE + 0xe7) +#define WSA884X_CLSH_VTH8 (WSA884X_DIG_CTRL0_BASE + 0xe8) +#define WSA884X_CLSH_VTH9 (WSA884X_DIG_CTRL0_BASE + 0xe9) +#define WSA884X_CLSH_VTH10 (WSA884X_DIG_CTRL0_BASE + 0xea) +#define WSA884X_CLSH_VTH11 (WSA884X_DIG_CTRL0_BASE + 0xeb) +#define WSA884X_CLSH_VTH12 (WSA884X_DIG_CTRL0_BASE + 0xec) +#define WSA884X_CLSH_VTH13 (WSA884X_DIG_CTRL0_BASE + 0xed) +#define WSA884X_CLSH_VTH14 (WSA884X_DIG_CTRL0_BASE + 0xee) +#define WSA884X_CLSH_VTH15 (WSA884X_DIG_CTRL0_BASE + 0xef) + +#define WSA884X_DIG_CTRL1_BASE (WSA884X_BASE + 0x0500) +#define WSA884X_DIG_CTRL1_PAGE (WSA884X_DIG_CTRL1_BASE + 0x00) +#define WSA884X_VPHX_SYS_EN_STATUS (WSA884X_DIG_CTRL1_BASE + 0x01) +#define WSA884X_ANA_WO_CTL_0 (WSA884X_DIG_CTRL1_BASE + 0x04) +#define WSA884X_ANA_WO_CTL_0_MODE_SHIFT 0 +#define WSA884X_ANA_WO_CTL_0_VPHX_SYS_EN_MASK 0xc0 +#define WSA884X_ANA_WO_CTL_0_PA_AUX_DISABLE 0x0 +#define WSA884X_ANA_WO_CTL_0_PA_AUX_18_DB 0xa +#define WSA884X_ANA_WO_CTL_0_PA_AUX_0_DB 0x7 +#define WSA884X_ANA_WO_CTL_0_PA_AUX_GAIN_MASK 0x3c +#define WSA884X_ANA_WO_CTL_0_PA_MIN_GAIN_BYP_MASK 0x02 +#define WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MODE_SPEAKER 0x1 +#define WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MASK 0x01 +#define WSA884X_ANA_WO_CTL_1 (WSA884X_DIG_CTRL1_BASE + 0x05) +#define WSA884X_PIN_CTL (WSA884X_DIG_CTRL1_BASE + 0x10) +#define WSA884X_PIN_CTL_OE (WSA884X_DIG_CTRL1_BASE + 0x11) +#define WSA884X_PIN_WDATA_IOPAD (WSA884X_DIG_CTRL1_BASE + 0x12) +#define WSA884X_PIN_STATUS (WSA884X_DIG_CTRL1_BASE + 0x13) +#define WSA884X_I2C_SLAVE_CTL (WSA884X_DIG_CTRL1_BASE + 0x14) +#define WSA884X_SPMI_PAD_CTL0 (WSA884X_DIG_CTRL1_BASE + 0x15) +#define WSA884X_SPMI_PAD_CTL1 (WSA884X_DIG_CTRL1_BASE + 0x16) +#define WSA884X_SPMI_PAD_CTL2 (WSA884X_DIG_CTRL1_BASE + 0x17) +#define WSA884X_MEM_CTL (WSA884X_DIG_CTRL1_BASE + 0x18) +#define WSA884X_SWR_HM_TEST0 (WSA884X_DIG_CTRL1_BASE + 0x19) +#define WSA884X_SWR_HM_TEST1 (WSA884X_DIG_CTRL1_BASE + 0x1a) +#define WSA884X_OTP_CTRL0 (WSA884X_DIG_CTRL1_BASE + 0x30) +#define WSA884X_OTP_CTRL1 (WSA884X_DIG_CTRL1_BASE + 0x31) +#define WSA884X_OTP_CTRL2 (WSA884X_DIG_CTRL1_BASE + 0x32) +#define WSA884X_OTP_STAT (WSA884X_DIG_CTRL1_BASE + 0x33) +#define WSA884X_OTP_PRG_TCSP0 (WSA884X_DIG_CTRL1_BASE + 0x34) +#define WSA884X_OTP_PRG_TCSP1 (WSA884X_DIG_CTRL1_BASE + 0x35) +#define WSA884X_OTP_PRG_TPPS (WSA884X_DIG_CTRL1_BASE + 0x36) +#define WSA884X_OTP_PRG_TVPS (WSA884X_DIG_CTRL1_BASE + 0x37) +#define WSA884X_OTP_PRG_TVPH (WSA884X_DIG_CTRL1_BASE + 0x38) +#define WSA884X_OTP_PRG_TPPR0 (WSA884X_DIG_CTRL1_BASE + 0x39) +#define WSA884X_OTP_PRG_TPPR1 (WSA884X_DIG_CTRL1_BASE + 0x3a) +#define WSA884X_OTP_PRG_TPPH (WSA884X_DIG_CTRL1_BASE + 0x3b) +#define WSA884X_OTP_PRG_END (WSA884X_DIG_CTRL1_BASE + 0x3c) +#define WSA884X_WAVG_PLAY (WSA884X_DIG_CTRL1_BASE + 0x40) +#define WSA884X_WAVG_CTL (WSA884X_DIG_CTRL1_BASE + 0x41) +#define WSA884X_WAVG_LRA_PER_0 (WSA884X_DIG_CTRL1_BASE + 0x43) +#define WSA884X_WAVG_LRA_PER_1 (WSA884X_DIG_CTRL1_BASE + 0x44) +#define WSA884X_WAVG_DELTA_THETA_0 (WSA884X_DIG_CTRL1_BASE + 0x45) +#define WSA884X_WAVG_DELTA_THETA_1 (WSA884X_DIG_CTRL1_BASE + 0x46) +#define WSA884X_WAVG_DIRECT_AMP_0 (WSA884X_DIG_CTRL1_BASE + 0x47) +#define WSA884X_WAVG_DIRECT_AMP_1 (WSA884X_DIG_CTRL1_BASE + 0x48) +#define WSA884X_WAVG_PTRN_AMP0_0 (WSA884X_DIG_CTRL1_BASE + 0x49) +#define WSA884X_WAVG_PTRN_AMP0_1 (WSA884X_DIG_CTRL1_BASE + 0x4a) +#define WSA884X_WAVG_PTRN_AMP1_0 (WSA884X_DIG_CTRL1_BASE + 0x4b) +#define WSA884X_WAVG_PTRN_AMP1_1 (WSA884X_DIG_CTRL1_BASE + 0x4c) +#define WSA884X_WAVG_PTRN_AMP2_0 (WSA884X_DIG_CTRL1_BASE + 0x4d) +#define WSA884X_WAVG_PTRN_AMP2_1 (WSA884X_DIG_CTRL1_BASE + 0x4e) +#define WSA884X_WAVG_PTRN_AMP3_0 (WSA884X_DIG_CTRL1_BASE + 0x4f) +#define WSA884X_WAVG_PTRN_AMP3_1 (WSA884X_DIG_CTRL1_BASE + 0x50) +#define WSA884X_WAVG_PTRN_AMP4_0 (WSA884X_DIG_CTRL1_BASE + 0x51) +#define WSA884X_WAVG_PTRN_AMP4_1 (WSA884X_DIG_CTRL1_BASE + 0x52) +#define WSA884X_WAVG_PTRN_AMP5_0 (WSA884X_DIG_CTRL1_BASE + 0x53) +#define WSA884X_WAVG_PTRN_AMP5_1 (WSA884X_DIG_CTRL1_BASE + 0x54) +#define WSA884X_WAVG_PTRN_AMP6_0 (WSA884X_DIG_CTRL1_BASE + 0x55) +#define WSA884X_WAVG_PTRN_AMP6_1 (WSA884X_DIG_CTRL1_BASE + 0x56) +#define WSA884X_WAVG_PTRN_AMP7_0 (WSA884X_DIG_CTRL1_BASE + 0x57) +#define WSA884X_WAVG_PTRN_AMP7_1 (WSA884X_DIG_CTRL1_BASE + 0x58) +#define WSA884X_WAVG_PER_0_1 (WSA884X_DIG_CTRL1_BASE + 0x59) +#define WSA884X_WAVG_PER_2_3 (WSA884X_DIG_CTRL1_BASE + 0x5a) +#define WSA884X_WAVG_PER_4_5 (WSA884X_DIG_CTRL1_BASE + 0x5b) +#define WSA884X_WAVG_PER_6_7 (WSA884X_DIG_CTRL1_BASE + 0x5c) +#define WSA884X_WAVG_STA (WSA884X_DIG_CTRL1_BASE + 0x5d) +#define WSA884X_INTR_MODE (WSA884X_DIG_CTRL1_BASE + 0x80) +#define WSA884X_INTR_MASK0 (WSA884X_DIG_CTRL1_BASE + 0x81) +#define WSA884X_INTR_MASK1 (WSA884X_DIG_CTRL1_BASE + 0x82) +#define WSA884X_INTR_STATUS0 (WSA884X_DIG_CTRL1_BASE + 0x83) +#define WSA884X_INTR_STATUS1 (WSA884X_DIG_CTRL1_BASE + 0x84) +#define WSA884X_INTR_CLEAR0 (WSA884X_DIG_CTRL1_BASE + 0x85) +#define WSA884X_INTR_CLEAR1 (WSA884X_DIG_CTRL1_BASE + 0x86) +#define WSA884X_INTR_LEVEL0 (WSA884X_DIG_CTRL1_BASE + 0x87) +#define WSA884X_INTR_LEVEL1 (WSA884X_DIG_CTRL1_BASE + 0x88) +#define WSA884X_INTR_SET0 (WSA884X_DIG_CTRL1_BASE + 0x89) +#define WSA884X_INTR_SET1 (WSA884X_DIG_CTRL1_BASE + 0x8a) +#define WSA884X_INTR_TEST0 (WSA884X_DIG_CTRL1_BASE + 0x8b) +#define WSA884X_INTR_TEST1 (WSA884X_DIG_CTRL1_BASE + 0x8c) +#define WSA884X_PDM_TEST_MODE (WSA884X_DIG_CTRL1_BASE + 0xc0) +#define WSA884X_ATE_TEST_MODE (WSA884X_DIG_CTRL1_BASE + 0xc1) +#define WSA884X_PA_FSM_DBG (WSA884X_DIG_CTRL1_BASE + 0xc2) +#define WSA884X_DIG_DEBUG_MODE (WSA884X_DIG_CTRL1_BASE + 0xc3) +#define WSA884X_DIG_DEBUG_SEL (WSA884X_DIG_CTRL1_BASE + 0xc4) +#define WSA884X_DIG_DEBUG_EN (WSA884X_DIG_CTRL1_BASE + 0xc5) +#define WSA884X_TADC_DETECT_DBG_CTL (WSA884X_DIG_CTRL1_BASE + 0xc9) +#define WSA884X_TADC_DEBUG_MSB (WSA884X_DIG_CTRL1_BASE + 0xca) +#define WSA884X_TADC_DEBUG_LSB (WSA884X_DIG_CTRL1_BASE + 0xcb) +#define WSA884X_SAMPLE_EDGE_SEL (WSA884X_DIG_CTRL1_BASE + 0xcc) +#define WSA884X_SWR_EDGE_SEL (WSA884X_DIG_CTRL1_BASE + 0xcd) +#define WSA884X_TEST_MODE_CTL (WSA884X_DIG_CTRL1_BASE + 0xce) +#define WSA884X_IOPAD_CTL (WSA884X_DIG_CTRL1_BASE + 0xcf) +#define WSA884X_ANA_CSR_DBG_ADD (WSA884X_DIG_CTRL1_BASE + 0xd0) +#define WSA884X_ANA_CSR_DBG_CTL (WSA884X_DIG_CTRL1_BASE + 0xd1) +#define WSA884X_CLK_DBG_CTL (WSA884X_DIG_CTRL1_BASE + 0xd2) +#define WSA884X_SPARE_R (WSA884X_DIG_CTRL1_BASE + 0xf0) +#define WSA884X_SPARE_0 (WSA884X_DIG_CTRL1_BASE + 0xf1) +#define WSA884X_SPARE_1 (WSA884X_DIG_CTRL1_BASE + 0xf2) +#define WSA884X_SPARE_2 (WSA884X_DIG_CTRL1_BASE + 0xf3) +#define WSA884X_SCODE (WSA884X_DIG_CTRL1_BASE + 0xff) + +#define WSA884X_DIG_TRIM_BASE (WSA884X_BASE + 0x0800) +#define WSA884X_DIG_TRIM_PAGE (WSA884X_DIG_TRIM_BASE + 0x00) +#define WSA884X_OTP_REG_0 (WSA884X_DIG_TRIM_BASE + 0x80) +#define WSA884X_OTP_ID_WSA8840 0x0 +#define WSA884X_OTP_ID_WSA8845 0x5 +#define WSA884X_OTP_ID_WSA8845H 0xc +#define WSA884X_OTP_REG_0_ID_MASK 0x0f +#define WSA884X_OTP_REG_1 (WSA884X_DIG_TRIM_BASE + 0x81) +#define WSA884X_OTP_REG_2 (WSA884X_DIG_TRIM_BASE + 0x82) +#define WSA884X_OTP_REG_3 (WSA884X_DIG_TRIM_BASE + 0x83) +#define WSA884X_OTP_REG_4 (WSA884X_DIG_TRIM_BASE + 0x84) +#define WSA884X_OTP_REG_5 (WSA884X_DIG_TRIM_BASE + 0x85) +#define WSA884X_OTP_REG_6 (WSA884X_DIG_TRIM_BASE + 0x86) +#define WSA884X_OTP_REG_7 (WSA884X_DIG_TRIM_BASE + 0x87) +#define WSA884X_OTP_REG_8 (WSA884X_DIG_TRIM_BASE + 0x88) +#define WSA884X_OTP_REG_9 (WSA884X_DIG_TRIM_BASE + 0x89) +#define WSA884X_OTP_REG_10 (WSA884X_DIG_TRIM_BASE + 0x8a) +#define WSA884X_OTP_REG_11 (WSA884X_DIG_TRIM_BASE + 0x8b) +#define WSA884X_OTP_REG_12 (WSA884X_DIG_TRIM_BASE + 0x8c) +#define WSA884X_OTP_REG_13 (WSA884X_DIG_TRIM_BASE + 0x8d) +#define WSA884X_OTP_REG_14 (WSA884X_DIG_TRIM_BASE + 0x8e) +#define WSA884X_OTP_REG_15 (WSA884X_DIG_TRIM_BASE + 0x8f) +#define WSA884X_OTP_REG_16 (WSA884X_DIG_TRIM_BASE + 0x90) +#define WSA884X_OTP_REG_17 (WSA884X_DIG_TRIM_BASE + 0x91) +#define WSA884X_OTP_REG_18 (WSA884X_DIG_TRIM_BASE + 0x92) +#define WSA884X_OTP_REG_19 (WSA884X_DIG_TRIM_BASE + 0x93) +#define WSA884X_OTP_REG_20 (WSA884X_DIG_TRIM_BASE + 0x94) +#define WSA884X_OTP_REG_21 (WSA884X_DIG_TRIM_BASE + 0x95) +#define WSA884X_OTP_REG_22 (WSA884X_DIG_TRIM_BASE + 0x96) +#define WSA884X_OTP_REG_23 (WSA884X_DIG_TRIM_BASE + 0x97) +#define WSA884X_OTP_REG_24 (WSA884X_DIG_TRIM_BASE + 0x98) +#define WSA884X_OTP_REG_25 (WSA884X_DIG_TRIM_BASE + 0x99) +#define WSA884X_OTP_REG_26 (WSA884X_DIG_TRIM_BASE + 0x9a) +#define WSA884X_OTP_REG_27 (WSA884X_DIG_TRIM_BASE + 0x9b) +#define WSA884X_OTP_REG_28 (WSA884X_DIG_TRIM_BASE + 0x9c) +#define WSA884X_OTP_REG_29 (WSA884X_DIG_TRIM_BASE + 0x9d) +#define WSA884X_OTP_REG_30 (WSA884X_DIG_TRIM_BASE + 0x9e) +#define WSA884X_OTP_REG_31 (WSA884X_DIG_TRIM_BASE + 0x9f) +#define WSA884X_OTP_REG_32 (WSA884X_DIG_TRIM_BASE + 0xa0) +#define WSA884X_OTP_REG_33 (WSA884X_DIG_TRIM_BASE + 0xa1) +#define WSA884X_OTP_REG_34 (WSA884X_DIG_TRIM_BASE + 0xa2) +#define WSA884X_OTP_REG_35 (WSA884X_DIG_TRIM_BASE + 0xa3) +#define WSA884X_OTP_REG_36 (WSA884X_DIG_TRIM_BASE + 0xa4) +#define WSA884X_OTP_REG_37 (WSA884X_DIG_TRIM_BASE + 0xa5) +#define WSA884X_OTP_REG_38 (WSA884X_DIG_TRIM_BASE + 0xa6) +#define WSA884X_OTP_REG_38_RESERVER_MASK 0xf0 +#define WSA884X_OTP_REG_38_RESERVER_SHIFT 4 +#define WSA884X_OTP_REG_38_BST_CFG_SEL_MASK 0x08 +#define WSA884X_OTP_REG_38_BST_CFG_SEL_SHIFT 3 +#define WSA884X_OTP_REG_38_BOOST_ILIM_TUNE_MASK 0x07 +#define WSA884X_OTP_REG_38_BOOST_ILIM_TUNE_SHIFT 0 +#define WSA884X_OTP_REG_39 (WSA884X_DIG_TRIM_BASE + 0xa7) +#define WSA884X_OTP_REG_40 (WSA884X_DIG_TRIM_BASE + 0xa8) +#define WSA884X_OTP_REG_40_SPARE_TYPE2_MASK 0xc0 +#define WSA884X_OTP_REG_40_SPARE_TYPE2_SHIFT 6 +#define WSA884X_OTP_REG_40_ISENSE_RESCAL_MASK 0x3c +#define WSA884X_OTP_REG_40_ISENSE_RESCAL_SHIFT 2 +#define WSA884X_OTP_REG_40_ATE_BOOST_RDSON_TEST_MASK 0x2 +#define WSA884X_OTP_REG_40_ATE_BOOST_RDSON_TEST_SHIFT 1 +#define WSA884X_OTP_REG_40_ATE_CLASSD_RDSON_TEST_MASK 0x1 +#define WSA884X_OTP_REG_40_ATE_CLASSD_RDSON_TEST_SHIFT 0 +#define WSA884X_OTP_REG_41 (WSA884X_DIG_TRIM_BASE + 0xa9) +#define WSA884X_OTP_REG_63 (WSA884X_DIG_TRIM_BASE + 0xbf) + +#define WSA884X_DIG_EMEM_BASE (WSA884X_BASE + 0x08C0) +#define WSA884X_EMEM_0 (WSA884X_DIG_EMEM_BASE + 0x00) +#define WSA884X_EMEM_1 (WSA884X_DIG_EMEM_BASE + 0x01) +#define WSA884X_EMEM_2 (WSA884X_DIG_EMEM_BASE + 0x02) +#define WSA884X_EMEM_3 (WSA884X_DIG_EMEM_BASE + 0x03) +#define WSA884X_EMEM_4 (WSA884X_DIG_EMEM_BASE + 0x04) +#define WSA884X_EMEM_5 (WSA884X_DIG_EMEM_BASE + 0x05) +#define WSA884X_EMEM_6 (WSA884X_DIG_EMEM_BASE + 0x06) +#define WSA884X_EMEM_7 (WSA884X_DIG_EMEM_BASE + 0x07) +#define WSA884X_EMEM_8 (WSA884X_DIG_EMEM_BASE + 0x08) +#define WSA884X_EMEM_9 (WSA884X_DIG_EMEM_BASE + 0x09) +#define WSA884X_EMEM_10 (WSA884X_DIG_EMEM_BASE + 0x0a) +#define WSA884X_EMEM_11 (WSA884X_DIG_EMEM_BASE + 0x0b) +#define WSA884X_EMEM_12 (WSA884X_DIG_EMEM_BASE + 0x0c) +#define WSA884X_EMEM_13 (WSA884X_DIG_EMEM_BASE + 0x0d) +#define WSA884X_EMEM_14 (WSA884X_DIG_EMEM_BASE + 0x0e) +#define WSA884X_EMEM_15 (WSA884X_DIG_EMEM_BASE + 0x0f) +#define WSA884X_EMEM_16 (WSA884X_DIG_EMEM_BASE + 0x10) +#define WSA884X_EMEM_17 (WSA884X_DIG_EMEM_BASE + 0x11) +#define WSA884X_EMEM_18 (WSA884X_DIG_EMEM_BASE + 0x12) +#define WSA884X_EMEM_19 (WSA884X_DIG_EMEM_BASE + 0x13) +#define WSA884X_EMEM_20 (WSA884X_DIG_EMEM_BASE + 0x14) +#define WSA884X_EMEM_21 (WSA884X_DIG_EMEM_BASE + 0x15) +#define WSA884X_EMEM_22 (WSA884X_DIG_EMEM_BASE + 0x16) +#define WSA884X_EMEM_23 (WSA884X_DIG_EMEM_BASE + 0x17) +#define WSA884X_EMEM_24 (WSA884X_DIG_EMEM_BASE + 0x18) +#define WSA884X_EMEM_25 (WSA884X_DIG_EMEM_BASE + 0x19) +#define WSA884X_EMEM_26 (WSA884X_DIG_EMEM_BASE + 0x1a) +#define WSA884X_EMEM_27 (WSA884X_DIG_EMEM_BASE + 0x1b) +#define WSA884X_EMEM_28 (WSA884X_DIG_EMEM_BASE + 0x1c) +#define WSA884X_EMEM_29 (WSA884X_DIG_EMEM_BASE + 0x1d) +#define WSA884X_EMEM_30 (WSA884X_DIG_EMEM_BASE + 0x1e) +#define WSA884X_EMEM_31 (WSA884X_DIG_EMEM_BASE + 0x1f) +#define WSA884X_EMEM_32 (WSA884X_DIG_EMEM_BASE + 0x20) +#define WSA884X_EMEM_33 (WSA884X_DIG_EMEM_BASE + 0x21) +#define WSA884X_EMEM_34 (WSA884X_DIG_EMEM_BASE + 0x22) +#define WSA884X_EMEM_35 (WSA884X_DIG_EMEM_BASE + 0x23) +#define WSA884X_EMEM_36 (WSA884X_DIG_EMEM_BASE + 0x24) +#define WSA884X_EMEM_37 (WSA884X_DIG_EMEM_BASE + 0x25) +#define WSA884X_EMEM_38 (WSA884X_DIG_EMEM_BASE + 0x26) +#define WSA884X_EMEM_39 (WSA884X_DIG_EMEM_BASE + 0x27) +#define WSA884X_EMEM_40 (WSA884X_DIG_EMEM_BASE + 0x28) +#define WSA884X_EMEM_41 (WSA884X_DIG_EMEM_BASE + 0x29) +#define WSA884X_EMEM_42 (WSA884X_DIG_EMEM_BASE + 0x2a) +#define WSA884X_EMEM_43 (WSA884X_DIG_EMEM_BASE + 0x2b) +#define WSA884X_EMEM_44 (WSA884X_DIG_EMEM_BASE + 0x2c) +#define WSA884X_EMEM_45 (WSA884X_DIG_EMEM_BASE + 0x2d) +#define WSA884X_EMEM_46 (WSA884X_DIG_EMEM_BASE + 0x2e) +#define WSA884X_EMEM_47 (WSA884X_DIG_EMEM_BASE + 0x2f) +#define WSA884X_EMEM_48 (WSA884X_DIG_EMEM_BASE + 0x30) +#define WSA884X_EMEM_49 (WSA884X_DIG_EMEM_BASE + 0x31) +#define WSA884X_EMEM_50 (WSA884X_DIG_EMEM_BASE + 0x32) +#define WSA884X_EMEM_51 (WSA884X_DIG_EMEM_BASE + 0x33) +#define WSA884X_EMEM_52 (WSA884X_DIG_EMEM_BASE + 0x34) +#define WSA884X_EMEM_53 (WSA884X_DIG_EMEM_BASE + 0x35) +#define WSA884X_EMEM_54 (WSA884X_DIG_EMEM_BASE + 0x36) +#define WSA884X_EMEM_55 (WSA884X_DIG_EMEM_BASE + 0x37) +#define WSA884X_EMEM_56 (WSA884X_DIG_EMEM_BASE + 0x38) +#define WSA884X_EMEM_57 (WSA884X_DIG_EMEM_BASE + 0x39) +#define WSA884X_EMEM_58 (WSA884X_DIG_EMEM_BASE + 0x3a) +#define WSA884X_EMEM_59 (WSA884X_DIG_EMEM_BASE + 0x3b) +#define WSA884X_EMEM_60 (WSA884X_DIG_EMEM_BASE + 0x3c) +#define WSA884X_EMEM_61 (WSA884X_DIG_EMEM_BASE + 0x3d) +#define WSA884X_EMEM_62 (WSA884X_DIG_EMEM_BASE + 0x3e) +#define WSA884X_EMEM_63 (WSA884X_DIG_EMEM_BASE + 0x3f) + +#define WSA884X_NUM_REGISTERS (WSA884X_EMEM_63 + 1) +#define WSA884X_MAX_REGISTER (WSA884X_NUM_REGISTERS - 1) + +#define WSA884X_SUPPLIES_NUM 2 +#define WSA884X_MAX_SWR_PORTS 6 +#define WSA884X_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000 |\ + SNDRV_PCM_RATE_384000) +/* Fractional Rates */ +#define WSA884X_FRAC_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_352800) + +#define WSA884X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct wsa884x_priv { + struct regmap *regmap; + struct device *dev; + struct regulator_bulk_data supplies[WSA884X_SUPPLIES_NUM]; + struct sdw_slave *slave; + struct sdw_stream_config sconfig; + struct sdw_stream_runtime *sruntime; + struct sdw_port_config port_config[WSA884X_MAX_SWR_PORTS]; + struct gpio_desc *sd_n; + bool port_prepared[WSA884X_MAX_SWR_PORTS]; + bool port_enable[WSA884X_MAX_SWR_PORTS]; + unsigned int variant; + int active_ports; + int dev_mode; + bool hw_init; +}; + +enum { + COMP_OFFSET0, + COMP_OFFSET1, + COMP_OFFSET2, + COMP_OFFSET3, + COMP_OFFSET4, +}; + +enum wsa884x_gain { + G_21_DB = 0, + G_19P5_DB, + G_18_DB, + G_16P5_DB, + G_15_DB, + G_13P5_DB, + G_12_DB, + G_10P5_DB, + G_9_DB, + G_7P5_DB, + G_6_DB, + G_4P5_DB, + G_3_DB, + G_1P5_DB, + G_0_DB, + G_M1P5_DB, + G_M3_DB, + G_M4P5_DB, + G_M6_DB, + G_MAX_DB, +}; + +enum wsa884x_isense { + ISENSE_6_DB = 0, + ISENSE_12_DB, + ISENSE_15_DB, + ISENSE_18_DB, +}; + +enum wsa884x_vsense { + VSENSE_M12_DB = 0, + VSENSE_M15_DB, + VSENSE_M18_DB, + VSENSE_M21_DB, + VSENSE_M24_DB, +}; + +enum wsa884x_port_ids { + WSA884X_PORT_DAC, + WSA884X_PORT_COMP, + WSA884X_PORT_BOOST, + WSA884X_PORT_PBR, + WSA884X_PORT_VISENSE, + WSA884X_PORT_CPS, +}; + +static const char * const wsa884x_supply_name[] = { + "vdd-io", + "vdd-1p8", +}; + +static const char * const wsa884x_dev_mode_text[] = { + "Speaker", "Receiver" +}; + +enum wsa884x_mode { + WSA884X_SPEAKER, + WSA884X_RECEIVER, +}; + +static const struct soc_enum wsa884x_dev_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(wsa884x_dev_mode_text), wsa884x_dev_mode_text); + +static struct sdw_dpn_prop wsa884x_sink_dpn_prop[WSA884X_MAX_SWR_PORTS] = { + { + .num = WSA884X_PORT_DAC + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, { + .num = WSA884X_PORT_COMP + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, { + .num = WSA884X_PORT_BOOST + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, { + .num = WSA884X_PORT_PBR + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, { + .num = WSA884X_PORT_VISENSE + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + }, { + .num = WSA884X_PORT_CPS + 1, + .type = SDW_DPN_SIMPLE, + .min_ch = 1, + .max_ch = 1, + .simple_ch_prep_sm = true, + .read_only_wordlength = true, + } +}; + +static const struct sdw_port_config wsa884x_pconfig[WSA884X_MAX_SWR_PORTS] = { + { + .num = WSA884X_PORT_DAC + 1, + .ch_mask = 0x1, + }, { + .num = WSA884X_PORT_COMP + 1, + .ch_mask = 0xf, + }, { + .num = WSA884X_PORT_BOOST + 1, + .ch_mask = 0x3, + }, { + .num = WSA884X_PORT_PBR + 1, + .ch_mask = 0x1, + }, { + .num = WSA884X_PORT_VISENSE + 1, + .ch_mask = 0x3, + }, { + .num = WSA884X_PORT_CPS + 1, + .ch_mask = 0x3, + }, +}; + +static struct reg_default wsa884x_defaults[] = { + { WSA884X_BG_CTRL, 0xa5 }, + { WSA884X_ADC_CTRL, 0x00 }, + { WSA884X_BOP1_PROG, 0x22 }, + { WSA884X_BOP2_PROG, 0x44 }, + { WSA884X_UVLO_PROG, 0x99 }, + { WSA884X_UVLO_PROG1, 0x70 }, + { WSA884X_SPARE_CTRL_0, 0x00 }, + { WSA884X_SPARE_CTRL_1, 0x00 }, + { WSA884X_SPARE_CTRL_2, 0x00 }, + { WSA884X_SPARE_CTRL_3, 0x00 }, + { WSA884X_REF_CTRL, 0xd2 }, + { WSA884X_BG_TEST_CTL, 0x06 }, + { WSA884X_BG_BIAS, 0xd7 }, + { WSA884X_ADC_PROG, 0x08 }, + { WSA884X_ADC_IREF_CTL, 0x57 }, + { WSA884X_ADC_ISENS_CTL, 0x47 }, + { WSA884X_ADC_CLK_CTL, 0x87 }, + { WSA884X_ADC_TEST_CTL, 0x00 }, + { WSA884X_ADC_BIAS, 0x51 }, + { WSA884X_VBAT_SNS, 0xa0 }, + { WSA884X_BOP_ATEST_SEL, 0x00 }, + { WSA884X_MISC0, 0x04 }, + { WSA884X_MISC1, 0x75 }, + { WSA884X_MISC2, 0x00 }, + { WSA884X_MISC3, 0x10 }, + { WSA884X_SPARE_TSBG_0, 0x00 }, + { WSA884X_SPARE_TUNE_0, 0x00 }, + { WSA884X_SPARE_TUNE_1, 0x00 }, + { WSA884X_VSENSE1, 0xe7 }, + { WSA884X_ISENSE2, 0x27 }, + { WSA884X_SPARE_CTL_1, 0x00 }, + { WSA884X_SPARE_CTL_2, 0x00 }, + { WSA884X_SPARE_CTL_3, 0x00 }, + { WSA884X_SPARE_CTL_4, 0x00 }, + { WSA884X_EN, 0x10 }, + { WSA884X_OVERRIDE1, 0x00 }, + { WSA884X_OVERRIDE2, 0x08 }, + { WSA884X_ISENSE1, 0xd4 }, + { WSA884X_ISENSE_CAL, 0x00 }, + { WSA884X_MISC, 0x00 }, + { WSA884X_ADC_0, 0x00 }, + { WSA884X_ADC_1, 0x00 }, + { WSA884X_ADC_2, 0x40 }, + { WSA884X_ADC_3, 0x80 }, + { WSA884X_ADC_4, 0x25 }, + { WSA884X_ADC_5, 0x24 }, + { WSA884X_ADC_6, 0x0a }, + { WSA884X_ADC_7, 0x81 }, + { WSA884X_IVSENSE_SPARE_TUNE_1, 0x00 }, + { WSA884X_SPARE_TUNE_2, 0x00 }, + { WSA884X_SPARE_TUNE_3, 0x00 }, + { WSA884X_SPARE_TUNE_4, 0x00 }, + { WSA884X_TOP_CTRL1, 0xd3 }, + { WSA884X_CLIP_DET_CTRL1, 0x7e }, + { WSA884X_CLIP_DET_CTRL2, 0x4c }, + { WSA884X_DAC_CTRL1, 0xa4 }, + { WSA884X_DAC_VCM_CTRL_REG1, 0x02 }, + { WSA884X_DAC_VCM_CTRL_REG2, 0x00 }, + { WSA884X_DAC_VCM_CTRL_REG3, 0x00 }, + { WSA884X_DAC_VCM_CTRL_REG4, 0x00 }, + { WSA884X_DAC_VCM_CTRL_REG5, 0x00 }, + { WSA884X_DAC_VCM_CTRL_REG6, 0x00 }, + { WSA884X_PWM_CLK_CTL, 0x20 }, + { WSA884X_DRV_LF_LDO_SEL, 0xaa }, + { WSA884X_OCP_CTL, 0xc6 }, + { WSA884X_PDRV_HS_CTL, 0x52 }, + { WSA884X_PDRV_LS_CTL, 0x4a }, + { WSA884X_SPK_TOP_SPARE_CTL_1, 0x00 }, + { WSA884X_SPK_TOP_SPARE_CTL_2, 0x00 }, + { WSA884X_SPK_TOP_SPARE_CTL_3, 0x00 }, + { WSA884X_SPK_TOP_SPARE_CTL_4, 0x00 }, + { WSA884X_SPARE_CTL_5, 0x00 }, + { WSA884X_DAC_EN_DEBUG_REG, 0x00 }, + { WSA884X_DAC_OPAMP_BIAS1_REG, 0x48 }, + { WSA884X_DAC_OPAMP_BIAS2_REG, 0x48 }, + { WSA884X_DAC_TUNE1, 0x02 }, + { WSA884X_DAC_VOLTAGE_CTRL_REG, 0x05 }, + { WSA884X_ATEST1_REG, 0x00 }, + { WSA884X_ATEST2_REG, 0x00 }, + { WSA884X_TOP_BIAS_REG1, 0x6a }, + { WSA884X_TOP_BIAS_REG2, 0x65 }, + { WSA884X_TOP_BIAS_REG3, 0x55 }, + { WSA884X_TOP_BIAS_REG4, 0xa9 }, + { WSA884X_PWRSTG_DBG2, 0x21 }, + { WSA884X_DRV_LF_BLK_EN, 0x0f }, + { WSA884X_DRV_LF_EN, 0x0a }, + { WSA884X_DRV_LF_MASK_DCC_CTL, 0x08 }, + { WSA884X_DRV_LF_MISC_CTL1, 0x30 }, + { WSA884X_DRV_LF_REG_GAIN, 0x00 }, + { WSA884X_DRV_OS_CAL_CTL, 0x00 }, + { WSA884X_DRV_OS_CAL_CTL1, 0x90 }, + { WSA884X_PWRSTG_DBG, 0x08 }, + { WSA884X_BBM_CTL, 0x92 }, + { WSA884X_TOP_MISC1, 0x00 }, + { WSA884X_DAC_VCM_CTRL_REG7, 0x00 }, + { WSA884X_TOP_BIAS_REG5, 0x15 }, + { WSA884X_DRV_LF_MISC_CTL2, 0x00 }, + { WSA884X_STB_CTRL1, 0x42 }, + { WSA884X_CURRENT_LIMIT, 0x54 }, + { WSA884X_BYP_CTRL1, 0x01 }, + { WSA884X_SPARE_CTL_0, 0x00 }, + { WSA884X_BOOST_SPARE_CTL_1, 0x00 }, + { WSA884X_IBIAS1, 0x00 }, + { WSA884X_IBIAS2, 0x00 }, + { WSA884X_IBIAS3, 0x00 }, + { WSA884X_EN_CTRL, 0x42 }, + { WSA884X_STB_CTRL2, 0x03 }, + { WSA884X_STB_CTRL3, 0x3c }, + { WSA884X_STB_CTRL4, 0x30 }, + { WSA884X_BYP_CTRL2, 0x97 }, + { WSA884X_BYP_CTRL3, 0x11 }, + { WSA884X_ZX_CTRL1, 0xf0 }, + { WSA884X_ZX_CTRL2, 0x04 }, + { WSA884X_BLEEDER_CTRL, 0x04 }, + { WSA884X_BOOST_MISC, 0x62 }, + { WSA884X_PWRSTAGE_CTRL1, 0x00 }, + { WSA884X_PWRSTAGE_CTRL2, 0x31 }, + { WSA884X_PWRSTAGE_CTRL3, 0x81 }, + { WSA884X_PWRSTAGE_CTRL4, 0x5f }, + { WSA884X_MAXD_REG1, 0x00 }, + { WSA884X_MAXD_REG2, 0x5b }, + { WSA884X_ILIM_CTRL1, 0xe2 }, + { WSA884X_ILIM_CTRL2, 0x90 }, + { WSA884X_TEST_CTRL1, 0x00 }, + { WSA884X_TEST_CTRL2, 0x00 }, + { WSA884X_SPARE1, 0x00 }, + { WSA884X_BOOT_CAP_CHECK, 0x01 }, + { WSA884X_PON_CTL_0, 0x12 }, + { WSA884X_PWRSAV_CTL, 0xaa }, + { WSA884X_PON_LDOL_SPARE_CTL_0, 0x00 }, + { WSA884X_PON_LDOL_SPARE_CTL_1, 0x00 }, + { WSA884X_PON_LDOL_SPARE_CTL_2, 0x00 }, + { WSA884X_PON_LDOL_SPARE_CTL_3, 0x00 }, + { WSA884X_PON_CLT_1, 0xe1 }, + { WSA884X_PON_CTL_2, 0x00 }, + { WSA884X_PON_CTL_3, 0x70 }, + { WSA884X_CKWD_CTL_0, 0x14 }, + { WSA884X_CKWD_CTL_1, 0x3b }, + { WSA884X_CKWD_CTL_2, 0x00 }, + { WSA884X_CKSK_CTL_0, 0x00 }, + { WSA884X_PADSW_CTL_0, 0x00 }, + { WSA884X_TEST_0, 0x00 }, + { WSA884X_TEST_1, 0x00 }, + { WSA884X_PON_LDOL_SPARE_TUNE_0, 0x00 }, + { WSA884X_PON_LDOL_SPARE_TUNE_1, 0x00 }, + { WSA884X_PON_LDOL_SPARE_TUNE_2, 0x00 }, + { WSA884X_PON_LDOL_SPARE_TUNE_3, 0x00 }, + { WSA884X_PON_LDOL_SPARE_TUNE_4, 0x00 }, + { WSA884X_DIG_CTRL0_PAGE, 0x00 }, + { WSA884X_CDC_RST_CTL, 0x01 }, + { WSA884X_SWR_RESET_EN, 0x00 }, + { WSA884X_TOP_CLK_CFG, 0x00 }, + { WSA884X_SWR_CLK_RATE, 0x00 }, + { WSA884X_CDC_PATH_MODE, 0x00 }, + { WSA884X_CDC_CLK_CTL, 0x1f }, + { WSA884X_PA_FSM_EN, 0x00 }, + { WSA884X_PA_FSM_CTL0, 0x00 }, + { WSA884X_PA_FSM_CTL1, 0xfe }, + { WSA884X_PA_FSM_TIMER0, 0x80 }, + { WSA884X_PA_FSM_TIMER1, 0x80 }, + { WSA884X_PA_FSM_ERR_CTL, 0x00 }, + { WSA884X_PA_FSM_MSK0, 0x00 }, + { WSA884X_PA_FSM_MSK1, 0x00 }, + { WSA884X_PA_FSM_BYP_CTL, 0x00 }, + { WSA884X_PA_FSM_BYP0, 0x00 }, + { WSA884X_PA_FSM_BYP1, 0x00 }, + { WSA884X_TADC_VALUE_CTL, 0x03 }, + { WSA884X_TEMP_DETECT_CTL, 0x01 }, + { WSA884X_TEMP_CONFIG0, 0x00 }, + { WSA884X_TEMP_CONFIG1, 0x00 }, + { WSA884X_VBAT_THRM_FLT_CTL, 0x7f }, + { WSA884X_VBAT_CAL_CTL, 0x01 }, + { WSA884X_UVLO_DEGLITCH_CTL, 0x05 }, + { WSA884X_BOP_DEGLITCH_CTL, 0x05 }, + { WSA884X_VBAT_ZONE_DETC_CTL, 0x31 }, + { WSA884X_CPS_CTL, 0x00 }, + { WSA884X_CDC_RX_CTL, 0xfe }, + { WSA884X_CDC_SPK_DSM_A1_0, 0x00 }, + { WSA884X_CDC_SPK_DSM_A1_1, 0x01 }, + { WSA884X_CDC_SPK_DSM_A2_0, 0x96 }, + { WSA884X_CDC_SPK_DSM_A2_1, 0x09 }, + { WSA884X_CDC_SPK_DSM_A3_0, 0xab }, + { WSA884X_CDC_SPK_DSM_A3_1, 0x05 }, + { WSA884X_CDC_SPK_DSM_A4_0, 0x1c }, + { WSA884X_CDC_SPK_DSM_A4_1, 0x02 }, + { WSA884X_CDC_SPK_DSM_A5_0, 0x17 }, + { WSA884X_CDC_SPK_DSM_A5_1, 0x02 }, + { WSA884X_CDC_SPK_DSM_A6_0, 0xaa }, + { WSA884X_CDC_SPK_DSM_A7_0, 0xe3 }, + { WSA884X_CDC_SPK_DSM_C_0, 0x69 }, + { WSA884X_CDC_SPK_DSM_C_1, 0x54 }, + { WSA884X_CDC_SPK_DSM_C_2, 0x02 }, + { WSA884X_CDC_SPK_DSM_C_3, 0x15 }, + { WSA884X_CDC_SPK_DSM_R1, 0xa4 }, + { WSA884X_CDC_SPK_DSM_R2, 0xb5 }, + { WSA884X_CDC_SPK_DSM_R3, 0x86 }, + { WSA884X_CDC_SPK_DSM_R4, 0x85 }, + { WSA884X_CDC_SPK_DSM_R5, 0xaa }, + { WSA884X_CDC_SPK_DSM_R6, 0xe2 }, + { WSA884X_CDC_SPK_DSM_R7, 0x62 }, + { WSA884X_CDC_SPK_GAIN_PDM_0, 0x00 }, + { WSA884X_CDC_SPK_GAIN_PDM_1, 0xfc }, + { WSA884X_CDC_SPK_GAIN_PDM_2, 0x05 }, + { WSA884X_PDM_WD_CTL, 0x00 }, + { WSA884X_DEM_BYPASS_DATA0, 0x00 }, + { WSA884X_DEM_BYPASS_DATA1, 0x00 }, + { WSA884X_DEM_BYPASS_DATA2, 0x00 }, + { WSA884X_DEM_BYPASS_DATA3, 0x00 }, + { WSA884X_DRE_CTL_0, 0x70 }, + { WSA884X_DRE_CTL_1, 0x04 }, + { WSA884X_DRE_IDLE_DET_CTL, 0x2f }, + { WSA884X_GAIN_RAMPING_CTL, 0x50 }, + { WSA884X_GAIN_RAMPING_MIN, 0x12 }, + { WSA884X_TAGC_CTL, 0x15 }, + { WSA884X_TAGC_TIME, 0xbc }, + { WSA884X_TAGC_FORCE_VAL, 0x00 }, + { WSA884X_VAGC_CTL, 0x01 }, + { WSA884X_VAGC_TIME, 0x0f }, + { WSA884X_VAGC_ATTN_LVL_1, 0x03 }, + { WSA884X_VAGC_ATTN_LVL_2, 0x06 }, + { WSA884X_VAGC_ATTN_LVL_3, 0x09 }, + { WSA884X_CLSH_CTL_0, 0x37 }, + { WSA884X_CLSH_CTL_1, 0x81 }, + { WSA884X_CLSH_V_HD_PA, 0x0c }, + { WSA884X_CLSH_V_PA_MIN, 0x00 }, + { WSA884X_CLSH_OVRD_VAL, 0x00 }, + { WSA884X_CLSH_HARD_MAX, 0xff }, + { WSA884X_CLSH_SOFT_MAX, 0xf5 }, + { WSA884X_CLSH_SIG_DP, 0x00 }, + { WSA884X_PBR_DELAY_CTL, 0x07 }, + { WSA884X_CLSH_SRL_MAX_PBR, 0x02 }, + { WSA884X_CLSH_VTH1, 0x00 }, + { WSA884X_CLSH_VTH2, 0x00 }, + { WSA884X_CLSH_VTH3, 0x00 }, + { WSA884X_CLSH_VTH4, 0x00 }, + { WSA884X_CLSH_VTH5, 0x00 }, + { WSA884X_CLSH_VTH6, 0x00 }, + { WSA884X_CLSH_VTH7, 0x00 }, + { WSA884X_CLSH_VTH8, 0x00 }, + { WSA884X_CLSH_VTH9, 0x00 }, + { WSA884X_CLSH_VTH10, 0x00 }, + { WSA884X_CLSH_VTH11, 0x00 }, + { WSA884X_CLSH_VTH12, 0x00 }, + { WSA884X_CLSH_VTH13, 0x00 }, + { WSA884X_CLSH_VTH14, 0x00 }, + { WSA884X_CLSH_VTH15, 0x00 }, + { WSA884X_DIG_CTRL1_PAGE, 0x00 }, + { WSA884X_PIN_CTL, 0x04 }, + { WSA884X_PIN_CTL_OE, 0x00 }, + { WSA884X_PIN_WDATA_IOPAD, 0x00 }, + { WSA884X_I2C_SLAVE_CTL, 0x00 }, + { WSA884X_SPMI_PAD_CTL0, 0x2f }, + { WSA884X_SPMI_PAD_CTL1, 0x2f }, + { WSA884X_SPMI_PAD_CTL2, 0x2f }, + { WSA884X_MEM_CTL, 0x00 }, + { WSA884X_SWR_HM_TEST0, 0x08 }, + { WSA884X_OTP_CTRL0, 0x00 }, + { WSA884X_OTP_CTRL2, 0x00 }, + { WSA884X_OTP_PRG_TCSP0, 0x77 }, + { WSA884X_OTP_PRG_TCSP1, 0x00 }, + { WSA884X_OTP_PRG_TPPS, 0x47 }, + { WSA884X_OTP_PRG_TVPS, 0x3b }, + { WSA884X_OTP_PRG_TVPH, 0x47 }, + { WSA884X_OTP_PRG_TPPR0, 0x47 }, + { WSA884X_OTP_PRG_TPPR1, 0x00 }, + { WSA884X_OTP_PRG_TPPH, 0x47 }, + { WSA884X_OTP_PRG_END, 0x47 }, + { WSA884X_WAVG_PLAY, 0x00 }, + { WSA884X_WAVG_CTL, 0x06 }, + { WSA884X_WAVG_LRA_PER_0, 0xd1 }, + { WSA884X_WAVG_LRA_PER_1, 0x00 }, + { WSA884X_WAVG_DELTA_THETA_0, 0xe6 }, + { WSA884X_WAVG_DELTA_THETA_1, 0x04 }, + { WSA884X_WAVG_DIRECT_AMP_0, 0x50 }, + { WSA884X_WAVG_DIRECT_AMP_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP0_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP0_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP1_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP1_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP2_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP2_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP3_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP3_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP4_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP4_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP5_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP5_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP6_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP6_1, 0x00 }, + { WSA884X_WAVG_PTRN_AMP7_0, 0x50 }, + { WSA884X_WAVG_PTRN_AMP7_1, 0x00 }, + { WSA884X_WAVG_PER_0_1, 0x88 }, + { WSA884X_WAVG_PER_2_3, 0x88 }, + { WSA884X_WAVG_PER_4_5, 0x88 }, + { WSA884X_WAVG_PER_6_7, 0x88 }, + { WSA884X_INTR_MODE, 0x00 }, + { WSA884X_INTR_MASK0, 0x90 }, + { WSA884X_INTR_MASK1, 0x00 }, + { WSA884X_INTR_CLEAR0, 0x00 }, + { WSA884X_INTR_CLEAR1, 0x00 }, + { WSA884X_INTR_LEVEL0, 0x04 }, + { WSA884X_INTR_LEVEL1, 0x00 }, + { WSA884X_INTR_SET0, 0x00 }, + { WSA884X_INTR_SET1, 0x00 }, + { WSA884X_INTR_TEST0, 0x00 }, + { WSA884X_INTR_TEST1, 0x00 }, + { WSA884X_PDM_TEST_MODE, 0x00 }, + { WSA884X_PA_FSM_DBG, 0x00 }, + { WSA884X_DIG_DEBUG_MODE, 0x00 }, + { WSA884X_DIG_DEBUG_SEL, 0x00 }, + { WSA884X_DIG_DEBUG_EN, 0x00 }, + { WSA884X_TADC_DETECT_DBG_CTL, 0x00 }, + { WSA884X_TADC_DEBUG_MSB, 0x00 }, + { WSA884X_TADC_DEBUG_LSB, 0x00 }, + { WSA884X_SAMPLE_EDGE_SEL, 0x7f }, + { WSA884X_SWR_EDGE_SEL, 0x00 }, + { WSA884X_TEST_MODE_CTL, 0x05 }, + { WSA884X_IOPAD_CTL, 0x00 }, + { WSA884X_ANA_CSR_DBG_ADD, 0x00 }, + { WSA884X_ANA_CSR_DBG_CTL, 0x12 }, + { WSA884X_CLK_DBG_CTL, 0x00 }, + { WSA884X_SPARE_0, 0x00 }, + { WSA884X_SPARE_1, 0x00 }, + { WSA884X_SPARE_2, 0x00 }, + { WSA884X_SCODE, 0x00 }, + { WSA884X_DIG_TRIM_PAGE, 0x00 }, + { WSA884X_EMEM_0, 0x00 }, + { WSA884X_EMEM_1, 0x00 }, + { WSA884X_EMEM_2, 0x00 }, + { WSA884X_EMEM_3, 0x00 }, + { WSA884X_EMEM_4, 0x00 }, + { WSA884X_EMEM_5, 0x00 }, + { WSA884X_EMEM_6, 0x00 }, + { WSA884X_EMEM_7, 0x00 }, + { WSA884X_EMEM_8, 0x00 }, + { WSA884X_EMEM_9, 0x00 }, + { WSA884X_EMEM_10, 0x00 }, + { WSA884X_EMEM_11, 0x00 }, + { WSA884X_EMEM_12, 0x00 }, + { WSA884X_EMEM_13, 0x00 }, + { WSA884X_EMEM_14, 0x00 }, + { WSA884X_EMEM_15, 0x00 }, + { WSA884X_EMEM_16, 0x00 }, + { WSA884X_EMEM_17, 0x00 }, + { WSA884X_EMEM_18, 0x00 }, + { WSA884X_EMEM_19, 0x00 }, + { WSA884X_EMEM_20, 0x00 }, + { WSA884X_EMEM_21, 0x00 }, + { WSA884X_EMEM_22, 0x00 }, + { WSA884X_EMEM_23, 0x00 }, + { WSA884X_EMEM_24, 0x00 }, + { WSA884X_EMEM_25, 0x00 }, + { WSA884X_EMEM_26, 0x00 }, + { WSA884X_EMEM_27, 0x00 }, + { WSA884X_EMEM_28, 0x00 }, + { WSA884X_EMEM_29, 0x00 }, + { WSA884X_EMEM_30, 0x00 }, + { WSA884X_EMEM_31, 0x00 }, + { WSA884X_EMEM_32, 0x00 }, + { WSA884X_EMEM_33, 0x00 }, + { WSA884X_EMEM_34, 0x00 }, + { WSA884X_EMEM_35, 0x00 }, + { WSA884X_EMEM_36, 0x00 }, + { WSA884X_EMEM_37, 0x00 }, + { WSA884X_EMEM_38, 0x00 }, + { WSA884X_EMEM_39, 0x00 }, + { WSA884X_EMEM_40, 0x00 }, + { WSA884X_EMEM_41, 0x00 }, + { WSA884X_EMEM_42, 0x00 }, + { WSA884X_EMEM_43, 0x00 }, + { WSA884X_EMEM_44, 0x00 }, + { WSA884X_EMEM_45, 0x00 }, + { WSA884X_EMEM_46, 0x00 }, + { WSA884X_EMEM_47, 0x00 }, + { WSA884X_EMEM_48, 0x00 }, + { WSA884X_EMEM_49, 0x00 }, + { WSA884X_EMEM_50, 0x00 }, + { WSA884X_EMEM_51, 0x00 }, + { WSA884X_EMEM_52, 0x00 }, + { WSA884X_EMEM_53, 0x00 }, + { WSA884X_EMEM_54, 0x00 }, + { WSA884X_EMEM_55, 0x00 }, + { WSA884X_EMEM_56, 0x00 }, + { WSA884X_EMEM_57, 0x00 }, + { WSA884X_EMEM_58, 0x00 }, + { WSA884X_EMEM_59, 0x00 }, + { WSA884X_EMEM_60, 0x00 }, + { WSA884X_EMEM_61, 0x00 }, + { WSA884X_EMEM_62, 0x00 }, + { WSA884X_EMEM_63, 0x00 }, +}; + +static bool wsa884x_readonly_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WSA884X_DOUT_MSB: + case WSA884X_DOUT_LSB: + case WSA884X_STATUS: + case WSA884X_SPK_TOP_SPARE_TUNE_2: + case WSA884X_SPK_TOP_SPARE_TUNE_3: + case WSA884X_SPK_TOP_SPARE_TUNE_4: + case WSA884X_SPARE_TUNE_5: + case WSA884X_SPARE_TUNE_6: + case WSA884X_SPARE_TUNE_7: + case WSA884X_SPARE_TUNE_8: + case WSA884X_SPARE_TUNE_9: + case WSA884X_SPARE_TUNE_10: + case WSA884X_PA_STATUS0: + case WSA884X_PA_STATUS1: + case WSA884X_PA_STATUS2: + case WSA884X_PA_STATUS3: + case WSA884X_PA_STATUS4: + case WSA884X_PA_STATUS5: + case WSA884X_SPARE_RO_1: + case WSA884X_SPARE_RO_2: + case WSA884X_SPARE_RO_3: + case WSA884X_SPARE_RO_0: + case WSA884X_BOOST_SPARE_RO_1: + case WSA884X_STATUS_0: + case WSA884X_STATUS_1: + case WSA884X_CHIP_ID0: + case WSA884X_CHIP_ID1: + case WSA884X_CHIP_ID2: + case WSA884X_CHIP_ID3: + case WSA884X_BUS_ID: + case WSA884X_PA_FSM_STA0: + case WSA884X_PA_FSM_STA1: + case WSA884X_PA_FSM_ERR_COND0: + case WSA884X_PA_FSM_ERR_COND1: + case WSA884X_TEMP_DIN_MSB: + case WSA884X_TEMP_DIN_LSB: + case WSA884X_TEMP_DOUT_MSB: + case WSA884X_TEMP_DOUT_LSB: + case WSA884X_VBAT_DIN_MSB: + case WSA884X_VBAT_DIN_LSB: + case WSA884X_VBAT_DOUT_MSB: + case WSA884X_VBAT_DOUT_LSB: + case WSA884X_VBAT_CAL_MSB: + case WSA884X_VBAT_CAL_LSB: + case WSA884X_VPHX_SYS_EN_STATUS: + case WSA884X_PIN_STATUS: + case WSA884X_SWR_HM_TEST1: + case WSA884X_OTP_CTRL1: + case WSA884X_OTP_STAT: + case WSA884X_WAVG_STA: + case WSA884X_INTR_STATUS0: + case WSA884X_INTR_STATUS1: + case WSA884X_ATE_TEST_MODE: + case WSA884X_SPARE_R: + return true; + } + return false; +} + +static bool wsa884x_writeable_register(struct device *dev, unsigned int reg) +{ + return !wsa884x_readonly_register(dev, reg); +} + +static bool wsa884x_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case WSA884X_ANA_WO_CTL_0: + case WSA884X_ANA_WO_CTL_1: + return true; + } + return wsa884x_readonly_register(dev, reg); +} + +static struct regmap_config wsa884x_regmap_config = { + .reg_bits = 32, + .val_bits = 8, + .cache_type = REGCACHE_MAPLE, + .reg_defaults = wsa884x_defaults, + .max_register = WSA884X_MAX_REGISTER, + .num_reg_defaults = ARRAY_SIZE(wsa884x_defaults), + .volatile_reg = wsa884x_volatile_register, + .writeable_reg = wsa884x_writeable_register, + .reg_format_endian = REGMAP_ENDIAN_NATIVE, + .val_format_endian = REGMAP_ENDIAN_NATIVE, + .use_single_read = true, +}; + +static const struct reg_sequence wsa884x_reg_init[] = { + { WSA884X_BOP2_PROG, FIELD_PREP_CONST(WSA884X_BOP2_PROG_BOP2_VTH_MASK, 0x6) | + FIELD_PREP_CONST(WSA884X_BOP2_PROG_BOP2_HYST_MASK, 0x6) }, + { WSA884X_REF_CTRL, (0xd2 & ~WSA884X_REF_CTRL_BG_RDY_SEL_MASK) | + FIELD_PREP_CONST(WSA884X_REF_CTRL_BG_RDY_SEL_MASK, 0x1) }, + /* + * Downstream suggests for batteries different than 1-Stacked (1S): + * { WSA884X_TOP_CTRL1, 0xd3 & ~WSA884X_TOP_CTRL1_OCP_LOWVBAT_ITH_EN_MASK }, + */ + { WSA884X_STB_CTRL1, (0x42 & ~WSA884X_STB_CTRL1_SLOPE_COMP_CURRENT_MASK) | + FIELD_PREP_CONST(WSA884X_STB_CTRL1_SLOPE_COMP_CURRENT_MASK, 0xd) }, + { WSA884X_CURRENT_LIMIT, (0x54 & ~WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_MASK) | + FIELD_PREP_CONST(WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_MASK, 0x9) }, + { WSA884X_ZX_CTRL1, (0xf0 & ~WSA884X_ZX_CTRL1_ZX_DET_SW_SEL_MASK) | + FIELD_PREP_CONST(WSA884X_ZX_CTRL1_ZX_DET_SW_SEL_MASK, 0x3) }, + { WSA884X_ILIM_CTRL1, (0xe2 & ~WSA884X_ILIM_CTRL1_ILIM_OFFSET_PB_MASK) | + FIELD_PREP_CONST(WSA884X_ILIM_CTRL1_ILIM_OFFSET_PB_MASK, 0x3) }, + { WSA884X_CKWD_CTL_1, FIELD_PREP_CONST(WSA884X_CKWD_CTL_1_VPP_SW_CTL_MASK, 0x0) | + FIELD_PREP_CONST(WSA884X_CKWD_CTL_1_CKWD_VCOMP_VREF_SEL_MASK, 0x13) }, + { WSA884X_PA_FSM_CTL1, (0xfe & ~WSA884X_PA_FSM_CTL1_NOISE_GATE_BLOCK_MASK) | + FIELD_PREP_CONST(WSA884X_PA_FSM_CTL1_NOISE_GATE_BLOCK_MASK, 0x4) }, /* == 0xfe */ + { WSA884X_VBAT_THRM_FLT_CTL, (0x7f & ~WSA884X_VBAT_THRM_FLT_CTL_VBAT_COEF_SEL_MASK) | + FIELD_PREP_CONST(WSA884X_VBAT_THRM_FLT_CTL_VBAT_COEF_SEL_MASK, 0x4) }, + { WSA884X_VBAT_CAL_CTL, FIELD_PREP_CONST(WSA884X_VBAT_CAL_CTL_RESERVE_MASK, 0x2) | + FIELD_PREP_CONST(WSA884X_VBAT_CAL_CTL_VBAT_CAL_EN_MASK, 0x1) }, + { WSA884X_BOP_DEGLITCH_CTL, FIELD_PREP_CONST(WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_SETTING_MASK, 0x8) | + FIELD_PREP_CONST(WSA884X_BOP_DEGLITCH_CTL_BOP_DEGLITCH_EN_MASK, 0x1) }, + { WSA884X_CDC_SPK_DSM_A2_0, 0x0a }, + { WSA884X_CDC_SPK_DSM_A2_1, 0x08 }, + { WSA884X_CDC_SPK_DSM_A3_0, 0xf3 }, + { WSA884X_CDC_SPK_DSM_A3_1, 0x07 }, + { WSA884X_CDC_SPK_DSM_A4_0, 0x79 }, + { WSA884X_CDC_SPK_DSM_A5_0, 0x0b }, + { WSA884X_CDC_SPK_DSM_A6_0, 0x8a }, + { WSA884X_CDC_SPK_DSM_A7_0, 0x9b }, + { WSA884X_CDC_SPK_DSM_C_0, FIELD_PREP_CONST(WSA884X_CDC_SPK_DSM_C_0_COEF_C3_MASK, 0x6) | + FIELD_PREP_CONST(WSA884X_CDC_SPK_DSM_C_0_COEF_C2_MASK, 0x8) }, + { WSA884X_CDC_SPK_DSM_C_2, FIELD_PREP_CONST(WSA884X_CDC_SPK_DSM_C_2_COEF_C7_MASK, 0xf) }, + { WSA884X_CDC_SPK_DSM_C_3, FIELD_PREP_CONST(WSA884X_CDC_SPK_DSM_C_3_COEF_C7_MASK, 0x20) }, + { WSA884X_CDC_SPK_DSM_R1, 0x83 }, + { WSA884X_CDC_SPK_DSM_R2, 0x7f }, + { WSA884X_CDC_SPK_DSM_R3, 0x9d }, + { WSA884X_CDC_SPK_DSM_R4, 0x82 }, + { WSA884X_CDC_SPK_DSM_R5, 0x8b }, + { WSA884X_CDC_SPK_DSM_R6, 0x9b }, + { WSA884X_CDC_SPK_DSM_R7, 0x3f }, + /* Speaker mode by default */ + { WSA884X_DRE_CTL_0, FIELD_PREP_CONST(WSA884X_DRE_CTL_0_PROG_DELAY_MASK, 0x7) }, + { WSA884X_CLSH_CTL_0, (0x37 & ~WSA884X_CLSH_CTL_0_DLY_CODE_MASK) | + FIELD_PREP_CONST(WSA884X_CLSH_CTL_0_DLY_CODE_MASK, 0x6) }, + /* + * WSA884X_CLSH_VTH values for speaker mode with G_21_DB system gain, + * battery 1S and rload 8 Ohms. + */ + { WSA884X_CLSH_VTH1, WSA884X_VTH_TO_REG(863), }, + { WSA884X_CLSH_VTH2, WSA884X_VTH_TO_REG(918), }, + { WSA884X_CLSH_VTH3, WSA884X_VTH_TO_REG(980), }, + { WSA884X_CLSH_VTH4, WSA884X_VTH_TO_REG(1043), }, + { WSA884X_CLSH_VTH5, WSA884X_VTH_TO_REG(1098), }, + { WSA884X_CLSH_VTH6, WSA884X_VTH_TO_REG(1137), }, + { WSA884X_CLSH_VTH7, WSA884X_VTH_TO_REG(1184), }, + { WSA884X_CLSH_VTH8, WSA884X_VTH_TO_REG(1239), }, + { WSA884X_CLSH_VTH9, WSA884X_VTH_TO_REG(1278), }, + { WSA884X_CLSH_VTH10, WSA884X_VTH_TO_REG(1380), }, + { WSA884X_CLSH_VTH11, WSA884X_VTH_TO_REG(1482), }, + { WSA884X_CLSH_VTH12, WSA884X_VTH_TO_REG(1584), }, + { WSA884X_CLSH_VTH13, WSA884X_VTH_TO_REG(1663), }, + { WSA884X_CLSH_VTH14, WSA884X_VTH_TO_REG(1780), }, + { WSA884X_CLSH_VTH15, WSA884X_VTH_TO_REG(2000), }, + { WSA884X_ANA_WO_CTL_1, 0x00 }, + { WSA884X_OTP_REG_38, 0x00 }, + { WSA884X_OTP_REG_40, FIELD_PREP_CONST(WSA884X_OTP_REG_40_ISENSE_RESCAL_MASK, 0x8) }, +}; + +static void wsa884x_set_gain_parameters(struct wsa884x_priv *wsa884x) +{ + struct regmap *regmap = wsa884x->regmap; + unsigned int min_gain, igain, vgain, comp_offset; + + /* + * Downstream sets gain parameters customized per boards per use-case. + * Choose here some sane values matching knowon users, like QRD8550 + * board:. + * + * Values match here downstream: + * For WSA884X_RECEIVER - G_7P5_DB system gain + * For WSA884X_SPEAKER - G_21_DB system gain + */ + if (wsa884x->dev_mode == WSA884X_RECEIVER) { + comp_offset = COMP_OFFSET4; + min_gain = G_M6_DB; + igain = ISENSE_18_DB; + vgain = VSENSE_M12_DB; + } else { + /* WSA884X_SPEAKER */ + comp_offset = COMP_OFFSET0; + min_gain = G_0_DB; + igain = ISENSE_12_DB; + vgain = VSENSE_M24_DB; + } + + regmap_update_bits(regmap, WSA884X_ISENSE2, + WSA884X_ISENSE2_ISENSE_GAIN_CTL_MASK, + FIELD_PREP(WSA884X_ISENSE2_ISENSE_GAIN_CTL_MASK, igain)); + regmap_update_bits(regmap, WSA884X_VSENSE1, + WSA884X_VSENSE1_GAIN_VSENSE_FE_MASK, + FIELD_PREP(WSA884X_VSENSE1_GAIN_VSENSE_FE_MASK, vgain)); + regmap_update_bits(regmap, WSA884X_GAIN_RAMPING_MIN, + WSA884X_GAIN_RAMPING_MIN_MIN_GAIN_MASK, + FIELD_PREP(WSA884X_GAIN_RAMPING_MIN_MIN_GAIN_MASK, min_gain)); + + if (wsa884x->port_enable[WSA884X_PORT_COMP]) { + regmap_update_bits(regmap, WSA884X_DRE_CTL_0, + WSA884X_DRE_CTL_0_OFFSET_MASK, + FIELD_PREP(WSA884X_DRE_CTL_0_OFFSET_MASK, comp_offset)); + + regmap_update_bits(regmap, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, + FIELD_PREP(WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, 0x0)); + } else { + regmap_update_bits(regmap, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, + FIELD_PREP(WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, 0x1)); + } +} + +static void wsa884x_init(struct wsa884x_priv *wsa884x) +{ + unsigned int wo_ctl_0; + unsigned int variant = 0; + + if (!regmap_read(wsa884x->regmap, WSA884X_OTP_REG_0, &variant)) + wsa884x->variant = variant & WSA884X_OTP_REG_0_ID_MASK; + + regmap_multi_reg_write(wsa884x->regmap, wsa884x_reg_init, + ARRAY_SIZE(wsa884x_reg_init)); + + wo_ctl_0 = 0xc; + wo_ctl_0 |= FIELD_PREP(WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MASK, + WSA884X_ANA_WO_CTL_0_DAC_CM_CLAMP_EN_MODE_SPEAKER); + /* Assume that compander is enabled by default unless it is haptics sku */ + if (wsa884x->variant == WSA884X_OTP_ID_WSA8845H) + wo_ctl_0 |= FIELD_PREP(WSA884X_ANA_WO_CTL_0_PA_AUX_GAIN_MASK, + WSA884X_ANA_WO_CTL_0_PA_AUX_18_DB); + else + wo_ctl_0 |= FIELD_PREP(WSA884X_ANA_WO_CTL_0_PA_AUX_GAIN_MASK, + WSA884X_ANA_WO_CTL_0_PA_AUX_0_DB); + regmap_write(wsa884x->regmap, WSA884X_ANA_WO_CTL_0, wo_ctl_0); + + wsa884x_set_gain_parameters(wsa884x); + + wsa884x->hw_init = false; +} + +static int wsa884x_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct wsa884x_priv *wsa884x = dev_get_drvdata(&slave->dev); + int ret; + + if (status == SDW_SLAVE_UNATTACHED) { + wsa884x->hw_init = false; + regcache_cache_only(wsa884x->regmap, true); + regcache_mark_dirty(wsa884x->regmap); + return 0; + } + + if (wsa884x->hw_init || status != SDW_SLAVE_ATTACHED) + return 0; + + regcache_cache_only(wsa884x->regmap, false); + ret = regcache_sync(wsa884x->regmap); + if (ret < 0) { + dev_err(&slave->dev, "Cannot sync regmap cache\n"); + return ret; + } + + wsa884x_init(wsa884x); + + return 0; +} + +static int wsa884x_port_prep(struct sdw_slave *slave, + struct sdw_prepare_ch *prepare_ch, + enum sdw_port_prep_ops state) +{ + struct wsa884x_priv *wsa884x = dev_get_drvdata(&slave->dev); + + if (state == SDW_OPS_PORT_POST_PREP) + wsa884x->port_prepared[prepare_ch->num - 1] = true; + else + wsa884x->port_prepared[prepare_ch->num - 1] = false; + + return 0; +} + +static const struct sdw_slave_ops wsa884x_slave_ops = { + .update_status = wsa884x_update_status, + .port_prep = wsa884x_port_prep, +}; + +static int wsa884x_dev_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = wsa884x->dev_mode; + + return 0; +} + +static int wsa884x_dev_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol); + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(component); + + if (wsa884x->dev_mode == ucontrol->value.enumerated.item[0]) + return 0; + + wsa884x->dev_mode = ucontrol->value.enumerated.item[0]; + + return 1; +} + +static int wsa884x_get_swr_port(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(comp); + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + int portidx = mixer->reg; + + ucontrol->value.integer.value[0] = wsa884x->port_enable[portidx]; + + return 0; +} + +static int wsa884x_set_swr_port(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_soc_kcontrol_component(kcontrol); + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(comp); + struct soc_mixer_control *mixer = (struct soc_mixer_control *)kcontrol->private_value; + int portidx = mixer->reg; + + if (ucontrol->value.integer.value[0]) { + if (wsa884x->port_enable[portidx]) + return 0; + + wsa884x->port_enable[portidx] = true; + } else { + if (!wsa884x->port_enable[portidx]) + return 0; + + wsa884x->port_enable[portidx] = false; + } + + return 1; +} + +static int wsa884x_codec_probe(struct snd_soc_component *comp) +{ + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(comp); + + snd_soc_component_init_regmap(comp, wsa884x->regmap); + + return 0; +} + +static void wsa884x_spkr_post_pmu(struct snd_soc_component *component, + struct wsa884x_priv *wsa884x) +{ + unsigned int curr_limit, curr_ovrd_en; + + wsa884x_set_gain_parameters(wsa884x); + if (wsa884x->dev_mode == WSA884X_RECEIVER) { + snd_soc_component_write_field(component, WSA884X_DRE_CTL_0, + WSA884X_DRE_CTL_0_PROG_DELAY_MASK, 0x3); + snd_soc_component_write_field(component, WSA884X_CDC_PATH_MODE, + WSA884X_CDC_PATH_MODE_RXD_MODE_MASK, + 0x1); + snd_soc_component_write_field(component, WSA884X_PWM_CLK_CTL, + WSA884X_PWM_CLK_CTL_PWM_CLK_FREQ_SEL_MASK, + 0x1); + } else { + /* WSA884X_SPEAKER */ + snd_soc_component_write_field(component, WSA884X_DRE_CTL_0, + WSA884X_DRE_CTL_0_PROG_DELAY_MASK, 0xf); + } + + if (wsa884x->port_enable[WSA884X_PORT_PBR]) { + curr_ovrd_en = 0x0; + curr_limit = 0x15; + } else { + curr_ovrd_en = 0x1; + if (wsa884x->dev_mode == WSA884X_RECEIVER) + curr_limit = 0x9; + else + curr_limit = 0x15; + } + snd_soc_component_write_field(component, WSA884X_CURRENT_LIMIT, + WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_OVRD_EN_MASK, + curr_ovrd_en); + snd_soc_component_write_field(component, WSA884X_CURRENT_LIMIT, + WSA884X_CURRENT_LIMIT_CURRENT_LIMIT_MASK, + curr_limit); +} + +static int wsa884x_spkr_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); + struct wsa884x_priv *wsa884x = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + wsa884x_spkr_post_pmu(component, wsa884x); + + snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x1); + snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, + 0x1); + + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, + 0x0); + snd_soc_component_write_field(component, WSA884X_PDM_WD_CTL, + WSA884X_PDM_WD_CTL_PDM_WD_EN_MASK, + 0x0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget wsa884x_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("IN"), + SND_SOC_DAPM_SPK("SPKR", wsa884x_spkr_event), +}; + +static const DECLARE_TLV_DB_SCALE(pa_gain, -900, 150, -900); + +static const struct snd_kcontrol_new wsa884x_snd_controls[] = { + SOC_SINGLE_RANGE_TLV("PA Volume", WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_SHIFT, + 0x0, 0x1f, 1, pa_gain), + SOC_ENUM_EXT("WSA MODE", wsa884x_dev_mode_enum, + wsa884x_dev_mode_get, wsa884x_dev_mode_put), + SOC_SINGLE_EXT("DAC Switch", WSA884X_PORT_DAC, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), + SOC_SINGLE_EXT("COMP Switch", WSA884X_PORT_COMP, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), + SOC_SINGLE_EXT("BOOST Switch", WSA884X_PORT_BOOST, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), + SOC_SINGLE_EXT("PBR Switch", WSA884X_PORT_PBR, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), + SOC_SINGLE_EXT("VISENSE Switch", WSA884X_PORT_VISENSE, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), + SOC_SINGLE_EXT("CPS Switch", WSA884X_PORT_CPS, 0, 1, 0, + wsa884x_get_swr_port, wsa884x_set_swr_port), +}; + +static const struct snd_soc_dapm_route wsa884x_audio_map[] = { + {"SPKR", NULL, "IN"}, +}; + +static const struct snd_soc_component_driver wsa884x_component_drv = { + .name = "WSA884x", + .probe = wsa884x_codec_probe, + .controls = wsa884x_snd_controls, + .num_controls = ARRAY_SIZE(wsa884x_snd_controls), + .dapm_widgets = wsa884x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(wsa884x_dapm_widgets), + .dapm_routes = wsa884x_audio_map, + .num_dapm_routes = ARRAY_SIZE(wsa884x_audio_map), +}; + +static int wsa884x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); + int i; + + wsa884x->active_ports = 0; + for (i = 0; i < WSA884X_MAX_SWR_PORTS; i++) { + if (!wsa884x->port_enable[i]) + continue; + + wsa884x->port_config[wsa884x->active_ports] = wsa884x_pconfig[i]; + wsa884x->active_ports++; + } + + wsa884x->sconfig.frame_rate = params_rate(params); + + return sdw_stream_add_slave(wsa884x->slave, &wsa884x->sconfig, + wsa884x->port_config, wsa884x->active_ports, + wsa884x->sruntime); +} + +static int wsa884x_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); + + sdw_stream_remove_slave(wsa884x->slave, wsa884x->sruntime); + + return 0; +} + +static int wsa884x_mute_stream(struct snd_soc_dai *dai, int mute, int stream) +{ + struct snd_soc_component *component = dai->component; + + if (mute) { + snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, + 0x0); + snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, + 0x0); + + } else { + snd_soc_component_write_field(component, WSA884X_DRE_CTL_1, + WSA884X_DRE_CTL_1_CSR_GAIN_EN_MASK, + 0x1); + snd_soc_component_write_field(component, WSA884X_PA_FSM_EN, + WSA884X_PA_FSM_EN_GLOBAL_PA_EN_MASK, + 0x1); + } + + return 0; +} + +static int wsa884x_set_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + struct wsa884x_priv *wsa884x = dev_get_drvdata(dai->dev); + + wsa884x->sruntime = stream; + + return 0; +} + +static const struct snd_soc_dai_ops wsa884x_dai_ops = { + .hw_params = wsa884x_hw_params, + .hw_free = wsa884x_hw_free, + .mute_stream = wsa884x_mute_stream, + .set_stream = wsa884x_set_stream, +}; + +static struct snd_soc_dai_driver wsa884x_dais[] = { + { + .name = "SPKR", + .playback = { + .stream_name = "SPKR Playback", + .rates = WSA884X_RATES | WSA884X_FRAC_RATES, + .formats = WSA884X_FORMATS, + .rate_min = 8000, + .rate_max = 384000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &wsa884x_dai_ops, + }, +}; + +static void wsa884x_gpio_powerdown(void *data) +{ + gpiod_direction_output(data, 1); +} + +static void wsa884x_regulator_disable(void *data) +{ + regulator_bulk_disable(WSA884X_SUPPLIES_NUM, data); +} + +static int wsa884x_probe(struct sdw_slave *pdev, + const struct sdw_device_id *id) +{ + struct device *dev = &pdev->dev; + struct wsa884x_priv *wsa884x; + unsigned int i; + int ret; + + wsa884x = devm_kzalloc(dev, sizeof(*wsa884x), GFP_KERNEL); + if (!wsa884x) + return -ENOMEM; + + for (i = 0; i < WSA884X_SUPPLIES_NUM; i++) + wsa884x->supplies[i].supply = wsa884x_supply_name[i]; + + ret = devm_regulator_bulk_get(dev, WSA884X_SUPPLIES_NUM, + wsa884x->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret = regulator_bulk_enable(WSA884X_SUPPLIES_NUM, wsa884x->supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + ret = devm_add_action_or_reset(dev, wsa884x_regulator_disable, + wsa884x->supplies); + if (ret) + return ret; + + wsa884x->sd_n = devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(wsa884x->sd_n)) + return dev_err_probe(dev, PTR_ERR(wsa884x->sd_n), + "Shutdown Control GPIO not found\n"); + + dev_set_drvdata(dev, wsa884x); + wsa884x->slave = pdev; + wsa884x->dev = dev; + wsa884x->dev_mode = WSA884X_SPEAKER; + wsa884x->sconfig.ch_count = 1; + wsa884x->sconfig.bps = 1; + wsa884x->sconfig.direction = SDW_DATA_DIR_RX; + wsa884x->sconfig.type = SDW_STREAM_PDM; + + pdev->prop.sink_ports = GENMASK(WSA884X_MAX_SWR_PORTS, 0); + pdev->prop.simple_clk_stop_capable = true; + pdev->prop.sink_dpn_prop = wsa884x_sink_dpn_prop; + pdev->prop.scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + + /* Bring out of reset */ + gpiod_direction_output(wsa884x->sd_n, 0); + ret = devm_add_action_or_reset(dev, wsa884x_gpio_powerdown, wsa884x->sd_n); + if (ret) + return ret; + + wsa884x->regmap = devm_regmap_init_sdw(pdev, &wsa884x_regmap_config); + if (IS_ERR(wsa884x->regmap)) + return dev_err_probe(dev, PTR_ERR(wsa884x->regmap), + "regmap_init failed\n"); + + /* Start in cache-only until device is enumerated */ + regcache_cache_only(wsa884x->regmap, true); + wsa884x->hw_init = true; + + pm_runtime_set_autosuspend_delay(dev, 3000); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return devm_snd_soc_register_component(dev, + &wsa884x_component_drv, + wsa884x_dais, + ARRAY_SIZE(wsa884x_dais)); +} + +static int __maybe_unused wsa884x_runtime_suspend(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, true); + regcache_mark_dirty(regmap); + + return 0; +} + +static int __maybe_unused wsa884x_runtime_resume(struct device *dev) +{ + struct regmap *regmap = dev_get_regmap(dev, NULL); + + regcache_cache_only(regmap, false); + regcache_sync(regmap); + + return 0; +} + +static const struct dev_pm_ops wsa884x_pm_ops = { + SET_RUNTIME_PM_OPS(wsa884x_runtime_suspend, wsa884x_runtime_resume, NULL) +}; + +static const struct sdw_device_id wsa884x_swr_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x204, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, wsa884x_swr_id); + +static struct sdw_driver wsa884x_codec_driver = { + .driver = { + .name = "wsa884x-codec", + .pm = &wsa884x_pm_ops, + }, + .probe = wsa884x_probe, + .ops = &wsa884x_slave_ops, + .id_table = wsa884x_swr_id, +}; +module_sdw_driver(wsa884x_codec_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_DESCRIPTION("WSA884x codec driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6ab11462c68499933bd9b5d52a710f4e18a9e43e Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 16 Jun 2023 17:39:13 -0300 Subject: ASoC: fsl-asoc-card: Allow passing the number of slots in use Currently, fsl-asoc-card supports passing the width of the TDM slot, but not the number of slots in use, as it harcodes it as two slots. Add support for passing the number of slots in use. Signed-off-by: Fabio Estevam Link: https://lore.kernel.org/r/20230616203913.551183-1-festevam@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl-asoc-card.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c index 8d0161ac8380..76b5bfc288fd 100644 --- a/sound/soc/fsl/fsl-asoc-card.c +++ b/sound/soc/fsl/fsl-asoc-card.c @@ -62,6 +62,7 @@ struct codec_priv { * @sysclk_dir: SYSCLK directions for set_sysclk() * @sysclk_id: SYSCLK ids for set_sysclk() * @slot_width: Slot width of each frame + * @slot_num: Number of slots of each frame * * Note: [1] for tx and [0] for rx */ @@ -70,6 +71,7 @@ struct cpu_priv { u32 sysclk_dir[2]; u32 sysclk_id[2]; u32 slot_width; + u32 slot_num; }; /** @@ -191,7 +193,11 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, } if (cpu_priv->slot_width) { - ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, 2, + if (!cpu_priv->slot_num) + cpu_priv->slot_num = 2; + + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_cpu(rtd, 0), 0x3, 0x3, + cpu_priv->slot_num, cpu_priv->slot_width); if (ret && ret != -ENOTSUPP) { dev_err(dev, "failed to set TDM slot for cpu dai\n"); -- cgit v1.2.3 From 724418b84e6248cd27599607b7e5fac365b8e3f5 Mon Sep 17 00:00:00 2001 From: Matthew Anderson Date: Wed, 21 Jun 2023 11:17:14 -0500 Subject: ALSA: hda/realtek: Add quirks for ROG ALLY CS35l41 audio This requires a patched ACPI table or a firmware from ASUS to work because the system does not come with the _DSD field for the CSC3551. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217550 Signed-off-by: Matthew Anderson Tested-by: Philip Mueller Link: https://lore.kernel.org/r/20230621161714.9442-1-ruinairas1992@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index ce9a9cb41e4b..2ab1065d5a1c 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -7121,6 +7121,10 @@ enum { ALC294_FIXUP_ASUS_DUAL_SPK, ALC285_FIXUP_THINKPAD_X1_GEN7, ALC285_FIXUP_THINKPAD_HEADSET_JACK, + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, + ALC294_FIXUP_ASUS_ALLY_SPEAKER, ALC294_FIXUP_ASUS_HPE, ALC294_FIXUP_ASUS_COEF_1B, ALC294_FIXUP_ASUS_GX502_HP, @@ -8417,6 +8421,47 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_SPK2_TO_DAC1 }, + [ALC294_FIXUP_ASUS_ALLY] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_PINS + }, + [ALC294_FIXUP_ASUS_ALLY_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, + { 0x1a, 0x03a11c30 }, + { 0x21, 0x03211420 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_VERBS + }, + [ALC294_FIXUP_ASUS_ALLY_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x46 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0004 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x47 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xa47a }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x49 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0049}, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x4a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x201b }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x6b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4278}, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER + }, + [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + }, [ALC285_FIXUP_THINKPAD_X1_GEN7] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_thinkpad_x1_gen7, @@ -9510,6 +9555,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x1740, "ASUS UX430UA", ALC295_FIXUP_ASUS_DACS), SND_PCI_QUIRK(0x1043, 0x17d1, "ASUS UX431FL", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x17f3, "ROG Ally RC71L_RC71L", ALC294_FIXUP_ASUS_ALLY), SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), -- cgit v1.2.3 From 2cc41db71a434844ca97b6e30c9a30a2464a996e Mon Sep 17 00:00:00 2001 From: Sameer Pujar Date: Thu, 22 Jun 2023 17:04:13 +0530 Subject: ASoC: tegra: Use normal system sleep for ASRC Align with other AHUB module drivers and use normal system sleep for ASRC as well. Signed-off-by: Sameer Pujar Link: https://lore.kernel.org/r/Message-Id: <1687433656-7892-6-git-send-email-spujar@nvidia.com> Signed-off-by: Mark Brown --- sound/soc/tegra/tegra186_asrc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/tegra/tegra186_asrc.c b/sound/soc/tegra/tegra186_asrc.c index e016a6a7f7c4..208e2fcefcf2 100644 --- a/sound/soc/tegra/tegra186_asrc.c +++ b/sound/soc/tegra/tegra186_asrc.c @@ -1024,8 +1024,8 @@ static void tegra186_asrc_platform_remove(struct platform_device *pdev) static const struct dev_pm_ops tegra186_asrc_pm_ops = { SET_RUNTIME_PM_OPS(tegra186_asrc_runtime_suspend, tegra186_asrc_runtime_resume, NULL) - SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, - pm_runtime_force_resume) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver tegra186_asrc_driver = { -- cgit v1.2.3 From f47d43283a4233528683deaaba95f0ee2cfe862d Mon Sep 17 00:00:00 2001 From: Sameer Pujar Date: Thu, 22 Jun 2023 17:04:14 +0530 Subject: ASoC: tegra: Remove stale comments in AHUB Remove stale comments in AHUB driver which is related to DAPM widgets and routes. This is misleading otherwise. Signed-off-by: Sameer Pujar Link: https://lore.kernel.org/r/Message-Id: <1687433656-7892-7-git-send-email-spujar@nvidia.com> Signed-off-by: Mark Brown --- sound/soc/tegra/tegra210_ahub.c | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'sound') diff --git a/sound/soc/tegra/tegra210_ahub.c b/sound/soc/tegra/tegra210_ahub.c index 8c00c09eeefb..3f114a2adfce 100644 --- a/sound/soc/tegra/tegra210_ahub.c +++ b/sound/soc/tegra/tegra210_ahub.c @@ -712,11 +712,6 @@ MUX_ENUM_CTRL_DECL_234(t234_asrc15_tx, 0x68); MUX_ENUM_CTRL_DECL_234(t234_asrc16_tx, 0x69); MUX_ENUM_CTRL_DECL_234(t234_asrc17_tx, 0x6a); -/* - * The number of entries in, and order of, this array is closely tied to the - * calculation of tegra210_ahub_codec.num_dapm_widgets near the end of - * tegra210_ahub_probe() - */ static const struct snd_soc_dapm_widget tegra210_ahub_widgets[] = { WIDGETS("ADMAIF1", t210_admaif1_tx), WIDGETS("ADMAIF2", t210_admaif2_tx), @@ -1092,11 +1087,6 @@ static const struct snd_soc_dapm_widget tegra234_ahub_widgets[] = { { name " XBAR-Capture", NULL, name " XBAR-TX" }, \ { name " Capture", NULL, name " XBAR-Capture" }, -/* - * The number of entries in, and order of, this array is closely tied to the - * calculation of tegra210_ahub_codec.num_dapm_routes near the end of - * tegra210_ahub_probe() - */ static const struct snd_soc_dapm_route tegra210_ahub_routes[] = { TEGRA_FE_ROUTES("ADMAIF1") TEGRA_FE_ROUTES("ADMAIF2") -- cgit v1.2.3 From 012fa2622e30675f61413485785e708ba02be78b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 22 Jun 2023 12:12:22 +0200 Subject: ASoC: loongson: fix address space confusion The i2s driver uses the mapped __iomem address of the FIFO as the DMA address for the device. This apparently works on loongarch because of the way it handles __iomem pointers as aliases of physical addresses, but this is not portable to other architectures and causes a compiler warning when dma addresses are not the same size as pointers: sound/soc/loongson/loongson_i2s_pci.c: In function 'loongson_i2s_pci_probe': sound/soc/loongson/loongson_i2s_pci.c:110:29: error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast] 110 | tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA; | ^ sound/soc/loongson/loongson_i2s_pci.c:113:29: error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast] 113 | rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA; | ^ Change the driver to instead use the physical address as stored in the PCI BAR resource directly. Since 'dev_addr' is a 32-bit address, I think this results in the same truncated address on loongarch but is otherwise closer to portable code and avoids the warning. Fixes: d84881e06836d ("ASoC: Add support for Loongson I2S controller") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/Message-Id: <20230622101235.3230941-1-arnd@kernel.org> Signed-off-by: Mark Brown --- sound/soc/loongson/loongson_i2s_pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/loongson/loongson_i2s_pci.c b/sound/soc/loongson/loongson_i2s_pci.c index 6dcfb17d3276..fa90361865c6 100644 --- a/sound/soc/loongson/loongson_i2s_pci.c +++ b/sound/soc/loongson/loongson_i2s_pci.c @@ -107,10 +107,10 @@ static int loongson_i2s_pci_probe(struct pci_dev *pdev, tx_data = &i2s->tx_dma_data; rx_data = &i2s->rx_dma_data; - tx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_TX_DATA; + tx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_TX_DATA; tx_data->order_addr = i2s->reg_base + LS_I2S_TX_ORDER; - rx_data->dev_addr = (dma_addr_t)i2s->reg_base + LS_I2S_RX_DATA; + rx_data->dev_addr = pci_resource_start(pdev, 0) + LS_I2S_RX_DATA; rx_data->order_addr = i2s->reg_base + LS_I2S_RX_ORDER; tx_data->irq = fwnode_irq_get_byname(fwnode, "tx"); -- cgit v1.2.3 From 82f76ac26c601c5b0c0db7f69500efc42f2ee7ed Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Thu, 2 Mar 2023 12:03:27 +0000 Subject: ASoC: qcom: common: add default jack dapm pins If the soundcard does not specify the dapm pins, let the common code add these pins for jack. Signed-off-by: Srinivas Kandagatla Tested-by: Johan Hovold Link: https://lore.kernel.org/r/Message-Id: <20230302120327.10823-1-srinivas.kandagatla@linaro.org> Signed-off-by: Mark Brown --- sound/soc/qcom/common.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'sound') diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c index 43b0a888f1e8..e2d8c41945fa 100644 --- a/sound/soc/qcom/common.c +++ b/sound/soc/qcom/common.c @@ -8,6 +8,11 @@ #include "qdsp6/q6afe.h" #include "common.h" +static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + int qcom_snd_parse_of(struct snd_soc_card *card) { struct device_node *np; @@ -153,6 +158,11 @@ int qcom_snd_parse_of(struct snd_soc_card *card) of_node_put(platform); } + if (!card->dapm_widgets) { + card->dapm_widgets = qcom_jack_snd_widgets; + card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets); + } + return 0; err: of_node_put(cpu); -- cgit v1.2.3 From fb180283c00b435019bd9500ae027872da9faa3b Mon Sep 17 00:00:00 2001 From: Maxim Kochetkov Date: Thu, 22 Jun 2023 17:20:36 +0300 Subject: ASoC: codecs: max98090: Allow dsp_a mode TDM mode for max98090 is dsp_a compatible with such limitations: 1) Up to four timeslots supported. 2) Only 16 bits timeslots supported. 3) Only 2 active timeslots (L/R) supported. We want to setup TDM mode only when dsp_a mode is selected. So move M98090_REG_TDM_FORMAT/M98090_REG_TDM_CONTROL registers setup from max98090_set_tdm_slot() to the max98090_dai_set_fmt(). Also extend max98090_set_tdm_slot() with all TDM limitations check. Signed-off-by: Maxim Kochetkov Link: https://lore.kernel.org/r/Message-Id: <20230622142038.63388-1-fido_max@inbox.ru> Signed-off-by: Mark Brown --- sound/soc/codecs/max98090.c | 53 ++++++++++++++++++++++++--------------------- sound/soc/codecs/max98090.h | 3 ++- 2 files changed, 30 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/max98090.c b/sound/soc/codecs/max98090.c index 7bc463910d4f..2adf744c6526 100644 --- a/sound/soc/codecs/max98090.c +++ b/sound/soc/codecs/max98090.c @@ -1581,7 +1581,7 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_component *component = codec_dai->component; struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); struct max98090_cdata *cdata; - u8 regval; + u8 regval, tdm_regval; max98090->dai_fmt = fmt; cdata = &max98090->dai[0]; @@ -1590,6 +1590,7 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai, cdata->fmt = fmt; regval = 0; + tdm_regval = 0; switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { case SND_SOC_DAIFMT_CBC_CFC: /* Set to consumer mode PLL - MAS mode off */ @@ -1635,7 +1636,8 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai, regval |= M98090_RJ_MASK; break; case SND_SOC_DAIFMT_DSP_A: - /* Not supported mode */ + tdm_regval |= M98090_TDM_MASK; + break; default: dev_err(component->dev, "DAI format unsupported"); return -EINVAL; @@ -1664,11 +1666,20 @@ static int max98090_dai_set_fmt(struct snd_soc_dai *codec_dai, * seen for the case of TDM mode. The remaining cases have * normal logic. */ - if (max98090->tdm_slots > 1) + if (tdm_regval) regval ^= M98090_BCI_MASK; snd_soc_component_write(component, M98090_REG_INTERFACE_FORMAT, regval); + + regval = 0; + if (tdm_regval) + regval = max98090->tdm_lslot << M98090_TDM_SLOTL_SHIFT | + max98090->tdm_rslot << M98090_TDM_SLOTR_SHIFT | + 0 << M98090_TDM_SLOTDLY_SHIFT; + + snd_soc_component_write(component, M98090_REG_TDM_FORMAT, regval); + snd_soc_component_write(component, M98090_REG_TDM_CONTROL, tdm_regval); } return 0; @@ -1679,33 +1690,22 @@ static int max98090_set_tdm_slot(struct snd_soc_dai *codec_dai, { struct snd_soc_component *component = codec_dai->component; struct max98090_priv *max98090 = snd_soc_component_get_drvdata(component); - struct max98090_cdata *cdata; - cdata = &max98090->dai[0]; if (slots < 0 || slots > 4) return -EINVAL; - max98090->tdm_slots = slots; - max98090->tdm_width = slot_width; - - if (max98090->tdm_slots > 1) { - /* SLOTL SLOTR SLOTDLY */ - snd_soc_component_write(component, M98090_REG_TDM_FORMAT, - 0 << M98090_TDM_SLOTL_SHIFT | - 1 << M98090_TDM_SLOTR_SHIFT | - 0 << M98090_TDM_SLOTDLY_SHIFT); - - /* FSW TDM */ - snd_soc_component_update_bits(component, M98090_REG_TDM_CONTROL, - M98090_TDM_MASK, - M98090_TDM_MASK); - } + if (slot_width != 16) + return -EINVAL; - /* - * Normally advisable to set TDM first, but this permits either order - */ - cdata->fmt = 0; - max98090_dai_set_fmt(codec_dai, max98090->dai_fmt); + if (rx_mask != tx_mask) + return -EINVAL; + + if (!rx_mask) + return -EINVAL; + + max98090->tdm_slots = slots; + max98090->tdm_lslot = ffs(rx_mask) - 1; + max98090->tdm_rslot = fls(rx_mask) - 1; return 0; } @@ -2408,6 +2408,9 @@ static int max98090_probe(struct snd_soc_component *component) max98090->pa1en = 0; max98090->pa2en = 0; + max98090->tdm_lslot = 0; + max98090->tdm_rslot = 1; + ret = snd_soc_component_read(component, M98090_REG_REVISION_ID); if (ret < 0) { dev_err(component->dev, "Failed to read device revision: %d\n", diff --git a/sound/soc/codecs/max98090.h b/sound/soc/codecs/max98090.h index a197114b0dad..6ce8dd176e48 100644 --- a/sound/soc/codecs/max98090.h +++ b/sound/soc/codecs/max98090.h @@ -1533,7 +1533,8 @@ struct max98090_priv { struct snd_soc_jack *jack; unsigned int dai_fmt; int tdm_slots; - int tdm_width; + int tdm_lslot; + int tdm_rslot; u8 lin_state; unsigned int pa1en; unsigned int pa2en; -- cgit v1.2.3 From ad60672394bd1f95c58d3d9336902f47e05126fc Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 22 Jun 2023 20:53:38 +0530 Subject: ASoC: amd: acp: clear pdm dma interrupt mask Clear pdm dma interrupt mask in acp_dmic_shutdown(). 'Fixes: c32bd332ce5c9 ("ASoC: amd: acp: Add generic support for PDM controller on ACP")' Signed-off-by: Syed Saba Kareem Link: https://lore.kernel.org/r/Message-Id: <20230622152406.3709231-1-Syed.SabaKareem@amd.com> Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-pdm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/amd/acp/acp-pdm.c b/sound/soc/amd/acp/acp-pdm.c index 66ec6b6a5972..f8030b79ac17 100644 --- a/sound/soc/amd/acp/acp-pdm.c +++ b/sound/soc/amd/acp/acp-pdm.c @@ -176,7 +176,7 @@ static void acp_dmic_dai_shutdown(struct snd_pcm_substream *substream, /* Disable DMIC interrupts */ ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0)); - ext_int_ctrl |= ~PDM_DMA_INTR_MASK; + ext_int_ctrl &= ~PDM_DMA_INTR_MASK; writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0)); } -- cgit v1.2.3 From 3eb96217c16cb7be0fe6e1d416ff4fe47f686bea Mon Sep 17 00:00:00 2001 From: Syed Saba Kareem Date: Thu, 22 Jun 2023 20:53:41 +0530 Subject: ASoC: amd: acp: remove acp poweroff function BIOS invokes ACP Power off sequence based on ACP device state. Remove redundant code from ACP PCI driver for ACP Power off sequence. Signed-off-by: Syed Saba Kareem Link: https://lore.kernel.org/r/Message-Id: <20230622152406.3709231-4-Syed.SabaKareem@amd.com> Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-rembrandt.c | 25 ------------------------- sound/soc/amd/acp/acp-renoir.c | 17 ----------------- 2 files changed, 42 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/acp/acp-rembrandt.c b/sound/soc/amd/acp/acp-rembrandt.c index 5c455cc04113..1b997837c7d8 100644 --- a/sound/soc/amd/acp/acp-rembrandt.c +++ b/sound/soc/amd/acp/acp-rembrandt.c @@ -204,23 +204,6 @@ static int acp6x_power_on(void __iomem *base) return -ETIMEDOUT; } -static int acp6x_power_off(void __iomem *base) -{ - u32 val; - int timeout; - - writel(ACP_PGFSM_CNTL_POWER_OFF_MASK, - base + ACP6X_PGFSM_CONTROL); - timeout = 0; - while (++timeout < 500) { - val = readl(base + ACP6X_PGFSM_STATUS); - if ((val & ACP_PGFSM_STATUS_MASK) == ACP_POWERED_OFF) - return 0; - udelay(1); - } - return -ETIMEDOUT; -} - static int acp6x_reset(void __iomem *base) { u32 val; @@ -299,14 +282,6 @@ static int rmb_acp_deinit(void __iomem *base) } writel(0x00, base + ACP_CONTROL); - - /* power off */ - ret = acp6x_power_off(base); - if (ret) { - pr_err("ACP power off failed\n"); - return ret; - } - return 0; } diff --git a/sound/soc/amd/acp/acp-renoir.c b/sound/soc/amd/acp/acp-renoir.c index b3cbc7f19ec5..f188365fe214 100644 --- a/sound/soc/amd/acp/acp-renoir.c +++ b/sound/soc/amd/acp/acp-renoir.c @@ -169,17 +169,6 @@ static int acp3x_power_on(void __iomem *base) return readl_poll_timeout(base + ACP_PGFSM_STATUS, val, !val, DELAY_US, ACP_TIMEOUT); } -static int acp3x_power_off(void __iomem *base) -{ - u32 val; - - writel(ACP_PWR_OFF_MASK, base + ACP_PGFSM_CONTROL); - - return readl_poll_timeout(base + ACP_PGFSM_STATUS, val, - (val & ACP_PGFSM_STAT_MASK) == ACP_POWERED_OFF, - DELAY_US, ACP_TIMEOUT); -} - static int acp3x_reset(void __iomem *base) { u32 val; @@ -246,12 +235,6 @@ static int rn_acp_deinit(void __iomem *base) return ret; writel(0x00, base + ACP_CONTROL); - - /* power off */ - ret = acp3x_power_off(base); - if (ret) - return ret; - return 0; } static int renoir_audio_probe(struct platform_device *pdev) -- cgit v1.2.3 From fcb66ee8d16aa0f88efcc9cb41083c0412e9db8a Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 22 Jun 2023 12:11:23 +0200 Subject: ASoC: tas2781: fix Kconfig dependencies The new driver has two modules that both get enabled for build testing when all codecs are selected. The comlib part has an i2c dependency, so this remains disabled on builds without i2c, but then the other one fails to link: ERROR: modpost: "tasdevice_dev_bulk_write" [sound/soc/codecs/snd-soc-tas2781-fmwlib.ko] undefined! ERROR: modpost: "tasdevice_dev_update_bits" [sound/soc/codecs/snd-soc-tas2781-fmwlib.ko] undefined! ERROR: modpost: "tasdevice_dev_bulk_read" [sound/soc/codecs/snd-soc-tas2781-fmwlib.ko] undefined! ERROR: modpost: "tasdevice_dev_read" [sound/soc/codecs/snd-soc-tas2781-fmwlib.ko] undefined! ERROR: modpost: "tasdevice_dev_write" [sound/soc/codecs/snd-soc-tas2781-fmwlib.ko] undefined! There are many ways to address this, adding an explicit dependency seems to be the clearest method that keeps the structure of the driver otherwise unchanged. Fixes: ef3bcde75d06d ("ASoC: tas2781: Add tas2781 driver") Signed-off-by: Arnd Bergmann Link: https://lore.kernel.org/r/Message-Id: <20230622101205.3180938-1-arnd@kernel.org> Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7895969bcc39..0cd107fa112f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1741,6 +1741,7 @@ config SND_SOC_TAS2781_COMLIB tristate config SND_SOC_TAS2781_FMWLIB + depends on SND_SOC_TAS2781_COMLIB tristate default n -- cgit v1.2.3 From 33cd7630782df2230529c3e8f1a6d0ae9cd6ab49 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 23 Jun 2023 09:55:30 +0200 Subject: ALSA: ump: Export MIDI1 / UMP conversion helpers Yet more preliminary work for the upcoming USB gadget support. Now export the helpers to convert between legacy MIDI1 and UMP data for handling the MIDI 1.0 USB interface. The header file is moved to include/sound. The API functions are slightly changed, so that they can be used without the direct access to snd_ump object. The allocation is done in ump.c itself as it's a simple kcalloc(). Link: https://lore.kernel.org/r/20230623075530.10976-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump_convert.h | 46 ++++++++++++++++++++++++++ sound/core/ump.c | 18 +++++----- sound/core/ump_convert.c | 80 +++++++++++++++++++-------------------------- sound/core/ump_convert.h | 43 ------------------------ 4 files changed, 89 insertions(+), 98 deletions(-) create mode 100644 include/sound/ump_convert.h delete mode 100644 sound/core/ump_convert.h (limited to 'sound') diff --git a/include/sound/ump_convert.h b/include/sound/ump_convert.h new file mode 100644 index 000000000000..28c364c63245 --- /dev/null +++ b/include/sound/ump_convert.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __SOUND_UMP_CONVERT_H +#define __SOUND_UMP_CONVERT_H + +#include + +/* context for converting from legacy control messages to UMP packet */ +struct ump_cvt_to_ump_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +/* context for converting from MIDI1 byte stream to UMP packet */ +struct ump_cvt_to_ump { + /* MIDI1 intermediate buffer */ + unsigned char buf[4]; + int len; + int cmd_bytes; + + /* UMP output packet */ + u32 ump[4]; + int ump_bytes; + + /* various status */ + unsigned int in_sysex; + struct ump_cvt_to_ump_bank bank[16]; /* per channel */ +}; + +int snd_ump_convert_from_ump(const u32 *data, unsigned char *dst, + unsigned char *group_ret); +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c); + +/* reset the converter context, called at each open to ump */ +static inline void snd_ump_convert_reset(struct ump_cvt_to_ump *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + +} + +#endif /* __SOUND_UMP_CONVERT_H */ diff --git a/sound/core/ump.c b/sound/core/ump.c index 5e17351ca984..246348766ec1 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -11,7 +11,7 @@ #include #include #include -#include "ump_convert.h" +#include #define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) #define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) @@ -87,7 +87,7 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) ump->private_free(ump); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) - snd_ump_convert_free(ump); + kfree(ump->out_cvts); #endif } @@ -1002,7 +1002,7 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) goto unlock; } ump->legacy_out_opens++; - snd_ump_reset_convert_to_ump(ump, group); + snd_ump_convert_reset(&ump->out_cvts[group]); } spin_lock_irq(&ump->legacy_locks[dir]); ump->legacy_substreams[dir][group] = substream; @@ -1091,7 +1091,7 @@ static int process_legacy_output(struct snd_ump_endpoint *ump, ctx = &ump->out_cvts[group]; while (!ctx->ump_bytes && snd_rawmidi_transmit(substream, &c, 1) > 0) - snd_ump_convert_to_ump(ump, group, c); + snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c); if (ctx->ump_bytes && ctx->ump_bytes <= count) { size = ctx->ump_bytes; memcpy(buffer, ctx->ump, size); @@ -1113,7 +1113,7 @@ static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, const int dir = SNDRV_RAWMIDI_STREAM_INPUT; int size; - size = snd_ump_convert_from_ump(ump, src, buf, &group); + size = snd_ump_convert_from_ump(src, buf, &group); if (size <= 0) return; spin_lock_irqsave(&ump->legacy_locks[dir], flags); @@ -1130,9 +1130,9 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, bool input, output; int err; - err = snd_ump_convert_init(ump); - if (err < 0) - return err; + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; @@ -1140,7 +1140,7 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, output ? 16 : 0, input ? 16 : 0, &rmidi); if (err < 0) { - snd_ump_convert_free(ump); + kfree(ump->out_cvts); return err; } diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 48ab3e1bd62e..fb61df424a87 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -8,7 +8,7 @@ #include #include #include -#include "ump_convert.h" +#include /* * Upgrade / downgrade value bits @@ -205,12 +205,18 @@ static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) return size; } -/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf; - * the target group is stored at @group_ret, - * returns the number of bytes of MIDI 1.0 stream +/** + * snd_ump_convert_from_ump - convert from UMP to legacy MIDI + * @data: UMP packet + * @buf: buffer to store legacy MIDI data + * @group_ret: pointer to store the target group + * + * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf. + * The target group is stored at @group_ret. + * + * The function returns the number of bytes of MIDI 1.0 stream. */ -int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, - const u32 *data, +int snd_ump_convert_from_ump(const u32 *data, unsigned char *buf, unsigned char *group_ret) { @@ -230,6 +236,7 @@ int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, return 0; } +EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump); /* * MIDI 1 byte stream -> UMP conversion @@ -302,10 +309,10 @@ static void fill_rpn(struct ump_cvt_to_ump_bank *cc, } /* convert to a MIDI 1.0 Channel Voice message */ -static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, - struct ump_cvt_to_ump *cvt, - unsigned char group, u32 *data, - unsigned char bytes) +static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, + unsigned int protocol, + u32 *data, unsigned char bytes) { const unsigned char *buf = cvt->buf; struct ump_cvt_to_ump_bank *cc; @@ -316,7 +323,7 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ - if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + if (protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, group, 0, buf[0]); data[0] |= buf[1] << 8; @@ -413,8 +420,8 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, return 8; } -static int do_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c, u32 *data) +static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c, u32 *data) { /* bytes for 0x80-0xf0 */ static unsigned char cmd_bytes[8] = { @@ -424,7 +431,6 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, static unsigned char system_bytes[16] = { 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 }; - struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; unsigned char bytes; if (c == UMP_MIDI1_MSG_SYSEX_START) { @@ -478,40 +484,22 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, cvt->len = 1; if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) return cvt_legacy_system_to_ump(cvt, group, data); - return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes); + return cvt_legacy_cmd_to_ump(cvt, group, protocol, data, cvt->cmd_bytes); } -/* feed a MIDI 1.0 byte @c and convert to a UMP packet; - * the target group is @group, - * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes +/** + * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet + * @cvt: converter context + * @group: target UMP group + * @protocol: target UMP protocol + * @c: MIDI 1.0 byte data + * + * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed. + * The result is stored in the buffer in @cvt. */ -void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c) +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c) { - struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; - - cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump); -} - -/* reset the converter context, called at each open */ -void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group) -{ - memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts)); -} - -/* initialize converters */ -int snd_ump_convert_init(struct snd_ump_endpoint *ump) -{ - ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); - if (!ump->out_cvts) - return -ENOMEM; - return 0; -} - -/* release resources */ -void snd_ump_convert_free(struct snd_ump_endpoint *ump) -{ - kfree(ump->out_cvts); - ump->out_cvts = NULL; + cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump); } +EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump); diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h deleted file mode 100644 index bbfe96084779..000000000000 --- a/sound/core/ump_convert.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef __UMP_CONVERT_H -#define __UMP_CONVERT_H - -#include - -/* context for converting from legacy control messages to UMP packet */ -struct ump_cvt_to_ump_bank { - bool rpn_set; - bool nrpn_set; - bool bank_set; - unsigned char cc_rpn_msb, cc_rpn_lsb; - unsigned char cc_nrpn_msb, cc_nrpn_lsb; - unsigned char cc_data_msb, cc_data_lsb; - unsigned char cc_bank_msb, cc_bank_lsb; -}; - -/* context for converting from MIDI1 byte stream to UMP packet */ -struct ump_cvt_to_ump { - /* MIDI1 intermediate buffer */ - unsigned char buf[4]; - int len; - int cmd_bytes; - - /* UMP output packet */ - u32 ump[4]; - int ump_bytes; - - /* various status */ - unsigned int in_sysex; - struct ump_cvt_to_ump_bank bank[16]; /* per channel */ -}; - -int snd_ump_convert_init(struct snd_ump_endpoint *ump); -void snd_ump_convert_free(struct snd_ump_endpoint *ump); -int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, - const u32 *data, unsigned char *dst, - unsigned char *group_ret); -void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c); -void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group); -#endif /* __UMP_CONVERT_H */ -- cgit v1.2.3 From 154756319cc6f8b8b86241da02da6a8fcc6abd1f Mon Sep 17 00:00:00 2001 From: Arun Gopal Kondaveeti Date: Sat, 24 Jun 2023 03:11:40 +0530 Subject: ASoC: amd: update pm_runtime enable sequence pm_runtime_allow() is not needed for ACP child platform devices. Replace pm_runtime_allow() with pm_runtime_mark_last_busy() & pm_runtime_set_active() in pm_runtime enable sequence for ACP child platform drivers. Signed-off-by: Arun Gopal Link: https://lore.kernel.org/r/Message-Id: <20230623214150.4058721-1-arungopal.kondaveeti@amd.com> Signed-off-by: Mark Brown --- sound/soc/amd/ps/ps-pdm-dma.c | 3 ++- sound/soc/amd/raven/acp3x-pcm-dma.c | 3 ++- sound/soc/amd/renoir/acp3x-pdm-dma.c | 3 ++- sound/soc/amd/vangogh/acp5x-pcm-dma.c | 4 ++-- sound/soc/amd/yc/acp6x-pdm-dma.c | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/soc/amd/ps/ps-pdm-dma.c b/sound/soc/amd/ps/ps-pdm-dma.c index bdbbb797c74d..d48f7c5af289 100644 --- a/sound/soc/amd/ps/ps-pdm-dma.c +++ b/sound/soc/amd/ps/ps-pdm-dma.c @@ -391,8 +391,9 @@ static int acp63_pdm_audio_probe(struct platform_device *pdev) } pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_allow(&pdev->dev); return 0; } diff --git a/sound/soc/amd/raven/acp3x-pcm-dma.c b/sound/soc/amd/raven/acp3x-pcm-dma.c index 7362dd15ad30..9538f3ffc5d9 100644 --- a/sound/soc/amd/raven/acp3x-pcm-dma.c +++ b/sound/soc/amd/raven/acp3x-pcm-dma.c @@ -416,8 +416,9 @@ static int acp3x_audio_probe(struct platform_device *pdev) pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_allow(&pdev->dev); return 0; } diff --git a/sound/soc/amd/renoir/acp3x-pdm-dma.c b/sound/soc/amd/renoir/acp3x-pdm-dma.c index 4e299f96521f..c3b47e9bd239 100644 --- a/sound/soc/amd/renoir/acp3x-pdm-dma.c +++ b/sound/soc/amd/renoir/acp3x-pdm-dma.c @@ -430,8 +430,9 @@ static int acp_pdm_audio_probe(struct platform_device *pdev) } pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_allow(&pdev->dev); return 0; } diff --git a/sound/soc/amd/vangogh/acp5x-pcm-dma.c b/sound/soc/amd/vangogh/acp5x-pcm-dma.c index 29901ee4bfe3..587dec5bb33d 100644 --- a/sound/soc/amd/vangogh/acp5x-pcm-dma.c +++ b/sound/soc/amd/vangogh/acp5x-pcm-dma.c @@ -409,9 +409,9 @@ static int acp5x_audio_probe(struct platform_device *pdev) } pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_allow(&pdev->dev); - return 0; } diff --git a/sound/soc/amd/yc/acp6x-pdm-dma.c b/sound/soc/amd/yc/acp6x-pdm-dma.c index d818eba48546..72c4591e451b 100644 --- a/sound/soc/amd/yc/acp6x-pdm-dma.c +++ b/sound/soc/amd/yc/acp6x-pdm-dma.c @@ -383,8 +383,9 @@ static int acp6x_pdm_audio_probe(struct platform_device *pdev) } pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS); pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); - pm_runtime_allow(&pdev->dev); return 0; } -- cgit v1.2.3 From ed959833db7bdb4e57fa8f4076babf3810296f5b Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 23 Jun 2023 15:09:48 +0300 Subject: ASoC: tas2781: Fix error code in tas2781_load_calibration() Return -EINVAL instead of success on this error path. Fixes: 915f5eadebd2 ("ASoC: tas2781: firmware lib") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/Message-Id: <729bb6b3-bc1d-4b3d-8b65-077a492c753c@moroto.mountain> Signed-off-by: Mark Brown --- sound/soc/codecs/tas2781-fmwlib.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/codecs/tas2781-fmwlib.c b/sound/soc/codecs/tas2781-fmwlib.c index cbf0aef2c001..eb55abae0d7b 100644 --- a/sound/soc/codecs/tas2781-fmwlib.c +++ b/sound/soc/codecs/tas2781-fmwlib.c @@ -1924,6 +1924,7 @@ int tas2781_load_calibration(void *context, char *file_name, if (!fw_entry->size) { dev_err(tas_priv->dev, "%s: file read error: size = %lu\n", __func__, (unsigned long)fw_entry->size); + ret = -EINVAL; goto out; } fmw.size = fw_entry->size; -- cgit v1.2.3 From 2d0cad0473bd1ffbc5842be0b9f2546265acb011 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 23 Jun 2023 22:04:39 +0100 Subject: ASoC: core: Always store of_node when getting DAI link component The generic snd_soc_dai_get_dlc() contains a default translation function for DAI names which has factored out common code in a number of card drivers, resolving the dai_name and of_node either using a driver provided translation function or with a generic implementation. Unfortunately the of_node can't be set by the translation function since it currently doesn't have an interface to do that but snd_soc_dai_get_dlc() only initialises the of_node in the case where there is no translation function. This breaks the Meson support after conversion to use the generic helpers since the DPCM cards for it check which component of the SoC is connected to each link by checking the compatible for the component and the Meson components provide a custom operation so don't use the generic code. Fix this and potentially other cards by unconditionally storing the node in the dai_link_component, there shouldn't be a binding specific of_node selected since that's how we determine the translation function. Fixes: 2e1dbea1f8a3 ("ASoC: meson: use snd_soc_{of_}get_dlc()") Fixes: 3c8b5861850c ("ASoC: soc-core.c: add index on snd_soc_of_get_dai_name()") Link: https://lore.kernel.org/r/Message-Id: <20230623-asoc-fix-meson-probe-v1-1-82b2c2ec5ca4@kernel.org> Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f06a20773a34..11bc5250ffd0 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3262,6 +3262,8 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ struct snd_soc_component *pos; int ret = -EPROBE_DEFER; + dlc->of_node = args->np; + mutex_lock(&client_mutex); for_each_component(pos) { struct device_node *component_of_node = soc_component_to_node(pos); @@ -3300,7 +3302,6 @@ int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_ id--; } - dlc->of_node = args->np; dlc->dai_name = dai->driver->name; if (!dlc->dai_name) dlc->dai_name = pos->name; -- cgit v1.2.3 From 04b49b90caeed0b5544ff616d654900d27d403b6 Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sat, 24 Jun 2023 18:52:16 +0200 Subject: ALSA: pcm: fix ELD constraints for (E)AC3, DTS(-HD) and MLP formats The SADs of compressed formats contain the channel and sample rate info of the audio data inside the compressed stream, but when building constraints we must use the rates and channels used to transport the compressed streams. eg 48kHz 6ch EAC3 needs to be transmitted as a 2ch 192kHz stream. This patch fixes the constraints for the common AC3 and DTS formats, the constraints for the less common MPEG, DSD etc formats are copied directly from the info in the SADs as before as I don't have the specs and equipment to test those. Signed-off-by: Matthias Reichl Link: https://lore.kernel.org/r/20230624165216.5719-1-hias@horus.com Signed-off-by: Takashi Iwai --- sound/core/pcm_drm_eld.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c index 4b5faae5d16e..07075071972d 100644 --- a/sound/core/pcm_drm_eld.c +++ b/sound/core/pcm_drm_eld.c @@ -2,11 +2,25 @@ /* * PCM DRM helpers */ +#include #include +#include #include #include #include +#define SAD0_CHANNELS_MASK GENMASK(2, 0) /* max number of channels - 1 */ +#define SAD0_FORMAT_MASK GENMASK(6, 3) /* audio format */ + +#define SAD1_RATE_MASK GENMASK(6, 0) /* bitfield of supported rates */ +#define SAD1_RATE_32000_MASK BIT(0) +#define SAD1_RATE_44100_MASK BIT(1) +#define SAD1_RATE_48000_MASK BIT(2) +#define SAD1_RATE_88200_MASK BIT(3) +#define SAD1_RATE_96000_MASK BIT(4) +#define SAD1_RATE_176400_MASK BIT(5) +#define SAD1_RATE_192000_MASK BIT(6) + static const unsigned int eld_rates[] = { 32000, 44100, @@ -17,9 +31,62 @@ static const unsigned int eld_rates[] = { 192000, }; +static unsigned int map_rate_families(const u8 *sad, + unsigned int mask_32000, + unsigned int mask_44100, + unsigned int mask_48000) +{ + unsigned int rate_mask = 0; + + if (sad[1] & SAD1_RATE_32000_MASK) + rate_mask |= mask_32000; + if (sad[1] & (SAD1_RATE_44100_MASK | SAD1_RATE_88200_MASK | SAD1_RATE_176400_MASK)) + rate_mask |= mask_44100; + if (sad[1] & (SAD1_RATE_48000_MASK | SAD1_RATE_96000_MASK | SAD1_RATE_192000_MASK)) + rate_mask |= mask_48000; + return rate_mask; +} + +static unsigned int sad_rate_mask(const u8 *sad) +{ + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return sad[1] & SAD1_RATE_MASK; + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + return map_rate_families(sad, + SAD1_RATE_32000_MASK, + SAD1_RATE_44100_MASK, + SAD1_RATE_48000_MASK); + case HDMI_AUDIO_CODING_TYPE_EAC3: + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return map_rate_families(sad, + 0, + SAD1_RATE_176400_MASK, + SAD1_RATE_192000_MASK); + default: + /* TODO adjust for other compressed formats as well */ + return sad[1] & SAD1_RATE_MASK; + } +} + static unsigned int sad_max_channels(const u8 *sad) { - return 1 + (sad[0] & 7); + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + case HDMI_AUDIO_CODING_TYPE_EAC3: + return 2; + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return 8; + default: + /* TODO adjust for other compressed formats as well */ + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + } } static int eld_limit_rates(struct snd_pcm_hw_params *params, @@ -42,7 +109,7 @@ static int eld_limit_rates(struct snd_pcm_hw_params *params, * requested number of channels. */ if (c->min <= max_channels) - rate_mask |= sad[1]; + rate_mask |= sad_rate_mask(sad); } } @@ -70,7 +137,7 @@ static int eld_limit_channels(struct snd_pcm_hw_params *params, rate_mask |= BIT(i); for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) - if (rate_mask & sad[1]) + if (rate_mask & sad_rate_mask(sad)) t.max = max(t.max, sad_max_channels(sad)); } -- cgit v1.2.3 From 4e0871333661d2ec0ed3dc00a945c2160eccae77 Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sat, 24 Jun 2023 18:52:32 +0200 Subject: ASoC: hdmi-codec: fix channel info for compressed formats According to CTA 861 the channel/speaker allocation info in the audio infoframe only applies to uncompressed (PCM) audio streams. The channel count info should indicate the number of channels in the transmitted audio, which usually won't match the number of channels used to transmit the compressed bitstream. Some devices (eg some Sony TVs) will refuse to decode compressed audio if these values are not set correctly. To fix this we can simply set the channel count to 0 (which means "refer to stream header") and set the channel/speaker allocation to 0 as well (which would mean stereo FL/FR for PCM, a safe value all sinks will support) when transmitting compressed audio. Signed-off-by: Matthias Reichl Link: https://lore.kernel.org/r/20230624165232.5751-1-hias@horus.com Signed-off-by: Takashi Iwai --- sound/soc/codecs/hdmi-codec.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 6d980fbc4207..d21f69f05342 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -495,31 +495,43 @@ static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, struct hdmi_codec_params *hp) { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); - int idx; - - /* Select a channel allocation that matches with ELD and pcm channels */ - idx = hdmi_codec_get_ch_alloc_table_idx(hcp, channels); - if (idx < 0) { - dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", - idx); - hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; - return idx; + int idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + u8 ca_id = 0; + bool pcm_audio = !(hcp->iec_status[0] & IEC958_AES0_NONAUDIO); + + if (pcm_audio) { + /* Select a channel allocation that matches with ELD and pcm channels */ + idx = hdmi_codec_get_ch_alloc_table_idx(hcp, channels); + + if (idx < 0) { + dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", + idx); + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + return idx; + } + + ca_id = hdmi_codec_channel_alloc[idx].ca_id; } memset(hp, 0, sizeof(*hp)); hdmi_audio_infoframe_init(&hp->cea); - hp->cea.channels = channels; + + if (pcm_audio) + hp->cea.channels = channels; + else + hp->cea.channels = 0; + hp->cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; hp->cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; hp->cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; - hp->cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; + hp->cea.channel_allocation = ca_id; hp->sample_width = sample_width; hp->sample_rate = sample_rate; hp->channels = channels; - hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; + hcp->chmap_idx = idx; return 0; } -- cgit v1.2.3 From e94f1f96f108ba96c0ed8bf3fbdd8ee6a6703880 Mon Sep 17 00:00:00 2001 From: Andy Chi Date: Mon, 26 Jun 2023 21:03:00 +0800 Subject: ALSA: hda/realtek: Enable mute/micmute LEDs and limit mic boost on EliteBook On HP EliteBook 835/845/845W G10, the audio LEDs can be enabled by ALC285_FIXUP_HP_MUTE_LED. So use it accordingly. Signed-off-by: Andy Chi Cc: Fixes: 3e10f6ca76c4 ("ALSA: hda/realtek: Add quirk for HP EliteBook G10 laptops") Link: https://lore.kernel.org/r/20230626130301.301712-1-andy.chi@canonical.com Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index dabfdecece26..f64dd09692b5 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -9490,9 +9490,9 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8b63, "HP Elite Dragonfly 13.5 inch G4", ALC245_FIXUP_CS35L41_SPI_4_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b65, "HP ProBook 455 15.6 inch G10 Notebook PC", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8b66, "HP", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), - SND_PCI_QUIRK(0x103c, 0x8b70, "HP EliteBook 835 G10", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8b72, "HP EliteBook 845 G10", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8b74, "HP EliteBook 845W G10", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8b70, "HP EliteBook 835 G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b72, "HP EliteBook 845 G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8b74, "HP EliteBook 845W G10", ALC287_FIXUP_CS35L41_I2C_2_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b77, "HP ElieBook 865 G10", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8b7a, "HP", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8b7d, "HP", ALC236_FIXUP_HP_GPIO_LED), -- cgit v1.2.3