1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec. |
4 | * |
5 | * Author: Stephen Warren <swarren@nvidia.com> |
6 | * Copyright (C) 2010-2012 - NVIDIA, Inc. |
7 | * |
8 | * Based on code copyright/by: |
9 | * |
10 | * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. |
11 | * |
12 | * Copyright 2007 Wolfson Microelectronics PLC. |
13 | * Author: Graeme Gregory |
14 | * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com |
15 | */ |
16 | |
17 | #include <linux/gpio/consumer.h> |
18 | #include <linux/of.h> |
19 | #include <linux/module.h> |
20 | #include <linux/platform_device.h> |
21 | |
22 | #include <sound/core.h> |
23 | #include <sound/jack.h> |
24 | #include <sound/soc.h> |
25 | |
26 | #include "../codecs/wm8903.h" |
27 | |
28 | #include "tegra_asoc_machine.h" |
29 | |
30 | static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = { |
31 | { .pin = "Mic Jack" , .mask = SND_JACK_MICROPHONE }, |
32 | }; |
33 | |
34 | static unsigned int tegra_wm8903_mclk_rate(unsigned int srate) |
35 | { |
36 | unsigned int mclk; |
37 | |
38 | switch (srate) { |
39 | case 64000: |
40 | case 88200: |
41 | case 96000: |
42 | mclk = 128 * srate; |
43 | break; |
44 | default: |
45 | mclk = 256 * srate; |
46 | break; |
47 | } |
48 | /* FIXME: Codec only requires >= 3MHz if OSR==0 */ |
49 | while (mclk < 6000000) |
50 | mclk *= 2; |
51 | |
52 | return mclk; |
53 | } |
54 | |
55 | static int tegra_wm8903_init(struct snd_soc_pcm_runtime *rtd) |
56 | { |
57 | struct tegra_machine *machine = snd_soc_card_get_drvdata(card: rtd->card); |
58 | struct snd_soc_card *card = rtd->card; |
59 | int err; |
60 | |
61 | /* |
62 | * Older version of machine driver was ignoring GPIO polarity, |
63 | * forcing it to active-low. This means that all older device-trees |
64 | * which set the polarity to active-high are wrong and we need to fix |
65 | * them up. |
66 | */ |
67 | if (machine->asoc->hp_jack_gpio_active_low) { |
68 | bool active_low = gpiod_is_active_low(desc: machine->gpiod_hp_det); |
69 | |
70 | machine->hp_jack_gpio->invert = !active_low; |
71 | } |
72 | |
73 | err = tegra_asoc_machine_init(rtd); |
74 | if (err) |
75 | return err; |
76 | |
77 | if (!machine->gpiod_mic_det && machine->asoc->add_mic_jack) { |
78 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
79 | struct snd_soc_component *component = codec_dai->component; |
80 | int shrt = 0; |
81 | |
82 | err = snd_soc_card_jack_new_pins(card: rtd->card, id: "Mic Jack" , |
83 | type: SND_JACK_MICROPHONE, |
84 | jack: machine->mic_jack, |
85 | pins: tegra_wm8903_mic_jack_pins, |
86 | ARRAY_SIZE(tegra_wm8903_mic_jack_pins)); |
87 | if (err) { |
88 | dev_err(rtd->dev, "Mic Jack creation failed: %d\n" , err); |
89 | return err; |
90 | } |
91 | |
92 | if (of_property_read_bool(np: card->dev->of_node, propname: "nvidia,headset" )) |
93 | shrt = SND_JACK_MICROPHONE; |
94 | |
95 | wm8903_mic_detect(component, jack: machine->mic_jack, |
96 | det: SND_JACK_MICROPHONE, shrt); |
97 | } |
98 | |
99 | snd_soc_dapm_force_enable_pin(dapm: &card->dapm, pin: "MICBIAS" ); |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int tegra_wm8903_remove(struct snd_soc_card *card) |
105 | { |
106 | struct snd_soc_dai_link *link = &card->dai_link[0]; |
107 | struct snd_soc_pcm_runtime *rtd = snd_soc_get_pcm_runtime(card, dai_link: link); |
108 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
109 | struct snd_soc_component *component = codec_dai->component; |
110 | |
111 | wm8903_mic_detect(component, NULL, det: 0, shrt: 0); |
112 | |
113 | return 0; |
114 | } |
115 | |
116 | SND_SOC_DAILINK_DEFS(hifi, |
117 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
118 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "wm8903-hifi" )), |
119 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
120 | |
121 | static struct snd_soc_dai_link tegra_wm8903_dai = { |
122 | .name = "WM8903" , |
123 | .stream_name = "WM8903 PCM" , |
124 | .init = tegra_wm8903_init, |
125 | .dai_fmt = SND_SOC_DAIFMT_I2S | |
126 | SND_SOC_DAIFMT_NB_NF | |
127 | SND_SOC_DAIFMT_CBS_CFS, |
128 | SND_SOC_DAILINK_REG(hifi), |
129 | }; |
130 | |
131 | static struct snd_soc_card snd_soc_tegra_wm8903 = { |
132 | .components = "codec:wm8903" , |
133 | .owner = THIS_MODULE, |
134 | .dai_link = &tegra_wm8903_dai, |
135 | .num_links = 1, |
136 | .remove = tegra_wm8903_remove, |
137 | .fully_routed = true, |
138 | }; |
139 | |
140 | /* older device-trees used wrong polarity for the headphones-detection GPIO */ |
141 | static const struct tegra_asoc_data tegra_wm8903_data_legacy = { |
142 | .mclk_rate = tegra_wm8903_mclk_rate, |
143 | .card = &snd_soc_tegra_wm8903, |
144 | .hp_jack_gpio_active_low = true, |
145 | .add_common_dapm_widgets = true, |
146 | .add_common_controls = true, |
147 | .add_common_snd_ops = true, |
148 | .add_mic_jack = true, |
149 | .add_hp_jack = true, |
150 | }; |
151 | |
152 | static const struct tegra_asoc_data tegra_wm8903_data = { |
153 | .mclk_rate = tegra_wm8903_mclk_rate, |
154 | .card = &snd_soc_tegra_wm8903, |
155 | .add_common_dapm_widgets = true, |
156 | .add_common_controls = true, |
157 | .add_common_snd_ops = true, |
158 | .add_mic_jack = true, |
159 | .add_hp_jack = true, |
160 | }; |
161 | |
162 | static const struct of_device_id tegra_wm8903_of_match[] = { |
163 | { .compatible = "ad,tegra-audio-plutux" , .data = &tegra_wm8903_data_legacy }, |
164 | { .compatible = "ad,tegra-audio-wm8903-medcom-wide" , .data = &tegra_wm8903_data_legacy }, |
165 | { .compatible = "ad,tegra-audio-wm8903-tec" , .data = &tegra_wm8903_data_legacy }, |
166 | { .compatible = "nvidia,tegra-audio-wm8903-cardhu" , .data = &tegra_wm8903_data_legacy }, |
167 | { .compatible = "nvidia,tegra-audio-wm8903-harmony" , .data = &tegra_wm8903_data_legacy }, |
168 | { .compatible = "nvidia,tegra-audio-wm8903-picasso" , .data = &tegra_wm8903_data_legacy }, |
169 | { .compatible = "nvidia,tegra-audio-wm8903-seaboard" , .data = &tegra_wm8903_data_legacy }, |
170 | { .compatible = "nvidia,tegra-audio-wm8903-ventana" , .data = &tegra_wm8903_data_legacy }, |
171 | { .compatible = "nvidia,tegra-audio-wm8903" , .data = &tegra_wm8903_data }, |
172 | {}, |
173 | }; |
174 | MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match); |
175 | |
176 | static struct platform_driver tegra_wm8903_driver = { |
177 | .driver = { |
178 | .name = "tegra-wm8903" , |
179 | .of_match_table = tegra_wm8903_of_match, |
180 | .pm = &snd_soc_pm_ops, |
181 | }, |
182 | .probe = tegra_asoc_machine_probe, |
183 | }; |
184 | module_platform_driver(tegra_wm8903_driver); |
185 | |
186 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>" ); |
187 | MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver" ); |
188 | MODULE_LICENSE("GPL" ); |
189 | |