Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * tegra_asoc_machine.c - Universal ASoC machine driver for NVIDIA Tegra boards. |
| 4 | */ |
| 5 | |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 6 | #include <linux/clk.h> |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 7 | #include <linux/export.h> |
| 8 | #include <linux/gpio/consumer.h> |
| 9 | #include <linux/module.h> |
| 10 | #include <linux/of.h> |
| 11 | #include <linux/of_device.h> |
| 12 | #include <linux/platform_device.h> |
| 13 | #include <linux/slab.h> |
| 14 | |
| 15 | #include <sound/core.h> |
| 16 | #include <sound/jack.h> |
| 17 | #include <sound/pcm.h> |
| 18 | #include <sound/pcm_params.h> |
| 19 | #include <sound/soc.h> |
| 20 | |
| 21 | #include "tegra_asoc_machine.h" |
| 22 | |
| 23 | /* Headphones Jack */ |
| 24 | |
| 25 | static struct snd_soc_jack tegra_machine_hp_jack; |
| 26 | |
| 27 | static struct snd_soc_jack_pin tegra_machine_hp_jack_pins[] = { |
| 28 | { .pin = "Headphone", .mask = SND_JACK_HEADPHONE }, |
| 29 | { .pin = "Headphones", .mask = SND_JACK_HEADPHONE }, |
| 30 | }; |
| 31 | |
| 32 | static struct snd_soc_jack_gpio tegra_machine_hp_jack_gpio = { |
| 33 | .name = "Headphones detection", |
| 34 | .report = SND_JACK_HEADPHONE, |
| 35 | .debounce_time = 150, |
| 36 | }; |
| 37 | |
| 38 | /* Headset Jack */ |
| 39 | |
| 40 | static struct snd_soc_jack tegra_machine_headset_jack; |
| 41 | |
| 42 | static struct snd_soc_jack_pin tegra_machine_headset_jack_pins[] = { |
| 43 | { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE }, |
| 44 | { .pin = "Headset Stereophone", .mask = SND_JACK_HEADPHONE }, |
| 45 | }; |
| 46 | |
| 47 | static struct snd_soc_jack_gpio tegra_machine_headset_jack_gpio = { |
| 48 | .name = "Headset detection", |
| 49 | .report = SND_JACK_HEADSET, |
| 50 | .debounce_time = 150, |
| 51 | }; |
| 52 | |
| 53 | /* Mic Jack */ |
| 54 | |
| 55 | static struct snd_soc_jack tegra_machine_mic_jack; |
| 56 | |
| 57 | static struct snd_soc_jack_pin tegra_machine_mic_jack_pins[] = { |
| 58 | { .pin = "Mic Jack", .mask = SND_JACK_MICROPHONE }, |
| 59 | { .pin = "Headset Mic", .mask = SND_JACK_MICROPHONE }, |
| 60 | }; |
| 61 | |
| 62 | static struct snd_soc_jack_gpio tegra_machine_mic_jack_gpio = { |
| 63 | .name = "Mic detection", |
| 64 | .report = SND_JACK_MICROPHONE, |
| 65 | .debounce_time = 150, |
| 66 | }; |
| 67 | |
| 68 | static int tegra_machine_event(struct snd_soc_dapm_widget *w, |
| 69 | struct snd_kcontrol *k, int event) |
| 70 | { |
| 71 | struct snd_soc_dapm_context *dapm = w->dapm; |
| 72 | struct tegra_machine *machine = snd_soc_card_get_drvdata(dapm->card); |
| 73 | |
| 74 | if (!strcmp(w->name, "Int Spk") || !strcmp(w->name, "Speakers")) |
| 75 | gpiod_set_value_cansleep(machine->gpiod_spkr_en, |
| 76 | SND_SOC_DAPM_EVENT_ON(event)); |
| 77 | |
| 78 | if (!strcmp(w->name, "Mic Jack")) |
| 79 | gpiod_set_value_cansleep(machine->gpiod_ext_mic_en, |
| 80 | SND_SOC_DAPM_EVENT_ON(event)); |
| 81 | |
| 82 | if (!strcmp(w->name, "Int Mic")) |
| 83 | gpiod_set_value_cansleep(machine->gpiod_int_mic_en, |
| 84 | SND_SOC_DAPM_EVENT_ON(event)); |
| 85 | |
| 86 | if (!strcmp(w->name, "Headphone") || !strcmp(w->name, "Headphone Jack")) |
| 87 | gpiod_set_value_cansleep(machine->gpiod_hp_mute, |
| 88 | !SND_SOC_DAPM_EVENT_ON(event)); |
| 89 | |
| 90 | return 0; |
| 91 | } |
| 92 | |
| 93 | static const struct snd_soc_dapm_widget tegra_machine_dapm_widgets[] = { |
| 94 | SND_SOC_DAPM_HP("Headphone Jack", tegra_machine_event), |
| 95 | SND_SOC_DAPM_HP("Headphone", tegra_machine_event), |
| 96 | SND_SOC_DAPM_HP("Headset Stereophone", NULL), |
| 97 | SND_SOC_DAPM_HP("Headphones", NULL), |
| 98 | SND_SOC_DAPM_SPK("Speakers", tegra_machine_event), |
| 99 | SND_SOC_DAPM_SPK("Int Spk", tegra_machine_event), |
| 100 | SND_SOC_DAPM_MIC("Int Mic", tegra_machine_event), |
| 101 | SND_SOC_DAPM_MIC("Mic Jack", tegra_machine_event), |
| 102 | SND_SOC_DAPM_MIC("Internal Mic 1", NULL), |
| 103 | SND_SOC_DAPM_MIC("Internal Mic 2", NULL), |
| 104 | SND_SOC_DAPM_MIC("Headset Mic", NULL), |
| 105 | SND_SOC_DAPM_MIC("Digital Mic", NULL), |
| 106 | SND_SOC_DAPM_MIC("Mic", NULL), |
| 107 | SND_SOC_DAPM_LINE("Line In Jack", NULL), |
| 108 | SND_SOC_DAPM_LINE("Line In", NULL), |
| 109 | SND_SOC_DAPM_LINE("LineIn", NULL), |
| 110 | }; |
| 111 | |
| 112 | static const struct snd_kcontrol_new tegra_machine_controls[] = { |
| 113 | SOC_DAPM_PIN_SWITCH("Speakers"), |
| 114 | SOC_DAPM_PIN_SWITCH("Int Spk"), |
| 115 | SOC_DAPM_PIN_SWITCH("Int Mic"), |
| 116 | SOC_DAPM_PIN_SWITCH("Headset Mic"), |
| 117 | SOC_DAPM_PIN_SWITCH("Internal Mic 1"), |
| 118 | SOC_DAPM_PIN_SWITCH("Internal Mic 2"), |
| 119 | }; |
| 120 | |
| 121 | int tegra_asoc_machine_init(struct snd_soc_pcm_runtime *rtd) |
| 122 | { |
| 123 | struct snd_soc_card *card = rtd->card; |
| 124 | struct tegra_machine *machine = snd_soc_card_get_drvdata(card); |
| 125 | int err; |
| 126 | |
| 127 | if (machine->gpiod_hp_det && machine->asoc->add_hp_jack) { |
| 128 | err = snd_soc_card_jack_new(card, "Headphones Jack", |
| 129 | SND_JACK_HEADPHONE, |
| 130 | &tegra_machine_hp_jack, |
| 131 | tegra_machine_hp_jack_pins, |
| 132 | ARRAY_SIZE(tegra_machine_hp_jack_pins)); |
| 133 | if (err) { |
| 134 | dev_err(rtd->dev, |
| 135 | "Headphones Jack creation failed: %d\n", err); |
| 136 | return err; |
| 137 | } |
| 138 | |
| 139 | tegra_machine_hp_jack_gpio.desc = machine->gpiod_hp_det; |
| 140 | |
| 141 | err = snd_soc_jack_add_gpios(&tegra_machine_hp_jack, 1, |
| 142 | &tegra_machine_hp_jack_gpio); |
| 143 | if (err) |
| 144 | dev_err(rtd->dev, "HP GPIOs not added: %d\n", err); |
| 145 | } |
| 146 | |
| 147 | if (machine->gpiod_hp_det && machine->asoc->add_headset_jack) { |
| 148 | err = snd_soc_card_jack_new(card, "Headset Jack", |
| 149 | SND_JACK_HEADSET, |
| 150 | &tegra_machine_headset_jack, |
| 151 | tegra_machine_headset_jack_pins, |
| 152 | ARRAY_SIZE(tegra_machine_headset_jack_pins)); |
| 153 | if (err) { |
| 154 | dev_err(rtd->dev, |
| 155 | "Headset Jack creation failed: %d\n", err); |
| 156 | return err; |
| 157 | } |
| 158 | |
| 159 | tegra_machine_headset_jack_gpio.desc = machine->gpiod_hp_det; |
| 160 | |
| 161 | err = snd_soc_jack_add_gpios(&tegra_machine_headset_jack, 1, |
| 162 | &tegra_machine_headset_jack_gpio); |
| 163 | if (err) |
| 164 | dev_err(rtd->dev, "Headset GPIOs not added: %d\n", err); |
| 165 | } |
| 166 | |
| 167 | if (machine->gpiod_mic_det && machine->asoc->add_mic_jack) { |
| 168 | err = snd_soc_card_jack_new(rtd->card, "Mic Jack", |
| 169 | SND_JACK_MICROPHONE, |
| 170 | &tegra_machine_mic_jack, |
| 171 | tegra_machine_mic_jack_pins, |
| 172 | ARRAY_SIZE(tegra_machine_mic_jack_pins)); |
| 173 | if (err) { |
| 174 | dev_err(rtd->dev, "Mic Jack creation failed: %d\n", err); |
| 175 | return err; |
| 176 | } |
| 177 | |
| 178 | tegra_machine_mic_jack_gpio.desc = machine->gpiod_mic_det; |
| 179 | |
| 180 | err = snd_soc_jack_add_gpios(&tegra_machine_mic_jack, 1, |
| 181 | &tegra_machine_mic_jack_gpio); |
| 182 | if (err) |
| 183 | dev_err(rtd->dev, "Mic GPIOs not added: %d\n", err); |
| 184 | } |
| 185 | |
| 186 | return 0; |
| 187 | } |
| 188 | EXPORT_SYMBOL_GPL(tegra_asoc_machine_init); |
| 189 | |
| 190 | static unsigned int tegra_machine_mclk_rate_128(unsigned int srate) |
| 191 | { |
| 192 | return 128 * srate; |
| 193 | } |
| 194 | |
| 195 | static unsigned int tegra_machine_mclk_rate_256(unsigned int srate) |
| 196 | { |
| 197 | return 256 * srate; |
| 198 | } |
| 199 | |
| 200 | static unsigned int tegra_machine_mclk_rate_512(unsigned int srate) |
| 201 | { |
| 202 | return 512 * srate; |
| 203 | } |
| 204 | |
| 205 | static unsigned int tegra_machine_mclk_rate_12mhz(unsigned int srate) |
| 206 | { |
| 207 | unsigned int mclk; |
| 208 | |
| 209 | switch (srate) { |
| 210 | case 8000: |
| 211 | case 16000: |
| 212 | case 24000: |
| 213 | case 32000: |
| 214 | case 48000: |
| 215 | case 64000: |
| 216 | case 96000: |
| 217 | mclk = 12288000; |
| 218 | break; |
| 219 | case 11025: |
| 220 | case 22050: |
| 221 | case 44100: |
| 222 | case 88200: |
| 223 | mclk = 11289600; |
| 224 | break; |
| 225 | default: |
| 226 | mclk = 12000000; |
| 227 | break; |
| 228 | } |
| 229 | |
| 230 | return mclk; |
| 231 | } |
| 232 | |
| 233 | static int tegra_machine_hw_params(struct snd_pcm_substream *substream, |
| 234 | struct snd_pcm_hw_params *params) |
| 235 | { |
| 236 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| 237 | struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); |
| 238 | struct snd_soc_card *card = rtd->card; |
| 239 | struct tegra_machine *machine = snd_soc_card_get_drvdata(card); |
| 240 | unsigned int srate = params_rate(params); |
| 241 | unsigned int mclk = machine->asoc->mclk_rate(srate); |
| 242 | unsigned int clk_id = machine->asoc->mclk_id; |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 243 | unsigned int new_baseclock; |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 244 | int err; |
| 245 | |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 246 | switch (srate) { |
| 247 | case 11025: |
| 248 | case 22050: |
| 249 | case 44100: |
| 250 | case 88200: |
| 251 | if (of_machine_is_compatible("nvidia,tegra20")) |
| 252 | new_baseclock = 56448000; |
| 253 | else if (of_machine_is_compatible("nvidia,tegra30")) |
| 254 | new_baseclock = 564480000; |
| 255 | else |
| 256 | new_baseclock = 282240000; |
| 257 | break; |
| 258 | case 8000: |
| 259 | case 16000: |
| 260 | case 32000: |
| 261 | case 48000: |
| 262 | case 64000: |
| 263 | case 96000: |
| 264 | if (of_machine_is_compatible("nvidia,tegra20")) |
| 265 | new_baseclock = 73728000; |
| 266 | else if (of_machine_is_compatible("nvidia,tegra30")) |
| 267 | new_baseclock = 552960000; |
| 268 | else |
| 269 | new_baseclock = 368640000; |
| 270 | break; |
| 271 | default: |
| 272 | dev_err(card->dev, "Invalid sound rate: %u\n", srate); |
| 273 | return -EINVAL; |
| 274 | } |
| 275 | |
| 276 | if (new_baseclock != machine->set_baseclock || |
| 277 | mclk != machine->set_mclk) { |
| 278 | machine->set_baseclock = 0; |
| 279 | machine->set_mclk = 0; |
| 280 | |
| 281 | clk_disable_unprepare(machine->clk_cdev1); |
| 282 | |
| 283 | err = clk_set_rate(machine->clk_pll_a, new_baseclock); |
| 284 | if (err) { |
| 285 | dev_err(card->dev, "Can't set pll_a rate: %d\n", err); |
| 286 | return err; |
| 287 | } |
| 288 | |
| 289 | err = clk_set_rate(machine->clk_pll_a_out0, mclk); |
| 290 | if (err) { |
| 291 | dev_err(card->dev, "Can't set pll_a_out0 rate: %d\n", err); |
| 292 | return err; |
| 293 | } |
| 294 | |
| 295 | /* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */ |
| 296 | |
| 297 | err = clk_prepare_enable(machine->clk_cdev1); |
| 298 | if (err) { |
| 299 | dev_err(card->dev, "Can't enable cdev1: %d\n", err); |
| 300 | return err; |
| 301 | } |
| 302 | |
| 303 | machine->set_baseclock = new_baseclock; |
| 304 | machine->set_mclk = mclk; |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 305 | } |
| 306 | |
| 307 | err = snd_soc_dai_set_sysclk(codec_dai, clk_id, mclk, SND_SOC_CLOCK_IN); |
| 308 | if (err < 0) { |
| 309 | dev_err(card->dev, "codec_dai clock not set: %d\n", err); |
| 310 | return err; |
| 311 | } |
| 312 | |
| 313 | return 0; |
| 314 | } |
| 315 | |
Rikard Falkeborn | 620868b | 2021-10-01 13:45:17 +0200 | [diff] [blame] | 316 | static const struct snd_soc_ops tegra_machine_snd_ops = { |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 317 | .hw_params = tegra_machine_hw_params, |
| 318 | }; |
| 319 | |
| 320 | static void tegra_machine_node_release(void *of_node) |
| 321 | { |
| 322 | of_node_put(of_node); |
| 323 | } |
| 324 | |
| 325 | static struct device_node * |
| 326 | tegra_machine_parse_phandle(struct device *dev, const char *name) |
| 327 | { |
| 328 | struct device_node *np; |
| 329 | int err; |
| 330 | |
| 331 | np = of_parse_phandle(dev->of_node, name, 0); |
| 332 | if (!np) { |
| 333 | dev_err(dev, "Property '%s' missing or invalid\n", name); |
| 334 | return ERR_PTR(-EINVAL); |
| 335 | } |
| 336 | |
| 337 | err = devm_add_action_or_reset(dev, tegra_machine_node_release, np); |
| 338 | if (err) |
| 339 | return ERR_PTR(err); |
| 340 | |
| 341 | return np; |
| 342 | } |
| 343 | |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 344 | static void tegra_machine_unregister_codec(void *pdev) |
| 345 | { |
| 346 | platform_device_unregister(pdev); |
| 347 | } |
| 348 | |
| 349 | static int tegra_machine_register_codec(struct device *dev, const char *name) |
| 350 | { |
| 351 | struct platform_device *pdev; |
| 352 | int err; |
| 353 | |
| 354 | if (!name) |
| 355 | return 0; |
| 356 | |
| 357 | pdev = platform_device_register_simple(name, -1, NULL, 0); |
| 358 | if (IS_ERR(pdev)) |
| 359 | return PTR_ERR(pdev); |
| 360 | |
| 361 | err = devm_add_action_or_reset(dev, tegra_machine_unregister_codec, |
| 362 | pdev); |
| 363 | if (err) |
| 364 | return err; |
| 365 | |
| 366 | return 0; |
| 367 | } |
| 368 | |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 369 | int tegra_asoc_machine_probe(struct platform_device *pdev) |
| 370 | { |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 371 | struct device_node *np_codec, *np_i2s, *np_ac97; |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 372 | const struct tegra_asoc_data *asoc; |
| 373 | struct device *dev = &pdev->dev; |
| 374 | struct tegra_machine *machine; |
| 375 | struct snd_soc_card *card; |
| 376 | struct gpio_desc *gpiod; |
| 377 | int err; |
| 378 | |
| 379 | machine = devm_kzalloc(dev, sizeof(*machine), GFP_KERNEL); |
| 380 | if (!machine) |
| 381 | return -ENOMEM; |
| 382 | |
| 383 | asoc = of_device_get_match_data(dev); |
| 384 | card = asoc->card; |
| 385 | card->dev = dev; |
| 386 | |
| 387 | machine->asoc = asoc; |
| 388 | machine->mic_jack = &tegra_machine_mic_jack; |
| 389 | machine->hp_jack_gpio = &tegra_machine_hp_jack_gpio; |
| 390 | snd_soc_card_set_drvdata(card, machine); |
| 391 | |
| 392 | gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-mute", GPIOD_OUT_HIGH); |
| 393 | machine->gpiod_hp_mute = gpiod; |
| 394 | if (IS_ERR(gpiod)) |
| 395 | return PTR_ERR(gpiod); |
| 396 | |
| 397 | gpiod = devm_gpiod_get_optional(dev, "nvidia,hp-det", GPIOD_IN); |
| 398 | machine->gpiod_hp_det = gpiod; |
| 399 | if (IS_ERR(gpiod)) |
| 400 | return PTR_ERR(gpiod); |
| 401 | |
| 402 | gpiod = devm_gpiod_get_optional(dev, "nvidia,mic-det", GPIOD_IN); |
| 403 | machine->gpiod_mic_det = gpiod; |
| 404 | if (IS_ERR(gpiod)) |
| 405 | return PTR_ERR(gpiod); |
| 406 | |
| 407 | gpiod = devm_gpiod_get_optional(dev, "nvidia,spkr-en", GPIOD_OUT_LOW); |
| 408 | machine->gpiod_spkr_en = gpiod; |
| 409 | if (IS_ERR(gpiod)) |
| 410 | return PTR_ERR(gpiod); |
| 411 | |
| 412 | gpiod = devm_gpiod_get_optional(dev, "nvidia,int-mic-en", GPIOD_OUT_LOW); |
| 413 | machine->gpiod_int_mic_en = gpiod; |
| 414 | if (IS_ERR(gpiod)) |
| 415 | return PTR_ERR(gpiod); |
| 416 | |
| 417 | gpiod = devm_gpiod_get_optional(dev, "nvidia,ext-mic-en", GPIOD_OUT_LOW); |
| 418 | machine->gpiod_ext_mic_en = gpiod; |
| 419 | if (IS_ERR(gpiod)) |
| 420 | return PTR_ERR(gpiod); |
| 421 | |
| 422 | err = snd_soc_of_parse_card_name(card, "nvidia,model"); |
| 423 | if (err) |
| 424 | return err; |
| 425 | |
| 426 | if (!card->dapm_routes) { |
| 427 | err = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); |
| 428 | if (err) |
| 429 | return err; |
| 430 | } |
| 431 | |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 432 | if (asoc->set_ac97) { |
| 433 | err = tegra_machine_register_codec(dev, asoc->codec_dev_name); |
| 434 | if (err) |
| 435 | return err; |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 436 | |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 437 | np_ac97 = tegra_machine_parse_phandle(dev, "nvidia,ac97-controller"); |
| 438 | if (IS_ERR(np_ac97)) |
| 439 | return PTR_ERR(np_ac97); |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 440 | |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 441 | card->dai_link->cpus->of_node = np_ac97; |
| 442 | card->dai_link->platforms->of_node = np_ac97; |
| 443 | } else { |
| 444 | np_codec = tegra_machine_parse_phandle(dev, "nvidia,audio-codec"); |
| 445 | if (IS_ERR(np_codec)) |
| 446 | return PTR_ERR(np_codec); |
| 447 | |
| 448 | np_i2s = tegra_machine_parse_phandle(dev, "nvidia,i2s-controller"); |
| 449 | if (IS_ERR(np_i2s)) |
| 450 | return PTR_ERR(np_i2s); |
| 451 | |
| 452 | card->dai_link->cpus->of_node = np_i2s; |
| 453 | card->dai_link->codecs->of_node = np_codec; |
| 454 | card->dai_link->platforms->of_node = np_i2s; |
| 455 | } |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 456 | |
| 457 | if (asoc->add_common_controls) { |
| 458 | card->controls = tegra_machine_controls; |
| 459 | card->num_controls = ARRAY_SIZE(tegra_machine_controls); |
| 460 | } |
| 461 | |
| 462 | if (asoc->add_common_dapm_widgets) { |
| 463 | card->dapm_widgets = tegra_machine_dapm_widgets; |
| 464 | card->num_dapm_widgets = ARRAY_SIZE(tegra_machine_dapm_widgets); |
| 465 | } |
| 466 | |
| 467 | if (asoc->add_common_snd_ops) |
| 468 | card->dai_link->ops = &tegra_machine_snd_ops; |
| 469 | |
| 470 | if (!card->owner) |
| 471 | card->owner = THIS_MODULE; |
| 472 | if (!card->driver_name) |
| 473 | card->driver_name = "tegra"; |
| 474 | |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 475 | machine->clk_pll_a = devm_clk_get(dev, "pll_a"); |
| 476 | if (IS_ERR(machine->clk_pll_a)) { |
| 477 | dev_err(dev, "Can't retrieve clk pll_a\n"); |
| 478 | return PTR_ERR(machine->clk_pll_a); |
| 479 | } |
| 480 | |
| 481 | machine->clk_pll_a_out0 = devm_clk_get(dev, "pll_a_out0"); |
| 482 | if (IS_ERR(machine->clk_pll_a_out0)) { |
| 483 | dev_err(dev, "Can't retrieve clk pll_a_out0\n"); |
| 484 | return PTR_ERR(machine->clk_pll_a_out0); |
| 485 | } |
| 486 | |
| 487 | machine->clk_cdev1 = devm_clk_get(dev, "mclk"); |
| 488 | if (IS_ERR(machine->clk_cdev1)) { |
| 489 | dev_err(dev, "Can't retrieve clk cdev1\n"); |
| 490 | return PTR_ERR(machine->clk_cdev1); |
| 491 | } |
| 492 | |
| 493 | /* |
| 494 | * If clock parents are not set in DT, configure here to use clk_out_1 |
| 495 | * as mclk and extern1 as parent for Tegra30 and higher. |
| 496 | */ |
| 497 | if (!of_find_property(dev->of_node, "assigned-clock-parents", NULL) && |
| 498 | !of_machine_is_compatible("nvidia,tegra20")) { |
| 499 | struct clk *clk_out_1, *clk_extern1; |
| 500 | |
| 501 | dev_warn(dev, "Configuring clocks for a legacy device-tree\n"); |
| 502 | dev_warn(dev, "Please update DT to use assigned-clock-parents\n"); |
| 503 | |
| 504 | clk_extern1 = devm_clk_get(dev, "extern1"); |
| 505 | if (IS_ERR(clk_extern1)) { |
| 506 | dev_err(dev, "Can't retrieve clk extern1\n"); |
| 507 | return PTR_ERR(clk_extern1); |
| 508 | } |
| 509 | |
| 510 | err = clk_set_parent(clk_extern1, machine->clk_pll_a_out0); |
| 511 | if (err < 0) { |
| 512 | dev_err(dev, "Set parent failed for clk extern1\n"); |
| 513 | return err; |
| 514 | } |
| 515 | |
| 516 | clk_out_1 = devm_clk_get(dev, "pmc_clk_out_1"); |
| 517 | if (IS_ERR(clk_out_1)) { |
| 518 | dev_err(dev, "Can't retrieve pmc_clk_out_1\n"); |
| 519 | return PTR_ERR(clk_out_1); |
| 520 | } |
| 521 | |
| 522 | err = clk_set_parent(clk_out_1, clk_extern1); |
| 523 | if (err < 0) { |
| 524 | dev_err(dev, "Set parent failed for pmc_clk_out_1\n"); |
| 525 | return err; |
| 526 | } |
| 527 | |
| 528 | machine->clk_cdev1 = clk_out_1; |
| 529 | } |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 530 | |
| 531 | if (asoc->set_ac97) { |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 532 | /* |
| 533 | * AC97 rate is fixed at 24.576MHz and is used for both the |
| 534 | * host controller and the external codec |
| 535 | */ |
| 536 | err = clk_set_rate(machine->clk_pll_a, 73728000); |
| 537 | if (err) { |
| 538 | dev_err(dev, "Can't set pll_a rate: %d\n", err); |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 539 | return err; |
Dmitry Osipenko | 8c1b3b1 | 2021-05-29 18:46:49 +0300 | [diff] [blame] | 540 | } |
| 541 | |
| 542 | err = clk_set_rate(machine->clk_pll_a_out0, 24576000); |
| 543 | if (err) { |
| 544 | dev_err(dev, "Can't set pll_a_out0 rate: %d\n", err); |
| 545 | return err; |
| 546 | } |
| 547 | |
| 548 | machine->set_baseclock = 73728000; |
| 549 | machine->set_mclk = 24576000; |
| 550 | } |
| 551 | |
| 552 | /* |
| 553 | * FIXME: There is some unknown dependency between audio MCLK disable |
| 554 | * and suspend-resume functionality on Tegra30, although audio MCLK is |
| 555 | * only needed for audio. |
| 556 | */ |
| 557 | err = clk_prepare_enable(machine->clk_cdev1); |
| 558 | if (err) { |
| 559 | dev_err(dev, "Can't enable cdev1: %d\n", err); |
| 560 | return err; |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 561 | } |
| 562 | |
| 563 | err = devm_snd_soc_register_card(dev, card); |
| 564 | if (err) |
| 565 | return err; |
| 566 | |
| 567 | return 0; |
| 568 | } |
| 569 | EXPORT_SYMBOL_GPL(tegra_asoc_machine_probe); |
| 570 | |
| 571 | /* WM8753 machine */ |
| 572 | |
| 573 | SND_SOC_DAILINK_DEFS(wm8753_hifi, |
| 574 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 575 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8753-hifi")), |
| 576 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 577 | |
| 578 | static struct snd_soc_dai_link tegra_wm8753_dai = { |
| 579 | .name = "WM8753", |
| 580 | .stream_name = "WM8753 PCM", |
| 581 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 582 | SND_SOC_DAIFMT_NB_NF | |
| 583 | SND_SOC_DAIFMT_CBS_CFS, |
| 584 | SND_SOC_DAILINK_REG(wm8753_hifi), |
| 585 | }; |
| 586 | |
| 587 | static struct snd_soc_card snd_soc_tegra_wm8753 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 588 | .components = "codec:wm8753", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 589 | .dai_link = &tegra_wm8753_dai, |
| 590 | .num_links = 1, |
| 591 | .fully_routed = true, |
| 592 | }; |
| 593 | |
| 594 | static const struct tegra_asoc_data tegra_wm8753_data = { |
| 595 | .mclk_rate = tegra_machine_mclk_rate_12mhz, |
| 596 | .card = &snd_soc_tegra_wm8753, |
| 597 | .add_common_dapm_widgets = true, |
| 598 | .add_common_snd_ops = true, |
| 599 | }; |
| 600 | |
| 601 | /* WM9712 machine */ |
| 602 | |
| 603 | static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd) |
| 604 | { |
| 605 | return snd_soc_dapm_force_enable_pin(&rtd->card->dapm, "Mic Bias"); |
| 606 | } |
| 607 | |
| 608 | SND_SOC_DAILINK_DEFS(wm9712_hifi, |
| 609 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 610 | DAILINK_COMP_ARRAY(COMP_CODEC("wm9712-codec", "wm9712-hifi")), |
| 611 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 612 | |
| 613 | static struct snd_soc_dai_link tegra_wm9712_dai = { |
| 614 | .name = "AC97 HiFi", |
| 615 | .stream_name = "AC97 HiFi", |
| 616 | .init = tegra_wm9712_init, |
| 617 | SND_SOC_DAILINK_REG(wm9712_hifi), |
| 618 | }; |
| 619 | |
| 620 | static struct snd_soc_card snd_soc_tegra_wm9712 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 621 | .components = "codec:wm9712", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 622 | .dai_link = &tegra_wm9712_dai, |
| 623 | .num_links = 1, |
| 624 | .fully_routed = true, |
| 625 | }; |
| 626 | |
| 627 | static const struct tegra_asoc_data tegra_wm9712_data = { |
| 628 | .card = &snd_soc_tegra_wm9712, |
| 629 | .add_common_dapm_widgets = true, |
Dmitry Osipenko | de8fc2b | 2021-10-24 22:28:52 +0300 | [diff] [blame] | 630 | .codec_dev_name = "wm9712-codec", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 631 | .set_ac97 = true, |
| 632 | }; |
| 633 | |
| 634 | /* MAX98090 machine */ |
| 635 | |
| 636 | SND_SOC_DAILINK_DEFS(max98090_hifi, |
| 637 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 638 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")), |
| 639 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 640 | |
| 641 | static struct snd_soc_dai_link tegra_max98090_dai = { |
| 642 | .name = "max98090", |
| 643 | .stream_name = "max98090 PCM", |
| 644 | .init = tegra_asoc_machine_init, |
| 645 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 646 | SND_SOC_DAIFMT_NB_NF | |
| 647 | SND_SOC_DAIFMT_CBS_CFS, |
| 648 | SND_SOC_DAILINK_REG(max98090_hifi), |
| 649 | }; |
| 650 | |
| 651 | static struct snd_soc_card snd_soc_tegra_max98090 = { |
| 652 | .components = "codec:max98090", |
| 653 | .dai_link = &tegra_max98090_dai, |
| 654 | .num_links = 1, |
| 655 | .fully_routed = true, |
| 656 | }; |
| 657 | |
| 658 | static const struct tegra_asoc_data tegra_max98090_data = { |
| 659 | .mclk_rate = tegra_machine_mclk_rate_12mhz, |
| 660 | .card = &snd_soc_tegra_max98090, |
| 661 | .add_common_dapm_widgets = true, |
| 662 | .add_common_controls = true, |
| 663 | .add_common_snd_ops = true, |
| 664 | .add_mic_jack = true, |
| 665 | .add_hp_jack = true, |
| 666 | }; |
| 667 | |
| 668 | /* SGTL5000 machine */ |
| 669 | |
| 670 | SND_SOC_DAILINK_DEFS(sgtl5000_hifi, |
| 671 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 672 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "sgtl5000")), |
| 673 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 674 | |
| 675 | static struct snd_soc_dai_link tegra_sgtl5000_dai = { |
| 676 | .name = "sgtl5000", |
| 677 | .stream_name = "HiFi", |
| 678 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 679 | SND_SOC_DAIFMT_NB_NF | |
| 680 | SND_SOC_DAIFMT_CBS_CFS, |
| 681 | SND_SOC_DAILINK_REG(sgtl5000_hifi), |
| 682 | }; |
| 683 | |
| 684 | static struct snd_soc_card snd_soc_tegra_sgtl5000 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 685 | .components = "codec:sgtl5000", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 686 | .dai_link = &tegra_sgtl5000_dai, |
| 687 | .num_links = 1, |
| 688 | .fully_routed = true, |
| 689 | }; |
| 690 | |
| 691 | static const struct tegra_asoc_data tegra_sgtl5000_data = { |
| 692 | .mclk_rate = tegra_machine_mclk_rate_12mhz, |
| 693 | .card = &snd_soc_tegra_sgtl5000, |
| 694 | .add_common_dapm_widgets = true, |
| 695 | .add_common_snd_ops = true, |
| 696 | }; |
| 697 | |
| 698 | /* TLV320AIC23 machine */ |
| 699 | |
| 700 | static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { |
| 701 | SND_SOC_DAPM_HP("Line Out", NULL), |
| 702 | SND_SOC_DAPM_LINE("Line In", NULL), |
| 703 | }; |
| 704 | |
| 705 | static const struct snd_soc_dapm_route trimslice_audio_map[] = { |
| 706 | {"Line Out", NULL, "LOUT"}, |
| 707 | {"Line Out", NULL, "ROUT"}, |
| 708 | |
| 709 | {"LLINEIN", NULL, "Line In"}, |
| 710 | {"RLINEIN", NULL, "Line In"}, |
| 711 | }; |
| 712 | |
| 713 | SND_SOC_DAILINK_DEFS(tlv320aic23_hifi, |
| 714 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 715 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic23-hifi")), |
| 716 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 717 | |
| 718 | static struct snd_soc_dai_link tegra_tlv320aic23_dai = { |
| 719 | .name = "TLV320AIC23", |
| 720 | .stream_name = "AIC23", |
| 721 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 722 | SND_SOC_DAIFMT_NB_NF | |
| 723 | SND_SOC_DAIFMT_CBS_CFS, |
| 724 | SND_SOC_DAILINK_REG(tlv320aic23_hifi), |
| 725 | }; |
| 726 | |
| 727 | static struct snd_soc_card snd_soc_tegra_trimslice = { |
Dmitry Osipenko | 824edd8 | 2021-10-24 22:28:53 +0300 | [diff] [blame] | 728 | .name = "tegra-trimslice", |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 729 | .components = "codec:tlv320aic23", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 730 | .dai_link = &tegra_tlv320aic23_dai, |
| 731 | .num_links = 1, |
| 732 | .dapm_widgets = trimslice_dapm_widgets, |
| 733 | .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), |
| 734 | .dapm_routes = trimslice_audio_map, |
| 735 | .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), |
| 736 | .fully_routed = true, |
| 737 | }; |
| 738 | |
| 739 | static const struct tegra_asoc_data tegra_trimslice_data = { |
| 740 | .mclk_rate = tegra_machine_mclk_rate_128, |
| 741 | .card = &snd_soc_tegra_trimslice, |
| 742 | .add_common_snd_ops = true, |
| 743 | }; |
| 744 | |
| 745 | /* RT5677 machine */ |
| 746 | |
| 747 | static int tegra_rt5677_init(struct snd_soc_pcm_runtime *rtd) |
| 748 | { |
| 749 | struct snd_soc_card *card = rtd->card; |
| 750 | int err; |
| 751 | |
| 752 | err = tegra_asoc_machine_init(rtd); |
| 753 | if (err) |
| 754 | return err; |
| 755 | |
| 756 | snd_soc_dapm_force_enable_pin(&card->dapm, "MICBIAS1"); |
| 757 | |
| 758 | return 0; |
| 759 | } |
| 760 | |
| 761 | SND_SOC_DAILINK_DEFS(rt5677_aif1, |
| 762 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 763 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5677-aif1")), |
| 764 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 765 | |
| 766 | static struct snd_soc_dai_link tegra_rt5677_dai = { |
| 767 | .name = "RT5677", |
| 768 | .stream_name = "RT5677 PCM", |
| 769 | .init = tegra_rt5677_init, |
| 770 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 771 | SND_SOC_DAIFMT_NB_NF | |
| 772 | SND_SOC_DAIFMT_CBS_CFS, |
| 773 | SND_SOC_DAILINK_REG(rt5677_aif1), |
| 774 | }; |
| 775 | |
| 776 | static struct snd_soc_card snd_soc_tegra_rt5677 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 777 | .components = "codec:rt5677", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 778 | .dai_link = &tegra_rt5677_dai, |
| 779 | .num_links = 1, |
| 780 | .fully_routed = true, |
| 781 | }; |
| 782 | |
| 783 | static const struct tegra_asoc_data tegra_rt5677_data = { |
| 784 | .mclk_rate = tegra_machine_mclk_rate_256, |
| 785 | .card = &snd_soc_tegra_rt5677, |
| 786 | .add_common_dapm_widgets = true, |
| 787 | .add_common_controls = true, |
| 788 | .add_common_snd_ops = true, |
| 789 | .add_mic_jack = true, |
| 790 | .add_hp_jack = true, |
| 791 | }; |
| 792 | |
| 793 | /* RT5640 machine */ |
| 794 | |
| 795 | SND_SOC_DAILINK_DEFS(rt5640_aif1, |
| 796 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 797 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5640-aif1")), |
| 798 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 799 | |
| 800 | static struct snd_soc_dai_link tegra_rt5640_dai = { |
| 801 | .name = "RT5640", |
| 802 | .stream_name = "RT5640 PCM", |
| 803 | .init = tegra_asoc_machine_init, |
| 804 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 805 | SND_SOC_DAIFMT_NB_NF | |
| 806 | SND_SOC_DAIFMT_CBS_CFS, |
| 807 | SND_SOC_DAILINK_REG(rt5640_aif1), |
| 808 | }; |
| 809 | |
| 810 | static struct snd_soc_card snd_soc_tegra_rt5640 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 811 | .components = "codec:rt5640", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 812 | .dai_link = &tegra_rt5640_dai, |
| 813 | .num_links = 1, |
| 814 | .fully_routed = true, |
| 815 | }; |
| 816 | |
| 817 | static const struct tegra_asoc_data tegra_rt5640_data = { |
| 818 | .mclk_rate = tegra_machine_mclk_rate_256, |
| 819 | .card = &snd_soc_tegra_rt5640, |
| 820 | .add_common_dapm_widgets = true, |
| 821 | .add_common_controls = true, |
| 822 | .add_common_snd_ops = true, |
| 823 | .add_hp_jack = true, |
| 824 | }; |
| 825 | |
| 826 | /* RT5632 machine */ |
| 827 | |
| 828 | SND_SOC_DAILINK_DEFS(rt5632_hifi, |
| 829 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
| 830 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "alc5632-hifi")), |
| 831 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
| 832 | |
| 833 | static struct snd_soc_dai_link tegra_rt5632_dai = { |
| 834 | .name = "ALC5632", |
| 835 | .stream_name = "ALC5632 PCM", |
| 836 | .init = tegra_rt5677_init, |
| 837 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
| 838 | SND_SOC_DAIFMT_NB_NF | |
| 839 | SND_SOC_DAIFMT_CBS_CFS, |
| 840 | SND_SOC_DAILINK_REG(rt5632_hifi), |
| 841 | }; |
| 842 | |
| 843 | static struct snd_soc_card snd_soc_tegra_rt5632 = { |
Dmitry Osipenko | c16aab8 | 2021-05-29 18:46:48 +0300 | [diff] [blame] | 844 | .components = "codec:rt5632", |
Dmitry Osipenko | cc8f70f | 2021-05-29 18:46:47 +0300 | [diff] [blame] | 845 | .dai_link = &tegra_rt5632_dai, |
| 846 | .num_links = 1, |
| 847 | .fully_routed = true, |
| 848 | }; |
| 849 | |
| 850 | static const struct tegra_asoc_data tegra_rt5632_data = { |
| 851 | .mclk_rate = tegra_machine_mclk_rate_512, |
| 852 | .card = &snd_soc_tegra_rt5632, |
| 853 | .add_common_dapm_widgets = true, |
| 854 | .add_common_controls = true, |
| 855 | .add_common_snd_ops = true, |
| 856 | .add_headset_jack = true, |
| 857 | }; |
| 858 | |
| 859 | static const struct of_device_id tegra_machine_of_match[] = { |
| 860 | { .compatible = "nvidia,tegra-audio-trimslice", .data = &tegra_trimslice_data }, |
| 861 | { .compatible = "nvidia,tegra-audio-max98090", .data = &tegra_max98090_data }, |
| 862 | { .compatible = "nvidia,tegra-audio-sgtl5000", .data = &tegra_sgtl5000_data }, |
| 863 | { .compatible = "nvidia,tegra-audio-wm9712", .data = &tegra_wm9712_data }, |
| 864 | { .compatible = "nvidia,tegra-audio-wm8753", .data = &tegra_wm8753_data }, |
| 865 | { .compatible = "nvidia,tegra-audio-rt5677", .data = &tegra_rt5677_data }, |
| 866 | { .compatible = "nvidia,tegra-audio-rt5640", .data = &tegra_rt5640_data }, |
| 867 | { .compatible = "nvidia,tegra-audio-alc5632", .data = &tegra_rt5632_data }, |
| 868 | {}, |
| 869 | }; |
| 870 | MODULE_DEVICE_TABLE(of, tegra_machine_of_match); |
| 871 | |
| 872 | static struct platform_driver tegra_asoc_machine_driver = { |
| 873 | .driver = { |
| 874 | .name = "tegra-audio", |
| 875 | .of_match_table = tegra_machine_of_match, |
| 876 | .pm = &snd_soc_pm_ops, |
| 877 | }, |
| 878 | .probe = tegra_asoc_machine_probe, |
| 879 | }; |
| 880 | module_platform_driver(tegra_asoc_machine_driver); |
| 881 | |
| 882 | MODULE_AUTHOR("Anatol Pomozov <anatol@google.com>"); |
| 883 | MODULE_AUTHOR("Andrey Danin <danindrey@mail.ru>"); |
| 884 | MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>"); |
| 885 | MODULE_AUTHOR("Ion Agorria <ion@agorria.com>"); |
| 886 | MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); |
| 887 | MODULE_AUTHOR("Lucas Stach <dev@lynxeye.de>"); |
| 888 | MODULE_AUTHOR("Marc Dietrich <marvin24@gmx.de>"); |
| 889 | MODULE_AUTHOR("Marcel Ziswiler <marcel@ziswiler.com>"); |
| 890 | MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); |
| 891 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); |
| 892 | MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>"); |
| 893 | MODULE_DESCRIPTION("Tegra machine ASoC driver"); |
| 894 | MODULE_LICENSE("GPL"); |