// SPDX-License-Identifier: GPL-2.0 // // Copyright(c) 2021-2022 Intel Corporation. All rights reserved. // // Author: Cezary Rojewski // #include #include #include #include #include #include #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) { int stream; list_for_each_entry(pcm, &codec->pcm_list_head, list) { if (strcmp(dai->driver->name, pcm->name)) continue; for_each_pcm_streams(stream) snd_soc_dapm_free_widget(snd_soc_dai_get_widget(dai, stream)); 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_get_hlink_by_addr(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; } 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->core.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_get_hlink_by_addr(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->core.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 "); MODULE_LICENSE("GPL");