1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * poodle.c  --  SoC audio for Poodle
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/i2c.h>
16 #include <linux/interrupt.h>
17 #include <linux/platform_device.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 <asm/hardware/locomo.h>
24 #include <linux/platform_data/asoc-pxa.h>
25 #include <linux/platform_data/asoc-poodle.h>
26 
27 #include "../codecs/wm8731.h"
28 #include "pxa2xx-i2s.h"
29 
30 #define POODLE_HP        1
31 #define POODLE_HP_OFF    0
32 #define POODLE_SPK_ON    1
33 #define POODLE_SPK_OFF   0
34 
35  /* audio clock in Hz - rounded from 12.235MHz */
36 #define POODLE_AUDIO_CLOCK 12288000
37 
38 static int poodle_jack_func;
39 static int poodle_spk_func;
40 
41 static struct poodle_audio_platform_data *poodle_pdata;
42 
poodle_ext_control(struct snd_soc_dapm_context * dapm)43 static void poodle_ext_control(struct snd_soc_dapm_context *dapm)
44 {
45 	/* set up jack connection */
46 	if (poodle_jack_func == POODLE_HP) {
47 		/* set = unmute headphone */
48 		locomo_gpio_write(poodle_pdata->locomo_dev,
49 			poodle_pdata->gpio_mute_l, 1);
50 		locomo_gpio_write(poodle_pdata->locomo_dev,
51 			poodle_pdata->gpio_mute_r, 1);
52 		snd_soc_dapm_enable_pin(dapm, "Headphone Jack");
53 	} else {
54 		locomo_gpio_write(poodle_pdata->locomo_dev,
55 			poodle_pdata->gpio_mute_l, 0);
56 		locomo_gpio_write(poodle_pdata->locomo_dev,
57 			poodle_pdata->gpio_mute_r, 0);
58 		snd_soc_dapm_disable_pin(dapm, "Headphone Jack");
59 	}
60 
61 	/* set the endpoints to their new connection states */
62 	if (poodle_spk_func == POODLE_SPK_ON)
63 		snd_soc_dapm_enable_pin(dapm, "Ext Spk");
64 	else
65 		snd_soc_dapm_disable_pin(dapm, "Ext Spk");
66 
67 	/* signal a DAPM event */
68 	snd_soc_dapm_sync(dapm);
69 }
70 
poodle_startup(struct snd_pcm_substream * substream)71 static int poodle_startup(struct snd_pcm_substream *substream)
72 {
73 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
74 
75 	/* check the jack status at stream startup */
76 	poodle_ext_control(&rtd->card->dapm);
77 
78 	return 0;
79 }
80 
81 /* we need to unmute the HP at shutdown as the mute burns power on poodle */
poodle_shutdown(struct snd_pcm_substream * substream)82 static void poodle_shutdown(struct snd_pcm_substream *substream)
83 {
84 	/* set = unmute headphone */
85 	locomo_gpio_write(poodle_pdata->locomo_dev,
86 		poodle_pdata->gpio_mute_l, 1);
87 	locomo_gpio_write(poodle_pdata->locomo_dev,
88 		poodle_pdata->gpio_mute_r, 1);
89 }
90 
poodle_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)91 static int poodle_hw_params(struct snd_pcm_substream *substream,
92 	struct snd_pcm_hw_params *params)
93 {
94 	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
95 	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
96 	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
97 	unsigned int clk = 0;
98 	int ret = 0;
99 
100 	switch (params_rate(params)) {
101 	case 8000:
102 	case 16000:
103 	case 48000:
104 	case 96000:
105 		clk = 12288000;
106 		break;
107 	case 11025:
108 	case 22050:
109 	case 44100:
110 		clk = 11289600;
111 		break;
112 	}
113 
114 	/* set the codec system clock for DAC and ADC */
115 	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, clk,
116 		SND_SOC_CLOCK_IN);
117 	if (ret < 0)
118 		return ret;
119 
120 	/* set the I2S system clock as input (unused) */
121 	ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
122 		SND_SOC_CLOCK_IN);
123 	if (ret < 0)
124 		return ret;
125 
126 	return 0;
127 }
128 
129 static const struct snd_soc_ops poodle_ops = {
130 	.startup = poodle_startup,
131 	.hw_params = poodle_hw_params,
132 	.shutdown = poodle_shutdown,
133 };
134 
poodle_get_jack(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)135 static int poodle_get_jack(struct snd_kcontrol *kcontrol,
136 	struct snd_ctl_elem_value *ucontrol)
137 {
138 	ucontrol->value.enumerated.item[0] = poodle_jack_func;
139 	return 0;
140 }
141 
poodle_set_jack(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)142 static int poodle_set_jack(struct snd_kcontrol *kcontrol,
143 	struct snd_ctl_elem_value *ucontrol)
144 {
145 	struct snd_soc_card *card =  snd_kcontrol_chip(kcontrol);
146 
147 	if (poodle_jack_func == ucontrol->value.enumerated.item[0])
148 		return 0;
149 
150 	poodle_jack_func = ucontrol->value.enumerated.item[0];
151 	poodle_ext_control(&card->dapm);
152 	return 1;
153 }
154 
poodle_get_spk(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)155 static int poodle_get_spk(struct snd_kcontrol *kcontrol,
156 	struct snd_ctl_elem_value *ucontrol)
157 {
158 	ucontrol->value.enumerated.item[0] = poodle_spk_func;
159 	return 0;
160 }
161 
poodle_set_spk(struct snd_kcontrol * kcontrol,struct snd_ctl_elem_value * ucontrol)162 static int poodle_set_spk(struct snd_kcontrol *kcontrol,
163 	struct snd_ctl_elem_value *ucontrol)
164 {
165 	struct snd_soc_card *card =  snd_kcontrol_chip(kcontrol);
166 
167 	if (poodle_spk_func == ucontrol->value.enumerated.item[0])
168 		return 0;
169 
170 	poodle_spk_func = ucontrol->value.enumerated.item[0];
171 	poodle_ext_control(&card->dapm);
172 	return 1;
173 }
174 
poodle_amp_event(struct snd_soc_dapm_widget * w,struct snd_kcontrol * k,int event)175 static int poodle_amp_event(struct snd_soc_dapm_widget *w,
176 	struct snd_kcontrol *k, int event)
177 {
178 	if (SND_SOC_DAPM_EVENT_ON(event))
179 		locomo_gpio_write(poodle_pdata->locomo_dev,
180 			poodle_pdata->gpio_amp_on, 0);
181 	else
182 		locomo_gpio_write(poodle_pdata->locomo_dev,
183 			poodle_pdata->gpio_amp_on, 1);
184 
185 	return 0;
186 }
187 
188 /* poodle machine dapm widgets */
189 static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
190 SND_SOC_DAPM_HP("Headphone Jack", NULL),
191 SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event),
192 SND_SOC_DAPM_MIC("Microphone", NULL),
193 };
194 
195 /* Corgi machine connections to the codec pins */
196 static const struct snd_soc_dapm_route poodle_audio_map[] = {
197 
198 	/* headphone connected to LHPOUT1, RHPOUT1 */
199 	{"Headphone Jack", NULL, "LHPOUT"},
200 	{"Headphone Jack", NULL, "RHPOUT"},
201 
202 	/* speaker connected to LOUT, ROUT */
203 	{"Ext Spk", NULL, "ROUT"},
204 	{"Ext Spk", NULL, "LOUT"},
205 
206 	{"MICIN", NULL, "Microphone"},
207 };
208 
209 static const char * const jack_function[] = {"Off", "Headphone"};
210 static const char * const spk_function[] = {"Off", "On"};
211 static const struct soc_enum poodle_enum[] = {
212 	SOC_ENUM_SINGLE_EXT(2, jack_function),
213 	SOC_ENUM_SINGLE_EXT(2, spk_function),
214 };
215 
216 static const struct snd_kcontrol_new wm8731_poodle_controls[] = {
217 	SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack,
218 		poodle_set_jack),
219 	SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
220 		poodle_set_spk),
221 };
222 
223 /* poodle digital audio interface glue - connects codec <--> CPU */
224 SND_SOC_DAILINK_DEFS(wm8731,
225 	DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-i2s")),
226 	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b", "wm8731-hifi")),
227 	DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
228 
229 static struct snd_soc_dai_link poodle_dai = {
230 	.name = "WM8731",
231 	.stream_name = "WM8731",
232 	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
233 		   SND_SOC_DAIFMT_CBS_CFS,
234 	.ops = &poodle_ops,
235 	SND_SOC_DAILINK_REG(wm8731),
236 };
237 
238 /* poodle audio machine driver */
239 static struct snd_soc_card poodle = {
240 	.name = "Poodle",
241 	.dai_link = &poodle_dai,
242 	.num_links = 1,
243 	.owner = THIS_MODULE,
244 
245 	.controls = wm8731_poodle_controls,
246 	.num_controls = ARRAY_SIZE(wm8731_poodle_controls),
247 	.dapm_widgets = wm8731_dapm_widgets,
248 	.num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
249 	.dapm_routes = poodle_audio_map,
250 	.num_dapm_routes = ARRAY_SIZE(poodle_audio_map),
251 	.fully_routed = true,
252 };
253 
poodle_probe(struct platform_device * pdev)254 static int poodle_probe(struct platform_device *pdev)
255 {
256 	struct snd_soc_card *card = &poodle;
257 	int ret;
258 
259 	poodle_pdata = pdev->dev.platform_data;
260 	locomo_gpio_set_dir(poodle_pdata->locomo_dev,
261 		poodle_pdata->gpio_amp_on, 0);
262 	/* should we mute HP at startup - burning power ?*/
263 	locomo_gpio_set_dir(poodle_pdata->locomo_dev,
264 		poodle_pdata->gpio_mute_l, 0);
265 	locomo_gpio_set_dir(poodle_pdata->locomo_dev,
266 		poodle_pdata->gpio_mute_r, 0);
267 
268 	card->dev = &pdev->dev;
269 
270 	ret = devm_snd_soc_register_card(&pdev->dev, card);
271 	if (ret)
272 		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
273 			ret);
274 	return ret;
275 }
276 
277 static struct platform_driver poodle_driver = {
278 	.driver		= {
279 		.name	= "poodle-audio",
280 		.pm     = &snd_soc_pm_ops,
281 	},
282 	.probe		= poodle_probe,
283 };
284 
285 module_platform_driver(poodle_driver);
286 
287 /* Module information */
288 MODULE_AUTHOR("Richard Purdie");
289 MODULE_DESCRIPTION("ALSA SoC Poodle");
290 MODULE_LICENSE("GPL");
291 MODULE_ALIAS("platform:poodle-audio");
292