1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * spitz.c -- SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita |
4 | * |
5 | * Copyright 2005 Wolfson Microelectronics PLC. |
6 | * Copyright 2005 Openedhand Ltd. |
7 | * |
8 | * Authors: Liam Girdwood <lrg@slimlogic.co.uk> |
9 | * Richard Purdie <richard@openedhand.com> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/moduleparam.h> |
14 | #include <linux/timer.h> |
15 | #include <linux/interrupt.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/gpio/consumer.h> |
18 | #include <sound/core.h> |
19 | #include <sound/pcm.h> |
20 | #include <sound/soc.h> |
21 | |
22 | #include <asm/mach-types.h> |
23 | #include "../codecs/wm8750.h" |
24 | #include "pxa2xx-i2s.h" |
25 | |
26 | #define SPITZ_HP 0 |
27 | #define SPITZ_MIC 1 |
28 | #define SPITZ_LINE 2 |
29 | #define SPITZ_HEADSET 3 |
30 | #define SPITZ_HP_OFF 4 |
31 | #define SPITZ_SPK_ON 0 |
32 | #define SPITZ_SPK_OFF 1 |
33 | |
34 | /* audio clock in Hz - rounded from 12.235MHz */ |
35 | #define SPITZ_AUDIO_CLOCK 12288000 |
36 | |
37 | static int spitz_jack_func; |
38 | static int spitz_spk_func; |
39 | static struct gpio_desc *gpiod_mic, *gpiod_mute_l, *gpiod_mute_r; |
40 | |
41 | static void spitz_ext_control(struct snd_soc_dapm_context *dapm) |
42 | { |
43 | snd_soc_dapm_mutex_lock(dapm); |
44 | |
45 | if (spitz_spk_func == SPITZ_SPK_ON) |
46 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Ext Spk" ); |
47 | else |
48 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Ext Spk" ); |
49 | |
50 | /* set up jack connection */ |
51 | switch (spitz_jack_func) { |
52 | case SPITZ_HP: |
53 | /* enable and unmute hp jack, disable mic bias */ |
54 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headset Jack" ); |
55 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Mic Jack" ); |
56 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Line Jack" ); |
57 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
58 | gpiod_set_value(desc: gpiod_mute_l, value: 1); |
59 | gpiod_set_value(desc: gpiod_mute_r, value: 1); |
60 | break; |
61 | case SPITZ_MIC: |
62 | /* enable mic jack and bias, mute hp */ |
63 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
64 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headset Jack" ); |
65 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Line Jack" ); |
66 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Mic Jack" ); |
67 | gpiod_set_value(desc: gpiod_mute_l, value: 0); |
68 | gpiod_set_value(desc: gpiod_mute_r, value: 0); |
69 | break; |
70 | case SPITZ_LINE: |
71 | /* enable line jack, disable mic bias and mute hp */ |
72 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
73 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headset Jack" ); |
74 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Mic Jack" ); |
75 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Line Jack" ); |
76 | gpiod_set_value(desc: gpiod_mute_l, value: 0); |
77 | gpiod_set_value(desc: gpiod_mute_r, value: 0); |
78 | break; |
79 | case SPITZ_HEADSET: |
80 | /* enable and unmute headset jack enable mic bias, mute L hp */ |
81 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
82 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Mic Jack" ); |
83 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Line Jack" ); |
84 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Headset Jack" ); |
85 | gpiod_set_value(desc: gpiod_mute_l, value: 0); |
86 | gpiod_set_value(desc: gpiod_mute_r, value: 1); |
87 | break; |
88 | case SPITZ_HP_OFF: |
89 | |
90 | /* jack removed, everything off */ |
91 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
92 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headset Jack" ); |
93 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Mic Jack" ); |
94 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Line Jack" ); |
95 | gpiod_set_value(desc: gpiod_mute_l, value: 0); |
96 | gpiod_set_value(desc: gpiod_mute_r, value: 0); |
97 | break; |
98 | } |
99 | |
100 | snd_soc_dapm_sync_unlocked(dapm); |
101 | |
102 | snd_soc_dapm_mutex_unlock(dapm); |
103 | } |
104 | |
105 | static int spitz_startup(struct snd_pcm_substream *substream) |
106 | { |
107 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
108 | |
109 | /* check the jack status at stream startup */ |
110 | spitz_ext_control(dapm: &rtd->card->dapm); |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | static int spitz_hw_params(struct snd_pcm_substream *substream, |
116 | struct snd_pcm_hw_params *params) |
117 | { |
118 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
119 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
120 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
121 | unsigned int clk = 0; |
122 | int ret = 0; |
123 | |
124 | switch (params_rate(p: params)) { |
125 | case 8000: |
126 | case 16000: |
127 | case 48000: |
128 | case 96000: |
129 | clk = 12288000; |
130 | break; |
131 | case 11025: |
132 | case 22050: |
133 | case 44100: |
134 | clk = 11289600; |
135 | break; |
136 | } |
137 | |
138 | /* set the codec system clock for DAC and ADC */ |
139 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8750_SYSCLK, freq: clk, |
140 | SND_SOC_CLOCK_IN); |
141 | if (ret < 0) |
142 | return ret; |
143 | |
144 | /* set the I2S system clock as input (unused) */ |
145 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, PXA2XX_I2S_SYSCLK, freq: 0, |
146 | SND_SOC_CLOCK_IN); |
147 | if (ret < 0) |
148 | return ret; |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | static const struct snd_soc_ops spitz_ops = { |
154 | .startup = spitz_startup, |
155 | .hw_params = spitz_hw_params, |
156 | }; |
157 | |
158 | static int spitz_get_jack(struct snd_kcontrol *kcontrol, |
159 | struct snd_ctl_elem_value *ucontrol) |
160 | { |
161 | ucontrol->value.enumerated.item[0] = spitz_jack_func; |
162 | return 0; |
163 | } |
164 | |
165 | static int spitz_set_jack(struct snd_kcontrol *kcontrol, |
166 | struct snd_ctl_elem_value *ucontrol) |
167 | { |
168 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
169 | |
170 | if (spitz_jack_func == ucontrol->value.enumerated.item[0]) |
171 | return 0; |
172 | |
173 | spitz_jack_func = ucontrol->value.enumerated.item[0]; |
174 | spitz_ext_control(dapm: &card->dapm); |
175 | return 1; |
176 | } |
177 | |
178 | static int spitz_get_spk(struct snd_kcontrol *kcontrol, |
179 | struct snd_ctl_elem_value *ucontrol) |
180 | { |
181 | ucontrol->value.enumerated.item[0] = spitz_spk_func; |
182 | return 0; |
183 | } |
184 | |
185 | static int spitz_set_spk(struct snd_kcontrol *kcontrol, |
186 | struct snd_ctl_elem_value *ucontrol) |
187 | { |
188 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
189 | |
190 | if (spitz_spk_func == ucontrol->value.enumerated.item[0]) |
191 | return 0; |
192 | |
193 | spitz_spk_func = ucontrol->value.enumerated.item[0]; |
194 | spitz_ext_control(dapm: &card->dapm); |
195 | return 1; |
196 | } |
197 | |
198 | static int spitz_mic_bias(struct snd_soc_dapm_widget *w, |
199 | struct snd_kcontrol *k, int event) |
200 | { |
201 | gpiod_set_value_cansleep(desc: gpiod_mic, SND_SOC_DAPM_EVENT_ON(event)); |
202 | return 0; |
203 | } |
204 | |
205 | /* spitz machine dapm widgets */ |
206 | static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = { |
207 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
208 | SND_SOC_DAPM_MIC("Mic Jack" , spitz_mic_bias), |
209 | SND_SOC_DAPM_SPK("Ext Spk" , NULL), |
210 | SND_SOC_DAPM_LINE("Line Jack" , NULL), |
211 | |
212 | /* headset is a mic and mono headphone */ |
213 | SND_SOC_DAPM_HP("Headset Jack" , NULL), |
214 | }; |
215 | |
216 | /* Spitz machine audio_map */ |
217 | static const struct snd_soc_dapm_route spitz_audio_map[] = { |
218 | |
219 | /* headphone connected to LOUT1, ROUT1 */ |
220 | {"Headphone Jack" , NULL, "LOUT1" }, |
221 | {"Headphone Jack" , NULL, "ROUT1" }, |
222 | |
223 | /* headset connected to ROUT1 and LINPUT1 with bias (def below) */ |
224 | {"Headset Jack" , NULL, "ROUT1" }, |
225 | |
226 | /* ext speaker connected to LOUT2, ROUT2 */ |
227 | {"Ext Spk" , NULL, "ROUT2" }, |
228 | {"Ext Spk" , NULL, "LOUT2" }, |
229 | |
230 | /* mic is connected to input 1 - with bias */ |
231 | {"LINPUT1" , NULL, "Mic Bias" }, |
232 | {"Mic Bias" , NULL, "Mic Jack" }, |
233 | |
234 | /* line is connected to input 1 - no bias */ |
235 | {"LINPUT1" , NULL, "Line Jack" }, |
236 | }; |
237 | |
238 | static const char * const jack_function[] = {"Headphone" , "Mic" , "Line" , |
239 | "Headset" , "Off" }; |
240 | static const char * const spk_function[] = {"On" , "Off" }; |
241 | static const struct soc_enum spitz_enum[] = { |
242 | SOC_ENUM_SINGLE_EXT(5, jack_function), |
243 | SOC_ENUM_SINGLE_EXT(2, spk_function), |
244 | }; |
245 | |
246 | static const struct snd_kcontrol_new wm8750_spitz_controls[] = { |
247 | SOC_ENUM_EXT("Jack Function" , spitz_enum[0], spitz_get_jack, |
248 | spitz_set_jack), |
249 | SOC_ENUM_EXT("Speaker Function" , spitz_enum[1], spitz_get_spk, |
250 | spitz_set_spk), |
251 | }; |
252 | |
253 | /* spitz digital audio interface glue - connects codec <--> CPU */ |
254 | SND_SOC_DAILINK_DEFS(wm8750, |
255 | DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s" )), |
256 | DAILINK_COMP_ARRAY(COMP_CODEC("wm8750.0-001b" , "wm8750-hifi" )), |
257 | DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio" ))); |
258 | |
259 | static struct snd_soc_dai_link spitz_dai = { |
260 | .name = "wm8750" , |
261 | .stream_name = "WM8750" , |
262 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
263 | SND_SOC_DAIFMT_CBS_CFS, |
264 | .ops = &spitz_ops, |
265 | SND_SOC_DAILINK_REG(wm8750), |
266 | }; |
267 | |
268 | /* spitz audio machine driver */ |
269 | static struct snd_soc_card snd_soc_spitz = { |
270 | .name = "Spitz" , |
271 | .owner = THIS_MODULE, |
272 | .dai_link = &spitz_dai, |
273 | .num_links = 1, |
274 | |
275 | .controls = wm8750_spitz_controls, |
276 | .num_controls = ARRAY_SIZE(wm8750_spitz_controls), |
277 | .dapm_widgets = wm8750_dapm_widgets, |
278 | .num_dapm_widgets = ARRAY_SIZE(wm8750_dapm_widgets), |
279 | .dapm_routes = spitz_audio_map, |
280 | .num_dapm_routes = ARRAY_SIZE(spitz_audio_map), |
281 | .fully_routed = true, |
282 | }; |
283 | |
284 | static int spitz_probe(struct platform_device *pdev) |
285 | { |
286 | struct snd_soc_card *card = &snd_soc_spitz; |
287 | int ret; |
288 | |
289 | gpiod_mic = devm_gpiod_get(dev: &pdev->dev, con_id: "mic" , flags: GPIOD_OUT_LOW); |
290 | if (IS_ERR(ptr: gpiod_mic)) |
291 | return PTR_ERR(ptr: gpiod_mic); |
292 | gpiod_mute_l = devm_gpiod_get(dev: &pdev->dev, con_id: "mute-l" , flags: GPIOD_OUT_LOW); |
293 | if (IS_ERR(ptr: gpiod_mute_l)) |
294 | return PTR_ERR(ptr: gpiod_mute_l); |
295 | gpiod_mute_r = devm_gpiod_get(dev: &pdev->dev, con_id: "mute-r" , flags: GPIOD_OUT_LOW); |
296 | if (IS_ERR(ptr: gpiod_mute_r)) |
297 | return PTR_ERR(ptr: gpiod_mute_r); |
298 | |
299 | card->dev = &pdev->dev; |
300 | |
301 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
302 | if (ret) |
303 | dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n" , |
304 | ret); |
305 | |
306 | return ret; |
307 | } |
308 | |
309 | static struct platform_driver spitz_driver = { |
310 | .driver = { |
311 | .name = "spitz-audio" , |
312 | .pm = &snd_soc_pm_ops, |
313 | }, |
314 | .probe = spitz_probe, |
315 | }; |
316 | |
317 | module_platform_driver(spitz_driver); |
318 | |
319 | MODULE_AUTHOR("Richard Purdie" ); |
320 | MODULE_DESCRIPTION("ALSA SoC Spitz" ); |
321 | MODULE_LICENSE("GPL" ); |
322 | MODULE_ALIAS("platform:spitz-audio" ); |
323 | |