1/*
2 * spitz.c  --  SoC audio for Sharp SL-Cxx00 models Spitz, Borzoi and Akita
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 *  Revision history
16 *    30th Nov 2005   Initial version.
17 *
18 */
19
20#include <linux/module.h>
21#include <linux/moduleparam.h>
22#include <linux/timer.h>
23#include <linux/interrupt.h>
24#include <linux/platform_device.h>
25#include <sound/driver.h>
26#include <sound/core.h>
27#include <sound/pcm.h>
28#include <sound/soc.h>
29#include <sound/soc-dapm.h>
30
31#include <asm/mach-types.h>
32#include <asm/hardware/scoop.h>
33#include <asm/arch/pxa-regs.h>
34#include <asm/arch/hardware.h>
35#include <asm/arch/akita.h>
36#include <asm/arch/spitz.h>
37#include <asm/mach-types.h>
38#include "../codecs/wm8750.h"
39#include "pxa2xx-pcm.h"
40#include "pxa2xx-i2s.h"
41
42#define SPITZ_HP        0
43#define SPITZ_MIC       1
44#define SPITZ_LINE      2
45#define SPITZ_HEADSET   3
46#define SPITZ_HP_OFF    4
47#define SPITZ_SPK_ON    0
48#define SPITZ_SPK_OFF   1
49
50 /* audio clock in Hz - rounded from 12.235MHz */
51#define SPITZ_AUDIO_CLOCK 12288000
52
53static int spitz_jack_func;
54static int spitz_spk_func;
55
56static void spitz_ext_control(struct snd_soc_codec *codec)
57{
58	if (spitz_spk_func == SPITZ_SPK_ON)
59		snd_soc_dapm_set_endpoint(codec, "Ext Spk", 1);
60	else
61		snd_soc_dapm_set_endpoint(codec, "Ext Spk", 0);
62
63	/* set up jack connection */
64	switch (spitz_jack_func) {
65	case SPITZ_HP:
66		/* enable and unmute hp jack, disable mic bias */
67		snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0);
68		snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0);
69		snd_soc_dapm_set_endpoint(codec, "Line Jack", 0);
70		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 1);
71		set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L);
72		set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R);
73		break;
74	case SPITZ_MIC:
75		/* enable mic jack and bias, mute hp */
76		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0);
77		snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0);
78		snd_soc_dapm_set_endpoint(codec, "Line Jack", 0);
79		snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1);
80		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L);
81		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R);
82		break;
83	case SPITZ_LINE:
84		/* enable line jack, disable mic bias and mute hp */
85		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0);
86		snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0);
87		snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0);
88		snd_soc_dapm_set_endpoint(codec, "Line Jack", 1);
89		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L);
90		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R);
91		break;
92	case SPITZ_HEADSET:
93		/* enable and unmute headset jack enable mic bias, mute L hp */
94		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0);
95		snd_soc_dapm_set_endpoint(codec, "Mic Jack", 1);
96		snd_soc_dapm_set_endpoint(codec, "Line Jack", 0);
97		snd_soc_dapm_set_endpoint(codec, "Headset Jack", 1);
98		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L);
99		set_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R);
100		break;
101	case SPITZ_HP_OFF:
102
103		/* jack removed, everything off */
104		snd_soc_dapm_set_endpoint(codec, "Headphone Jack", 0);
105		snd_soc_dapm_set_endpoint(codec, "Headset Jack", 0);
106		snd_soc_dapm_set_endpoint(codec, "Mic Jack", 0);
107		snd_soc_dapm_set_endpoint(codec, "Line Jack", 0);
108		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_L);
109		reset_scoop_gpio(&spitzscoop_device.dev, SPITZ_SCP_MUTE_R);
110		break;
111	}
112	snd_soc_dapm_sync_endpoints(codec);
113}
114
115static int spitz_startup(struct snd_pcm_substream *substream)
116{
117	struct snd_soc_pcm_runtime *rtd = substream->private_data;
118	struct snd_soc_codec *codec = rtd->socdev->codec;
119
120	/* check the jack status at stream startup */
121	spitz_ext_control(codec);
122	return 0;
123}
124
125static int spitz_hw_params(struct snd_pcm_substream *substream,
126	struct snd_pcm_hw_params *params)
127{
128	struct snd_soc_pcm_runtime *rtd = substream->private_data;
129	struct snd_soc_codec_dai *codec_dai = rtd->dai->codec_dai;
130	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
131	unsigned int clk = 0;
132	int ret = 0;
133
134	switch (params_rate(params)) {
135	case 8000:
136	case 16000:
137	case 48000:
138	case 96000:
139		clk = 12288000;
140		break;
141	case 11025:
142	case 22050:
143	case 44100:
144		clk = 11289600;
145		break;
146	}
147
148	/* set codec DAI configuration */
149	ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
150		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
151	if (ret < 0)
152		return ret;
153
154	/* set cpu DAI configuration */
155	ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
156		SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
157	if (ret < 0)
158		return ret;
159
160	/* set the codec system clock for DAC and ADC */
161	ret = codec_dai->dai_ops.set_sysclk(codec_dai, WM8750_SYSCLK, clk,
162		SND_SOC_CLOCK_IN);
163	if (ret < 0)
164		return ret;
165
166	/* set the I2S system clock as input (unused) */
167	ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, 0,
168		SND_SOC_CLOCK_IN);
169	if (ret < 0)
170		return ret;
171
172	return 0;
173}
174
175static struct snd_soc_ops spitz_ops = {
176	.startup = spitz_startup,
177	.hw_params = spitz_hw_params,
178};
179
180static int spitz_get_jack(struct snd_kcontrol *kcontrol,
181	struct snd_ctl_elem_value *ucontrol)
182{
183	ucontrol->value.integer.value[0] = spitz_jack_func;
184	return 0;
185}
186
187static int spitz_set_jack(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 (spitz_jack_func == ucontrol->value.integer.value[0])
193		return 0;
194
195	spitz_jack_func = ucontrol->value.integer.value[0];
196	spitz_ext_control(codec);
197	return 1;
198}
199
200static int spitz_get_spk(struct snd_kcontrol *kcontrol,
201	struct snd_ctl_elem_value *ucontrol)
202{
203	ucontrol->value.integer.value[0] = spitz_spk_func;
204	return 0;
205}
206
207static int spitz_set_spk(struct snd_kcontrol *kcontrol,
208	struct snd_ctl_elem_value *ucontrol)
209{
210	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
211
212	if (spitz_spk_func == ucontrol->value.integer.value[0])
213		return 0;
214
215	spitz_spk_func = ucontrol->value.integer.value[0];
216	spitz_ext_control(codec);
217	return 1;
218}
219
220static int spitz_mic_bias(struct snd_soc_dapm_widget *w, int event)
221{
222	if (machine_is_borzoi() || machine_is_spitz()) {
223		if (SND_SOC_DAPM_EVENT_ON(event))
224			set_scoop_gpio(&spitzscoop2_device.dev,
225				SPITZ_SCP2_MIC_BIAS);
226		else
227			reset_scoop_gpio(&spitzscoop2_device.dev,
228				SPITZ_SCP2_MIC_BIAS);
229	}
230
231	if (machine_is_akita()) {
232		if (SND_SOC_DAPM_EVENT_ON(event))
233			akita_set_ioexp(&akitaioexp_device.dev,
234				AKITA_IOEXP_MIC_BIAS);
235		else
236			akita_reset_ioexp(&akitaioexp_device.dev,
237				AKITA_IOEXP_MIC_BIAS);
238	}
239	return 0;
240}
241
242/* spitz machine dapm widgets */
243static const struct snd_soc_dapm_widget wm8750_dapm_widgets[] = {
244	SND_SOC_DAPM_HP("Headphone Jack", NULL),
245	SND_SOC_DAPM_MIC("Mic Jack", spitz_mic_bias),
246	SND_SOC_DAPM_SPK("Ext Spk", NULL),
247	SND_SOC_DAPM_LINE("Line Jack", NULL),
248
249	/* headset is a mic and mono headphone */
250	SND_SOC_DAPM_HP("Headset Jack", NULL),
251};
252
253/* Spitz machine audio_map */
254static const char *audio_map[][3] = {
255
256	/* headphone connected to LOUT1, ROUT1 */
257	{"Headphone Jack", NULL, "LOUT1"},
258	{"Headphone Jack", NULL, "ROUT1"},
259
260	/* headset connected to ROUT1 and LINPUT1 with bias (def below) */
261	{"Headset Jack", NULL, "ROUT1"},
262
263	/* ext speaker connected to LOUT2, ROUT2  */
264	{"Ext Spk", NULL , "ROUT2"},
265	{"Ext Spk", NULL , "LOUT2"},
266
267	/* mic is connected to input 1 - with bias */
268	{"LINPUT1", NULL, "Mic Bias"},
269	{"Mic Bias", NULL, "Mic Jack"},
270
271	/* line is connected to input 1 - no bias */
272	{"LINPUT1", NULL, "Line Jack"},
273
274	{NULL, NULL, NULL},
275};
276
277static const char *jack_function[] = {"Headphone", "Mic", "Line", "Headset",
278	"Off"};
279static const char *spk_function[] = {"On", "Off"};
280static const struct soc_enum spitz_enum[] = {
281	SOC_ENUM_SINGLE_EXT(5, jack_function),
282	SOC_ENUM_SINGLE_EXT(2, spk_function),
283};
284
285static const struct snd_kcontrol_new wm8750_spitz_controls[] = {
286	SOC_ENUM_EXT("Jack Function", spitz_enum[0], spitz_get_jack,
287		spitz_set_jack),
288	SOC_ENUM_EXT("Speaker Function", spitz_enum[1], spitz_get_spk,
289		spitz_set_spk),
290};
291
292/*
293 * Logic for a wm8750 as connected on a Sharp SL-Cxx00 Device
294 */
295static int spitz_wm8750_init(struct snd_soc_codec *codec)
296{
297	int i, err;
298
299	/* NC codec pins */
300	snd_soc_dapm_set_endpoint(codec, "RINPUT1", 0);
301	snd_soc_dapm_set_endpoint(codec, "LINPUT2", 0);
302	snd_soc_dapm_set_endpoint(codec, "RINPUT2", 0);
303	snd_soc_dapm_set_endpoint(codec, "LINPUT3", 0);
304	snd_soc_dapm_set_endpoint(codec, "RINPUT3", 0);
305	snd_soc_dapm_set_endpoint(codec, "OUT3", 0);
306	snd_soc_dapm_set_endpoint(codec, "MONO", 0);
307
308	/* Add spitz specific controls */
309	for (i = 0; i < ARRAY_SIZE(wm8750_spitz_controls); i++) {
310		err = snd_ctl_add(codec->card,
311			snd_soc_cnew(&wm8750_spitz_controls[i], codec, NULL));
312		if (err < 0)
313			return err;
314	}
315
316	/* Add spitz specific widgets */
317	for (i = 0; i < ARRAY_SIZE(wm8750_dapm_widgets); i++) {
318		snd_soc_dapm_new_control(codec, &wm8750_dapm_widgets[i]);
319	}
320
321	/* Set up spitz specific audio path audio_map */
322	for (i = 0; audio_map[i][0] != NULL; i++) {
323		snd_soc_dapm_connect_input(codec, audio_map[i][0],
324			audio_map[i][1], audio_map[i][2]);
325	}
326
327	snd_soc_dapm_sync_endpoints(codec);
328	return 0;
329}
330
331/* spitz digital audio interface glue - connects codec <--> CPU */
332static struct snd_soc_dai_link spitz_dai = {
333	.name = "wm8750",
334	.stream_name = "WM8750",
335	.cpu_dai = &pxa_i2s_dai,
336	.codec_dai = &wm8750_dai,
337	.init = spitz_wm8750_init,
338	.ops = &spitz_ops,
339};
340
341/* spitz audio machine driver */
342static struct snd_soc_machine snd_soc_machine_spitz = {
343	.name = "Spitz",
344	.dai_link = &spitz_dai,
345	.num_links = 1,
346};
347
348/* spitz audio private data */
349static struct wm8750_setup_data spitz_wm8750_setup = {
350	.i2c_address = 0x1b,
351};
352
353/* spitz audio subsystem */
354static struct snd_soc_device spitz_snd_devdata = {
355	.machine = &snd_soc_machine_spitz,
356	.platform = &pxa2xx_soc_platform,
357	.codec_dev = &soc_codec_dev_wm8750,
358	.codec_data = &spitz_wm8750_setup,
359};
360
361static struct platform_device *spitz_snd_device;
362
363static int __init spitz_init(void)
364{
365	int ret;
366
367	if (!(machine_is_spitz() || machine_is_borzoi() || machine_is_akita()))
368		return -ENODEV;
369
370	spitz_snd_device = platform_device_alloc("soc-audio", -1);
371	if (!spitz_snd_device)
372		return -ENOMEM;
373
374	platform_set_drvdata(spitz_snd_device, &spitz_snd_devdata);
375	spitz_snd_devdata.dev = &spitz_snd_device->dev;
376	ret = platform_device_add(spitz_snd_device);
377
378	if (ret)
379		platform_device_put(spitz_snd_device);
380
381	return ret;
382}
383
384static void __exit spitz_exit(void)
385{
386	platform_device_unregister(spitz_snd_device);
387}
388
389module_init(spitz_init);
390module_exit(spitz_exit);
391
392MODULE_AUTHOR("Richard Purdie");
393MODULE_DESCRIPTION("ALSA SoC Spitz");
394MODULE_LICENSE("GPL");
395