[PATCH 00/14] ASoC: Intel: avs: Machine boards and HDA codec support
This series focuses on populating boards/ subdirectory with supported configurations by the avs-driver. Note: it is independent of recently provided "Driver code and PCM operations" series [1], that is, code found here should not collide with it.
Series starts with a small change that adds a helper to sound pcm header, allowing for retrieving string naming a direction without the need of substream pointer. Said helper is used by codec driver code that follows it but I believe it's generic and helpful enough that it can be called an independent addition to the sound core.
Code for generic HD-Audio codec driver follows. It is a ASoC wrapper for existing HD-Audio codec code found in sound/pci/hda/. There is basically no custom logic involved up to the point that driver follows HDA_DEV_LEGACY convention, rather than the HDA_DEV_ASOC one. Commit message for the given patch iterates on this and explains crucial parts of the implementation.
From there on is a range of boards appended. All of them follow the same
scheme:
- define avs_create_dai_link() so DAI-LINKs can be created dynamically, based on the link_mask (I2S) or the number of entries in the ->pcm_list_head list (HDA) - define avs_create_dapm_routes() so DAPM routes can be created dynamically, same rules as above apply - define probe() function that creates new ASoC card, assign all required operations and resources along with calling the two above
[1]: https://lore.kernel.org/all/20220426172346.3508411-1-cezary.rojewski@intel.c...
Amadeusz Sławiński (1): ASoC: Intel: avs: Add max98373 machine board
Cezary Rojewski (13): ALSA: Add snd_pcm_direction_name() helper ASoC: codecs: Add HD-Audio codec driver ASoC: Intel: avs: Add HDAudio machine board ASoC: Intel: avs: Add DMIC machine board ASoC: Intel: avs: Add ssp-test machine board ASoC: Intel: avs: Add rt274 machine board ASoC: Intel: avs: Add rt286 machine board ASoC: Intel: avs: Add rt298 machine board ASoC: Intel: avs: Add rt5682 machine board ASoC: Intel: avs: Add nau8825 machine board ASoC: Intel: avs: Add ssm4567 machine board ASoC: Intel: avs: Add max98357a machine board ASoC: Intel: avs: Add da7219 machine board
include/sound/pcm.h | 19 +- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hda-dai.c | 102 +++++++ sound/soc/codecs/hda.c | 395 +++++++++++++++++++++++++ sound/soc/codecs/hda.h | 19 ++ sound/soc/intel/Kconfig | 3 + sound/soc/intel/avs/Makefile | 3 + sound/soc/intel/avs/boards/Kconfig | 121 ++++++++ sound/soc/intel/avs/boards/Makefile | 27 ++ sound/soc/intel/avs/boards/da7219.c | 281 ++++++++++++++++++ sound/soc/intel/avs/boards/dmic.c | 93 ++++++ sound/soc/intel/avs/boards/hdaudio.c | 294 ++++++++++++++++++ sound/soc/intel/avs/boards/max98357a.c | 153 ++++++++++ sound/soc/intel/avs/boards/max98373.c | 238 +++++++++++++++ sound/soc/intel/avs/boards/nau8825.c | 352 ++++++++++++++++++++++ sound/soc/intel/avs/boards/rt274.c | 309 +++++++++++++++++++ sound/soc/intel/avs/boards/rt286.c | 280 ++++++++++++++++++ sound/soc/intel/avs/boards/rt298.c | 280 ++++++++++++++++++ sound/soc/intel/avs/boards/rt5682.c | 339 +++++++++++++++++++++ sound/soc/intel/avs/boards/ssm4567.c | 270 +++++++++++++++++ sound/soc/intel/avs/boards/ssp_test.c | 178 +++++++++++ 22 files changed, 3759 insertions(+), 4 deletions(-) create mode 100644 sound/soc/codecs/hda-dai.c create mode 100644 sound/soc/codecs/hda.c create mode 100644 sound/soc/codecs/hda.h create mode 100644 sound/soc/intel/avs/boards/Kconfig create mode 100644 sound/soc/intel/avs/boards/Makefile create mode 100644 sound/soc/intel/avs/boards/da7219.c create mode 100644 sound/soc/intel/avs/boards/dmic.c create mode 100644 sound/soc/intel/avs/boards/hdaudio.c create mode 100644 sound/soc/intel/avs/boards/max98357a.c create mode 100644 sound/soc/intel/avs/boards/max98373.c create mode 100644 sound/soc/intel/avs/boards/nau8825.c create mode 100644 sound/soc/intel/avs/boards/rt274.c create mode 100644 sound/soc/intel/avs/boards/rt286.c create mode 100644 sound/soc/intel/avs/boards/rt298.c create mode 100644 sound/soc/intel/avs/boards/rt5682.c create mode 100644 sound/soc/intel/avs/boards/ssm4567.c create mode 100644 sound/soc/intel/avs/boards/ssp_test.c
base-commit: fa96ec9fa9f8f0c80e8a0129a819f592a39b1044
Allow for retrieving string naming a direction of a stream without the need of substream pointer.
Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- include/sound/pcm.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 6b99310b5b88..26523cfe428d 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -1392,6 +1392,20 @@ static inline void snd_pcm_limit_isa_dma_size(int dma, size_t *max)
const char *snd_pcm_format_name(snd_pcm_format_t format);
+/** + * snd_pcm_direction_name - Get a string naming the direction of a stream + * @direction: Stream's direction, one of SNDRV_PCM_STREAM_XXX + * + * Returns a string naming the direction of the stream. + */ +static inline const char *snd_pcm_direction_name(int direction) +{ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + return "Playback"; + else + return "Capture"; +} + /** * snd_pcm_stream_str - Get a string naming the direction of a stream * @substream: the pcm substream instance @@ -1400,10 +1414,7 @@ const char *snd_pcm_format_name(snd_pcm_format_t format); */ static inline const char *snd_pcm_stream_str(struct snd_pcm_substream *substream) { - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - return "Playback"; - else - return "Capture"; + return snd_pcm_direction_name(substream->stream); }
/*
Add generic ASoC equivalent of ALSA HD-Audio codec. This codec is designed to follow HDA_DEV_LEGACY convention. Driver wrapps existing hda_codec.c handlers to prevent code duplication within the newly added code. Number of DAIs created is dependent on capabilities exposed by the codec itself. Because of this, single solution can be applied to support every single HD-Audio codec type.
At the same, through the ASoC topology, platform drivers may limit the number of endpoints available to the userspace as codec driver exposes BE DAIs only.
Both hda_codec_probe() and hda_codec_remove() declare their expectations on device's usage_count and suspended-status. This is to catch any unexpected behavior as PM-related code for HD-Audio has been changing quite a bit throughout the years.
In order for codec DAI list to reflect its actual PCM capabilities, PCMs need to be built and that can only happen once codec device is constructed. To do that, a valid component->card->snd_card pointer is needed. Said pointer will be provided by the framework once all card components are accounted for and their probing can begin. Usage of "binder" BE DAI solves the problem - codec can be listed as one of HD-Audio card components without declaring any actual BE DAIs statically.
Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hda-dai.c | 102 ++++++++++ sound/soc/codecs/hda.c | 395 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hda.h | 19 ++ 5 files changed, 523 insertions(+) create mode 100644 sound/soc/codecs/hda-dai.c create mode 100644 sound/soc/codecs/hda.c create mode 100644 sound/soc/codecs/hda.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b106e5517090..23fdbf97e453 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -937,6 +937,11 @@ config SND_SOC_HDAC_HDA tristate select SND_HDA
+config SND_SOC_HDA + tristate "HD-Audio codec driver" + select SND_HDA_EXT_CORE + select SND_HDA + config SND_SOC_ICS43432 tristate "ICS43423 and compatible i2s microphones"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 28dc4edfd01f..d32026ae326f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -106,6 +106,7 @@ snd-soc-es8328-spi-objs := es8328-spi.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-hdac-hda-objs := hdac_hda.o +snd-soc-hda-codec-objs := hda.o hda-dai.o snd-soc-ics43432-objs := ics43432.o snd-soc-inno-rk3036-objs := inno_rk3036.o snd-soc-isabelle-objs := isabelle.o @@ -458,6 +459,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o +obj-$(CONFIG_SND_SOC_HDA) += snd-soc-hda-codec.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o diff --git a/sound/soc/codecs/hda-dai.c b/sound/soc/codecs/hda-dai.c new file mode 100644 index 000000000000..5371ff086261 --- /dev/null +++ b/sound/soc/codecs/hda-dai.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +// + +#include <sound/soc.h> +#include <sound/hda_codec.h> +#include "hda.h" + +static int hda_codec_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct hda_pcm_stream *stream_info; + struct hda_codec *codec; + struct hda_pcm *pcm; + int ret; + + codec = dev_to_hda_codec(dai->dev); + stream_info = snd_soc_dai_get_dma_data(dai, substream); + pcm = container_of(stream_info, struct hda_pcm, stream[substream->stream]); + + dev_dbg(dai->dev, "open stream codec: %08x, info: %p, pcm: %p %s substream: %p\n", + codec->core.vendor_id, stream_info, pcm, pcm->name, substream); + + snd_hda_codec_pcm_get(pcm); + + ret = stream_info->ops.open(stream_info, codec, substream); + if (ret < 0) { + dev_err(dai->dev, "codec open failed: %d\n", ret); + snd_hda_codec_pcm_put(pcm); + return ret; + } + + return 0; +} + +static void hda_codec_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct hda_pcm_stream *stream_info; + struct hda_codec *codec; + struct hda_pcm *pcm; + int ret; + + codec = dev_to_hda_codec(dai->dev); + stream_info = snd_soc_dai_get_dma_data(dai, substream); + pcm = container_of(stream_info, struct hda_pcm, stream[substream->stream]); + + dev_dbg(dai->dev, "close stream codec: %08x, info: %p, pcm: %p %s substream: %p\n", + codec->core.vendor_id, stream_info, pcm, pcm->name, substream); + + ret = stream_info->ops.close(stream_info, codec, substream); + if (ret < 0) + dev_err(dai->dev, "codec close failed: %d\n", ret); + + snd_hda_codec_pcm_put(pcm); +} + +static int hda_codec_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct hda_pcm_stream *stream_info; + struct hda_codec *codec; + + codec = dev_to_hda_codec(dai->dev); + stream_info = snd_soc_dai_get_dma_data(dai, substream); + + snd_hda_codec_cleanup(codec, stream_info, substream); + + return 0; +} + +static int hda_codec_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct hda_pcm_stream *stream_info; + struct hdac_stream *stream; + struct hda_codec *codec; + unsigned int format; + int ret; + + codec = dev_to_hda_codec(dai->dev); + stream = substream->runtime->private_data; + stream_info = snd_soc_dai_get_dma_data(dai, substream); + format = snd_hdac_calc_stream_format(runtime->rate, runtime->channels, runtime->format, + runtime->sample_bits, 0); + + ret = snd_hda_codec_prepare(codec, stream_info, stream->stream_tag, format, substream); + if (ret < 0) { + dev_err(dai->dev, "codec prepare failed: %d\n", ret); + return ret; + } + + return 0; +} + +const struct snd_soc_dai_ops snd_soc_hda_codec_dai_ops = { + .startup = hda_codec_dai_startup, + .shutdown = hda_codec_dai_shutdown, + .hw_free = hda_codec_dai_hw_free, + .prepare = hda_codec_dai_prepare, +}; +EXPORT_SYMBOL_GPL(snd_soc_hda_codec_dai_ops); diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c new file mode 100644 index 000000000000..edcb8bc6806b --- /dev/null +++ b/sound/soc/codecs/hda.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +// + +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_i915.h> +#include <sound/hda_codec.h> +#include "hda.h" + +static int hda_codec_create_dais(struct hda_codec *codec, int pcm_count, + struct snd_soc_dai_driver **drivers) +{ + struct device *dev = &codec->core.dev; + struct snd_soc_dai_driver *drvs; + struct hda_pcm *pcm; + int i; + + drvs = devm_kcalloc(dev, pcm_count, sizeof(*drvs), GFP_KERNEL); + if (!drvs) + 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 snd_soc_pcm_stream *stream; + int dir; + + dev_info(dev, "creating for %s %d\n", pcm->name, i); + drvs[i].id = i; + drvs[i].name = pcm->name; + drvs[i].ops = &snd_soc_hda_codec_dai_ops; + + dir = SNDRV_PCM_STREAM_PLAYBACK; + stream = &drvs[i].playback; + if (!pcm->stream[dir].substreams) { + dev_info(dev, "skipping playback dai for %s\n", pcm->name); + goto capture_dais; + } + + stream->stream_name = + devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, + snd_pcm_direction_name(dir)); + if (!stream->stream_name) + return -ENOMEM; + stream->channels_min = pcm->stream[dir].channels_min; + stream->channels_max = pcm->stream[dir].channels_max; + stream->rates = pcm->stream[dir].rates; + stream->formats = pcm->stream[dir].formats; + stream->sig_bits = pcm->stream[dir].maxbps; + +capture_dais: + dir = SNDRV_PCM_STREAM_CAPTURE; + stream = &drvs[i].capture; + if (!pcm->stream[dir].substreams) { + dev_info(dev, "skipping capture dai for %s\n", pcm->name); + continue; + } + + stream->stream_name = + devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name, + snd_pcm_direction_name(dir)); + if (!stream->stream_name) + return -ENOMEM; + stream->channels_min = pcm->stream[dir].channels_min; + stream->channels_max = pcm->stream[dir].channels_max; + stream->rates = pcm->stream[dir].rates; + stream->formats = pcm->stream[dir].formats; + stream->sig_bits = pcm->stream[dir].maxbps; + } + + *drivers = drvs; + return 0; +} + +static int hda_codec_register_dais(struct hda_codec *codec, struct snd_soc_component *component) +{ + struct snd_soc_dai_driver *drvs = NULL; + struct snd_soc_dapm_context *dapm; + struct hda_pcm *pcm; + int ret, pcm_count = 0; + + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + ret = hda_codec_create_dais(codec, pcm_count, &drvs); + if (ret < 0) + return ret; + + dapm = snd_soc_component_get_dapm(component); + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + struct snd_soc_dai *dai; + + dai = snd_soc_register_dai(component, drvs, false); + if (!dai) { + dev_err(component->dev, "register dai for %s failed\n", pcm->name); + return -EINVAL; + } + + ret = snd_soc_dapm_new_dai_widgets(dapm, dai); + if (ret < 0) { + dev_err(component->dev, "create widgets failed: %d\n", ret); + snd_soc_unregister_dai(dai); + return ret; + } + + snd_soc_dai_init_dma_data(dai, &pcm->stream[0], &pcm->stream[1]); + drvs++; + } + + return 0; +} + +static void hda_codec_unregister_dais(struct hda_codec *codec, + struct snd_soc_component *component) +{ + struct snd_soc_dai *dai, *save; + struct hda_pcm *pcm; + + for_each_component_dais_safe(component, dai, save) { + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + if (strcmp(dai->driver->name, pcm->name)) + continue; + + if (dai->playback_widget) + snd_soc_dapm_free_widget(dai->playback_widget); + if (dai->capture_widget) + snd_soc_dapm_free_widget(dai->capture_widget); + snd_soc_unregister_dai(dai); + break; + } + } +} + +int hda_codec_probe_complete(struct hda_codec *codec) +{ + struct hdac_device *hdev = &codec->core; + struct hdac_bus *bus = hdev->bus; + int ret; + + ret = snd_hda_codec_build_controls(codec); + if (ret < 0) { + dev_err(&hdev->dev, "unable to create controls %d\n", ret); + goto out; + } + + /* Bus suspended codecs as it does not manage their pm */ + pm_runtime_set_active(&hdev->dev); + /* rpm was forbidden in snd_hda_codec_device_new() */ + snd_hda_codec_set_power_save(codec, 2000); + snd_hda_codec_register(codec); +out: + /* Complement pm_runtime_get_sync(bus) in probe */ + pm_runtime_mark_last_busy(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(hda_codec_probe_complete); + +/* Expects codec with usage_count=1 and status=suspended */ +static int hda_codec_probe(struct snd_soc_component *component) +{ + struct hda_codec *codec = dev_to_hda_codec(component->dev); + struct hdac_device *hdev = &codec->core; + struct hdac_bus *bus = hdev->bus; + struct hdac_ext_link *hlink; + hda_codec_patch_t patch; + int ret; + +#ifdef CONFIG_PM + WARN_ON(atomic_read(&hdev->dev.power.usage_count) != 1 || + !pm_runtime_status_suspended(&hdev->dev)); +#endif + + hlink = snd_hdac_ext_bus_link_at(bus, hdev->addr); + if (!hlink) { + dev_err(&hdev->dev, "hdac link not found\n"); + return -EIO; + } + + pm_runtime_get_sync(bus->dev); + if (hda_codec_is_display(codec)) + snd_hdac_display_power(bus, hdev->addr, true); + snd_hdac_ext_bus_link_get(bus, hlink); + + ret = snd_hda_codec_device_new(codec->bus, component->card->snd_card, hdev->addr, codec, + false); + if (ret < 0) { + dev_err(&hdev->dev, "create hda codec failed: %d\n", ret); + goto device_new_err; + } + + ret = snd_hda_codec_set_name(codec, codec->preset->name); + if (ret < 0) { + dev_err(&hdev->dev, "name failed %s\n", codec->preset->name); + goto err; + } + + ret = snd_hdac_regmap_init(&codec->core); + if (ret < 0) { + dev_err(&hdev->dev, "regmap init failed\n"); + goto err; + } + + patch = (hda_codec_patch_t)codec->preset->driver_data; + if (!patch) { + dev_err(&hdev->dev, "no patch specified?\n"); + ret = -EINVAL; + goto err; + } + + ret = patch(codec); + if (ret < 0) { + dev_err(&hdev->dev, "patch failed %d\n", ret); + goto err; + } + + /* configure codec for 1:1 PCM:DAI mapping */ + codec->mst_no_extra_pcms = 1; + + ret = snd_hda_codec_parse_pcms(codec); + if (ret < 0) { + dev_err(&hdev->dev, "unable to map pcms to dai %d\n", ret); + goto parse_pcms_err; + } + + ret = hda_codec_register_dais(codec, component); + if (ret < 0) { + dev_err(&hdev->dev, "update dais failed: %d\n", ret); + goto parse_pcms_err; + } + + if (!hda_codec_is_display(codec)) { + ret = hda_codec_probe_complete(codec); + if (ret < 0) + goto complete_err; + } + + codec->core.lazy_cache = true; + + return 0; + +complete_err: + hda_codec_unregister_dais(codec, component); +parse_pcms_err: + if (codec->patch_ops.free) + codec->patch_ops.free(codec); +err: + snd_hda_codec_cleanup_for_unbind(codec); +device_new_err: + if (hda_codec_is_display(codec)) + snd_hdac_display_power(bus, hdev->addr, false); + + snd_hdac_ext_bus_link_put(bus, hlink); + + pm_runtime_mark_last_busy(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + return ret; +} + +/* Leaves codec with usage_count=1 and status=suspended */ +static void hda_codec_remove(struct snd_soc_component *component) +{ + struct hda_codec *codec = dev_to_hda_codec(component->dev); + struct hdac_device *hdev = &codec->core; + struct hdac_bus *bus = hdev->bus; + struct hdac_ext_link *hlink; + bool was_registered = codec->registered; + + /* Don't allow any more runtime suspends */ + pm_runtime_forbid(&hdev->dev); + + hda_codec_unregister_dais(codec, component); + + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + + snd_hda_codec_cleanup_for_unbind(codec); + pm_runtime_put_noidle(&hdev->dev); + /* snd_hdac_device_exit() is only called on bus remove */ + pm_runtime_set_suspended(&hdev->dev); + + if (hda_codec_is_display(codec)) + snd_hdac_display_power(bus, hdev->addr, false); + + hlink = snd_hdac_ext_bus_link_at(bus, hdev->addr); + if (hlink) + snd_hdac_ext_bus_link_put(bus, hlink); + /* + * HDMI card's hda_codec_probe_complete() (see late_probe()) may + * not be called due to early error, leaving bus uc unbalanced + */ + if (!was_registered) { + pm_runtime_mark_last_busy(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + } + +#ifdef CONFIG_PM + WARN_ON(atomic_read(&hdev->dev.power.usage_count) != 1 || + !pm_runtime_status_suspended(&hdev->dev)); +#endif +} + +static const struct snd_soc_dapm_route hda_dapm_routes[] = { + {"AIF1TX", NULL, "Codec Input Pin1"}, + {"AIF2TX", NULL, "Codec Input Pin2"}, + {"AIF3TX", NULL, "Codec Input Pin3"}, + + {"Codec Output Pin1", NULL, "AIF1RX"}, + {"Codec Output Pin2", NULL, "AIF2RX"}, + {"Codec Output Pin3", NULL, "AIF3RX"}, +}; + +static const struct snd_soc_dapm_widget hda_dapm_widgets[] = { + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "Analog Codec Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "Digital Codec Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF3RX", "Alt Analog Codec Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "Analog Codec Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "Digital Codec Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF3TX", "Alt Analog Codec Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Input Pins */ + SND_SOC_DAPM_INPUT("Codec Input Pin1"), + SND_SOC_DAPM_INPUT("Codec Input Pin2"), + SND_SOC_DAPM_INPUT("Codec Input Pin3"), + + /* Output Pins */ + SND_SOC_DAPM_OUTPUT("Codec Output Pin1"), + SND_SOC_DAPM_OUTPUT("Codec Output Pin2"), + SND_SOC_DAPM_OUTPUT("Codec Output Pin3"), +}; + +static struct snd_soc_dai_driver card_binder_dai = { + .id = -1, + .name = "codec-probing-DAI", +}; + +static int hda_hdev_attach(struct hdac_device *hdev) +{ + struct hda_codec *codec = dev_to_hda_codec(&hdev->dev); + struct snd_soc_component_driver *comp_drv; + + comp_drv = devm_kzalloc(&hdev->dev, sizeof(*comp_drv), GFP_KERNEL); + if (!comp_drv) + return -ENOMEM; + + /* + * It's save to rely on dev_name() rather than a copy as component + * driver's lifetime is directly tied to hda codec one + */ + comp_drv->name = dev_name(&hdev->dev); + comp_drv->probe = hda_codec_probe; + comp_drv->remove = hda_codec_remove; + comp_drv->idle_bias_on = false; + if (!hda_codec_is_display(codec)) { + comp_drv->dapm_widgets = hda_dapm_widgets; + comp_drv->num_dapm_widgets = ARRAY_SIZE(hda_dapm_widgets); + comp_drv->dapm_routes = hda_dapm_routes; + comp_drv->num_dapm_routes = ARRAY_SIZE(hda_dapm_routes); + } + + return snd_soc_register_component(&hdev->dev, comp_drv, &card_binder_dai, 1); +} + +static int hda_hdev_detach(struct hdac_device *hdev) +{ + struct hda_codec *codec = dev_to_hda_codec(&hdev->dev); + + if (codec->registered) + cancel_delayed_work_sync(&codec->jackpoll_work); + + snd_soc_unregister_component(&hdev->dev); + + return 0; +} + +const struct hdac_ext_bus_ops soc_hda_ext_bus_ops = { + .hdev_attach = hda_hdev_attach, + .hdev_detach = hda_hdev_detach, +}; +EXPORT_SYMBOL_GPL(soc_hda_ext_bus_ops); + +MODULE_DESCRIPTION("HD-Audio codec driver"); +MODULE_AUTHOR("Cezary Rojewski cezary.rojewski@intel.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/hda.h b/sound/soc/codecs/hda.h new file mode 100644 index 000000000000..78a2be4945b1 --- /dev/null +++ b/sound/soc/codecs/hda.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright(c) 2021-2022 Intel Corporation. All rights reserved. + * + * Author: Cezary Rojewski cezary.rojewski@intel.com + */ + +#ifndef SND_SOC_CODECS_HDA_H +#define SND_SOC_CODECS_HDA_H + +#define hda_codec_is_display(codec) \ + ((((codec)->core.vendor_id >> 16) & 0xFFFF) == 0x8086) + +extern const struct snd_soc_dai_ops snd_soc_hda_codec_dai_ops; + +extern const struct hdac_ext_bus_ops soc_hda_ext_bus_ops; +int hda_codec_probe_complete(struct hda_codec *codec); + +#endif
On 4/27/22 03:18, Cezary Rojewski wrote:
Add generic ASoC equivalent of ALSA HD-Audio codec. This codec is designed to follow HDA_DEV_LEGACY convention. Driver wrapps existing hda_codec.c handlers to prevent code duplication within the newly added code. Number of DAIs created is dependent on capabilities exposed by the codec itself. Because of this, single solution can be applied to support every single HD-Audio codec type.
At the same, through the ASoC topology, platform drivers may limit the number of endpoints available to the userspace as codec driver exposes BE DAIs only.
Both hda_codec_probe() and hda_codec_remove() declare their expectations on device's usage_count and suspended-status. This is to catch any unexpected behavior as PM-related code for HD-Audio has been changing quite a bit throughout the years.
In order for codec DAI list to reflect its actual PCM capabilities, PCMs need to be built and that can only happen once codec device is constructed. To do that, a valid component->card->snd_card pointer is needed. Said pointer will be provided by the framework once all card components are accounted for and their probing can begin. Usage of "binder" BE DAI solves the problem - codec can be listed as one of HD-Audio card components without declaring any actual BE DAIs statically.
I am surprised the explanations don't even mention the existence of hdac_hda.c
/*
* hdac_hda.c - ASoC extensions to reuse the legacy HDA codec drivers
* with ASoC platform drivers. These APIs are called by the legacy HDA
* codec drivers using hdac_ext_bus_ops ops.
*/
I thought the series was about adding machine drivers, but this also adds code on the sound/soc/codecs/ side which I didn't see coming.
I am not qualified to review this part of the code, I just wonder about duplication of functionality.
At the very least an explanation on why you decided to NOT use hdac_hda.c would be useful to reviewers and maintainers.
Thanks.
Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com
sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/hda-dai.c | 102 ++++++++++ sound/soc/codecs/hda.c | 395 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/hda.h | 19 ++ 5 files changed, 523 insertions(+) create mode 100644 sound/soc/codecs/hda-dai.c create mode 100644 sound/soc/codecs/hda.c create mode 100644 sound/soc/codecs/hda.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b106e5517090..23fdbf97e453 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -937,6 +937,11 @@ config SND_SOC_HDAC_HDA tristate select SND_HDA
+config SND_SOC_HDA
- tristate "HD-Audio codec driver"
- select SND_HDA_EXT_CORE
- select SND_HDA
config SND_SOC_ICS43432 tristate "ICS43423 and compatible i2s microphones"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 28dc4edfd01f..d32026ae326f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -106,6 +106,7 @@ snd-soc-es8328-spi-objs := es8328-spi.o snd-soc-gtm601-objs := gtm601.o snd-soc-hdac-hdmi-objs := hdac_hdmi.o snd-soc-hdac-hda-objs := hdac_hda.o +snd-soc-hda-codec-objs := hda.o hda-dai.o snd-soc-ics43432-objs := ics43432.o snd-soc-inno-rk3036-objs := inno_rk3036.o snd-soc-isabelle-objs := isabelle.o @@ -458,6 +459,7 @@ obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o +obj-$(CONFIG_SND_SOC_HDA) += snd-soc-hda-codec.o obj-$(CONFIG_SND_SOC_ICS43432) += snd-soc-ics43432.o obj-$(CONFIG_SND_SOC_INNO_RK3036) += snd-soc-inno-rk3036.o obj-$(CONFIG_SND_SOC_ISABELLE) += snd-soc-isabelle.o diff --git a/sound/soc/codecs/hda-dai.c b/sound/soc/codecs/hda-dai.c new file mode 100644 index 000000000000..5371ff086261 --- /dev/null +++ b/sound/soc/codecs/hda-dai.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +//
+#include <sound/soc.h> +#include <sound/hda_codec.h> +#include "hda.h"
+static int hda_codec_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct hda_pcm_stream *stream_info;
- struct hda_codec *codec;
- struct hda_pcm *pcm;
- int ret;
- codec = dev_to_hda_codec(dai->dev);
- stream_info = snd_soc_dai_get_dma_data(dai, substream);
- pcm = container_of(stream_info, struct hda_pcm, stream[substream->stream]);
- dev_dbg(dai->dev, "open stream codec: %08x, info: %p, pcm: %p %s substream: %p\n",
codec->core.vendor_id, stream_info, pcm, pcm->name, substream);
- snd_hda_codec_pcm_get(pcm);
- ret = stream_info->ops.open(stream_info, codec, substream);
- if (ret < 0) {
dev_err(dai->dev, "codec open failed: %d\n", ret);
snd_hda_codec_pcm_put(pcm);
return ret;
- }
- return 0;
+}
+static void hda_codec_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct hda_pcm_stream *stream_info;
- struct hda_codec *codec;
- struct hda_pcm *pcm;
- int ret;
- codec = dev_to_hda_codec(dai->dev);
- stream_info = snd_soc_dai_get_dma_data(dai, substream);
- pcm = container_of(stream_info, struct hda_pcm, stream[substream->stream]);
- dev_dbg(dai->dev, "close stream codec: %08x, info: %p, pcm: %p %s substream: %p\n",
codec->core.vendor_id, stream_info, pcm, pcm->name, substream);
- ret = stream_info->ops.close(stream_info, codec, substream);
- if (ret < 0)
dev_err(dai->dev, "codec close failed: %d\n", ret);
- snd_hda_codec_pcm_put(pcm);
+}
+static int hda_codec_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct hda_pcm_stream *stream_info;
- struct hda_codec *codec;
- codec = dev_to_hda_codec(dai->dev);
- stream_info = snd_soc_dai_get_dma_data(dai, substream);
- snd_hda_codec_cleanup(codec, stream_info, substream);
- return 0;
+}
+static int hda_codec_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct snd_pcm_runtime *runtime = substream->runtime;
- struct hda_pcm_stream *stream_info;
- struct hdac_stream *stream;
- struct hda_codec *codec;
- unsigned int format;
- int ret;
- codec = dev_to_hda_codec(dai->dev);
- stream = substream->runtime->private_data;
- stream_info = snd_soc_dai_get_dma_data(dai, substream);
- format = snd_hdac_calc_stream_format(runtime->rate, runtime->channels, runtime->format,
runtime->sample_bits, 0);
- ret = snd_hda_codec_prepare(codec, stream_info, stream->stream_tag, format, substream);
- if (ret < 0) {
dev_err(dai->dev, "codec prepare failed: %d\n", ret);
return ret;
- }
- return 0;
+}
+const struct snd_soc_dai_ops snd_soc_hda_codec_dai_ops = {
- .startup = hda_codec_dai_startup,
- .shutdown = hda_codec_dai_shutdown,
- .hw_free = hda_codec_dai_hw_free,
- .prepare = hda_codec_dai_prepare,
+}; +EXPORT_SYMBOL_GPL(snd_soc_hda_codec_dai_ops); diff --git a/sound/soc/codecs/hda.c b/sound/soc/codecs/hda.c new file mode 100644 index 000000000000..edcb8bc6806b --- /dev/null +++ b/sound/soc/codecs/hda.c @@ -0,0 +1,395 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +//
+#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <sound/hdaudio_ext.h> +#include <sound/hda_i915.h> +#include <sound/hda_codec.h> +#include "hda.h"
+static int hda_codec_create_dais(struct hda_codec *codec, int pcm_count,
struct snd_soc_dai_driver **drivers)
+{
- struct device *dev = &codec->core.dev;
- struct snd_soc_dai_driver *drvs;
- struct hda_pcm *pcm;
- int i;
- drvs = devm_kcalloc(dev, pcm_count, sizeof(*drvs), GFP_KERNEL);
- if (!drvs)
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 snd_soc_pcm_stream *stream;
int dir;
dev_info(dev, "creating for %s %d\n", pcm->name, i);
drvs[i].id = i;
drvs[i].name = pcm->name;
drvs[i].ops = &snd_soc_hda_codec_dai_ops;
dir = SNDRV_PCM_STREAM_PLAYBACK;
stream = &drvs[i].playback;
if (!pcm->stream[dir].substreams) {
dev_info(dev, "skipping playback dai for %s\n", pcm->name);
goto capture_dais;
}
stream->stream_name =
devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name,
snd_pcm_direction_name(dir));
if (!stream->stream_name)
return -ENOMEM;
stream->channels_min = pcm->stream[dir].channels_min;
stream->channels_max = pcm->stream[dir].channels_max;
stream->rates = pcm->stream[dir].rates;
stream->formats = pcm->stream[dir].formats;
stream->sig_bits = pcm->stream[dir].maxbps;
+capture_dais:
dir = SNDRV_PCM_STREAM_CAPTURE;
stream = &drvs[i].capture;
if (!pcm->stream[dir].substreams) {
dev_info(dev, "skipping capture dai for %s\n", pcm->name);
continue;
}
stream->stream_name =
devm_kasprintf(dev, GFP_KERNEL, "%s %s", pcm->name,
snd_pcm_direction_name(dir));
if (!stream->stream_name)
return -ENOMEM;
stream->channels_min = pcm->stream[dir].channels_min;
stream->channels_max = pcm->stream[dir].channels_max;
stream->rates = pcm->stream[dir].rates;
stream->formats = pcm->stream[dir].formats;
stream->sig_bits = pcm->stream[dir].maxbps;
- }
- *drivers = drvs;
- return 0;
+}
+static int hda_codec_register_dais(struct hda_codec *codec, struct snd_soc_component *component) +{
- struct snd_soc_dai_driver *drvs = NULL;
- struct snd_soc_dapm_context *dapm;
- struct hda_pcm *pcm;
- int ret, pcm_count = 0;
- if (list_empty(&codec->pcm_list_head))
return -EINVAL;
- list_for_each_entry(pcm, &codec->pcm_list_head, list)
pcm_count++;
- ret = hda_codec_create_dais(codec, pcm_count, &drvs);
- if (ret < 0)
return ret;
- dapm = snd_soc_component_get_dapm(component);
- list_for_each_entry(pcm, &codec->pcm_list_head, list) {
struct snd_soc_dai *dai;
dai = snd_soc_register_dai(component, drvs, false);
if (!dai) {
dev_err(component->dev, "register dai for %s failed\n", pcm->name);
return -EINVAL;
}
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
if (ret < 0) {
dev_err(component->dev, "create widgets failed: %d\n", ret);
snd_soc_unregister_dai(dai);
return ret;
}
snd_soc_dai_init_dma_data(dai, &pcm->stream[0], &pcm->stream[1]);
drvs++;
- }
- return 0;
+}
+static void hda_codec_unregister_dais(struct hda_codec *codec,
struct snd_soc_component *component)
+{
- struct snd_soc_dai *dai, *save;
- struct hda_pcm *pcm;
- for_each_component_dais_safe(component, dai, save) {
list_for_each_entry(pcm, &codec->pcm_list_head, list) {
if (strcmp(dai->driver->name, pcm->name))
continue;
if (dai->playback_widget)
snd_soc_dapm_free_widget(dai->playback_widget);
if (dai->capture_widget)
snd_soc_dapm_free_widget(dai->capture_widget);
snd_soc_unregister_dai(dai);
break;
}
- }
+}
+int hda_codec_probe_complete(struct hda_codec *codec) +{
- struct hdac_device *hdev = &codec->core;
- struct hdac_bus *bus = hdev->bus;
- int ret;
- ret = snd_hda_codec_build_controls(codec);
- if (ret < 0) {
dev_err(&hdev->dev, "unable to create controls %d\n", ret);
goto out;
- }
- /* Bus suspended codecs as it does not manage their pm */
- pm_runtime_set_active(&hdev->dev);
- /* rpm was forbidden in snd_hda_codec_device_new() */
- snd_hda_codec_set_power_save(codec, 2000);
- snd_hda_codec_register(codec);
+out:
- /* Complement pm_runtime_get_sync(bus) in probe */
- pm_runtime_mark_last_busy(bus->dev);
- pm_runtime_put_autosuspend(bus->dev);
- return ret;
+} +EXPORT_SYMBOL_GPL(hda_codec_probe_complete);
+/* Expects codec with usage_count=1 and status=suspended */ +static int hda_codec_probe(struct snd_soc_component *component) +{
- struct hda_codec *codec = dev_to_hda_codec(component->dev);
- struct hdac_device *hdev = &codec->core;
- struct hdac_bus *bus = hdev->bus;
- struct hdac_ext_link *hlink;
- hda_codec_patch_t patch;
- int ret;
+#ifdef CONFIG_PM
- WARN_ON(atomic_read(&hdev->dev.power.usage_count) != 1 ||
!pm_runtime_status_suspended(&hdev->dev));
+#endif
- hlink = snd_hdac_ext_bus_link_at(bus, hdev->addr);
- if (!hlink) {
dev_err(&hdev->dev, "hdac link not found\n");
return -EIO;
- }
- pm_runtime_get_sync(bus->dev);
- if (hda_codec_is_display(codec))
snd_hdac_display_power(bus, hdev->addr, true);
- snd_hdac_ext_bus_link_get(bus, hlink);
- ret = snd_hda_codec_device_new(codec->bus, component->card->snd_card, hdev->addr, codec,
false);
- if (ret < 0) {
dev_err(&hdev->dev, "create hda codec failed: %d\n", ret);
goto device_new_err;
- }
- ret = snd_hda_codec_set_name(codec, codec->preset->name);
- if (ret < 0) {
dev_err(&hdev->dev, "name failed %s\n", codec->preset->name);
goto err;
- }
- ret = snd_hdac_regmap_init(&codec->core);
- if (ret < 0) {
dev_err(&hdev->dev, "regmap init failed\n");
goto err;
- }
- patch = (hda_codec_patch_t)codec->preset->driver_data;
- if (!patch) {
dev_err(&hdev->dev, "no patch specified?\n");
ret = -EINVAL;
goto err;
- }
- ret = patch(codec);
- if (ret < 0) {
dev_err(&hdev->dev, "patch failed %d\n", ret);
goto err;
- }
- /* configure codec for 1:1 PCM:DAI mapping */
- codec->mst_no_extra_pcms = 1;
- ret = snd_hda_codec_parse_pcms(codec);
- if (ret < 0) {
dev_err(&hdev->dev, "unable to map pcms to dai %d\n", ret);
goto parse_pcms_err;
- }
- ret = hda_codec_register_dais(codec, component);
- if (ret < 0) {
dev_err(&hdev->dev, "update dais failed: %d\n", ret);
goto parse_pcms_err;
- }
- if (!hda_codec_is_display(codec)) {
ret = hda_codec_probe_complete(codec);
if (ret < 0)
goto complete_err;
- }
- codec->core.lazy_cache = true;
- return 0;
+complete_err:
- hda_codec_unregister_dais(codec, component);
+parse_pcms_err:
- if (codec->patch_ops.free)
codec->patch_ops.free(codec);
+err:
- snd_hda_codec_cleanup_for_unbind(codec);
+device_new_err:
- if (hda_codec_is_display(codec))
snd_hdac_display_power(bus, hdev->addr, false);
- snd_hdac_ext_bus_link_put(bus, hlink);
- pm_runtime_mark_last_busy(bus->dev);
- pm_runtime_put_autosuspend(bus->dev);
- return ret;
+}
+/* Leaves codec with usage_count=1 and status=suspended */ +static void hda_codec_remove(struct snd_soc_component *component) +{
- struct hda_codec *codec = dev_to_hda_codec(component->dev);
- struct hdac_device *hdev = &codec->core;
- struct hdac_bus *bus = hdev->bus;
- struct hdac_ext_link *hlink;
- bool was_registered = codec->registered;
- /* Don't allow any more runtime suspends */
- pm_runtime_forbid(&hdev->dev);
- hda_codec_unregister_dais(codec, component);
- if (codec->patch_ops.free)
codec->patch_ops.free(codec);
- snd_hda_codec_cleanup_for_unbind(codec);
- pm_runtime_put_noidle(&hdev->dev);
- /* snd_hdac_device_exit() is only called on bus remove */
- pm_runtime_set_suspended(&hdev->dev);
- if (hda_codec_is_display(codec))
snd_hdac_display_power(bus, hdev->addr, false);
- hlink = snd_hdac_ext_bus_link_at(bus, hdev->addr);
- if (hlink)
snd_hdac_ext_bus_link_put(bus, hlink);
- /*
* HDMI card's hda_codec_probe_complete() (see late_probe()) may
* not be called due to early error, leaving bus uc unbalanced
*/
- if (!was_registered) {
pm_runtime_mark_last_busy(bus->dev);
pm_runtime_put_autosuspend(bus->dev);
- }
+#ifdef CONFIG_PM
- WARN_ON(atomic_read(&hdev->dev.power.usage_count) != 1 ||
!pm_runtime_status_suspended(&hdev->dev));
+#endif +}
+static const struct snd_soc_dapm_route hda_dapm_routes[] = {
- {"AIF1TX", NULL, "Codec Input Pin1"},
- {"AIF2TX", NULL, "Codec Input Pin2"},
- {"AIF3TX", NULL, "Codec Input Pin3"},
- {"Codec Output Pin1", NULL, "AIF1RX"},
- {"Codec Output Pin2", NULL, "AIF2RX"},
- {"Codec Output Pin3", NULL, "AIF3RX"},
+};
+static const struct snd_soc_dapm_widget hda_dapm_widgets[] = {
- /* Audio Interface */
- SND_SOC_DAPM_AIF_IN("AIF1RX", "Analog Codec Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_IN("AIF2RX", "Digital Codec Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_IN("AIF3RX", "Alt Analog Codec Playback", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("AIF1TX", "Analog Codec Capture", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("AIF2TX", "Digital Codec Capture", 0, SND_SOC_NOPM, 0, 0),
- SND_SOC_DAPM_AIF_OUT("AIF3TX", "Alt Analog Codec Capture", 0, SND_SOC_NOPM, 0, 0),
- /* Input Pins */
- SND_SOC_DAPM_INPUT("Codec Input Pin1"),
- SND_SOC_DAPM_INPUT("Codec Input Pin2"),
- SND_SOC_DAPM_INPUT("Codec Input Pin3"),
- /* Output Pins */
- SND_SOC_DAPM_OUTPUT("Codec Output Pin1"),
- SND_SOC_DAPM_OUTPUT("Codec Output Pin2"),
- SND_SOC_DAPM_OUTPUT("Codec Output Pin3"),
+};
+static struct snd_soc_dai_driver card_binder_dai = {
- .id = -1,
- .name = "codec-probing-DAI",
+};
+static int hda_hdev_attach(struct hdac_device *hdev) +{
- struct hda_codec *codec = dev_to_hda_codec(&hdev->dev);
- struct snd_soc_component_driver *comp_drv;
- comp_drv = devm_kzalloc(&hdev->dev, sizeof(*comp_drv), GFP_KERNEL);
- if (!comp_drv)
return -ENOMEM;
- /*
* It's save to rely on dev_name() rather than a copy as component
* driver's lifetime is directly tied to hda codec one
*/
- comp_drv->name = dev_name(&hdev->dev);
- comp_drv->probe = hda_codec_probe;
- comp_drv->remove = hda_codec_remove;
- comp_drv->idle_bias_on = false;
- if (!hda_codec_is_display(codec)) {
comp_drv->dapm_widgets = hda_dapm_widgets;
comp_drv->num_dapm_widgets = ARRAY_SIZE(hda_dapm_widgets);
comp_drv->dapm_routes = hda_dapm_routes;
comp_drv->num_dapm_routes = ARRAY_SIZE(hda_dapm_routes);
- }
- return snd_soc_register_component(&hdev->dev, comp_drv, &card_binder_dai, 1);
+}
+static int hda_hdev_detach(struct hdac_device *hdev) +{
- struct hda_codec *codec = dev_to_hda_codec(&hdev->dev);
- if (codec->registered)
cancel_delayed_work_sync(&codec->jackpoll_work);
- snd_soc_unregister_component(&hdev->dev);
- return 0;
+}
+const struct hdac_ext_bus_ops soc_hda_ext_bus_ops = {
- .hdev_attach = hda_hdev_attach,
- .hdev_detach = hda_hdev_detach,
+}; +EXPORT_SYMBOL_GPL(soc_hda_ext_bus_ops);
+MODULE_DESCRIPTION("HD-Audio codec driver"); +MODULE_AUTHOR("Cezary Rojewski cezary.rojewski@intel.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/hda.h b/sound/soc/codecs/hda.h new file mode 100644 index 000000000000..78a2be4945b1 --- /dev/null +++ b/sound/soc/codecs/hda.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/*
- Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
- Author: Cezary Rojewski cezary.rojewski@intel.com
- */
+#ifndef SND_SOC_CODECS_HDA_H +#define SND_SOC_CODECS_HDA_H
+#define hda_codec_is_display(codec) \
- ((((codec)->core.vendor_id >> 16) & 0xFFFF) == 0x8086)
+extern const struct snd_soc_dai_ops snd_soc_hda_codec_dai_ops;
+extern const struct hdac_ext_bus_ops soc_hda_ext_bus_ops; +int hda_codec_probe_complete(struct hda_codec *codec);
+#endif
On Wed, Apr 27, 2022 at 10:47:12AM -0500, Pierre-Louis Bossart wrote:
On 4/27/22 03:18, Cezary Rojewski wrote:
Add generic ASoC equivalent of ALSA HD-Audio codec. This codec is designed to follow HDA_DEV_LEGACY convention. Driver wrapps existing hda_codec.c handlers to prevent code duplication within the newly added
I am surprised the explanations don't even mention the existence of hdac_hda.c
I thought the series was about adding machine drivers, but this also adds code on the sound/soc/codecs/ side which I didn't see coming.
I am not qualified to review this part of the code, I just wonder about duplication of functionality.
At the very least an explanation on why you decided to NOT use hdac_hda.c would be useful to reviewers and maintainers.
Right, why the duplication here? Can't we fix or extend the existing code to do whatever it's not currently doing which compels reimplementation?
On 2022-05-06 3:12 PM, Mark Brown wrote:
On Wed, Apr 27, 2022 at 10:47:12AM -0500, Pierre-Louis Bossart wrote:
On 4/27/22 03:18, Cezary Rojewski wrote:
Add generic ASoC equivalent of ALSA HD-Audio codec. This codec is designed to follow HDA_DEV_LEGACY convention. Driver wrapps existing hda_codec.c handlers to prevent code duplication within the newly added
I am surprised the explanations don't even mention the existence of hdac_hda.c
I thought the series was about adding machine drivers, but this also adds code on the sound/soc/codecs/ side which I didn't see coming.
I am not qualified to review this part of the code, I just wonder about duplication of functionality.
At the very least an explanation on why you decided to NOT use hdac_hda.c would be useful to reviewers and maintainers.
Right, why the duplication here? Can't we fix or extend the existing code to do whatever it's not currently doing which compels reimplementation?
Sorry for the late response, did not realize there is an unanswered comment here.
So, the rough list goes as: - hdac_hda.c hardcodes codec capabilities rather than aligning with what sound/pci/hda/ code does - merges HDMI (i.e. Intel i915 audio component) and HDA DAIs together whereas these are two separate devices - because of above, implements custom search/matching mechanism for PCM/DAI - cont. because of above, its header hosts private data struct, unnecessary complication - follows HDA_DEV_ASOC convention rather than HDA_DEV_LEGACY causing misalignments between sound/pci/hda and sound/soc/ behaviour - has basic PM runtime support and does not survive scenarios where resume/suspend + denylist + rmmod/modprobe are mixed together or invoked in unordered fashion between this module and several others in the audio stack
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
Regards, Czarek
On 5/6/22 08:39, Cezary Rojewski wrote:
On 2022-05-06 3:12 PM, Mark Brown wrote:
On Wed, Apr 27, 2022 at 10:47:12AM -0500, Pierre-Louis Bossart wrote:
On 4/27/22 03:18, Cezary Rojewski wrote:
Add generic ASoC equivalent of ALSA HD-Audio codec. This codec is designed to follow HDA_DEV_LEGACY convention. Driver wrapps existing hda_codec.c handlers to prevent code duplication within the newly added
I am surprised the explanations don't even mention the existence of hdac_hda.c
I thought the series was about adding machine drivers, but this also adds code on the sound/soc/codecs/ side which I didn't see coming.
I am not qualified to review this part of the code, I just wonder about duplication of functionality.
At the very least an explanation on why you decided to NOT use hdac_hda.c would be useful to reviewers and maintainers.
Right, why the duplication here? Can't we fix or extend the existing code to do whatever it's not currently doing which compels reimplementation?
Sorry for the late response, did not realize there is an unanswered comment here.
So, the rough list goes as:
- hdac_hda.c hardcodes codec capabilities rather than aligning with what
sound/pci/hda/ code does
- merges HDMI (i.e. Intel i915 audio component) and HDA DAIs together
whereas these are two separate devices
- because of above, implements custom search/matching mechanism for PCM/DAI
- cont. because of above, its header hosts private data struct,
unnecessary complication
- follows HDA_DEV_ASOC convention rather than HDA_DEV_LEGACY causing
misalignments between sound/pci/hda and sound/soc/ behaviour
- has basic PM runtime support and does not survive scenarios where
resume/suspend + denylist + rmmod/modprobe are mixed together or invoked in unordered fashion between this module and several others in the audio stack
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
I am not against change and will agree that HDaudio support is far from perfect, but it's been released for multiple generations from dozens of OEMs and mostly works. All the issues reported to us are related to codec configurations that also don't work with the legacy HDaudio driver, DMIC configurations, CSME authentication or system hangs that have not been root-caused [1]. HDaudio/ASoC interfaces are not on our radar as problematic.
Disrupting basic HDaudio support to do things better has to be handled with extreme caution and a ton of testing involving distro maintainers and community members, so we are talking about an opt-in transition, not an immediate switch. We've done a similar transition in the past to stop using a dedicated hdac_hdmi.c codec, see all references to the 'use_common_hdmi' parameter in the SOF code. That transition seems to go exactly against your second point above on HDMI and HDA being different devices, so this could be an interesting debate.
Changes to the HDAudio/ASoC support would need to be handled with a separate patchset anyways, and the SOF side changes done after we are finished with the IPC4 and MeteorLake upstreaming. No one in our team has any bandwidth to help with reviews or tests on this topic at the moment.
I will also re-state that the removal of the skylake driver can only happen after a long period of deprecation, when firmware and topologies have been picked by distributions and all users are known to have switched, so it's very likely that any alignment between "all HD-Audio ASoC users" mentioned above does include the Skylake driver, doesn't it?
So to circle back: is there anything preventing the use of the existing hdac_hda.c codec in this "ASoC: Intel: avs: Machine boards and HDA codec support" series and can the HDaudio codec change be done "later" in a more organized way?
Thanks! -Pierre
[1] https://github.com/thesofproject/linux/issues?q=is%3Aissue+is%3Aopen+label%3...
On 2022-05-06 4:56 PM, Pierre-Louis Bossart wrote:
On 5/6/22 08:39, Cezary Rojewski wrote:
...
Sorry for the late response, did not realize there is an unanswered comment here.
So, the rough list goes as:
- hdac_hda.c hardcodes codec capabilities rather than aligning with what
sound/pci/hda/ code does
- merges HDMI (i.e. Intel i915 audio component) and HDA DAIs together
whereas these are two separate devices
- because of above, implements custom search/matching mechanism for PCM/DAI
- cont. because of above, its header hosts private data struct,
unnecessary complication
- follows HDA_DEV_ASOC convention rather than HDA_DEV_LEGACY causing
misalignments between sound/pci/hda and sound/soc/ behaviour
- has basic PM runtime support and does not survive scenarios where
resume/suspend + denylist + rmmod/modprobe are mixed together or invoked in unordered fashion between this module and several others in the audio stack
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
I am not against change and will agree that HDaudio support is far from perfect, but it's been released for multiple generations from dozens of OEMs and mostly works. All the issues reported to us are related to codec configurations that also don't work with the legacy HDaudio driver, DMIC configurations, CSME authentication or system hangs that have not been root-caused [1]. HDaudio/ASoC interfaces are not on our radar as problematic.
That's why aligning with sound/pci/hda behavior is better for both, ALSA and ASoC users -> one place to fix the problems, both clients happy.
Disrupting basic HDaudio support to do things better has to be handled with extreme caution and a ton of testing involving distro maintainers and community members, so we are talking about an opt-in transition, not an immediate switch. We've done a similar transition in the past to stop using a dedicated hdac_hdmi.c codec, see all references to the 'use_common_hdmi' parameter in the SOF code. That transition seems to go exactly against your second point above on HDMI and HDA being different devices, so this could be an interesting debate.
Changes to the HDAudio/ASoC support would need to be handled with a separate patchset anyways, and the SOF side changes done after we are finished with the IPC4 and MeteorLake upstreaming. No one in our team has any bandwidth to help with reviews or tests on this topic at the moment.
Agree. This won't be forced on anyone and that's why separate implementation needed to be provided. There is too much to cover if we were to refactor hdac_hda.c
I will also re-state that the removal of the skylake driver can only happen after a long period of deprecation, when firmware and topologies have been picked by distributions and all users are known to have switched, so it's very likely that any alignment between "all HD-Audio ASoC users" mentioned above does include the Skylake driver, doesn't it?
Nah, I don't believe we need to be saving skylake-driver here. By "all HD-Audio ASoC users" I meant sof-driver as it isn't going anywhere - what cannot be said about the skylake-driver :)
So to circle back: is there anything preventing the use of the existing hdac_hda.c codec in this "ASoC: Intel: avs: Machine boards and HDA codec support" series and can the HDaudio codec change be done "later" in a more organized way?
Yeah, all the pm scenarios will fail when paired with the avs-driver. The expectations are different. We'd have to fix probe() and remove() (and related) sequences for the hdac_hda.c, and given that its users did not notice prompts further problems with the refactor. This is very similar to the skylake-driver vs avs-driver case. We could have applied ~300 patches we had internally that prepare skylake-driver to be re-modeled and then apply patchsets which look more or less like the avs-driver series instead of providing a parallel driver.
But the reality shows that such approach applies too much pressure on the reviewers and leaves no fallback option for the end users if anything fails along the way.
Regards, Czarek
On 5/6/22 10:28, Cezary Rojewski wrote:
On 2022-05-06 4:56 PM, Pierre-Louis Bossart wrote:
On 5/6/22 08:39, Cezary Rojewski wrote:
...
Sorry for the late response, did not realize there is an unanswered comment here.
So, the rough list goes as:
- hdac_hda.c hardcodes codec capabilities rather than aligning with what
sound/pci/hda/ code does
- merges HDMI (i.e. Intel i915 audio component) and HDA DAIs together
whereas these are two separate devices
- because of above, implements custom search/matching mechanism for
PCM/DAI
- cont. because of above, its header hosts private data struct,
unnecessary complication
- follows HDA_DEV_ASOC convention rather than HDA_DEV_LEGACY causing
misalignments between sound/pci/hda and sound/soc/ behaviour
- has basic PM runtime support and does not survive scenarios where
resume/suspend + denylist + rmmod/modprobe are mixed together or invoked in unordered fashion between this module and several others in the audio stack
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
I am not against change and will agree that HDaudio support is far from perfect, but it's been released for multiple generations from dozens of OEMs and mostly works. All the issues reported to us are related to codec configurations that also don't work with the legacy HDaudio driver, DMIC configurations, CSME authentication or system hangs that have not been root-caused [1]. HDaudio/ASoC interfaces are not on our radar as problematic.
That's why aligning with sound/pci/hda behavior is better for both, ALSA and ASoC users -> one place to fix the problems, both clients happy.
Disrupting basic HDaudio support to do things better has to be handled with extreme caution and a ton of testing involving distro maintainers and community members, so we are talking about an opt-in transition, not an immediate switch. We've done a similar transition in the past to stop using a dedicated hdac_hdmi.c codec, see all references to the 'use_common_hdmi' parameter in the SOF code. That transition seems to go exactly against your second point above on HDMI and HDA being different devices, so this could be an interesting debate.
Changes to the HDAudio/ASoC support would need to be handled with a separate patchset anyways, and the SOF side changes done after we are finished with the IPC4 and MeteorLake upstreaming. No one in our team has any bandwidth to help with reviews or tests on this topic at the moment.
Agree. This won't be forced on anyone and that's why separate implementation needed to be provided. There is too much to cover if we were to refactor hdac_hda.c
I will also re-state that the removal of the skylake driver can only happen after a long period of deprecation, when firmware and topologies have been picked by distributions and all users are known to have switched, so it's very likely that any alignment between "all HD-Audio ASoC users" mentioned above does include the Skylake driver, doesn't it?
Nah, I don't believe we need to be saving skylake-driver here. By "all HD-Audio ASoC users" I meant sof-driver as it isn't going anywhere - what cannot be said about the skylake-driver :)
So to circle back: is there anything preventing the use of the existing hdac_hda.c codec in this "ASoC: Intel: avs: Machine boards and HDA codec support" series and can the HDaudio codec change be done "later" in a more organized way?
Yeah, all the pm scenarios will fail when paired with the avs-driver. The expectations are different. We'd have to fix probe() and remove() (and related) sequences for the hdac_hda.c, and given that its users did not notice prompts further problems with the refactor. This is very similar to the skylake-driver vs avs-driver case. We could have applied ~300 patches we had internally that prepare skylake-driver to be re-modeled and then apply patchsets which look more or less like the avs-driver series instead of providing a parallel driver.
But the reality shows that such approach applies too much pressure on the reviewers and leaves no fallback option for the end users if anything fails along the way.
I will stop commenting here to let others chime in, I don't have the background to provide useful technical feedback on this complicated HDaudio/ASoC interface.
I am however concerned about the lack of long-term plans and confusion coming having 3 different ways of dealing with HDaudio codecs on the same hardware platform (legacy, ASoC/SOF-Skylake, ASoC-AVS). 2 was already bad, I don't see how 3 is better?
Hi,
On Fri, 6 May 2022, Pierre-Louis Bossart wrote:
On 5/6/22 10:28, Cezary Rojewski wrote:
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
[...]
I am however concerned about the lack of long-term plans and confusion coming having 3 different ways of dealing with HDaudio codecs on the same hardware platform (legacy, ASoC/SOF-Skylake, ASoC-AVS). 2 was already bad, I don't see how 3 is better?
fortunately most of the actual logic is in the codec drivers (in sound/pci/hda/), so redundancy in the asoc-to-hda wrappers is not that bad.
As a slight correction to the thread, hdac_hda.c does support HDMI and all SOF machine drivers transitioned to this a few years back (and dropped use of hdac_hdmi.c which is mostly duplicating code in patch_hdmi.c).
In last few years, most of the changes have been to the actual codec drivers and we've not really had many fixes to hdac_hda.c anymore (there has been a few, and indeed PM related). So in this sense this has worked well enough. Patches to fix bugs and add support for new hardware, have benefitted all users (DSP and no-DSP) and changes have been only needed at a single location. So this is all good.
The fixed DAI capabilities of hdac_hda.c are indeed not ideal (although in practise hasn't really been an issue so far) and the approach in the new hda.c seems indeed more robust. We do have a lot of existing machine drivers (and shipping DSP topologies that we need to keep working), so transitioning e.g. SOF machine drivers is not going to be straightforward. For new mach drivers, this could be considered.
So while not ideal, maybe it makes sense to have two wrappers, hdac_hda.c for mach drivers with fixed DAI configuration, and the new hda.c that supports dynamic configuration (but requires mach drivers that match this). If the old SST driver is deprecated, we can then proceed to remove hdac_hdmi.c from the tree, so there's some savings.
Please do add some description to the commit+code+kconfig on how this new codec driver relates to hdac_hda. Otherwise it's very confusing to have two asoc codec drivers for the same thing.
Br, Kai
On 5/9/22 09:33, Kai Vehmanen wrote:
Hi,
On Fri, 6 May 2022, Pierre-Louis Bossart wrote:
On 5/6/22 10:28, Cezary Rojewski wrote:
My suggestion is different: have all HD-Audio ASoC users switch to this implementation when possible and remove the existing code along with skylake-driver.
[...]
I am however concerned about the lack of long-term plans and confusion coming having 3 different ways of dealing with HDaudio codecs on the same hardware platform (legacy, ASoC/SOF-Skylake, ASoC-AVS). 2 was already bad, I don't see how 3 is better?
fortunately most of the actual logic is in the codec drivers (in sound/pci/hda/), so redundancy in the asoc-to-hda wrappers is not that bad.
As a slight correction to the thread, hdac_hda.c does support HDMI and all SOF machine drivers transitioned to this a few years back (and dropped use of hdac_hdmi.c which is mostly duplicating code in patch_hdmi.c).
In last few years, most of the changes have been to the actual codec drivers and we've not really had many fixes to hdac_hda.c anymore (there has been a few, and indeed PM related). So in this sense this has worked well enough. Patches to fix bugs and add support for new hardware, have benefitted all users (DSP and no-DSP) and changes have been only needed at a single location. So this is all good.
The fixed DAI capabilities of hdac_hda.c are indeed not ideal (although in practise hasn't really been an issue so far) and the approach in the new hda.c seems indeed more robust. We do have a lot of existing machine drivers (and shipping DSP topologies that we need to keep working), so transitioning e.g. SOF machine drivers is not going to be straightforward. For new mach drivers, this could be considered.
We've just simplified the HDAudio topologies to support the Analog playback and capture only, for both IPC3 and IPC4 cases, so there's really no plan to support such dynamic capabilities. I am not even aware of a single device available in our team where the digital inputs and outputs are exposed on a connector, so even if we wanted we couldn't test this dynamic part.
So while not ideal, maybe it makes sense to have two wrappers, hdac_hda.c for mach drivers with fixed DAI configuration, and the new hda.c that supports dynamic configuration (but requires mach drivers that match this). If the old SST driver is deprecated, we can then proceed to remove hdac_hdmi.c from the tree, so there's some savings.
Such removal isn't going to happen for at least 3+ years, the time it takes for the slowest distros and users to switch kernels.
that means we're going to have to maintain for the foreseeable future:
hdac_hdmi.c: used only by SST hdac_hda.c: used by both SST and SOF hda.c: used only by AVS.
On 2022-05-09 5:55 PM, Pierre-Louis Bossart wrote:> On 5/9/22 09:33, Kai Vehmanen wrote:
The fixed DAI capabilities of hdac_hda.c are indeed not ideal (although in practise hasn't really been an issue so far) and the approach in the new hda.c seems indeed more robust. We do have a lot of existing machine drivers (and shipping DSP topologies that we need to keep working), so transitioning e.g. SOF machine drivers is not going to be straightforward. For new mach drivers, this could be considered.
We've just simplified the HDAudio topologies to support the Analog playback and capture only, for both IPC3 and IPC4 cases, so there's really no plan to support such dynamic capabilities. I am not even aware of a single device available in our team where the digital inputs and outputs are exposed on a connector, so even if we wanted we couldn't test this dynamic part.
As I've explained in one of the previous posts, you can always use topology to limit the number of FE(s) available while still adhering to behavior found in sound/pci/hda. hda.c is a prime example of how ASoC HD-Audio can align with ALSA HD-Audio in 1:1 fashion. hdac_hda.c does not do that a) because of hardcodes b) following HDA_DEV_ASOC.
Having no behavioral differences is a major gain here, fix one place, enjoy both solutions. By 'one place' I mean sound/pci/hda of course as there should be no logic outside of that directory.
So while not ideal, maybe it makes sense to have two wrappers, hdac_hda.c for mach drivers with fixed DAI configuration, and the new hda.c that supports dynamic configuration (but requires mach drivers that match this). If the old SST driver is deprecated, we can then proceed to remove hdac_hdmi.c from the tree, so there's some savings.
Such removal isn't going to happen for at least 3+ years, the time it takes for the slowest distros and users to switch kernels.
that means we're going to have to maintain for the foreseeable future:
hdac_hdmi.c: used only by SST hdac_hda.c: used by both SST and SOF hda.c: used only by AVS.
I and the team maintained almost all the solutions found in sound/soc/intel for couple of years already, doing couple more with the old stuff is not a problem. Don't believe 'sit and do nothing' is the answer here, not sure if this is even an option - but I'm sure that the final effect is going to be worth the initial cost.
Regards, Czarek
Connect AVS driver with ASoC HDAudio codec with help of this machine board. Similarly to its platform and codec components, DAI links and routes are being created dynamically so single board can be used across all HDAudio codec types.
Card makes use of "binder" BE DAI Link so HDAudio codec driver can be listed as one of its components. This allows for BE DAIs to be created dynamically, based on HDAudio codec capabilities.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/Kconfig | 3 + sound/soc/intel/avs/Makefile | 3 + sound/soc/intel/avs/boards/Kconfig | 15 ++ sound/soc/intel/avs/boards/Makefile | 5 + sound/soc/intel/avs/boards/hdaudio.c | 294 +++++++++++++++++++++++++++ 5 files changed, 320 insertions(+) create mode 100644 sound/soc/intel/avs/boards/Kconfig create mode 100644 sound/soc/intel/avs/boards/Makefile create mode 100644 sound/soc/intel/avs/boards/hdaudio.c
diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index be42c4eff165..851e8053e2fa 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -224,5 +224,8 @@ config SND_SOC_INTEL_AVS capabilities. This includes Skylake, Kabylake, Amberlake and Apollolake.
+# Machine board drivers +source "sound/soc/intel/avs/boards/Kconfig" + # ASoC codec drivers source "sound/soc/intel/boards/Kconfig" diff --git a/sound/soc/intel/avs/Makefile b/sound/soc/intel/avs/Makefile index 952f51977656..abd51fb6b55d 100644 --- a/sound/soc/intel/avs/Makefile +++ b/sound/soc/intel/avs/Makefile @@ -5,3 +5,6 @@ snd-soc-avs-objs := dsp.o ipc.o messages.o utils.o core.o loader.o \ snd-soc-avs-objs += cldma.o
obj-$(CONFIG_SND_SOC_INTEL_AVS) += snd-soc-avs.o + +# Machine support +obj-$(CONFIG_SND_SOC) += boards/ diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig new file mode 100644 index 000000000000..de62c0437f6e --- /dev/null +++ b/sound/soc/intel/avs/boards/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Intel AVS Machine drivers" + depends on SND_SOC_INTEL_AVS + +comment "Available DSP configurations" + +config SND_SOC_INTEL_AVS_MACH_HDAUDIO + tristate "HD-Audio generic board" + select SND_SOC_HDA + help + This adds support for AVS with HDAudio codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + +endmenu diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile new file mode 100644 index 000000000000..e5281148e5d4 --- /dev/null +++ b/sound/soc/intel/avs/boards/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +snd-soc-avs-hdaudio-objs := hdaudio.o + +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o diff --git a/sound/soc/intel/avs/boards/hdaudio.c b/sound/soc/intel/avs/boards/hdaudio.c new file mode 100644 index 000000000000..d2fc41d39448 --- /dev/null +++ b/sound/soc/intel/avs/boards/hdaudio.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/platform_device.h> +#include <sound/hda_codec.h> +#include <sound/hda_i915.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/hda.h" + +static int avs_create_dai_links(struct device *dev, struct hda_codec *codec, int pcm_count, + const char *platform_name, struct snd_soc_dai_link **links) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + struct hda_pcm *pcm; + const char *cname = dev_name(&codec->core.dev); + int i; + + dl = devm_kcalloc(dev, pcm_count, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + 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)) { + dl[i].name = devm_kasprintf(dev, GFP_KERNEL, "%s link%d", cname, i); + if (!dl[i].name) + return -ENOMEM; + + dl[i].id = i; + dl[i].nonatomic = 1; + dl[i].no_pcm = 1; + dl[i].dpcm_playback = 1; + dl[i].dpcm_capture = 1; + dl[i].platforms = platform; + dl[i].num_platforms = 1; + + dl[i].codecs = devm_kzalloc(dev, sizeof(*dl->codecs), GFP_KERNEL); + dl[i].cpus = devm_kzalloc(dev, sizeof(*dl->cpus), GFP_KERNEL); + if (!dl[i].codecs || !dl[i].cpus) + return -ENOMEM; + + dl[i].cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "%s-cpu%d", cname, i); + if (!dl[i].cpus->dai_name) + return -ENOMEM; + + dl[i].codecs->name = devm_kstrdup(dev, cname, GFP_KERNEL); + dl[i].codecs->dai_name = pcm->name; + dl[i].num_codecs = 1; + dl[i].num_cpus = 1; + } + + *links = dl; + 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" + +static struct snd_pcm * +avs_card_hdmi_pcm_at(struct snd_soc_card *card, int hdmi_idx) +{ + struct snd_soc_pcm_runtime *rtd; + int dir = SNDRV_PCM_STREAM_PLAYBACK; + + for_each_card_rtds(card, rtd) { + struct snd_pcm *spcm; + int ret, n; + + spcm = rtd->pcm ? rtd->pcm->streams[dir].pcm : NULL; + if (!spcm || !strstr(spcm->id, FEDAI_NAME_PREFIX)) + continue; + + ret = sscanf(spcm->id, FEDAI_NAME_PREFIX "%d", &n); + if (ret != 1) + continue; + if (n == hdmi_idx) + return rtd->pcm; + } + + return NULL; +} + +static int avs_card_late_probe(struct snd_soc_card *card) +{ + struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); + struct hda_codec *codec = mach->pdata; + struct hda_pcm *hpcm; + /* Topology pcm indexing is 1-based */ + int i = 1; + + list_for_each_entry(hpcm, &codec->pcm_list_head, list) { + struct snd_pcm *spcm; + + spcm = avs_card_hdmi_pcm_at(card, i); + if (spcm) { + hpcm->pcm = spcm; + hpcm->device = spcm->device; + dev_info(card->dev, "%s: mapping HDMI converter %d to PCM %d (%p)\n", + __func__, i, hpcm->device, spcm); + } else { + hpcm->pcm = NULL; + hpcm->device = SNDRV_PCM_INVALID_DEVICE; + dev_warn(card->dev, "%s: no PCM in topology for HDMI converter %d\n", + __func__, i); + } + i++; + } + + return hda_codec_probe_complete(codec); +} + +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; + + mach = dev_get_platdata(card->dev); + codec = mach->pdata; + + if (list_empty(&codec->pcm_list_head)) + return -EINVAL; + list_for_each_entry(pcm, &codec->pcm_list_head, list) + pcm_count++; + + ret = avs_create_dai_links(card->dev, codec, pcm_count, mach->mach_params.platform, &links); + if (ret < 0) { + dev_err(card->dev, "create links failed: %d\n", ret); + return ret; + } + + for (n = 0; n < pcm_count; n++) { + ret = snd_soc_add_pcm_runtime(card, &links[n]); + if (ret < 0) { + dev_err(card->dev, "add links failed: %d\n", ret); + 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; +} + +SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY())); + +static struct snd_soc_dai_link probing_link = { + .name = "probing-LINK", + .id = -1, + .nonatomic = 1, + .no_pcm = 1, + .dpcm_playback = 1, + .dpcm_capture = 1, + .cpus = dummy, + .num_cpus = ARRAY_SIZE(dummy), + .init = avs_probing_link_init, +}; + +static int avs_hdaudio_probe(struct platform_device *pdev) +{ + struct snd_soc_dai_link *binder; + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + struct hda_codec *codec; + + mach = dev_get_platdata(dev); + codec = mach->pdata; + + /* codec may be unloaded before card's probe() fires */ + if (!device_is_registered(&codec->core.dev)) + return -ENODEV; + + binder = devm_kmemdup(dev, &probing_link, sizeof(probing_link), GFP_KERNEL); + if (!binder) + return -ENOMEM; + + binder->platforms = devm_kzalloc(dev, sizeof(*binder->platforms), GFP_KERNEL); + binder->codecs = devm_kzalloc(dev, sizeof(*binder->codecs), GFP_KERNEL); + if (!binder->platforms || !binder->codecs) + return -ENOMEM; + + binder->codecs->name = devm_kstrdup(dev, dev_name(&codec->core.dev), GFP_KERNEL); + if (!binder->codecs->name) + return -ENOMEM; + + binder->platforms->name = mach->mach_params.platform; + binder->num_platforms = 1; + binder->codecs->dai_name = "codec-probing-DAI"; + binder->num_codecs = 1; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = binder->codecs->name; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = binder; + card->num_links = 1; + card->fully_routed = true; + if (hda_codec_is_display(codec)) + card->late_probe = avs_card_late_probe; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_hdaudio_driver = { + .probe = avs_hdaudio_probe, + .driver = { + .name = "avs_hdaudio", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_hdaudio_driver) + +MODULE_DESCRIPTION("Intel HD-Audio machine driver"); +MODULE_AUTHOR("Cezary Rojewski cezary.rojewski@intel.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_hdaudio");
Support AVS-DMIC configuration by implementing board connecting AVS platform component with DMIC codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 8 +++ sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/dmic.c | 93 +++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 sound/soc/intel/avs/boards/dmic.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index de62c0437f6e..1d4597fa9814 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -4,6 +4,14 @@ menu "Intel AVS Machine drivers"
comment "Available DSP configurations"
+config SND_SOC_INTEL_AVS_MACH_DMIC + tristate "DMIC generic board" + select SND_SOC_DMIC + help + This adds support for AVS with Digital Mic array configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_HDAUDIO tristate "HD-Audio generic board" select SND_SOC_HDA diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index e5281148e5d4..2ff35d4d97d8 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only
+snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o
+obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o diff --git a/sound/soc/intel/avs/boards/dmic.c b/sound/soc/intel/avs/boards/dmic.c new file mode 100644 index 000000000000..90a921638572 --- /dev/null +++ b/sound/soc/intel/avs/boards/dmic.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/device.h> +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> + +SND_SOC_DAILINK_DEF(dmic_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC Pin"))); +SND_SOC_DAILINK_DEF(dmic_wov_pin, DAILINK_COMP_ARRAY(COMP_CPU("DMIC WoV Pin"))); +SND_SOC_DAILINK_DEF(dmic_codec, DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec", "dmic-hifi"))); +/* Name overridden on probe */ +SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM(""))); + +static struct snd_soc_dai_link card_dai_links[] = { + /* Back ends */ + { + .name = "DMIC", + .id = 0, + .dpcm_capture = 1, + .nonatomic = 1, + .no_pcm = 1, + SND_SOC_DAILINK_REG(dmic_pin, dmic_codec, platform), + }, + { + .name = "DMIC WoV", + .id = 1, + .dpcm_capture = 1, + .nonatomic = 1, + .no_pcm = 1, + .ignore_suspend = 1, + SND_SOC_DAILINK_REG(dmic_wov_pin, dmic_codec, platform), + }, +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_MIC("SoC DMIC", NULL), +}; + +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) +{ + struct snd_soc_acpi_mach *mach; + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ret; + + mach = dev_get_platdata(dev); + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = "avs_dmic"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = card_dai_links; + card->num_links = ARRAY_SIZE(card_dai_links); + card->dapm_widgets = card_widgets; + card->num_dapm_widgets = ARRAY_SIZE(card_widgets); + card->dapm_routes = card_routes; + card->num_dapm_routes = ARRAY_SIZE(card_routes); + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, mach->mach_params.platform); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_dmic_driver = { + .probe = avs_dmic_probe, + .driver = { + .name = "avs_dmic", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_dmic_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_dmic");
Allow for testing audio streaming over I2S interface through SSP-test board. No actual codec is needed here as board is intended for SSP loopback scenarios only. One playback and one capture endpoint is exposed per SSP port.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 6 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/ssp_test.c | 178 ++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 sound/soc/intel/avs/boards/ssp_test.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index 1d4597fa9814..ea2f094758f9 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -20,4 +20,10 @@ config SND_SOC_INTEL_AVS_MACH_HDAUDIO Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_SSP_TEST + tristate "SSP test board" + help + This adds support for SSP test board which can be used to verify + transfer over I2S interface with loopback scenarios. + endmenu diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index 2ff35d4d97d8..1c860ca165de 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -2,6 +2,8 @@
snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o +snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/ssp_test.c b/sound/soc/intel/avs/boards/ssp_test.c new file mode 100644 index 000000000000..cabaf615b9ae --- /dev/null +++ b/sound/soc/intel/avs/boards/ssp_test.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + 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"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_dr = 2; + + dr = devm_kcalloc(dev, num_dr, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + dr[0].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%dpb", ssp_port); + dr[0].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Tx", ssp_port); + if (!dr[0].sink || !dr[0].source) + return -ENOMEM; + + dr[1].sink = devm_kasprintf(dev, GFP_KERNEL, "ssp%d Rx", ssp_port); + dr[1].source = devm_kasprintf(dev, GFP_KERNEL, "ssp%dcp", ssp_port); + if (!dr[1].sink || !dr[1].source) + return -ENOMEM; + + *routes = dr; + *num_routes = num_dr; + + return 0; +} + +static int avs_create_dapm_widgets(struct device *dev, int ssp_port, + struct snd_soc_dapm_widget **widgets, int *num_widgets) +{ + struct snd_soc_dapm_widget *dw; + const int num_dw = 2; + + dw = devm_kcalloc(dev, num_dw, sizeof(*dw), GFP_KERNEL); + if (!dw) + return -ENOMEM; + + dw[0].id = snd_soc_dapm_hp; + dw[0].reg = SND_SOC_NOPM; + dw[0].name = devm_kasprintf(dev, GFP_KERNEL, "ssp%dpb", ssp_port); + if (!dw[0].name) + return -ENOMEM; + + dw[1].id = snd_soc_dapm_mic; + dw[1].reg = SND_SOC_NOPM; + dw[1].name = devm_kasprintf(dev, GFP_KERNEL, "ssp%dcp", ssp_port); + if (!dw[1].name) + return -ENOMEM; + + *widgets = dw; + *num_widgets = num_dw; + + return 0; +} + +static int avs_ssp_test_probe(struct platform_device *pdev) +{ + struct snd_soc_dapm_widget *widgets; + 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, num_widgets, ret; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->name = devm_kasprintf(dev, GFP_KERNEL, "ssp%ld-loopback", __ffs(mach->link_mask)); + if (!card->name) + return -ENOMEM; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d\n", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &routes, &num_routes); + if (ret) { + dev_err(dev, "Failed to create dapm routes: %d\n", ret); + return ret; + } + + ret = avs_create_dapm_widgets(dev, __ffs(mach->link_mask), &widgets, &num_widgets); + if (ret) { + dev_err(dev, "Failed to create dapm widgets: %d\n", ret); + return ret; + } + + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->dapm_routes = routes; + card->num_dapm_routes = num_routes; + card->dapm_widgets = widgets; + card->num_dapm_widgets = num_widgets; + card->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_ssp_test_driver = { + .probe = avs_ssp_test_probe, + .driver = { + .name = "avs_ssp_test", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_ssp_test_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_ssp_test");
On 2022-04-27 10:18 AM, Cezary Rojewski wrote:
Allow for testing audio streaming over I2S interface through SSP-test board. No actual codec is needed here as board is intended for SSP loopback scenarios only. One playback and one capture endpoint is exposed per SSP port.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com
sound/soc/intel/avs/boards/Kconfig | 6 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/ssp_test.c | 178 ++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 sound/soc/intel/avs/boards/ssp_test.c
I'll send v2 later today that renames this board from ssp_test to i2s_test - so it matches naming convention used for all other boards.
Regards, Czarek
To support AVS-rt274 configuration add machine board connecting AVS platform component driver with rt274 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/rt274.c | 309 ++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 sound/soc/intel/avs/boards/rt274.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index ea2f094758f9..a8f598247548 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -20,6 +20,16 @@ config SND_SOC_INTEL_AVS_MACH_HDAUDIO Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_RT274 + tristate "rt274 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT274 + help + This adds support for ASoC machine driver with RT274 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_SSP_TEST tristate "SSP test board" help diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index 1c860ca165de..dbefc519e1e0 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -2,8 +2,10 @@
snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o +snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/rt274.c b/sound/soc/intel/avs/boards/rt274.c new file mode 100644 index 000000000000..3d4d0aa1f2a2 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt274.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt274.h" + +#define AVS_RT274_FREQ_OUT 24000000 +#define AVS_RT274_BE_FIXUP_RATE 48000 +#define RT274_CODEC_DAI "rt274-aif1" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), +}; + +static int +avs_rt274_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, RT274_CODEC_DAI); + if (!codec_dai) + return -EINVAL; + + /* Codec needs clock for Jack detection and button press */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT274_SCLK_S_PLL2, AVS_RT274_FREQ_OUT, + SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "set codec sysclk failed: %d\n", ret); + return ret; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + int ratio = 100; + + snd_soc_dai_set_bclk_ratio(codec_dai, ratio); + + ret = snd_soc_dai_set_pll(codec_dai, 0, RT274_PLL2_S_BCLK, + AVS_RT274_BE_FIXUP_RATE * ratio, AVS_RT274_FREQ_OUT); + if (ret) { + dev_err(codec_dai->dev, "failed to enable PLL2: %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_rt274_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC", NULL, "Mic Jack"}, + + {"Headphone Jack", NULL, "Platform Clock"}, + {"MIC", NULL, "Platform Clock"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt274_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET, jack, pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + /* TDM 4 slots 24 bit, set Rx & Tx bitmask to 4 active slots */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xF, 0xF, 4, 24); + if (ret < 0) { + dev_err(card->dev, "can't set codec pcm format %d\n", ret); + return ret; + } + + card->dapm.idle_bias_off = true; + + return 0; +} + +static int avs_rt274_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = AVS_RT274_BE_FIXUP_RATE; + channels->min = channels->max = 2; + + /* set SSPN to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT34C2:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt274-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt274_codec_init; + dl->be_hw_params_fixup = avs_rt274_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_rt274"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt274_driver = { + .probe = avs_rt274_probe, + .driver = { + .name = "avs_rt274", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt274_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt274");
To support AVS-rt286 configuration add machine board connecting AVS platform component driver with rt286 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/rt286.c | 280 ++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 sound/soc/intel/avs/boards/rt286.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index a8f598247548..7c29eb8cee70 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -30,6 +30,16 @@ config SND_SOC_INTEL_AVS_MACH_RT274 Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_RT286 + tristate "rt286 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT286 + help + This adds support for ASoC machine driver with RT286 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_SSP_TEST tristate "SSP test board" help diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index dbefc519e1e0..25631c03ffec 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -3,9 +3,11 @@ snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o snd-soc-avs-rt274-objs := rt274.o +snd-soc-avs-rt286-objs := rt286.o snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/rt286.c b/sound/soc/intel/avs/boards/rt286.c new file mode 100644 index 000000000000..58863cdcefd5 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt286.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt286.h" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt286_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, jack, + pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + return 0; +} + +static int avs_rt286_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt286_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = substream->private_data; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt286_ops = { + .hw_params = avs_rt286_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt286-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt286_codec_init; + dl->be_hw_params_fixup = avs_rt286_be_fixup; + dl->ops = &avs_rt286_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_rt286"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt286_driver = { + .probe = avs_rt286_probe, + .driver = { + .name = "avs_rt286", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt286_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt286");
To support AVS-rt298 configuration add machine board connecting AVS platform component driver with rt298 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/rt298.c | 280 ++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 sound/soc/intel/avs/boards/rt298.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index 7c29eb8cee70..ae83e930aa35 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -40,6 +40,16 @@ config SND_SOC_INTEL_AVS_MACH_RT286 Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_RT298 + tristate "rt298 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT298 + help + This adds support for ASoC machine driver with RT298 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_SSP_TEST tristate "SSP test board" help diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index 25631c03ffec..d1d261192d96 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -4,10 +4,12 @@ snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o +snd-soc-avs-rt298-objs := rt298.o snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/rt298.c b/sound/soc/intel/avs/boards/rt298.c new file mode 100644 index 000000000000..e241455dd72f --- /dev/null +++ b/sound/soc/intel/avs/boards/rt298.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/rt298.h" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Mic Jack"), + SOC_DAPM_PIN_SWITCH("Speaker"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + {"Headphone Jack", NULL, "HPO Pin"}, + {"MIC1", NULL, "Mic Jack"}, + + {"Speaker", NULL, "SPOR"}, + {"Speaker", NULL, "SPOL"}, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Mic Jack", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_rt298_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0, jack, + pins, num_pins); + if (ret) + return ret; + + snd_soc_component_set_jack(component, jack, NULL); + + return 0; +} + +static int avs_rt298_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int +avs_rt298_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 *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, RT298_SCLK_S_PLL, 19200000, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, "Set codec sysclk failed: %d\n", ret); + + return ret; +} + +static const struct snd_soc_ops avs_rt298_ops = { + .hw_params = avs_rt298_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt298-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_rt298_codec_init; + dl->be_hw_params_fixup = avs_rt298_be_fixup; + dl->ops = &avs_rt298_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_rt298"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt298_driver = { + .probe = avs_rt298_probe, + .driver = { + .name = "avs_rt298", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt298_driver); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt298");
To support AVS-rt5682 configuration add machine board connecting AVS platform component driver with rt5682 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/rt5682.c | 339 ++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 sound/soc/intel/avs/boards/rt5682.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index ae83e930aa35..f735b8c0898f 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -50,6 +50,16 @@ config SND_SOC_INTEL_AVS_MACH_RT298 Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_RT5682 + tristate "rt5682 in I2S mode" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_RT5682_I2C + help + This adds support for ASoC machine driver with RT5682 I2S audio codec. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_SSP_TEST tristate "SSP test board" help diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index d1d261192d96..a36f38ef87de 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -5,6 +5,7 @@ snd-soc-avs-hdaudio-objs := hdaudio.o snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o snd-soc-avs-rt298-objs := rt298.o +snd-soc-avs-rt5682-objs := rt5682.o snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o @@ -12,4 +13,5 @@ obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682) += snd-soc-avs-rt5682.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/rt5682.c b/sound/soc/intel/avs/boards/rt5682.c new file mode 100644 index 000000000000..25321c2d6003 --- /dev/null +++ b/sound/soc/intel/avs/boards/rt5682.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/clk.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rt5682.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../common/soc-intel-quirks.h" +#include "../../../codecs/rt5682.h" + +#define AVS_RT5682_SSP_CODEC(quirk) ((quirk) & GENMASK(2, 0)) +#define AVS_RT5682_SSP_CODEC_MASK (GENMASK(2, 0)) +#define AVS_RT5682_MCLK_EN BIT(3) +#define AVS_RT5682_MCLK_24MHZ BIT(4) + +/* Default: MCLK on, MCLK 19.2M, SSP0 */ +static unsigned long avs_rt5682_quirk = AVS_RT5682_MCLK_EN | AVS_RT5682_SSP_CODEC(0); + +static int avs_rt5682_quirk_cb(const struct dmi_system_id *id) +{ + avs_rt5682_quirk = (unsigned long)id->driver_data; + return 1; +} + +static const struct dmi_system_id avs_rt5682_quirk_table[] = { + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "WhiskeyLake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_MCLK_24MHZ | + AVS_RT5682_SSP_CODEC(1)), + }, + { + .callback = avs_rt5682_quirk_cb, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Ice Lake Client"), + }, + .driver_data = (void *)(AVS_RT5682_MCLK_EN | + AVS_RT5682_SSP_CODEC(0)), + }, + {} +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detect */ + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + /* other jacks */ + { "IN1P", NULL, "Headset Mic" }, +}; + +static int avs_rt5682_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int ret; + + jack = snd_soc_card_get_drvdata(card); + + /* Need to enable ASRC function for 24MHz mclk rate */ + if ((avs_rt5682_quirk & AVS_RT5682_MCLK_EN) && + (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ)) { + rt5682_sel_asrc_clk_src(component, RT5682_DA_STEREO1_FILTER | + RT5682_AD_STEREO1_FILTER, RT5682_CLK_SEL_I2S1_ASRC); + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, jack); + if (ret) { + dev_err(card->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(card->dev, "Headset Jack call-back failed: %d\n", ret); + return ret; + } + + return 0; +}; + +static int +avs_rt5682_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int clk_id, clk_freq; + int pll_out, ret; + + if (avs_rt5682_quirk & AVS_RT5682_MCLK_EN) { + clk_id = RT5682_PLL1_S_MCLK; + if (avs_rt5682_quirk & AVS_RT5682_MCLK_24MHZ) + clk_freq = 24000000; + else + clk_freq = 19200000; + } else { + clk_id = RT5682_PLL1_S_BCLK1; + clk_freq = params_rate(params) * 50; + } + + pll_out = params_rate(params) * 512; + + ret = snd_soc_dai_set_pll(codec_dai, 0, clk_id, clk_freq, pll_out); + if (ret < 0) + dev_err(runtime->dev, "snd_soc_dai_set_pll err = %d\n", ret); + + /* Configure sysclk for codec */ + ret = snd_soc_dai_set_sysclk(codec_dai, RT5682_SCLK_S_PLL1, pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(runtime->dev, "snd_soc_dai_set_sysclk err = %d\n", ret); + + /* slot_width should equal or large than data length, set them be the same */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0, 0x0, 2, params_width(params)); + if (ret < 0) { + dev_err(runtime->dev, "set TDM slot err:%d\n", ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops avs_rt5682_ops = { + .hw_params = avs_rt5682_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10EC5682:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "rt5682-aif1"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->init = avs_rt5682_codec_init; + dl->ops = &avs_rt5682_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + if (pdev->id_entry && pdev->id_entry->driver_data) + avs_rt5682_quirk = (unsigned long)pdev->id_entry->driver_data; + + dmi_check_system(avs_rt5682_quirk_table); + dev_dbg(dev, "avs_rt5682_quirk = %lx\n", avs_rt5682_quirk); + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_rt5682"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_rt5682_driver = { + .probe = avs_rt5682_probe, + .driver = { + .name = "avs_rt5682", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_rt5682_driver) + +MODULE_AUTHOR("Cezary Rojewski cezary.rojewski@intel.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_rt5682");
To support AVS-nau8825 configuration add machine board connecting AVS platform component driver with nau8825 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 11 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/nau8825.c | 352 +++++++++++++++++++++++++++ 3 files changed, 365 insertions(+) create mode 100644 sound/soc/intel/avs/boards/nau8825.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index f735b8c0898f..f9ecee648830 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -20,6 +20,17 @@ config SND_SOC_INTEL_AVS_MACH_HDAUDIO Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_NAU8825 + tristate "nau8825 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_NAU8825 + help + This adds support for ASoC machine driver with NAU8825 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_RT274 tristate "rt274 in I2S mode" depends on I2C diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index a36f38ef87de..ece733a96c1e 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -2,6 +2,7 @@
snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o +snd-soc-avs-nau8825-objs := nau8825.o snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o snd-soc-avs-rt298-objs := rt298.o @@ -10,6 +11,7 @@ snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825) += snd-soc-avs-nau8825.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o diff --git a/sound/soc/intel/avs/boards/nau8825.c b/sound/soc/intel/avs/boards/nau8825.c new file mode 100644 index 000000000000..970ccfed9392 --- /dev/null +++ b/sound/soc/intel/avs/boards/nau8825.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" + +static int +avs_nau8825_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (!SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(card->dev, "set sysclk err = %d\n", ret); + return ret; + } + } + + return 0; +} + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, avs_nau8825_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Headphone Jack", NULL, "HPOL" }, + { "Headphone Jack", NULL, "HPOR" }, + + { "MIC", NULL, "Headset Mic" }, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static struct snd_soc_jack_pin card_headset_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, + { + .pin = "Headset Mic", + .mask = SND_JACK_MICROPHONE, + }, +}; + +static int avs_nau8825_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + struct snd_soc_component *component = codec_dai->component; + struct snd_soc_jack_pin *pins; + struct snd_soc_jack *jack; + struct snd_soc_card *card = runtime->card; + int num_pins, ret; + + jack = snd_soc_card_get_drvdata(card); + num_pins = ARRAY_SIZE(card_headset_pins); + + pins = devm_kmemdup(card->dev, card_headset_pins, sizeof(*pins) * num_pins, GFP_KERNEL); + if (!pins) + return -ENOMEM; + + /* + * 4 buttons here map to the google Reference headset. + * The use of these buttons can be decided by the user space. + */ + ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | SND_JACK_BTN_3, + jack, pins, num_pins); + if (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); + + //snd_soc_component_set_jack(component, jack, NULL); + // TODO: Fix nau8825 codec to use .set_jack, like everyone else + nau8825_enable_jack_detect(component, jack); + + return 0; +} + +static int +avs_nau8825_be_fixup(struct snd_soc_pcm_runtime *runtime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will convert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + + return 0; +} + +static int avs_nau8825_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtm = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtm, 0); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "can't set FS clock %d\n", ret); + break; + } + + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, runtime->rate, runtime->rate * 256); + if (ret < 0) + dev_err(codec_dai->dev, "can't set FLL: %d\n", ret); + break; + } + + return ret; +} + + +static const struct snd_soc_ops avs_nau8825_ops = { + .trigger = avs_nau8825_trigger, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-10508825:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, SKL_NUVOTON_CODEC_DAI); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_nau8825_codec_init; + dl->be_hw_params_fixup = avs_nau8825_be_fixup; + dl->ops = &avs_nau8825_ops; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (codec_dai->stream_active[SNDRV_PCM_STREAM_PLAYBACK] && + codec_dai->playback_widget->active) + snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_FLL_FS, 0, SND_SOC_CLOCK_IN); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_nau8825"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_nau8825_driver = { + .probe = avs_nau8825_probe, + .driver = { + .name = "avs_nau8825", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_nau8825_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_nau8825");
To support AVS-ssm4567 configuration add machine board connecting AVS platform component driver with ssm4567 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 11 ++ sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/ssm4567.c | 270 +++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 sound/soc/intel/avs/boards/ssm4567.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index f9ecee648830..8af85953adac 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -71,6 +71,17 @@ config SND_SOC_INTEL_AVS_MACH_RT5682 Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_SSM4567 + tristate "ssm4567 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_SSM4567 + help + This adds support for ASoC machine driver with SSM4567 I2S audio codec. + It is meant to be used with AVS driver. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_SSP_TEST tristate "SSP test board" help diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index ece733a96c1e..30d0eac6c22a 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -7,6 +7,7 @@ snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o snd-soc-avs-rt298-objs := rt298.o snd-soc-avs-rt5682-objs := rt5682.o +snd-soc-avs-ssm4567-objs := ssm4567.o snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o @@ -16,4 +17,5 @@ obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT298) += snd-soc-avs-rt298.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682) += snd-soc-avs-rt5682.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567) += snd-soc-avs-ssm4567.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSP_TEST) += snd-soc-avs-ssp-test.o diff --git a/sound/soc/intel/avs/boards/ssm4567.c b/sound/soc/intel/avs/boards/ssm4567.c new file mode 100644 index 000000000000..d17f36701bc3 --- /dev/null +++ b/sound/soc/intel/avs/boards/ssm4567.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include "../../../codecs/nau8825.h" + +#define SKL_NUVOTON_CODEC_DAI "nau8825-hifi" +#define SKL_SSM_CODEC_DAI "ssm4567-hifi" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:00"), + .name_prefix = "Left", + }, + { + .dlc = COMP_CODEC_CONF("i2c-INT343B:01"), + .name_prefix = "Right", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Speaker"), + SOC_DAPM_PIN_SWITCH("Right Speaker"), +}; + +static int +platform_clock_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *control, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret; + + codec_dai = snd_soc_card_get_codec_dai(card, SKL_NUVOTON_CODEC_DAI); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found\n"); + return -EINVAL; + } + + if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_MCLK, 24000000, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "set sysclk err = %d\n", ret); + } else { + ret = snd_soc_dai_set_sysclk(codec_dai, NAU8825_CLK_INTERNAL, 0, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(card->dev, "set sysclk err = %d\n", ret); + } + + return ret; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Speaker", NULL), + SND_SOC_DAPM_SPK("Right Speaker", NULL), + SND_SOC_DAPM_SPK("DP1", NULL), + SND_SOC_DAPM_SPK("DP2", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + {"Left Speaker", NULL, "Left OUT"}, + {"Right Speaker", NULL, "Right OUT"}, +}; + +static int avs_ssm4567_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + int ret; + + /* Slot 1 for left */ + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(runtime, 0), 0x01, 0x01, 2, 48); + if (ret < 0) + return ret; + + /* Slot 2 for right */ + ret = snd_soc_dai_set_tdm_slot(asoc_rtd_to_codec(runtime, 1), 0x02, 0x02, 2, 48); + if (ret < 0) + return ret; + + return 0; +} + +static int +avs_ssm4567_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 24 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE); + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) * 2, GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:00"); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, "i2c-INT343B:01"); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, "ssm4567-hifi"); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_ssm4567_codec_init; + dl->be_hw_params_fixup = avs_ssm4567_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + dl->ignore_pmdown_time = 1; + + *dai_link = dl; + + 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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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; + + card->name = "avs_ssm4567-adi"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + 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->fully_routed = true; + card->disable_route_checks = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_ssm4567_driver = { + .probe = avs_ssm4567_probe, + .driver = { + .name = "avs_ssm4567", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_ssm4567_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_ssm4567");
To support AVS-max98357a configuration add machine board connecting AVS platform component driver with max98357a codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 ++ sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/max98357a.c | 153 +++++++++++++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 sound/soc/intel/avs/boards/max98357a.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index 8af85953adac..6865dc904326 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -20,6 +20,16 @@ config SND_SOC_INTEL_AVS_MACH_HDAUDIO Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_MAX98357A + tristate "max98357A I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98357A + help + This adds support for AVS with MAX98357A I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_NAU8825 tristate "nau8825 I2S board" depends on I2C diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index 30d0eac6c22a..ae890ad747ba 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -2,6 +2,7 @@
snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o +snd-soc-avs-max98357a-objs := max98357a.o snd-soc-avs-nau8825-objs := nau8825.o snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o @@ -12,6 +13,7 @@ snd-soc-avs-ssp-test-objs := ssp_test.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A) += snd-soc-avs-max98357a.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825) += snd-soc-avs-nau8825.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o diff --git a/sound/soc/intel/avs/boards/max98357a.c b/sound/soc/intel/avs/boards/max98357a.c new file mode 100644 index 000000000000..68ac47c675bd --- /dev/null +++ b/sound/soc/intel/avs/boards/max98357a.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Spk", NULL, "Speaker" }, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "MX98357A:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "HiFi"); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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; + + card->name = "avs_max98357a"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_max98357a_driver = { + .probe = avs_max98357a_probe, + .driver = { + .name = "avs_max98357a", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_max98357a_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_max98357a");
From: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com
To support AVS-max98373 configuration add machine board connecting AVS platform component driver with max98373 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 ++ sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/max98373.c | 238 ++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 sound/soc/intel/avs/boards/max98373.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index 6865dc904326..38a76f486d91 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -30,6 +30,16 @@ config SND_SOC_INTEL_AVS_MACH_MAX98357A Say Y or m if you have such a device. This is a recommended option. If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_MAX98373 + tristate "max98373 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_MAX98373 + help + This adds support for AVS with MAX98373 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_NAU8825 tristate "nau8825 I2S board" depends on I2C diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index ae890ad747ba..a9e27638d23c 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -3,6 +3,7 @@ snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o snd-soc-avs-max98357a-objs := max98357a.o +snd-soc-avs-max98373-objs := max98373.o snd-soc-avs-nau8825-objs := nau8825.o snd-soc-avs-rt274-objs := rt274.o snd-soc-avs-rt286-objs := rt286.o @@ -14,6 +15,7 @@ snd-soc-avs-ssp-test-objs := ssp_test.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A) += snd-soc-avs-max98357a.o +obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98373) += snd-soc-avs-max98373.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_NAU8825) += snd-soc-avs-nau8825.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT274) += snd-soc-avs-rt274.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT286) += snd-soc-avs-rt286.o diff --git a/sound/soc/intel/avs/boards/max98373.c b/sound/soc/intel/avs/boards/max98373.c new file mode 100644 index 000000000000..ced3549291f2 --- /dev/null +++ b/sound/soc/intel/avs/boards/max98373.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2022 Intel Corporation. All rights reserved. +// +// Authors: Cezary Rojewski cezary.rojewski@intel.com +// Amadeusz Slawinski amadeuszx.slawinski@linux.intel.com +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> + +#define MAX98373_DEV0_NAME "i2c-MX98373:00" +#define MAX98373_DEV1_NAME "i2c-MX98373:01" +#define MAX98373_CODEC_NAME "max98373-aif1" + +static struct snd_soc_codec_conf card_codec_conf[] = { + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV0_NAME), + .name_prefix = "Right", + }, + { + .dlc = COMP_CODEC_CONF(MAX98373_DEV1_NAME), + .name_prefix = "Left", + }, +}; + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Left Spk"), + SOC_DAPM_PIN_SWITCH("Right Spk"), +}; + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + { "Left Spk", NULL, "Left BE_OUT" }, + { "Right Spk", NULL, "Right BE_OUT" }, +}; + +static int +avs_max98373_be_fixup(struct snd_soc_pcm_runtime *runrime, struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate, *channels; + struct snd_mask *fmt; + + rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + /* The ADSP will covert the FE rate to 48k, stereo */ + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + /* set SSP0 to 16 bit */ + snd_mask_none(fmt); + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + return 0; +} + +static int avs_max98373_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai; + int ret, i; + + for_each_rtd_codec_dais(runtime, i, codec_dai) { + if (!strcmp(codec_dai->component->name, MAX98373_DEV0_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x30, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV0 TDM slot err:%d\n", ret); + return ret; + } + } + if (!strcmp(codec_dai->component->name, MAX98373_DEV1_NAME)) { + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xC0, 3, 8, 16); + if (ret < 0) { + dev_err(runtime->dev, "DEV1 TDM slot err:%d\n", ret); + return ret; + } + } + } + + return 0; +} + +static const struct snd_soc_ops avs_max98373_ops = { + .hw_params = avs_max98373_hw_params, +}; + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) * 2, GFP_KERNEL); + if (!dl->name || !dl->cpus || !dl->codecs) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs[0].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV0_NAME); + dl->codecs[0].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + dl->codecs[1].name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_DEV1_NAME); + dl->codecs[1].dai_name = devm_kasprintf(dev, GFP_KERNEL, MAX98373_CODEC_NAME); + if (!dl->cpus->dai_name || !dl->codecs[0].name || !dl->codecs[0].dai_name || + !dl->codecs[1].name || !dl->codecs[1].dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 2; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC; + dl->be_hw_params_fixup = avs_max98373_be_fixup; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + dl->ignore_pmdown_time = 1; + dl->ops = &avs_max98373_ops; + + *dai_link = dl; + + 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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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; + + card->name = "avs_max98373"; + card->dev = dev; + card->owner = THIS_MODULE; + card->dai_link = dai_link; + card->num_links = 1; + card->codec_conf = card_codec_conf; + card->num_configs = ARRAY_SIZE(card_codec_conf); + card->controls = card_controls; + 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->fully_routed = true; + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_max98373_driver = { + .probe = avs_max98373_probe, + .driver = { + .name = "avs_max98373", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_max98373_driver) + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_max98373");
To support AVS-da7219 configuration add machine board connecting AVS platform component driver with da7219 codec one.
Signed-off-by: Amadeusz Sławiński amadeuszx.slawinski@linux.intel.com Signed-off-by: Cezary Rojewski cezary.rojewski@intel.com --- sound/soc/intel/avs/boards/Kconfig | 10 + sound/soc/intel/avs/boards/Makefile | 2 + sound/soc/intel/avs/boards/da7219.c | 281 ++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 sound/soc/intel/avs/boards/da7219.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig index 38a76f486d91..3a24cc9ec64d 100644 --- a/sound/soc/intel/avs/boards/Kconfig +++ b/sound/soc/intel/avs/boards/Kconfig @@ -4,6 +4,16 @@ menu "Intel AVS Machine drivers"
comment "Available DSP configurations"
+config SND_SOC_INTEL_AVS_MACH_DA7219 + tristate "da7219 I2S board" + depends on I2C + depends on MFD_INTEL_LPSS || COMPILE_TEST + select SND_SOC_DA7219 + help + This adds support for AVS with DA7219 I2S codec configuration. + Say Y or m if you have such a device. This is a recommended option. + If unsure select "N". + config SND_SOC_INTEL_AVS_MACH_DMIC tristate "DMIC generic board" select SND_SOC_DMIC diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile index a9e27638d23c..5d8ddd1e08c5 100644 --- a/sound/soc/intel/avs/boards/Makefile +++ b/sound/soc/intel/avs/boards/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only
+snd-soc-avs-da7219-objs := da7219.o snd-soc-avs-dmic-objs := dmic.o snd-soc-avs-hdaudio-objs := hdaudio.o snd-soc-avs-max98357a-objs := max98357a.o @@ -12,6 +13,7 @@ snd-soc-avs-rt5682-objs := rt5682.o snd-soc-avs-ssm4567-objs := ssm4567.o snd-soc-avs-ssp-test-objs := ssp_test.o
+obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219) += snd-soc-avs-da7219.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_HDAUDIO) += snd-soc-avs-hdaudio.o obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_MAX98357A) += snd-soc-avs-max98357a.o diff --git a/sound/soc/intel/avs/boards/da7219.c b/sound/soc/intel/avs/boards/da7219.c new file mode 100644 index 000000000000..80ef0642649b --- /dev/null +++ b/sound/soc/intel/avs/boards/da7219.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright(c) 2021-2022 Intel Corporation. All rights reserved. +// +// Author: Cezary Rojewski cezary.rojewski@intel.com +// + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-acpi.h> +#include <sound/soc-dapm.h> +#include <uapi/linux/input-event-codes.h> +#include "../../../codecs/da7219.h" +#include "../../../codecs/da7219-aad.h" + +#define DA7219_DAI_NAME "da7219-hifi" + +static const struct snd_kcontrol_new card_controls[] = { + SOC_DAPM_PIN_SWITCH("Headphone Jack"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), +}; + +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct snd_soc_dai *codec_dai; + int ret = 0; + + codec_dai = snd_soc_card_get_codec_dai(card, DA7219_DAI_NAME); + if (!codec_dai) { + dev_err(card->dev, "Codec dai not found. Unable to set/unset codec pll\n"); + return -EIO; + } + + if (SND_SOC_DAPM_EVENT_OFF(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_MCLK, 0, 0); + if (ret) + dev_err(card->dev, "failed to stop PLL: %d\n", ret); + } else if (SND_SOC_DAPM_EVENT_ON(event)) { + ret = snd_soc_dai_set_pll(codec_dai, 0, DA7219_SYSCLK_PLL_SRM, + 0, DA7219_PLL_FREQ_OUT_98304); + if (ret) + dev_err(card->dev, "failed to start PLL: %d\n", ret); + } + + return ret; +} + +static const struct snd_soc_dapm_widget card_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, platform_clock_control, + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_PRE_PMU), +}; + +static const struct snd_soc_dapm_route card_base_routes[] = { + /* HP jack connectors - unknown if we have jack detection */ + {"Headphone Jack", NULL, "HPL"}, + {"Headphone Jack", NULL, "HPR"}, + + {"MIC", NULL, "Headset Mic"}, + + { "Headphone Jack", NULL, "Platform Clock" }, + { "Headset Mic", NULL, "Platform Clock" }, +}; + +static int avs_da7219_codec_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component; + struct snd_soc_card *card = runtime->card; + struct snd_soc_jack *jack; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(runtime, 0); + int clk_freq; + int ret; + + jack = snd_soc_card_get_drvdata(card); + clk_freq = 19200000; + + ret = snd_soc_dai_set_sysclk(codec_dai, DA7219_CLKSRC_MCLK, clk_freq, SND_SOC_CLOCK_IN); + if (ret) { + dev_err(card->dev, "can't set codec sysclk configuration\n"); + return ret; + } + + /* + * Headset buttons map to the google Reference headset. + * These can be configured by userspace. + */ + ret = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2 | + SND_JACK_BTN_3 | SND_JACK_LINEOUT, jack); + if (ret) { + dev_err(card->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_VOLUMEUP); + snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEDOWN); + snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOICECOMMAND); + + da7219_aad_jack_det(component, jack); + + return 0; +} + +static int avs_create_dai_link(struct device *dev, const char *platform_name, int ssp_port, + struct snd_soc_dai_link **dai_link) +{ + struct snd_soc_dai_link_component *platform; + struct snd_soc_dai_link *dl; + + dl = devm_kzalloc(dev, sizeof(*dl), GFP_KERNEL); + platform = devm_kzalloc(dev, sizeof(*platform), GFP_KERNEL); + if (!dl || !platform) + return -ENOMEM; + + platform->name = platform_name; + + dl->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) + return -ENOMEM; + + dl->cpus->dai_name = devm_kasprintf(dev, GFP_KERNEL, "SSP%d Pin", ssp_port); + dl->codecs->name = devm_kasprintf(dev, GFP_KERNEL, "i2c-DLGS7219:00"); + dl->codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, DA7219_DAI_NAME); + if (!dl->cpus->dai_name || !dl->codecs->name || !dl->codecs->dai_name) + return -ENOMEM; + + dl->num_cpus = 1; + dl->num_codecs = 1; + dl->platforms = platform; + dl->num_platforms = 1; + dl->id = 0; + dl->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS; + dl->init = avs_da7219_codec_init; + dl->nonatomic = 1; + dl->no_pcm = 1; + dl->dpcm_capture = 1; + dl->dpcm_playback = 1; + + *dai_link = dl; + + 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_set_jack(struct snd_soc_card *card, struct snd_soc_jack *jack) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) + snd_soc_component_set_jack(component, jack, NULL); + return 0; +} + +static int avs_card_remove(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_suspend_pre(struct snd_soc_card *card) +{ + return avs_card_set_jack(card, NULL); +} + +static int avs_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_jack *jack = snd_soc_card_get_drvdata(card); + + return avs_card_set_jack(card, jack); +} + +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 ret, num_routes; + + mach = dev_get_platdata(dev); + pname = mach->mach_params.platform; + + ret = avs_create_dai_link(dev, pname, __ffs(mach->link_mask), &dai_link); + if (ret) { + dev_err(dev, "Failed to create dai link: %d", ret); + return ret; + } + + ret = avs_create_dapm_routes(dev, __ffs(mach->link_mask), &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) + return -ENOMEM; + + card->name = "avs_da7219"; + card->dev = dev; + card->owner = THIS_MODULE; + card->remove = avs_card_remove; + card->suspend_pre = avs_card_suspend_pre; + card->resume_post = avs_card_resume_post; + card->dai_link = dai_link; + card->num_links = 1; + card->controls = card_controls; + 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->fully_routed = true; + snd_soc_card_set_drvdata(card, jack); + + ret = snd_soc_fixup_dai_links_platform_name(card, pname); + if (ret) + return ret; + + return devm_snd_soc_register_card(dev, card); +} + +static struct platform_driver avs_da7219_driver = { + .probe = avs_da7219_probe, + .driver = { + .name = "avs_da7219", + .pm = &snd_soc_pm_ops, + }, +}; + +module_platform_driver(avs_da7219_driver); + +MODULE_AUTHOR("Cezary Rojewski cezary.rojewski@intel.com"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:avs_da7219");
participants (4)
-
Cezary Rojewski
-
Kai Vehmanen
-
Mark Brown
-
Pierre-Louis Bossart