1/*
2 * poodle.c  --  SoC audio for Poodle
3 *
4 * Copyright 2005 Wolfson Microelectronics PLC.
5 * Copyright 2005 Openedhand Ltd.
6 *
7 * Authors: Liam Girdwood <liam.girdwood@wolfsonmicro.com>
8 *          Richard Purdie <richard@openedhand.com>
9 *
10 *  This program is free software; you can redistribute  it and/or modify it
11 *  under  the terms of  the GNU General  Public License as published by the
12 *  Free Software Foundation;  either version 2 of the  License, or (at your
13 *  option) any later version.
14 *
15 */
16
17#include <linux/module.h>
18#include <linux/moduleparam.h>
19#include <linux/timer.h>
20#include <linux/interrupt.h>
21#include <linux/platform_device.h>
22#include <sound/driver.h>
23#include <sound/core.h>
24#include <sound/pcm.h>
25#include <sound/soc.h>
26#include <sound/soc-dapm.h>
27
28#include <asm/mach-types.h>
29#include <asm/hardware/locomo.h>
30#include <asm/arch/pxa-regs.h>
31#include <asm/arch/hardware.h>
32#include <asm/arch/poodle.h>
33#include <asm/arch/audio.h>
34
35#include "../codecs/wm8731.h"
36#include "pxa2xx-pcm.h"
37#include "pxa2xx-i2s.h"
38
39#define POODLE_HP        1
40#define POODLE_HP_OFF    0
41#define POODLE_SPK_ON    1
42#define POODLE_SPK_OFF   0
43
44 /* audio clock in Hz - rounded from 12.235MHz */
45#define POODLE_AUDIO_CLOCK 12288000
46
47static int poodle_jack_func;
48static int poodle_spk_func;
49
50static void poodle_ext_control(struct snd_soc_codec *codec)
51{
52	int spk = 0;
53
54	/* set up jack connection */
55	if (poodle_jack_func == POODLE_HP) {
56		/* set = unmute headphone */
57		locomo_gpio_write(&poodle_locomo_device.dev,
58			POODLE_LOCOMO_GPIO_MUTE_L, 1);
59		locomo_gpio_write(&poodle_locomo_device.dev,
60			POODLE_LOCOMO_GPIO_MUTE_R, 1);
61		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1);
62	} else {
63		locomo_gpio_write(&poodle_locomo_device.dev,
64			POODLE_LOCOMO_GPIO_MUTE_L, 0);
65		locomo_gpio_write(&poodle_locomo_device.dev,
66			POODLE_LOCOMO_GPIO_MUTE_R, 0);
67		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0);
68	}
69
70	if (poodle_spk_func == POODLE_SPK_ON)
71		spk = 1;
72
73	/* set the enpoints to their new connetion states */
74	snd_soc_dapm_set_endpoint(codec, "Ext Spk", spk);
75
76	/* signal a DAPM event */
77	snd_soc_dapm_sync_endpoints(codec);
78}
79
80static int poodle_startup(struct snd_pcm_substream *substream)
81{
82	struct snd_soc_pcm_runtime *rtd = substream->private_data;
83	struct snd_soc_codec *codec = rtd->socdev->codec;
84
85	/* check the jack status at stream startup */
86	poodle_ext_control(codec);
87	return 0;
88}
89
90/* we need to unmute the HP at shutdown as the mute burns power on poodle */
91static int poodle_shutdown(struct snd_pcm_substream *substream)
92{
93	struct snd_soc_pcm_runtime *rtd = substream->private_data;
94	struct snd_soc_codec *codec = rtd->socdev->codec;
95
96	/* set = unmute headphone */
97	locomo_gpio_write(&poodle_locomo_device.dev,
98		POODLE_LOCOMO_GPIO_MUTE_L, 1);
99	locomo_gpio_write(&poodle_locomo_device.dev,
100		POODLE_LOCOMO_GPIO_MUTE_R, 1);
101	return 0;
102}
103
104static int poodle_hw_params(struct snd_pcm_substream *substream,
105	struct snd_pcm_hw_params *params)
106{
107	struct snd_soc_pcm_runtime *rtd = substream->private_data;
108	struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
109	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
110	unsigned int clk = 0;
111	int ret = 0;
112
113	switch (params_rate(params)) {
114	case 8000:
115	case 16000:
116	case 48000:
117	case 96000:
118		clk = 12288000;
119		break;
120	case 11025:
121	case 22050:
122	case 44100:
123		clk = 11289600;
124		break;
125	}
126
127	/* set codec DAI configuration */
128	ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
129		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
130	if (ret < 0)
131		return ret;
132
133	/* set cpu DAI configuration */
134	ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
135		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
136	if (ret < 0)
137		return ret;
138
139	/* set the codec system clock for DAC and ADC */
140	ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8731_SYSCLK, clk,
141		SND_SOC_CLOCK_IN);
142	if (ret < 0)
143		return ret;
144
145	/* set the I2S system clock as input (unused) */
146	ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
147		SND_SOC_CLOCK_IN);
148	if (ret < 0)
149		return ret;
150
151	return 0;
152}
153
154static struct snd_soc_ops poodle_ops = {
155	.startup = poodle_startup,
156	.hw_params = poodle_hw_params,
157	.shutdown = poodle_shutdown,
158};
159
160static int poodle_get_jack(struct snd_kcontrol *kcontrol,
161	struct snd_ctl_elem_value *ucontrol)
162{
163	ucontrol->value.integer.value[0] = poodle_jack_func;
164	return 0;
165}
166
167static int poodle_set_jack(struct snd_kcontrol *kcontrol,
168	struct snd_ctl_elem_value *ucontrol)
169{
170	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
171
172	if (poodle_jack_func == ucontrol->value.integer.value[0])
173		return 0;
174
175	poodle_jack_func = ucontrol->value.integer.value[0];
176	poodle_ext_control(codec);
177	return 1;
178}
179
180static int poodle_get_spk(struct snd_kcontrol *kcontrol,
181	struct snd_ctl_elem_value *ucontrol)
182{
183	ucontrol->value.integer.value[0] = poodle_spk_func;
184	return 0;
185}
186
187static int poodle_set_spk(struct snd_kcontrol *kcontrol,
188	struct snd_ctl_elem_value *ucontrol)
189{
190	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
191
192	if (poodle_spk_func == ucontrol->value.integer.value[0])
193		return 0;
194
195	poodle_spk_func = ucontrol->value.integer.value[0];
196	poodle_ext_control(codec);
197	return 1;
198}
199
200static int poodle_amp_event(struct snd_soc_dapm_widget *w, int event)
201{
202	if (SND_SOC_DAPM_EVENT_ON(event))
203		locomo_gpio_write(&poodle_locomo_device.dev,
204			POODLE_LOCOMO_GPIO_AMP_ON, 0);
205	else
206		locomo_gpio_write(&poodle_locomo_device.dev,
207			POODLE_LOCOMO_GPIO_AMP_ON, 1);
208
209	return 0;
210}
211
212/* poodle machine dapm widgets */
213static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
214SND_SOC_DAPM_HP("Headphone Jack", NULL),
215SND_SOC_DAPM_SPK("Ext Spk", poodle_amp_event),
216};
217
218/* Corgi machine audio_mapnections to the codec pins */
219static const char *audio_map[][3] = {
220
221	/* headphone connected to LHPOUT1, RHPOUT1 */
222	{"Headphone Jack", NULL, "LHPOUT"},
223	{"Headphone Jack", NULL, "RHPOUT"},
224
225	/* speaker connected to LOUT, ROUT */
226	{"Ext Spk", NULL, "ROUT"},
227	{"Ext Spk", NULL, "LOUT"},
228
229	{NULL, NULL, NULL},
230};
231
232static const char *jack_function[] = {"Off", "Headphone"};
233static const char *spk_function[] = {"Off", "On"};
234static const struct soc_enum poodle_enum[] = {
235	SOC_ENUM_SINGLE_EXT(2, jack_function),
236	SOC_ENUM_SINGLE_EXT(2, spk_function),
237};
238
239static const snd_kcontrol_new_t wm8731_poodle_controls[] = {
240	SOC_ENUM_EXT("Jack Function", poodle_enum[0], poodle_get_jack,
241		poodle_set_jack),
242	SOC_ENUM_EXT("Speaker Function", poodle_enum[1], poodle_get_spk,
243		poodle_set_spk),
244};
245
246/*
247 * Logic for a wm8731 as connected on a Sharp SL-C7x0 Device
248 */
249static int poodle_wm8731_init(struct snd_soc_codec *codec)
250{
251	int i, err;
252
253	snd_soc_dapm_set_endpoint(codec, "LLINEIN", 0);
254	snd_soc_dapm_set_endpoint(codec, "RLINEIN", 0);
255	snd_soc_dapm_set_endpoint(codec, "MICIN", 1);
256
257	/* Add poodle specific controls */
258	for (i = 0; i < ARRAY_SIZE(wm8731_poodle_controls); i++) {
259		err = snd_ctl_add(codec->card,
260			snd_soc_cnew(&wm8731_poodle_controls[i],codec, NULL));
261		if (err < 0)
262			return err;
263	}
264
265	/* Add poodle specific widgets */
266	for (i = 0; i < ARRAY_SIZE(wm8731_dapm_widgets); i++) {
267		snd_soc_dapm_new_control(codec, &wm8731_dapm_widgets[i]);
268	}
269
270	/* Set up poodle specific audio path audio_map */
271	for (i = 0; audio_map[i][0] != NULL; i++) {
272		snd_soc_dapm_connect_input(codec, audio_map[i][0],
273			audio_map[i][1], audio_map[i][2]);
274	}
275
276	snd_soc_dapm_sync_endpoints(codec);
277	return 0;
278}
279
280/* poodle digital audio interface glue - connects codec <--> CPU */
281static struct snd_soc_dai_link poodle_dai = {
282	.name = "WM8731",
283	.stream_name = "WM8731",
284	.cpu_dai = &pxa_i2s_dai,
285	.codec_dai = &wm8731_dai,
286	.init = poodle_wm8731_init,
287	.ops = &poodle_ops,
288};
289
290/* poodle audio machine driver */
291static struct snd_soc_machine snd_soc_machine_poodle = {
292	.name = "Poodle",
293	.dai_link = &poodle_dai,
294	.num_links = 1,
295};
296
297/* poodle audio private data */
298static struct wm8731_setup_data poodle_wm8731_setup = {
299	.i2c_address = 0x1b,
300};
301
302/* poodle audio subsystem */
303static struct snd_soc_device poodle_snd_devdata = {
304	.machine = &snd_soc_machine_poodle,
305	.platform = &pxa2xx_soc_platform,
306	.codec_dev = &soc_codec_dev_wm8731,
307	.codec_data = &poodle_wm8731_setup,
308};
309
310static struct platform_device *poodle_snd_device;
311
312static int __init poodle_init(void)
313{
314	int ret;
315
316	if (!machine_is_poodle())
317		return -ENODEV;
318
319	locomo_gpio_set_dir(&poodle_locomo_device.dev,
320		POODLE_LOCOMO_GPIO_AMP_ON, 0);
321	/* should we mute HP at startup - burning power ?*/
322	locomo_gpio_set_dir(&poodle_locomo_device.dev,
323		POODLE_LOCOMO_GPIO_MUTE_L, 0);
324	locomo_gpio_set_dir(&poodle_locomo_device.dev,
325		POODLE_LOCOMO_GPIO_MUTE_R, 0);
326
327	poodle_snd_device = platform_device_alloc("soc-audio", -1);
328	if (!poodle_snd_device)
329		return -ENOMEM;
330
331	platform_set_drvdata(poodle_snd_device, &poodle_snd_devdata);
332	poodle_snd_devdata.dev = &poodle_snd_device->dev;
333	ret = platform_device_add(poodle_snd_device);
334
335	if (ret)
336		platform_device_put(poodle_snd_device);
337
338	return ret;
339}
340
341static void __exit poodle_exit(void)
342{
343	platform_device_unregister(poodle_snd_device);
344}
345
346module_init(poodle_init);
347module_exit(poodle_exit);
348
349/* Module information */
350MODULE_AUTHOR("Richard Purdie");
351MODULE_DESCRIPTION("ALSA SoC Poodle");
352MODULE_LICENSE("GPL");
353