1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * omap3pandora.c -- SoC audio for Pandora Handheld Console |
4 | * |
5 | * Author: GraÅžvydas Ignotas <notasas@gmail.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/gpio/consumer.h> |
11 | #include <linux/delay.h> |
12 | #include <linux/regulator/consumer.h> |
13 | #include <linux/module.h> |
14 | |
15 | #include <sound/core.h> |
16 | #include <sound/pcm.h> |
17 | #include <sound/soc.h> |
18 | |
19 | #include <asm/mach-types.h> |
20 | #include <linux/platform_data/asoc-ti-mcbsp.h> |
21 | |
22 | #include "omap-mcbsp.h" |
23 | |
24 | #define PREFIX "ASoC omap3pandora: " |
25 | |
26 | static struct regulator *omap3pandora_dac_reg; |
27 | static struct gpio_desc *dac_power_gpio; |
28 | static struct gpio_desc *amp_power_gpio; |
29 | |
30 | static int omap3pandora_hw_params(struct snd_pcm_substream *substream, |
31 | struct snd_pcm_hw_params *params) |
32 | { |
33 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
34 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
35 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
36 | int ret; |
37 | |
38 | /* Set the codec system clock for DAC and ADC */ |
39 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: 26000000, |
40 | SND_SOC_CLOCK_IN); |
41 | if (ret < 0) { |
42 | pr_err(PREFIX "can't set codec system clock\n" ); |
43 | return ret; |
44 | } |
45 | |
46 | /* Set McBSP clock to external */ |
47 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, clk_id: OMAP_MCBSP_SYSCLK_CLKS_EXT, |
48 | freq: 256 * params_rate(p: params), |
49 | SND_SOC_CLOCK_IN); |
50 | if (ret < 0) { |
51 | pr_err(PREFIX "can't set cpu system clock\n" ); |
52 | return ret; |
53 | } |
54 | |
55 | ret = snd_soc_dai_set_clkdiv(dai: cpu_dai, div_id: OMAP_MCBSP_CLKGDV, div: 8); |
56 | if (ret < 0) { |
57 | pr_err(PREFIX "can't set SRG clock divider\n" ); |
58 | return ret; |
59 | } |
60 | |
61 | return 0; |
62 | } |
63 | |
64 | static int omap3pandora_dac_event(struct snd_soc_dapm_widget *w, |
65 | struct snd_kcontrol *k, int event) |
66 | { |
67 | int ret; |
68 | |
69 | /* |
70 | * The PCM1773 DAC datasheet requires 1ms delay between switching |
71 | * VCC power on/off and /PD pin high/low |
72 | */ |
73 | if (SND_SOC_DAPM_EVENT_ON(event)) { |
74 | ret = regulator_enable(regulator: omap3pandora_dac_reg); |
75 | if (ret) { |
76 | dev_err(w->dapm->dev, "Failed to power DAC: %d\n" , ret); |
77 | return ret; |
78 | } |
79 | mdelay(1); |
80 | gpiod_set_value(desc: dac_power_gpio, value: 1); |
81 | } else { |
82 | gpiod_set_value(desc: dac_power_gpio, value: 0); |
83 | mdelay(1); |
84 | regulator_disable(regulator: omap3pandora_dac_reg); |
85 | } |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w, |
91 | struct snd_kcontrol *k, int event) |
92 | { |
93 | if (SND_SOC_DAPM_EVENT_ON(event)) |
94 | gpiod_set_value(desc: amp_power_gpio, value: 1); |
95 | else |
96 | gpiod_set_value(desc: amp_power_gpio, value: 0); |
97 | |
98 | return 0; |
99 | } |
100 | |
101 | /* |
102 | * Audio paths on Pandora board: |
103 | * |
104 | * |O| ---> PCM DAC +-> AMP -> Headphone Jack |
105 | * |M| A +--------> Line Out |
106 | * |A| <~~clk~~+ |
107 | * |P| <--- TWL4030 <--------- Line In and MICs |
108 | */ |
109 | static const struct snd_soc_dapm_widget omap3pandora_dapm_widgets[] = { |
110 | SND_SOC_DAPM_DAC_E("PCM DAC" , "HiFi Playback" , SND_SOC_NOPM, |
111 | 0, 0, omap3pandora_dac_event, |
112 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
113 | SND_SOC_DAPM_PGA_E("Headphone Amplifier" , SND_SOC_NOPM, |
114 | 0, 0, NULL, 0, omap3pandora_hp_event, |
115 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
116 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
117 | SND_SOC_DAPM_LINE("Line Out" , NULL), |
118 | |
119 | SND_SOC_DAPM_MIC("Mic (internal)" , NULL), |
120 | SND_SOC_DAPM_MIC("Mic (external)" , NULL), |
121 | SND_SOC_DAPM_LINE("Line In" , NULL), |
122 | }; |
123 | |
124 | static const struct snd_soc_dapm_route omap3pandora_map[] = { |
125 | {"PCM DAC" , NULL, "APLL Enable" }, |
126 | {"Headphone Amplifier" , NULL, "PCM DAC" }, |
127 | {"Line Out" , NULL, "PCM DAC" }, |
128 | {"Headphone Jack" , NULL, "Headphone Amplifier" }, |
129 | |
130 | {"AUXL" , NULL, "Line In" }, |
131 | {"AUXR" , NULL, "Line In" }, |
132 | |
133 | {"MAINMIC" , NULL, "Mic (internal)" }, |
134 | {"Mic (internal)" , NULL, "Mic Bias 1" }, |
135 | |
136 | {"SUBMIC" , NULL, "Mic (external)" }, |
137 | {"Mic (external)" , NULL, "Mic Bias 2" }, |
138 | }; |
139 | |
140 | static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd) |
141 | { |
142 | struct snd_soc_dapm_context *dapm = &rtd->card->dapm; |
143 | |
144 | /* All TWL4030 output pins are floating */ |
145 | snd_soc_dapm_nc_pin(dapm, pin: "EARPIECE" ); |
146 | snd_soc_dapm_nc_pin(dapm, pin: "PREDRIVEL" ); |
147 | snd_soc_dapm_nc_pin(dapm, pin: "PREDRIVER" ); |
148 | snd_soc_dapm_nc_pin(dapm, pin: "HSOL" ); |
149 | snd_soc_dapm_nc_pin(dapm, pin: "HSOR" ); |
150 | snd_soc_dapm_nc_pin(dapm, pin: "CARKITL" ); |
151 | snd_soc_dapm_nc_pin(dapm, pin: "CARKITR" ); |
152 | snd_soc_dapm_nc_pin(dapm, pin: "HFL" ); |
153 | snd_soc_dapm_nc_pin(dapm, pin: "HFR" ); |
154 | snd_soc_dapm_nc_pin(dapm, pin: "VIBRA" ); |
155 | |
156 | return 0; |
157 | } |
158 | |
159 | static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd) |
160 | { |
161 | struct snd_soc_dapm_context *dapm = &rtd->card->dapm; |
162 | |
163 | /* Not comnnected */ |
164 | snd_soc_dapm_nc_pin(dapm, pin: "HSMIC" ); |
165 | snd_soc_dapm_nc_pin(dapm, pin: "CARKITMIC" ); |
166 | snd_soc_dapm_nc_pin(dapm, pin: "DIGIMIC0" ); |
167 | snd_soc_dapm_nc_pin(dapm, pin: "DIGIMIC1" ); |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static const struct snd_soc_ops omap3pandora_ops = { |
173 | .hw_params = omap3pandora_hw_params, |
174 | }; |
175 | |
176 | /* Digital audio interface glue - connects codec <--> CPU */ |
177 | SND_SOC_DAILINK_DEFS(out, |
178 | DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.2" )), |
179 | DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec" , "twl4030-hifi" )), |
180 | DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.2" ))); |
181 | |
182 | SND_SOC_DAILINK_DEFS(in, |
183 | DAILINK_COMP_ARRAY(COMP_CPU("omap-mcbsp.4" )), |
184 | DAILINK_COMP_ARRAY(COMP_CODEC("twl4030-codec" , "twl4030-hifi" )), |
185 | DAILINK_COMP_ARRAY(COMP_PLATFORM("omap-mcbsp.4" ))); |
186 | |
187 | static struct snd_soc_dai_link omap3pandora_dai[] = { |
188 | { |
189 | .name = "PCM1773" , |
190 | .stream_name = "HiFi Out" , |
191 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
192 | SND_SOC_DAIFMT_CBS_CFS, |
193 | .ops = &omap3pandora_ops, |
194 | .init = omap3pandora_out_init, |
195 | SND_SOC_DAILINK_REG(out), |
196 | }, { |
197 | .name = "TWL4030" , |
198 | .stream_name = "Line/Mic In" , |
199 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
200 | SND_SOC_DAIFMT_CBS_CFS, |
201 | .ops = &omap3pandora_ops, |
202 | .init = omap3pandora_in_init, |
203 | SND_SOC_DAILINK_REG(in), |
204 | } |
205 | }; |
206 | |
207 | /* SoC card */ |
208 | static struct snd_soc_card snd_soc_card_omap3pandora = { |
209 | .name = "omap3pandora" , |
210 | .owner = THIS_MODULE, |
211 | .dai_link = omap3pandora_dai, |
212 | .num_links = ARRAY_SIZE(omap3pandora_dai), |
213 | |
214 | .dapm_widgets = omap3pandora_dapm_widgets, |
215 | .num_dapm_widgets = ARRAY_SIZE(omap3pandora_dapm_widgets), |
216 | .dapm_routes = omap3pandora_map, |
217 | .num_dapm_routes = ARRAY_SIZE(omap3pandora_map), |
218 | }; |
219 | |
220 | static struct platform_device *omap3pandora_snd_device; |
221 | |
222 | static int __init omap3pandora_soc_init(void) |
223 | { |
224 | int ret; |
225 | |
226 | if (!machine_is_omap3_pandora()) |
227 | return -ENODEV; |
228 | |
229 | pr_info("OMAP3 Pandora SoC init\n" ); |
230 | |
231 | omap3pandora_snd_device = platform_device_alloc(name: "soc-audio" , id: -1); |
232 | if (omap3pandora_snd_device == NULL) { |
233 | pr_err(PREFIX "Platform device allocation failed\n" ); |
234 | return -ENOMEM; |
235 | } |
236 | |
237 | platform_set_drvdata(pdev: omap3pandora_snd_device, data: &snd_soc_card_omap3pandora); |
238 | |
239 | ret = platform_device_add(pdev: omap3pandora_snd_device); |
240 | if (ret) { |
241 | pr_err(PREFIX "Unable to add platform device\n" ); |
242 | goto fail2; |
243 | } |
244 | |
245 | dac_power_gpio = devm_gpiod_get(dev: &omap3pandora_snd_device->dev, |
246 | con_id: "dac" , flags: GPIOD_OUT_LOW); |
247 | if (IS_ERR(ptr: dac_power_gpio)) { |
248 | ret = PTR_ERR(ptr: dac_power_gpio); |
249 | goto fail3; |
250 | } |
251 | |
252 | amp_power_gpio = devm_gpiod_get(dev: &omap3pandora_snd_device->dev, |
253 | con_id: "amp" , flags: GPIOD_OUT_LOW); |
254 | if (IS_ERR(ptr: amp_power_gpio)) { |
255 | ret = PTR_ERR(ptr: amp_power_gpio); |
256 | goto fail3; |
257 | } |
258 | |
259 | omap3pandora_dac_reg = regulator_get(dev: &omap3pandora_snd_device->dev, id: "vcc" ); |
260 | if (IS_ERR(ptr: omap3pandora_dac_reg)) { |
261 | pr_err(PREFIX "Failed to get DAC regulator from %s: %ld\n" , |
262 | dev_name(&omap3pandora_snd_device->dev), |
263 | PTR_ERR(omap3pandora_dac_reg)); |
264 | ret = PTR_ERR(ptr: omap3pandora_dac_reg); |
265 | goto fail3; |
266 | } |
267 | |
268 | return 0; |
269 | |
270 | fail3: |
271 | platform_device_del(pdev: omap3pandora_snd_device); |
272 | fail2: |
273 | platform_device_put(pdev: omap3pandora_snd_device); |
274 | |
275 | return ret; |
276 | } |
277 | module_init(omap3pandora_soc_init); |
278 | |
279 | static void __exit omap3pandora_soc_exit(void) |
280 | { |
281 | regulator_put(regulator: omap3pandora_dac_reg); |
282 | platform_device_unregister(omap3pandora_snd_device); |
283 | } |
284 | module_exit(omap3pandora_soc_exit); |
285 | |
286 | MODULE_AUTHOR("Grazvydas Ignotas <notasas@gmail.com>" ); |
287 | MODULE_DESCRIPTION("ALSA SoC OMAP3 Pandora" ); |
288 | MODULE_LICENSE("GPL" ); |
289 | |