Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | // SPDX-License-Identifier: GPL-2.0-only /* * This file defines data structures and functions used in Machine * Driver for Intel platforms with Cirrus Logic Codecs. * * Copyright 2022 Intel Corporation. */ #include <linux/module.h> #include <sound/sof.h> #include "../../codecs/cs35l41.h" #include "sof_cirrus_common.h" #define CS35L41_HID "CSC3541" #define CS35L41_MAX_AMPS 4 /* * Cirrus Logic CS35L41/CS35L53 */ static const struct snd_kcontrol_new cs35l41_kcontrols[] = { SOC_DAPM_PIN_SWITCH("WL Spk"), SOC_DAPM_PIN_SWITCH("WR Spk"), SOC_DAPM_PIN_SWITCH("TL Spk"), SOC_DAPM_PIN_SWITCH("TR Spk"), }; static const struct snd_soc_dapm_widget cs35l41_dapm_widgets[] = { SND_SOC_DAPM_SPK("WL Spk", NULL), SND_SOC_DAPM_SPK("WR Spk", NULL), SND_SOC_DAPM_SPK("TL Spk", NULL), SND_SOC_DAPM_SPK("TR Spk", NULL), }; static const struct snd_soc_dapm_route cs35l41_dapm_routes[] = { /* speaker */ {"WL Spk", NULL, "WL SPK"}, {"WR Spk", NULL, "WR SPK"}, {"TL Spk", NULL, "TL SPK"}, {"TR Spk", NULL, "TR SPK"}, }; static struct snd_soc_dai_link_component cs35l41_components[CS35L41_MAX_AMPS]; /* * Mapping between ACPI instance id and speaker position. */ static struct snd_soc_codec_conf cs35l41_codec_conf[CS35L41_MAX_AMPS]; static int cs35l41_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; int ret; ret = snd_soc_dapm_new_controls(&card->dapm, cs35l41_dapm_widgets, ARRAY_SIZE(cs35l41_dapm_widgets)); if (ret) { dev_err(rtd->dev, "fail to add dapm controls, ret %d\n", ret); return ret; } ret = snd_soc_add_card_controls(card, cs35l41_kcontrols, ARRAY_SIZE(cs35l41_kcontrols)); if (ret) { dev_err(rtd->dev, "fail to add card controls, ret %d\n", ret); return ret; } ret = snd_soc_dapm_add_routes(&card->dapm, cs35l41_dapm_routes, ARRAY_SIZE(cs35l41_dapm_routes)); if (ret) dev_err(rtd->dev, "fail to add dapm routes, ret %d\n", ret); return ret; } /* * Channel map: * * TL/WL: ASPRX1 on slot 0, ASPRX2 on slot 1 (default) * TR/WR: ASPRX1 on slot 1, ASPRX2 on slot 0 */ static const struct { unsigned int rx[2]; } cs35l41_channel_map[] = { {.rx = {0, 1}}, /* WL */ {.rx = {1, 0}}, /* WR */ {.rx = {0, 1}}, /* TL */ {.rx = {1, 0}}, /* TR */ }; static int cs35l41_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); struct snd_soc_dai *codec_dai; int clk_freq, i, ret; clk_freq = sof_dai_get_bclk(rtd); /* BCLK freq */ if (clk_freq <= 0) { dev_err(rtd->dev, "fail to get bclk freq, ret %d\n", clk_freq); return -EINVAL; } for_each_rtd_codec_dais(rtd, i, codec_dai) { /* call dai driver's set_sysclk() callback */ ret = snd_soc_dai_set_sysclk(codec_dai, CS35L41_CLKID_SCLK, clk_freq, SND_SOC_CLOCK_IN); if (ret < 0) { dev_err(codec_dai->dev, "fail to set sysclk, ret %d\n", ret); return ret; } /* call component driver's set_sysclk() callback */ ret = snd_soc_component_set_sysclk(codec_dai->component, CS35L41_CLKID_SCLK, 0, clk_freq, SND_SOC_CLOCK_IN); if (ret < 0) { dev_err(codec_dai->dev, "fail to set component sysclk, ret %d\n", ret); return ret; } /* setup channel map */ ret = snd_soc_dai_set_channel_map(codec_dai, 0, NULL, ARRAY_SIZE(cs35l41_channel_map[i].rx), (unsigned int *)cs35l41_channel_map[i].rx); if (ret < 0) { dev_err(codec_dai->dev, "fail to set channel map, ret %d\n", ret); return ret; } } return 0; } static const struct snd_soc_ops cs35l41_ops = { .hw_params = cs35l41_hw_params, }; static const char * const cs35l41_name_prefixes[] = { "WL", "WR", "TL", "TR" }; /* * Expected UIDs are integers (stored as strings). * UID Mapping is fixed: * UID 0x0 -> WL * UID 0x1 -> WR * UID 0x2 -> TL * UID 0x3 -> TR * Note: If there are less than 4 Amps, UIDs still map to WL/WR/TL/TR. Dynamic code will only create * dai links for UIDs which exist, and ignore non-existant ones. Only 2 or 4 amps are expected. * Return number of codecs found. */ static int cs35l41_compute_codec_conf(void) { static const char * const uid_strings[] = { "0", "1", "2", "3" }; unsigned int uid, sz = 0; struct acpi_device *adev; struct device *physdev; for (uid = 0; uid < CS35L41_MAX_AMPS; uid++) { adev = acpi_dev_get_first_match_dev(CS35L41_HID, uid_strings[uid], -1); if (!adev) { pr_devel("Cannot find match for HID %s UID %u (%s)\n", CS35L41_HID, uid, cs35l41_name_prefixes[uid]); continue; } physdev = get_device(acpi_get_first_physical_node(adev)); cs35l41_components[sz].name = dev_name(physdev); cs35l41_components[sz].dai_name = CS35L41_CODEC_DAI; cs35l41_codec_conf[sz].dlc.name = dev_name(physdev); cs35l41_codec_conf[sz].name_prefix = cs35l41_name_prefixes[uid]; acpi_dev_put(adev); sz++; } if (sz != 2 && sz != 4) pr_warn("Invalid number of cs35l41 amps found: %d, expected 2 or 4\n", sz); return sz; } void cs35l41_set_dai_link(struct snd_soc_dai_link *link) { link->num_codecs = cs35l41_compute_codec_conf(); link->codecs = cs35l41_components; link->init = cs35l41_init; link->ops = &cs35l41_ops; } EXPORT_SYMBOL_NS(cs35l41_set_dai_link, SND_SOC_INTEL_SOF_CIRRUS_COMMON); void cs35l41_set_codec_conf(struct snd_soc_card *card) { card->codec_conf = cs35l41_codec_conf; card->num_configs = ARRAY_SIZE(cs35l41_codec_conf); } EXPORT_SYMBOL_NS(cs35l41_set_codec_conf, SND_SOC_INTEL_SOF_CIRRUS_COMMON); MODULE_DESCRIPTION("ASoC Intel SOF Cirrus Logic helpers"); MODULE_LICENSE("GPL"); |